setTimeout(function() { window.location.reload(true) }, 1000)'); } if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { date_default_timezone_set(@date_default_timezone_get()); } if ('cli' === PHP_SAPI || !isset($_SERVER['REQUEST_URI'])) { if (isset($_SERVER['argv'][1]) && 'test' === $_SERVER['argv'][1]) { die(json_encode(['version' => PHP_VERSION, 'version_id' => PHP_VERSION_ID, 'sapi' => PHP_SAPI])); } Phar::mapPhar('contao-manager.phar'); require 'phar://contao-manager.phar/api/console'; } else { function rewrites() { // The function argument is unreliable across servers, Nginx for example is always empty list(,$url) = explode(basename(__FILE__), $_SERVER['REQUEST_URI'], 2); if (strpos($url, '..')) { return false; } if ('' === $url) { header('Location: /'.basename(__FILE__).'/'); exit; } if (0 === strpos($url, '/api/')) { return '/dist/api.php'.$url; } if (!empty($url) && is_file('phar://'.__FILE__.'/dist'.$url)) { return '/dist'.$url; } return '/dist/index.html'; } Phar::webPhar( null, 'index.html', null, array( 'log' => 'text/plain', 'txt' => 'text/plain', 'php' => Phar::PHP, // parse as PHP 'css' => 'text/css', 'gif' => 'image/gif', 'html' => 'text/html', 'ico' => 'image/x-ico', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'js' => 'application/x-javascript', 'png' => 'image/png', 'svg' => 'image/svg+xml', 'json' => 'application/json' ), 'rewrites' ); } __HALT_COMPILER(); ?> (v) .box/bin/check-requirements.phpme^ʨ.box/.requirements.php me if..box/vendor/autoload.phpmes,.box/vendor/composer/autoload_namespaces.phpmet!פ.box/vendor/composer/LICENSE.me. $.box/vendor/composer/ClassLoader.php>me>5Ky&.box/vendor/composer/autoload_psr4.php6me60Ӥ*.box/vendor/composer/autoload_classmap.php, me, ӽǤ(.box/vendor/composer/autoload_static.phpKmeK?&.box/vendor/composer/autoload_real.phpmeyi\..box/vendor/composer/semver/src/Comparator.phpumeu 74*.box/vendor/composer/semver/src/Semver.phpmeׯ1.box/vendor/composer/semver/src/VersionParser.php9me9Wz B.box/vendor/composer/semver/src/Constraint/ConstraintInterface.phpmewQ>.box/vendor/composer/semver/src/Constraint/MultiConstraint.php~me~5[e9.box/vendor/composer/semver/src/Constraint/Constraint.php'&me'&`.box/src/Terminal.php me %V.box/src/IO.php me [l%!.box/src/IsExtensionFulfilled.phpvmev'0Ҥ.box/src/Checker.phpme)W>".box/src/IsPhpVersionFulfilled.phpme!83".box/src/RequirementCollection.phpmeWb?.box/src/Printer.php me )8AV.box/src/IsFulfilled.phpvmev%*H.box/src/Requirement.phpmmemBLICENSEmepdist/index.htmlb!meb! dist/api.php--mefddist/css/app.a9bbe961.css&me& #dist/css/chunk-vendors.e94d59f6.cssلmeلOdist/js/716-legacy.fe76ad03.js me dist/js/486-legacy.57687748.js me <' dist/js/606-legacy.b2749c38.jsmeKdist/js/477-legacy.af30486c.jsmebdist/js/606.9b8de109.jsmeAKdist/js/540-legacy.ff66b145.js\me\!dist/js/557-legacy.20f3f31a.jsme~>ndist/js/523.5cd13a0a.js% me% -hdist/js/953.9ee534da.js me Sdist/js/721-legacy.1171d135.js'me'F*dist/js/367-legacy.63846bd9.jsTmeTDTxdist/js/560-legacy.9e06a119.jsŽmeŽlKdist/js/710.3935c7a1.js|me|dist/js/581-legacy.06d31426.jsmeP6dist/js/560.9e06a119.jsŽmeŽlKdist/js/553.185d25f6.jsEmeE٤dist/js/551-legacy.64bff36a.jsimei(Ǥdist/js/874-legacy.b224e448.js me _:dist/js/542.d2c16b19.js me B dist/js/769.b25e985b.jsJ meJ ֭dist/js/669-legacy.3e71dbf5.jsmedist/js/194.a4186ef7.jse mee jpdist/js/669.3e71dbf5.jsmedist/js/28-legacy.e44c5c50.jswmewIg0dist/js/518-legacy.3245ef3b.js me 0dist/js/120-legacy.6f92589c.js me }^ݤdist/js/139-legacy.5c0071ca.jsUmeUx0L6dist/js/486.57687748.js me <' dist/js/638-legacy.b5c80b23.js@me@cdist/js/367.63846bd9.jsTmeTDTxdist/js/554.6d242bb5.jsme:"dist/js/140-legacy.686354c2.js= me= 3N(dist/js/chunk-vendors-legacy.8131a05f.jsSmeSv!dist/js/710-legacy.3935c7a1.js|me|dist/js/716.fe76ad03.js me dist/js/app.1ba94aee.jsme䱤dist/js/139.5c0071ca.jsUmeUx0L6dist/js/180-legacy.8dfeddb5.jsW meW q3ܤdist/js/706.bcc0a7d7.jsߕmeߕBdist/js/851.f0f7ce97.jsImeI惣dist/js/164.5ae18c19.js_me_pXdist/js/app-legacy.4548ebe5.jsmecBdist/js/518.3245ef3b.js me 0dist/js/52.68515644.jsW meW kdist/js/557.20f3f31a.jsme~>ndist/js/551.64bff36a.jsimei(Ǥdist/js/581.06d31426.jsmeP6dist/js/958-legacy.18ef1cc5.jsSmeSCIsƤdist/js/44.31475fcc.js me dist/js/553-legacy.185d25f6.jsEmeE٤dist/js/523-legacy.5cd13a0a.js% me% -hdist/js/643-legacy.bbc22e0a.jsmeRjdist/js/638.b5c80b23.js@me@cdist/js/194-legacy.a4186ef7.jse mee jpdist/js/554-legacy.6a208c8e.jsme'~dist/js/542-legacy.d2c16b19.js me B dist/js/715-legacy.5da18439.jsemee2(dist/js/219-legacy.76497ed6.jsh meh wdist/js/140.686354c2.js= me= 3Ndist/js/953-legacy.9ee534da.js me Sdist/js/44-legacy.31475fcc.js me dist/js/643.bbc22e0a.jsmeRjdist/js/721.1171d135.js'me'F*dist/js/706-legacy.bcc0a7d7.jsߕmeߕBdist/js/219.76497ed6.jsh meh wdist/js/958.18ef1cc5.jsSmeSCIsƤdist/js/874.b224e448.js me _:!dist/js/chunk-vendors.41a4e2b1.jsmevAdist/js/52-legacy.68515644.jsW meW kdist/js/540.ff66b145.js\me\!dist/js/851-legacy.f0f7ce97.jsImeI惣dist/js/477.af30486c.jsmebdist/js/120.6f92589c.js me }^ݤdist/js/180.8dfeddb5.jsW meW q3ܤdist/js/715.5da18439.jsemee2(dist/js/164-legacy.5ae18c19.js_me_pXdist/js/28.e44c5c50.jswmewIg0dist/js/769-legacy.b25e985b.jsJ meJ ֭!dist/img/button-show.2336e1d9.svgSmeSUdist/img/favers.31587387.svgOmeOfڤ$dist/img/button-details.78532630.svgmeEΤdist/img/funding.8a3c0e0e.svgBmeBrAݤdist/img/task.30a3fdc0.svgtmetPƤ dist/img/button-add.759df12e.svgmeO dist/img/logo.9c3b3279.svgme ͤ%dist/img/button-download.ce5cd0d3.svg-me-F"dist/img/button-cloud.5015c41d.svgme2攕!dist/img/button-edit.d4a8737e.svgmeuC#dist/img/server-config.1a2d0888.svggmegRN"dist/img/button-power.6b957f3a.svg/me/^Sdist/img/sad.c9a06488.svg6me6lE1dist/img/updated.455f42dc.svg[me[~z;$dist/img/create-project.b22522ee.svgmeʅ#dist/img/document-root.0e6dd2e4.svgmeDdist/img/language.95d0a00b.svgmeῤ$dist/img/button-console.93e68609.svgmeo۪!dist/img/button-hide.6a3eaaf7.svgmeq"dist/img/button-check.8170bc92.svgmejG1&dist/img/button-cloud-off.37842942.svgme4Ndist/img/offline.1f95ae24.svgme9dist/img/person.00d78897.svgmew*!dist/img/button-lock.98988f08.svgcmec5=6z"dist/img/symfony-logo.9bfb375e.svg me y3dist/img/php-logo.e15778f8.svg& me& 5ûdist/img/warning.a0297242.svgmeJdist/img/user.129e863d.svgmecǤ"dist/img/button-trash.a2f11028.svgtmetT[#dist/img/button-update.c63bd5a5.svgXmeXtPɤ'dist/img/search-by-algolia.47401192.svgmew1'dist/img/widget-radio--off.4e93f443.svgme!dist/img/button-save.7704614c.svg me G dist/img/hint.ba2ac97e.svg!me!dist/img/database.7f0a8c49.svgme< (dist/img/button-maintenance.18fa2ce6.svgBmeB/Ӥdist/img/lock.3c42a55f.svgfmefgG)dist/img/widget-checkbox--on.c05d996c.svgmeTs>dist/img/recovery.70ee118a.svgmeOTM`dist/img/boot.c1251f6b.svgmeb"dist/img/link-funding.c73bb011.svgmei!dist/img/button-gear.75fa79f0.svgme3#dist/img/button-unlock.51b76e07.svgimei !dist/img/button-more.e3eb2622.svgmevdist/img/private.c66d3582.svg1me1:D#dist/img/button-search.6f2edfbf.svgHmeH2Z dist/img/button-run.5c7703ae.svgme 7&dist/img/widget-radio--on.181461b6.svg:me:j M dist/img/link-blank.d5149b7c.svgmedY$#dist/img/button-upload.b489ceec.svg/me/}%dist/img/button-database.1f267fe1.svgme}l!dist/img/button-link.e4488c1f.svgme6dist/img/close.88e95867.svgmeF$dist/img/downloads.aa84cdf1.svg*me*=P*dist/img/widget-checkbox--off.73856538.svgme~ dist/img/link-blank.c018a22c.svgme1o(dist/icons/task-active/favicon-16x16.png3me3K"dist/icons/task-active/favicon.ico:me:8(dist/icons/task-active/favicon-32x32.pngFmeF)fx'dist/icons/task-error/favicon-16x16.png;me;!dist/icons/task-error/favicon.ico:me:u'dist/icons/task-error/favicon-32x32.pngBmeB)dist/icons/task-success/favicon-16x16.pngUmeU{ؤ#dist/icons/task-success/favicon.ico:me:TIm)dist/icons/task-success/favicon-32x32.pngqmeq?. dist/api.phpmedist/assets/favicon-16x16.pngme dist/assets/mstile-310x310.pngme=I&dist/assets/android-chrome-384x384.png 'me '@H3dist/assets/apple-touch-startup-image-1125x2436.pngme*OW2dist/assets/apple-touch-startup-image-1792x828.pngCmeC .+2dist/assets/apple-touch-startup-image-1334x750.pngEmeE+vdist/assets/mstile-144x144.png me Ҥdist/assets/favicon.icome}*~(dist/assets/apple-touch-icon-120x120.png me !J3dist/assets/apple-touch-startup-image-2732x2048.pngrmer&dist/assets/android-chrome-192x192.pngmmem"u, dist/assets/apple-touch-icon.png'me'83dist/assets/apple-touch-startup-image-1242x2688.png me r2&dist/assets/android-chrome-256x256.pngmeX3@3dist/assets/apple-touch-startup-image-2436x1125.png*me*J)dist/assets/mstile-70x70.pngmeeݺ=2dist/assets/apple-touch-startup-image-640x1136.pngVmeVӣ3dist/assets/apple-touch-startup-image-1170x2532.pngrmer*dist/assets/apple-touch-icon-1024x1024.pngmey(dist/assets/apple-touch-icon-152x152.png me K3dist/assets/apple-touch-startup-image-1620x2160.png+me+פ3dist/assets/apple-touch-startup-image-1668x2388.png>me>h[2dist/assets/apple-touch-startup-image-828x1792.pngxmexKtS3dist/assets/apple-touch-startup-image-2048x1536.png~Wme~Wx(dist/assets/apple-touch-icon-180x180.png'me'8(dist/assets/yandex-browser-manifest.jsonmep#Ȥ3dist/assets/apple-touch-startup-image-2224x1668.pngme&Idist/assets/favicon-48x48.pngme.idist/assets/mstile-310x150.png%me%{3dist/assets/apple-touch-startup-image-1242x2208.pngme^w(dist/assets/apple-touch-icon-114x114.pngS meS U&dist/assets/apple-touch-icon-76x76.png*me*<&&dist/assets/android-chrome-512x512.png8me8=l?2dist/assets/apple-touch-startup-image-1136x640.pngkmek.3dist/assets/apple-touch-startup-image-2532x1170.png|me|~Z3dist/assets/apple-touch-startup-image-2208x1242.png0 me0 =:&dist/assets/apple-touch-icon-57x57.pngme1@2dist/assets/apple-touch-startup-image-750x1334.pngjmej3Fä,dist/assets/apple-touch-icon-precomposed.png'me'83dist/assets/apple-touch-startup-image-2160x1620.pngvmevk;3dist/assets/apple-touch-startup-image-1284x2778.pngme3dist/assets/apple-touch-startup-image-1668x2224.png8me8ϳ3dist/assets/apple-touch-startup-image-2688x1242.pngmeS&dist/assets/apple-touch-icon-72x72.pngmeO3dist/assets/apple-touch-startup-image-2778x1284.png%me%3dist/assets/apple-touch-startup-image-1536x2048.pngmegl3dist/assets/apple-touch-startup-image-2388x1668.pngmeso.dist/assets/mstile-150x150.png me z dist/assets/manifest.webmanifestme`l$dist/assets/android-chrome-36x36.png+me+$dist/assets/android-chrome-96x96.pngmeAf(dist/assets/apple-touch-icon-167x167.png me uD$dist/assets/android-chrome-72x72.pngmehQ\&dist/assets/apple-touch-icon-60x60.pngme3dist/assets/apple-touch-startup-image-2048x2732.png7me7M_Ť(dist/assets/apple-touch-icon-144x144.png me A0$dist/assets/yandex-browser-50x50.pngXmeXXdist/assets/browserconfig.xmlrmerv,$dist/assets/android-chrome-48x48.pngme.idist/assets/favicon-32x32.pngme#&dist/assets/android-chrome-144x144.png me Ҥ%api/HttpKernel/ApiProblemResponse.php me F$X api/console--A meA C~Ƥapi/Config/UploadsConfig.phpme4api/Config/UserConfig.php!me!Hapi/Config/ManagerConfig.phpmeapi/Config/AbstractConfig.phpme5qn¤api/Config/ComposerConfig.phpmeapi/Config/PartialConfig.phpme;Τapi/Config/AuthConfig.phpmeT6*api/Security/PasswordlessAuthenticator.phphmeh̟#api/Security/TokenAuthenticator.phpmeQkbapi/Security/UserProvider.php me deapi/Security/User.phpme۾Ť!api/Security/JwtAuthenticator.php me A-api/Security/AbstractBrowserAuthenticator.php me %0QŤ#api/Security/LoginAuthenticator.phpf mef ⴤapi/Security/JwtManager.php me eH3.api/TaskOperation/Composer/RemoveOperation.phpmeEA2api/TaskOperation/Composer/ClearCacheOperation.phpmeȝe4api/TaskOperation/Composer/DumpAutoloadOperation.phpmerǀ/api/TaskOperation/Composer/InstallOperation.phpmeJ\.api/TaskOperation/Composer/UpdateOperation.phpme4/api/TaskOperation/Composer/RequireOperation.phpme|gO5api/TaskOperation/Composer/CreateProjectOperation.phpme;t-api/TaskOperation/Composer/CloudOperation.php0me0ᜤ8api/TaskOperation/Filesystem/InstallUploadsOperation.php me 26api/TaskOperation/Filesystem/RemoveVendorOperation.phpme#07api/TaskOperation/Filesystem/RemoveUploadsOperation.php& me& 8GJ¤5api/TaskOperation/Filesystem/RemoveCacheOperation.phpme2)=1api/TaskOperation/Manager/SelfUpdateOperation.php0me0pUT-api/TaskOperation/AbstractInlineOperation.phpme=x1api/TaskOperation/SponsoredOperationInterface.phpmeo&!#api/TaskOperation/ConsoleOutput.php)me)-.api/TaskOperation/AbstractProcessOperation.php me _5api/TaskOperation/Contao/MaintenanceModeOperation.php7me7jJB2api/TaskOperation/Contao/BackupCreateOperation.phpme1api/TaskOperation/Contao/CacheWarmupOperation.phpmeݤ2api/TaskOperation/Contao/CreateContaoOperation.phpPmePQ U3api/TaskOperation/Contao/BackupRestoreOperation.phpmeդ0api/TaskOperation/Contao/CacheClearOperation.phpmei,api/TaskOperation/TaskOperationInterface.phpmejapi/Composer/CloudResolver.phpHmeH/api/Composer/CloudChanges.php&me&Dapi/Resources/cache/pools/system/OsQ8hrw2r5/R/P/2D3NGVsB2klPFDF4unVgmeNqDapi/Resources/cache/pools/system/OsQ8hrw2r5/U/0/UOEV-iZUDwJO3IZrD4jgmeە}Dapi/Resources/cache/pools/system/OsQ8hrw2r5/U/I/kTkBor3QesIhvAUCBjGgme JZDapi/Resources/cache/pools/system/OsQ8hrw2r5/U/I/fdJRp068D3mGBmrtZWBAme @ Dapi/Resources/cache/pools/system/OsQ8hrw2r5/U/Z/-SP2SMqd3fksOPgcjXAgmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/U/T/ACEmMTkeKq2zJNrQp1YAme{hDapi/Resources/cache/pools/system/OsQ8hrw2r5/U/O/MNUfyeybfwQ1np4Jgl6wme`UDapi/Resources/cache/pools/system/OsQ8hrw2r5/U/Q/RC7h6P3ZN9qrj5AJpv-Qme4֤Dapi/Resources/cache/pools/system/OsQ8hrw2r5/U/B/aFGbCjH4No5dIE86tm1wme13FDapi/Resources/cache/pools/system/OsQ8hrw2r5/9/T/nyDVAEYeU58Up2Kq4Bmwme&`mDapi/Resources/cache/pools/system/OsQ8hrw2r5/9/X/Ei+Xa-YpA3jDTw3bQMXQmeC,|Dapi/Resources/cache/pools/system/OsQ8hrw2r5/9/W/DCuPAEECfl7gjXqA25sgme7[Dapi/Resources/cache/pools/system/OsQ8hrw2r5/0/G/27i4Mke1ocWZYgjzErXwme{Dapi/Resources/cache/pools/system/OsQ8hrw2r5/0/G/4moiUYMtI6IqqTqMzblAmeӺhDapi/Resources/cache/pools/system/OsQ8hrw2r5/7/R/0nGqYSuyHzbuPMBNjX0Ame61Dapi/Resources/cache/pools/system/OsQ8hrw2r5/7/I/vvQtFDwqqVkBfPVfQEDAmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/7/G/LmnxNwNp3t4fkK8J7MEAmeoʤDapi/Resources/cache/pools/system/OsQ8hrw2r5/7/M/rmeNbVB5DeCBdaxgCl2gmeS}HyDapi/Resources/cache/pools/system/OsQ8hrw2r5/7/V/Slraxjry7zIhnWaIz3Ggme$Dapi/Resources/cache/pools/system/OsQ8hrw2r5/7/V/bBR5rKuDenloX+mtT-Ggme/YDapi/Resources/cache/pools/system/OsQ8hrw2r5/7/X/5+VJwRInhQxngCplj3eAmeiDapi/Resources/cache/pools/system/OsQ8hrw2r5/7/B/Ep4F96GUQiFMkLh75w0gme͏Dapi/Resources/cache/pools/system/OsQ8hrw2r5/I/H/OY1FHCvqAS0P1S5LT-wgmetFDapi/Resources/cache/pools/system/OsQ8hrw2r5/I/V/FDTFq9HdFPtnwD0MTd4Ame2ɤDapi/Resources/cache/pools/system/OsQ8hrw2r5/I/Y/WYfL3JD4aUS3cK6t7u3gmeSDDapi/Resources/cache/pools/system/OsQ8hrw2r5/I/W/4X-GtvI4JB4nNVcUy2JQmeUdDapi/Resources/cache/pools/system/OsQ8hrw2r5/N/9/V6Rdtb4jnA6+azeCCjswme ӤDapi/Resources/cache/pools/system/OsQ8hrw2r5/N/Z/Tu6kTd9yUFIEmzTlJipAme}f_Dapi/Resources/cache/pools/system/OsQ8hrw2r5/N/A/PIILre824TeYHtR3JjSgme?٤Dapi/Resources/cache/pools/system/OsQ8hrw2r5/N/-/Imp8dIyNcyFCr3RH-xRwmeN-Dapi/Resources/cache/pools/system/OsQ8hrw2r5/N/J/C51v9J7ULPkrAjRvpMCAmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/N/J/GqfF4qBBlmsg7SvOAKIgmedDapi/Resources/cache/pools/system/OsQ8hrw2r5/N/C/PZXpa3z02m60JGDzjfIwmeMDapi/Resources/cache/pools/system/OsQ8hrw2r5/N/L/gCe+wHAPasIwlCfS3oggme3EDapi/Resources/cache/pools/system/OsQ8hrw2r5/G/Z/latXG9ggiuXZNoa8EqowmeEqDapi/Resources/cache/pools/system/OsQ8hrw2r5/G/A/KZH6256TOL39f4ObPRXgmeDDapi/Resources/cache/pools/system/OsQ8hrw2r5/G/M/fT53C+-yl8hOLH3VmLAAme9Dapi/Resources/cache/pools/system/OsQ8hrw2r5/G/4/+a9LPIVE6fUVq7pjQJsgme}zO&Dapi/Resources/cache/pools/system/OsQ8hrw2r5/+/7/V3hwwqO1oa5TQJAKaGcwmeJ&bDapi/Resources/cache/pools/system/OsQ8hrw2r5/6/K/PCCDQzufWJzzLxMZNGiQmee=0Dapi/Resources/cache/pools/system/OsQ8hrw2r5/6/K/-q0MYe9pysPbCWOG18PAmeiDapi/Resources/cache/pools/system/OsQ8hrw2r5/Z/-/n+kGfS8uBJDSk3Ieq5YQmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/Z/F/rLwo5ESj++dtcAgvVwXAmeaTDapi/Resources/cache/pools/system/OsQ8hrw2r5/Z/C/RIaqUMyxqNPdHJEyTrYgmeWDapi/Resources/cache/pools/system/OsQ8hrw2r5/Z/X/T+jMZ2pToPP1OZ9J0E4gme"Dapi/Resources/cache/pools/system/OsQ8hrw2r5/Z/K/wj2D6nO2qHqFZ3bsnQVwme٤Dapi/Resources/cache/pools/system/OsQ8hrw2r5/Z/L/6BwioJyuQxWkxKei7HEwmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/1/S/NNDhBH1Aow3f4VPKTWnAmevKDapi/Resources/cache/pools/system/OsQ8hrw2r5/1/3/6A4lCP0BNlgEko8v-TsAmeJ%Dapi/Resources/cache/pools/system/OsQ8hrw2r5/1/E/ZgZxupE8C17NYevudlVgmeEDapi/Resources/cache/pools/system/OsQ8hrw2r5/1/B/Tii38M0BR1cwTM+uYWBQmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/8/J/XOe97mjD0WUWEm5CIuqwvmevDapi/Resources/cache/pools/system/OsQ8hrw2r5/T/R/8-UGopXIcK5EBHRU14kwmeUO=Dapi/Resources/cache/pools/system/OsQ8hrw2r5/T/R/sW08sUZWg-6banDCH2-Qme)Dapi/Resources/cache/pools/system/OsQ8hrw2r5/T/N/Au+QEfDh3WPMluNyeldgmeK\Dapi/Resources/cache/pools/system/OsQ8hrw2r5/T/S/F24haDgeS+QoMfuFZdAAmeؐSDapi/Resources/cache/pools/system/OsQ8hrw2r5/T/O/uWCgNRwAoVl3ZvvDe8fwme SDapi/Resources/cache/pools/system/OsQ8hrw2r5/T/D/y5Wd01yQp6EMeTe-Uswwme(gѤDapi/Resources/cache/pools/system/OsQ8hrw2r5/T/W/f+pXqNq24DTiccv7Y2yQmeӵeDapi/Resources/cache/pools/system/OsQ8hrw2r5/S/N/09R1sWgEyovhSP8yo4dwme<^ҤDapi/Resources/cache/pools/system/OsQ8hrw2r5/S/A/HOTbPtyMxx9jQW53nHYwme2\Dapi/Resources/cache/pools/system/OsQ8hrw2r5/S/O/fGCLQbg3F0OOYlaPNz1Qme{Dapi/Resources/cache/pools/system/OsQ8hrw2r5/S/P/f+VrsqWwJCIGWSbMGsBgmeUrDapi/Resources/cache/pools/system/OsQ8hrw2r5/S/P/VeJkVIItnwBQN9PcO9vwme0Dapi/Resources/cache/pools/system/OsQ8hrw2r5/S/P/Oj0vJ6JMF2HEamn4SJJQme쎂Dapi/Resources/cache/pools/system/OsQ8hrw2r5/A/7/z8lyPuBMM5Yj6xapSqJQme>5Dapi/Resources/cache/pools/system/OsQ8hrw2r5/A/N/Fk7c3J+nTIDxSQtlGZhQme#sDapi/Resources/cache/pools/system/OsQ8hrw2r5/A/A/B+GGgc7wTs2QF5lA1UQwmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/A/O/zkQYN1br6r2EJXKGYoswme(LODapi/Resources/cache/pools/system/OsQ8hrw2r5/A/P/GkQGsahBzeJMo+cRMhTwmeIDapi/Resources/cache/pools/system/OsQ8hrw2r5/A/P/gdnFqbWQoLJkGctqgvewme].Dapi/Resources/cache/pools/system/OsQ8hrw2r5/A/W/tdk22APgGrq8bXup2ILQme|+Dapi/Resources/cache/pools/system/OsQ8hrw2r5/-/7/pB1GuYSfTqDWswsp-GGwme Dapi/Resources/cache/pools/system/OsQ8hrw2r5/-/A/Pio+HqrwbHA-vjKaKQIAmeBDapi/Resources/cache/pools/system/OsQ8hrw2r5/F/R/tbBU+MxR5rV1Htdjnb9gmemBDapi/Resources/cache/pools/system/OsQ8hrw2r5/F/7/g2wbRsxLOsH8jWtyh0Agme1q Dapi/Resources/cache/pools/system/OsQ8hrw2r5/F/O/QK+PzWAT5fubrPfojomQmeh*Dapi/Resources/cache/pools/system/OsQ8hrw2r5/F/J/rVdlCFOpiVQbA1VOVLkAme7Dapi/Resources/cache/pools/system/OsQ8hrw2r5/F/W/m8cf6BPeiPbqCZGazZzQme)Dapi/Resources/cache/pools/system/OsQ8hrw2r5/O/I/MyaY3WAUKnzcblXn6MRAme! Dapi/Resources/cache/pools/system/OsQ8hrw2r5/O/Z/Je5ks7GkiNY9RDTGMZRwmen)Dapi/Resources/cache/pools/system/OsQ8hrw2r5/O/T/-UM-bxgKxDx8fQBu-gJgmeGDapi/Resources/cache/pools/system/OsQ8hrw2r5/O/M/RnWlDkfy0H1NGFvJcmXgme'6VDapi/Resources/cache/pools/system/OsQ8hrw2r5/O/V/Wu0gDCYRESB-UFqCjEqwme㠌JDapi/Resources/cache/pools/system/OsQ8hrw2r5/O/4/ZbuR7rSan7QmqcyHno1wme@4Dapi/Resources/cache/pools/system/OsQ8hrw2r5/O/X/KI1FBtjE4f4HEt-LP2vwmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/O/3/JfqSSAuCpNTNUjM2iXNQme jRDapi/Resources/cache/pools/system/OsQ8hrw2r5/H/T/uqBvMcblHFy+UCbOF7QwmeM<Dapi/Resources/cache/pools/system/OsQ8hrw2r5/H/J/iAoNEJ3EyEpAPi-Fep1gme Dapi/Resources/cache/pools/system/OsQ8hrw2r5/H/V/nG2KxyRbTzExzbdCMcGgmewĤDapi/Resources/cache/pools/system/OsQ8hrw2r5/H/2/YGNEyazPh14BqnQp0yiwmeAYDapi/Resources/cache/pools/system/OsQ8hrw2r5/H/W/1UkXYuOmaHArDiXwAs9gme?Dapi/Resources/cache/pools/system/OsQ8hrw2r5/M/F/rKH5qWKVya9sQZTdDkxQme$:Dapi/Resources/cache/pools/system/OsQ8hrw2r5/M/H/Hb88Rpe4YJP+fCxHGt7AmeKDapi/Resources/cache/pools/system/OsQ8hrw2r5/M/V/4ez107KY-wes1Elt1SoAmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/M/L/i6cJOd7uINIx0zVILCGAme-Dapi/Resources/cache/pools/system/OsQ8hrw2r5/M/2/tH7oSLMSkT5QhSrtarOwme7Dapi/Resources/cache/pools/system/OsQ8hrw2r5/J/+/-hri6Ku8aRxDOQwdkcrgmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/J/M/W+Yk6l0-sY1Gr6NiGeQwmeΛڤDapi/Resources/cache/pools/system/OsQ8hrw2r5/J/J/ZI3dlsBQbp5p5E7GPfQgmefDapi/Resources/cache/pools/system/OsQ8hrw2r5/J/X/3WTOwDNz5ZJCduG0uebwmeDapi/Resources/cache/pools/system/OsQ8hrw2r5/J/Y/EzzpwETJoQ-5KRWfQKVAme9Dapi/Resources/cache/pools/system/OsQ8hrw2r5/C/R/KjFWvG5VoK9VJZMgKVjQmeVDapi/Resources/cache/pools/system/OsQ8hrw2r5/C/9/RgP-Pc8QLoz3o7FedUPgmeiDapi/Resources/cache/pools/system/OsQ8hrw2r5/C/8/1kfbh1Hu6GYndVt4eXygmeWapi/Resources/cache/Symfony/Config/Framework/WebLinkConfig.php/me/hFapi/Resources/cache/Symfony/Config/Framework/Mailer/EnvelopeConfig.phpmecJˤDapi/Resources/cache/Symfony/Config/Framework/Mailer/HeaderConfig.phpme٤:api/Resources/cache/Symfony/Config/Framework/SsiConfig.php+me+qTapi/Resources/cache/Symfony/Config/Framework/Translator/PseudoLocalizationConfig.php}me}4Japi/Resources/cache/Symfony/Config/Framework/Translator/ProviderConfig.php me P?api/Resources/cache/Symfony/Config/Framework/ProfilerConfig.phpmer>api/Resources/cache/Symfony/Config/Framework/SecretsConfig.php me 3Eapi/Resources/cache/Symfony/Config/Framework/PropertyAccessConfig.phpmeЛhJapi/Resources/cache/Symfony/Config/Framework/Messenger/TransportConfig.php*me*]api/Resources/cache/Symfony/Config/Framework/Messenger/Serializer/SymfonySerializerConfig.php"me"%<Kapi/Resources/cache/Symfony/Config/Framework/Messenger/SerializerConfig.phpl mel Za^api/Resources/cache/Symfony/Config/Framework/Messenger/TransportConfig/RetryStrategyConfig.php5me5Hapi/Resources/cache/Symfony/Config/Framework/Messenger/RoutingConfig.php<me<~̤Uapi/Resources/cache/Symfony/Config/Framework/Messenger/BusConfig/MiddlewareConfig.phpme̳Dapi/Resources/cache/Symfony/Config/Framework/Messenger/BusConfig.php me =Capi/Resources/cache/Symfony/Config/Framework/PropertyInfoConfig.php3me3N=api/Resources/cache/Symfony/Config/Framework/MailerConfig.phpme5:api/Resources/cache/Symfony/Config/Framework/UidConfig.php me 6@api/Resources/cache/Symfony/Config/Framework/ExceptionConfig.phpKmeKr >api/Resources/cache/Symfony/Config/Framework/SessionConfig.php4me4e@Aapi/Resources/cache/Symfony/Config/Framework/HttpClientConfig.phpmeܤ@api/Resources/cache/Symfony/Config/Framework/PhpErrorsConfig.phpme?api/Resources/cache/Symfony/Config/Framework/NotifierConfig.phpmeStVȤ>api/Resources/cache/Symfony/Config/Framework/RequestConfig.phpme:Bapi/Resources/cache/Symfony/Config/Framework/AnnotationsConfig.php} me} j(Eapi/Resources/cache/Symfony/Config/Framework/Assets/PackageConfig.phpmeXapi/Resources/cache/Symfony/Config/Framework/Validation/NotCompromisedPasswordConfig.php5me5 6Iapi/Resources/cache/Symfony/Config/Framework/Validation/MappingConfig.php%me%,Mapi/Resources/cache/Symfony/Config/Framework/Validation/AutoMappingConfig.phpMmeMBФ;api/Resources/cache/Symfony/Config/Framework/LockConfig.phpme;api/Resources/cache/Symfony/Config/Framework/FormConfig.php me Papi/Resources/cache/Symfony/Config/Security/ProviderConfig/Memory/UserConfig.phpmeU`Iapi/Resources/cache/Symfony/Config/Security/ProviderConfig/LdapConfig.php~me~bKapi/Resources/cache/Symfony/Config/Security/ProviderConfig/MemoryConfig.phpE meE Japi/Resources/cache/Symfony/Config/Security/ProviderConfig/ChainConfig.phpVmeVBFc)Dapi/Resources/cache/Symfony/Config/Security/PasswordHasherConfig.phpmeT>api/Resources/cache/Symfony/Config/Security/ProviderConfig.phpmep=api/Resources/cache/Symfony/Config/Security/EncoderConfig.phpzmezؤ>api/Resources/cache/Symfony/Config/Security/FirewallConfig.phppmepYOapi/Resources/cache/Symfony/Config/Security/FirewallConfig/RememberMeConfig.php0me0aܤNapi/Resources/cache/Symfony/Config/Security/FirewallConfig/FormLoginConfig.phpAmeAwRapi/Resources/cache/Symfony/Config/Security/FirewallConfig/JsonLoginLdapConfig.phpp)mep)Napi/Resources/cache/Symfony/Config/Security/FirewallConfig/HttpBasicConfig.phpme&Japi/Resources/cache/Symfony/Config/Security/FirewallConfig/GuardConfig.php me 'iNapi/Resources/cache/Symfony/Config/Security/FirewallConfig/JsonLoginConfig.phpme}Xapi/Resources/cache/Symfony/Config/Security/FirewallConfig/Logout/DeleteCookieConfig.php. me. [rOapi/Resources/cache/Symfony/Config/Security/FirewallConfig/SwitchUserConfig.php me [BTapi/Resources/cache/Symfony/Config/Security/FirewallConfig/LoginThrottlingConfig.php me nNapi/Resources/cache/Symfony/Config/Security/FirewallConfig/LoginLinkConfig.php]3me]3 kfapi/Resources/cache/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProvider/DoctrineConfig.phpmeҤ]api/Resources/cache/Symfony/Config/Security/FirewallConfig/RememberMe/TokenProviderConfig.php) me) TפRapi/Resources/cache/Symfony/Config/Security/FirewallConfig/FormLoginLdapConfig.phpxMmexM5WRapi/Resources/cache/Symfony/Config/Security/FirewallConfig/HttpBasicLdapConfig.phpme ĞȤIapi/Resources/cache/Symfony/Config/Security/FirewallConfig/X509Config.php* me* yNapi/Resources/cache/Symfony/Config/Security/FirewallConfig/AnonymousConfig.phpme+fOapi/Resources/cache/Symfony/Config/Security/FirewallConfig/RemoteUserConfig.phpme&Kapi/Resources/cache/Symfony/Config/Security/FirewallConfig/LogoutConfig.phpmeʿ,Kapi/Resources/cache/Symfony/Config/Security/AccessDecisionManagerConfig.phpmeICapi/Resources/cache/Symfony/Config/Security/AccessControlConfig.phpmeϐ5api/Resources/cache/Symfony/Config/SecurityConfig.php:me:m4api/Resources/cache/Symfony/Config/MonologConfig.phpmetˤ<api/Resources/cache/Symfony/Config/Monolog/HandlerConfig.phpmeyդVapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/ProcessPsr3MessagesConfig.php me ӬxLapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/PublisherConfig.php me Hapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/RedisConfig.phpme=Papi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/ElasticsearchConfig.phpmep7Qapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/EmailPrototypeConfig.phpVmeVˊHapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/MongoConfig.phpmed%7Rapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/VerbosityLevelsConfig.phpmeQ,CIapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/PredisConfig.php6me6|!vSapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/ExcludedHttpCodeConfig.php[me[Kapi/Resources/cache/Symfony/Config/Monolog/HandlerConfig/ChannelsConfig.phpmev6api/Resources/cache/Symfony/Config/FrameworkConfig.phpmeMTapi/Resources/cache/ContainerUyuaDAM/getSecurity_Firewall_Map_Context_ApiService.php me ͜Fapi/Resources/cache/ContainerUyuaDAM/getComposerController2Service.phpmea;UNapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_SecretsListService.phpmeHiPapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_SecretsRemoveService.phpmeWؤCapi/Resources/cache/ContainerUyuaDAM/getGraphicsLibCheckService.phpme>ڤCapi/Resources/cache/ContainerUyuaDAM/getPhpCliControllerService.phpemeei_Dapi/Resources/cache/ContainerUyuaDAM/getTaskDeleteCommandService.phpmeL]api/Resources/cache/ContainerUyuaDAM/get_Security_Command_UserPasswordEncoder_LazyService.phpmeTh`api/Resources/cache/ContainerUyuaDAM/get_Console_Command_SecretsEncryptFromLocal_LazyService.phpme\jҤGapi/Resources/cache/ContainerUyuaDAM/getConstraintControllerService.php\me\A4api/Resources/cache/ContainerUyuaDAM/removed-ids.phpʎmeʎ Gapi/Resources/cache/ContainerUyuaDAM/getProcessRunnerCommandService.phpPmePCapi/Resources/cache/ContainerUyuaDAM/getJwtAuthenticatorService.phpmelڤCapi/Resources/cache/ContainerUyuaDAM/getContaoControllerService.php>me>wEapi/Resources/cache/ContainerUyuaDAM/getSecurity_AccessMapService.php:me:*5EuHapi/Resources/cache/ContainerUyuaDAM/getIntegrityCheckFactoryService.phpmekJapi/Resources/cache/ContainerUyuaDAM/get_ServiceLocator_DieSC3PService.phpme/Qapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_ContainerDebugService.phpme9Wapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_ContainerDebug_LazyService.phpmeDi1aapi/Resources/cache/ContainerUyuaDAM/getSecurity_Authentication_Listener_Anonymous_ApiService.phpmeͤFapi/Resources/cache/ContainerUyuaDAM/getJwtCookieControllerService.phpmev%ʤEapi/Resources/cache/ContainerUyuaDAM/getRouter_CacheWarmerService.phpme-mDapi/Resources/cache/ContainerUyuaDAM/getBackupRestoreTaskService.phpme Hapi/Resources/cache/ContainerUyuaDAM/getConsole_CommandLoaderService.phpme^b<api/Resources/cache/ContainerUyuaDAM/getCache_AppService.phpmeh/Mapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_SecretsSetService.phpmet"匤Tapi/Resources/cache/ContainerUyuaDAM/getSecurity_Command_UserPasswordHashService.phpmeߎHapi/Resources/cache/ContainerUyuaDAM/getConsoleProcessFactoryService.phpcmecV V\api/Resources/cache/ContainerUyuaDAM/get_Console_Command_ConfigDumpReference_LazyService.phpmeMTCapi/Resources/cache/ContainerUyuaDAM/getCache_AppClearerService.phpjmej:ŤAapi/Resources/cache/ContainerUyuaDAM/getUserControllerService.phpme]Napi/Resources/cache/ContainerUyuaDAM/getConsole_Command_RouterDebugService.phpmeZQapi/Resources/cache/ContainerUyuaDAM/Contao_ManagerApi_ApiKernelProdContainer.phpmehEKapi/Resources/cache/ContainerUyuaDAM/getAnnotations_CachedReaderService.phplmel1Wapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_CachePoolPrune_LazyService.phpjmej?44Dapi/Resources/cache/ContainerUyuaDAM/getTaskUpdateCommandService.phpmeHAapi/Resources/cache/ContainerUyuaDAM/getTaskControllerService.phpme?\Lapi/Resources/cache/ContainerUyuaDAM/getMaintenanceModeControllerService.phpmeݡAapi/Resources/cache/ContainerUyuaDAM/getFileControllerService.php.me.CIǤAapi/Resources/cache/ContainerUyuaDAM/getRouting_LoaderService.phpmecj >api/Resources/cache/ContainerUyuaDAM/getEnvironmentService.phpmeCapi/Resources/cache/ContainerUyuaDAM/getServicesResetterService.php< me< ӃDapi/Resources/cache/ContainerUyuaDAM/getOpcacheControllerService.phpjmejwPapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_AssetsInstallService.phpme}LEapi/Resources/cache/ContainerUyuaDAM/getLoginAuthenticatorService.php.me.y ۤOapi/Resources/cache/ContainerUyuaDAM/getSecurity_EventDispatcher_ApiService.php me V¤?api/Resources/cache/ContainerUyuaDAM/getCache_SystemService.phpmeןAapi/Resources/cache/ContainerUyuaDAM/getMonolog_LoggerService.phpme3Xapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_CachePoolDelete_LazyService.phpmeΑJapi/Resources/cache/ContainerUyuaDAM/getMonolog_Logger_SecurityService.phpme\@@api/Resources/cache/ContainerUyuaDAM/getUpdateCommandService.phpmeojZUapi/Resources/cache/ContainerUyuaDAM/getSecurity_Access_AuthenticatedVoterService.phpAmeAV*"ۤ?api/Resources/cache/ContainerUyuaDAM/getProcessCheckService.phpme\CjDapi/Resources/cache/ContainerUyuaDAM/getSessionControllerService.phpme)Aapi/Resources/cache/ContainerUyuaDAM/getSelfUpdateTaskService.phpmeb"Fapi/Resources/cache/ContainerUyuaDAM/getCache_GlobalClearerService.phpHmeH C >api/Resources/cache/ContainerUyuaDAM/getTaskManagerService.phpA meA o Hapi/Resources/cache/ContainerUyuaDAM/getConsole_ErrorListenerService.phpme&IKapi/Resources/cache/ContainerUyuaDAM/getUploadPackagesControllerService.phpmeMn<api/Resources/cache/ContainerUyuaDAM/getContaoApiService.php]me]#OjɤCapi/Resources/cache/ContainerUyuaDAM/getMemoryLimitCheckService.phpme ejWapi/Resources/cache/ContainerUyuaDAM/get_Security_Command_DebugFirewall_LazyService.phpmefBapi/Resources/cache/ContainerUyuaDAM/getSysTempDirCheckService.phpme7q ]api/Resources/cache/ContainerUyuaDAM/getSecurity_Authentication_Listener_Guard_ApiService.php me 8BVapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_SecretsRemove_LazyService.phplmeloMmRapi/Resources/cache/ContainerUyuaDAM/getSecurity_Authentication_ManagerService.phpmegUHWapi/Resources/cache/ContainerUyuaDAM/getSecurity_Authentication_GuardHandlerService.phplmelsrCapi/Resources/cache/ContainerUyuaDAM/getConfigControllerService.php[me[y=api/Resources/cache/ContainerUyuaDAM/getSelfUpdateService.phpmedCapi/Resources/cache/ContainerUyuaDAM/getTaskAbortCommandService.phpmet@api/Resources/cache/ContainerUyuaDAM/getCloudResolverService.phpGmeGz$^api/Resources/cache/ContainerUyuaDAM/get_Console_Command_SecretsDecryptToLocal_LazyService.phpmekƤRapi/Resources/cache/ContainerUyuaDAM/getSecurity_EncoderFactory_GenericService.phpmeT3Fapi/Resources/cache/ContainerUyuaDAM/getAccessKeyControllerService.phpme6<$Capi/Resources/cache/ContainerUyuaDAM/getPhpWebControllerService.phpmea}"Papi/Resources/cache/ContainerUyuaDAM/getConsole_Command_ContainerLintService.phpmeNapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_About_LazyService.php?me?J(,6Tapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_RouterMatch_LazyService.phpqmeq[;8=api/Resources/cache/ContainerUyuaDAM/getTranslatorService.phpmeGapi/Resources/cache/ContainerUyuaDAM/getSelfUpdateControllerService.phpymey˘Kapi/Resources/cache/ContainerUyuaDAM/getSecurity_ChannelListenerService.phpme4 lDapi/Resources/cache/ContainerUyuaDAM/getManagerControllerService.phpme#k|$Capi/Resources/cache/ContainerUyuaDAM/getBackupControllerService.phpymeyqQapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_YamlLint_LazyService.phpZmeZc-u^api/Resources/cache/ContainerUyuaDAM/get_Container_Private_Security_PasswordEncoderService.phpGmeG_0 @api/Resources/cache/ContainerUyuaDAM/getSecrets_VaultService.phpme`tVapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_ConfigDumpReferenceService.phpmeBNapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_CacheWarmupService.phpme(Bapi/Resources/cache/ContainerUyuaDAM/getLoaderInterfaceService.phpme)Capi/Resources/cache/ContainerUyuaDAM/getBackupCreateTaskService.phpme]LϤaapi/Resources/cache/ContainerUyuaDAM/getSecurity_Authentication_Provider_Anonymous_ApiService.php<me<ܲURapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_CachePoolDeleteService.phpme$!8Bapi/Resources/cache/ContainerUyuaDAM/getCloudControllerService.phpbmeb6ˤ<api/Resources/cache/ContainerUyuaDAM/getSetupTaskService.phpWmeW{Sapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_SecretsSet_LazyService.phpRmeRg|Eapi/Resources/cache/ContainerUyuaDAM/getTemplateControllerService.phpqmeqmʝ ]api/Resources/cache/ContainerUyuaDAM/get_Console_Command_EventDispatcherDebug_LazyService.phpmeĤVapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_CachePoolList_LazyService.phplmelؙWapi/Resources/cache/ContainerUyuaDAM/getSecurity_Command_UserPasswordEncoderService.php me -٠Wapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_EventDispatcherDebugService.phppmep_ĤKapi/Resources/cache/ContainerUyuaDAM/getArgumentResolver_ServiceService.phpme1N+ŤTapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_CacheWarmup_LazyService.phpVmeV^NIgFapi/Resources/cache/ContainerUyuaDAM/getCache_SystemClearerService.phpme ƤGapi/Resources/cache/ContainerUyuaDAM/getMonolog_Logger_TasksService.phpme5{DFapi/Resources/cache/ContainerUyuaDAM/getAdminUserControllerService.phpme7_Hapi/Resources/cache/ContainerUyuaDAM/getIntegrityCheckCommandService.phpmeVapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_AssetsInstall_LazyService.phpme Eapi/Resources/cache/ContainerUyuaDAM/getPhpExtensionsCheckService.phpmeAapi/Resources/cache/ContainerUyuaDAM/getAuthControllerService.phpmeQ]api/Resources/cache/ContainerUyuaDAM/getSecurity_Authentication_Provider_Guard_ApiService.php me 29ƤLapi/Resources/cache/ContainerUyuaDAM/getInstallToolLockControllerService.phpme`F]Eapi/Resources/cache/ContainerUyuaDAM/getComposerControllerService.phpme+$>api/Resources/cache/ContainerUyuaDAM/getCacheWarmerService.php%me%/Eapi/Resources/cache/ContainerUyuaDAM/getRedirectControllerService.phpBmeB=eݤEapi/Resources/cache/ContainerUyuaDAM/getAnnotations_ReaderService.phpme>"@api/Resources/cache/ContainerUyuaDAM/getUploadsConfigService.phpmeNapi/Resources/cache/ContainerUyuaDAM/getDatabaseMigrationControllerService.phpmeJapi/Resources/cache/ContainerUyuaDAM/get_ServiceLocator_4uC2EhjService.phpme{AsKapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_YamlLintService.phpme@Gapi/Resources/cache/ContainerUyuaDAM/getConfigBuilder_WarmerService.phpmeBm^qLapi/Resources/cache/ContainerUyuaDAM/getPasswordlessAuthenticatorService.phpLmeLۻ$Aapi/Resources/cache/ContainerUyuaDAM/getClearCacheTaskService.phpmeFᩤHapi/Resources/cache/ContainerUyuaDAM/getRootPackageControllerService.phpmelQapi/Resources/cache/ContainerUyuaDAM/getSecurity_PasswordHasherFactoryService.phpameai,@api/Resources/cache/ContainerUyuaDAM/getContaoConsoleService.phpme#P@api/Resources/cache/ContainerUyuaDAM/getManagerConfigService.phpmeM(4YMapi/Resources/cache/ContainerUyuaDAM/getSecurity_UserValueResolverService.phpmeMgXapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_DebugAutowiring_LazyService.phpme>a>Qapi/Resources/cache/ContainerUyuaDAM/getSecurity_Command_DebugFirewallService.phpme{Japi/Resources/cache/ContainerUyuaDAM/getAnnotations_CacheWarmerService.php6me6NӤWapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_CachePoolClear_LazyService.phpjmej(ФRapi/Resources/cache/ContainerUyuaDAM/get_Container_Private_CacheClearerService.phpme&yMapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_CacheClearService.phpmeB[Japi/Resources/cache/ContainerUyuaDAM/get_ServiceLocator_XUrKPVUService.php me n#ѤDapi/Resources/cache/ContainerUyuaDAM/getPhpinfoControllerService.phpjmejrPapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_CachePoolListService.phpmevәEapi/Resources/cache/ContainerUyuaDAM/getAllowUrlFopenCheckService.phpme9vCapi/Resources/cache/ContainerUyuaDAM/getRebuildCacheTaskService.phpomeos ld[api/Resources/cache/ContainerUyuaDAM/get_Console_Command_SecretsGenerateKey_LazyService.phpmeX?Tapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_RouterDebug_LazyService.phpimeiO値Qapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_CachePoolClearService.phpmeV@api/Resources/cache/ContainerUyuaDAM/getLogControllerService.php)me)GLapi/Resources/cache/ContainerUyuaDAM/getContainer_EnvVarProcessorService.phpme1>*>api/Resources/cache/ContainerUyuaDAM/getInstallTaskService.php"me"±Tapi/Resources/cache/ContainerUyuaDAM/getContainer_EnvVarProcessorsLocatorService.php3 me3 YդDapi/Resources/cache/ContainerUyuaDAM/getExceptionListenerService.phpme:?api/Resources/cache/ContainerUyuaDAM/getSymlinkCheckService.phpme{]:api/Resources/cache/ContainerUyuaDAM/getRequestService.phpme)4?api/Resources/cache/ContainerUyuaDAM/getSessionCheckService.phpme[Lapi/Resources/cache/ContainerUyuaDAM/getMissingPackagesControllerService.phpmeWUapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_SecretsGenerateKeyService.phpme6%=api/Resources/cache/ContainerUyuaDAM/getUpdateTaskService.phpme:¤Tapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_SecretsList_LazyService.phpPmePI\Aapi/Resources/cache/ContainerUyuaDAM/getComposerConfigService.phpme\ Zapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_SecretsEncryptFromLocalService.phpBmeB?Japi/Resources/cache/ContainerUyuaDAM/getSecurity_AccessListenerService.phpme7T٤Qapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_CachePoolPruneService.phpymeyɤZapi/Resources/cache/ContainerUyuaDAM/get_Security_Command_UserPasswordHash_LazyService.phpmeCTapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_ConfigDebug_LazyService.phpomeo ФEapi/Resources/cache/ContainerUyuaDAM/getDatabaseControllerService.phpkmekJapi/Resources/cache/ContainerUyuaDAM/get_ServiceLocator_3CSu656Service.phpmeoE}Xapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_SecretsDecryptToLocalService.phpFmeF\ Japi/Resources/cache/ContainerUyuaDAM/getLocalPackagesControllerService.phpmeѾ?api/Resources/cache/ContainerUyuaDAM/getAboutCommandService.phpme;Eapi/Resources/cache/ContainerUyuaDAM/getTokenAuthenticatorService.phpmeC>Rapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_DebugAutowiringService.phpme0I=api/Resources/cache/ContainerUyuaDAM/getServerInfoService.php6me67Napi/Resources/cache/ContainerUyuaDAM/getConsole_Command_RouterMatchService.php~me~PCBapi/Resources/cache/ContainerUyuaDAM/getErrorControllerService.phplmel#,Japi/Resources/cache/ContainerUyuaDAM/get_ServiceLocator_XRi_MOZService.phpmeSapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_CacheClear_LazyService.phpHmeHHapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_AboutService.phpmepjCapi/Resources/cache/ContainerUyuaDAM/getDumpAutoloadTaskService.phpmeLJCVapi/Resources/cache/ContainerUyuaDAM/get_Console_Command_ContainerLint_LazyService.phpmeäNapi/Resources/cache/ContainerUyuaDAM/getConsole_Command_ConfigDebugService.phpmeo #api/Resources/cache/annotations.map)me)8J)f#api/Resources/config/config_dev.ymlme=y$api/Resources/config/config_prod.ymlmeݣ!api/Resources/config/services.ymlmegѤapi/Resources/config/config.yml9me9zHapi/Resources/i18n/cs.ymlmee%ߤapi/Resources/i18n/br.ymlmeapi/Resources/i18n/es.ymlmeapi/Resources/i18n/pt.ymlmeۛ|api/Resources/i18n/de.yml.me.(Lapi/Resources/i18n/it.ymlmekapi/Resources/i18n/pl.ymlme6Tapi/Resources/i18n/fa.yml; me; 1,api/Resources/i18n/sv.ymlme{͜Ԥapi/Resources/i18n/en.ymlmefZ/api/Resources/i18n/ja.ymlmePfapi/Resources/i18n/ru.ymlmeapi/Resources/i18n/sr.ymlmeGapi/Resources/i18n/fr.ymlme>{[api/Resources/i18n/lv.ymlNmeN'ݤapi/Resources/i18n/tr.ymlmefapi/Resources/i18n/nl.ymlme4api/Resources/i18n/zh.yml me ֤api/ApiApplication.php5 me5 4#api/Tests/Composer/CloudJobTest.phpme^!api/Controller/FileController.phpImeIYe@!api/Controller/UserController.phpme@H api/Controller/LogController.php<me<l (api/Controller/Config/AuthController.phpmeQ2api/Controller/Config/AbstractConfigController.php)me)s,api/Controller/Config/ComposerController.php7me7r +api/Controller/Config/ManagerController.php3me3&Y!api/Controller/TaskController.php me /*api/Controller/Server/PhpWebController.phpme;ۉ*api/Controller/Server/ContaoController.php"me"Rˤ,api/Controller/Server/DatabaseController.php me o-api/Controller/Server/AdminUserController.php[ me[ +api/Controller/Server/PhpinfoController.phpmep%+api/Controller/Server/OpcacheController.php_me_ؚ*api/Controller/Server/ConfigController.phpme@n-,api/Controller/Server/ComposerController.phpmeoϤ*api/Controller/Server/PhpCliController.phpme$.api/Controller/Server/SelfUpdateController.phpme 쟤$api/Controller/SessionController.phpme5api/Controller/Packages/MissingPackagesController.php] me] T v4api/Controller/Packages/UploadPackagesController.php@'me@' 1api/Controller/Packages/RootPackageController.phpme%I+api/Controller/Packages/CloudController.php[ me[ ى3api/Controller/Packages/LocalPackagesController.phpme㦜'api/Controller/ConstraintController.phpme;rs3api/Controller/Contao/InstallToolLockController.phpc mec G?5api/Controller/Contao/DatabaseMigrationController.phpL#meL##-api/Controller/Contao/AccessKeyController.php me ؤ*api/Controller/Contao/BackupController.phpme<43api/Controller/Contao/MaintenanceModeController.phpmeCӗh-api/Controller/Contao/JwtCookieController.phpz mez Ҵٝapi/ApiKernel.php"'me"'api/System/ServerInfo.phpmeZ$api/System/Request.phpme8api/System/SelfUpdate.phpnmenapi/Task/TaskStatus.phpGmeGHK&api/Task/Composer/DumpAutoloadTask.phpme($api/Task/Composer/ClearCacheTask.phpdmed: #api/Task/Manager/SelfUpdateTask.phpmeݤapi/Task/AbstractTask.phpTmeTۤapi/Task/Packages/SetupTask.php.me.&L api/Task/Packages/UpdateTask.php")me")59 !api/Task/Packages/InstallTask.phpU meU sE*api/Task/Packages/AbstractPackagesTask.php<me<iz=$api/Task/Contao/BackupCreateTask.phphmeh "=%api/Task/Contao/BackupRestoreTask.phpSmeS0$api/Task/Contao/RebuildCacheTask.php3 me3 ]`api/Task/TaskManager.phpme=p$api/Task/TaskInterface.phpme]$api/Task/TaskConfig.php} me} HYq%api/Command/IntegrityCheckCommand.php* me* \wapi/Command/UpdateCommand.php7me7< api/Command/TaskAbortCommand.phpme>Ϥ!api/Command/TaskUpdateCommand.phpLmeLTԤ$api/Command/ProcessRunnerCommand.phpmeg|0api/Command/AboutCommand.phpme5!api/Command/TaskDeleteCommand.php"me"+vapi/I18n/Translator.phpt met ߤ%api/Exception/ApiProblemException.phpsmes Q\(api/Exception/ProcessOutputException.phpmeZ &api/Exception/InvalidJsonException.phpme껤"api/Exception/RequestException.phpmeW91)api/IntegrityCheck/AllowUrlFopenCheck.php!me!-.'api/IntegrityCheck/GraphicsLibCheck.phpme&api/IntegrityCheck/SysTempDirCheck.phpmeP.api/IntegrityCheck/IntegrityCheckInterface.phpzmez{ |#api/IntegrityCheck/ProcessCheck.phpmenW$#api/IntegrityCheck/SymlinkCheck.phpmeߠ#api/IntegrityCheck/SessionCheck.php me 5 ˋ'api/IntegrityCheck/MemoryLimitCheck.phpme)api/IntegrityCheck/PhpExtensionsCheck.phpmeP-api/IntegrityCheck/AbstractIntegrityCheck.phpme:{,api/IntegrityCheck/IntegrityCheckFactory.phpme7'api/Process/ContaoApi.phpmeU!api/Process/ProcessController.phpemeexƤ%api/Process/Forker/AbstractForker.php me [2&api/Process/Forker/ForkerInterface.phpdmed/^#api/Process/Forker/DisownForker.phpme] "api/Process/Forker/NohupForker.phpme!)api/Process/Forker/WindowsStartForker.phpme #api/Process/Forker/InlineForker.phpmeѶ#api/Process/Utf8Process.phpmeSҤapi/Process/ProcessRunner.phpmeäapi/Process/AbstractProcess.phpme/ api/Process/ContaoConsole.php!me!vE#api/Process/PhpExecutableFinder.phpnmenL %api/Process/ConsoleProcessFactory.phpme` api/console8 me8 i.$api/EventListener/LocaleListener.phpme.ʴ,'api/EventListener/ExceptionListener.phpQ meQ Hq&api/EventListener/SecurityListener.phpme_m)api/EventListener/JsonRequestListener.phpmeĩ downgrade.php0me0q2$vendor/seld/phar-utils/composer.lock/me/cւ(vendor/seld/phar-utils/LICENSE"me"?e vendor/seld/phar-utils/README.mdemee:N$vendor/seld/phar-utils/composer.json;me;=%vendor/seld/phar-utils/src/Linter.php me #: )vendor/seld/phar-utils/src/Timestamps.phpkmekFvendor/seld/jsonlint/LICENSE"me"asy!vendor/seld/jsonlint/bin/jsonlint me !vendor/seld/jsonlint/CHANGELOG.md me ݣʤvendor/seld/jsonlint/README.md me p'j"vendor/seld/jsonlint/composer.jsonTmeTz|s@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.phpmeArQ;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.phpmem2;0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.php"me"; 4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php*me*5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.php}Yme}Y3q"vendor/seld/signal-handler/LICENSE"me"?e(vendor/seld/signal-handler/composer.jsonmec0vendor/seld/signal-handler/src/SignalHandler.phpOmeO,Cvendor/autoload.phpmelvendor/bin/yaml-lint me 8Fvendor/composer/composer/src/Composer/Repository/RepositoryManager.phpmenHEvendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.phpJVmeJV!qBvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.phpmerOBvendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php4me4매Avendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.phpme٤Gvendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.phpme%6ɤKvendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php8me8MnBvendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php"me"QǪȤEvendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.phpRmeR#Kvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php me O 4ۤEvendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.phpme{YߤCvendor/composer/composer/src/Composer/Repository/PathRepository.php!me!OJvendor/composer/composer/src/Composer/Repository/VersionCacheInterface.phpdmed~Kvendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.phpme=Evendor/composer/composer/src/Composer/Repository/FilterRepository.phpmeXHvendor/composer/composer/src/Composer/Repository/InstalledRepository.php3me3!Nvendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.phpmeXD@Ivendor/composer/composer/src/Composer/Repository/FilesystemRepository.php>me>/lMvendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.phpmef~Tvendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.phpmeF*'Ovendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.phpmef=<vendor/composer/composer/src/Composer/Util/PackageSorter.phpNmeN8 >vendor/composer/composer/src/Composer/Util/ConfigValidator.php[$me[$zf2vendor/composer/composer/src/Composer/Util/Git.php Zme ZKOܤ2vendor/composer/composer/src/Composer/Util/Svn.php%me%Bͤ=vendor/composer/composer/src/Composer/Util/NoProxyPattern.php)me)x=vendor/composer/composer/src/Composer/Util/ComposerMirror.phpG meG |u:vendor/composer/composer/src/Composer/Util/PackageInfo.phpAmeA#R9vendor/composer/composer/src/Composer/Util/AuthHelper.php8me82vendor/composer/composer/src/Composer/Util/Zip.phpR meR Aä7vendor/composer/composer/src/Composer/Util/Silencer.php;me;@vendor/composer/composer/src/Composer/Util/Http/RequestProxy.phpmeHh9Bvendor/composer/composer/src/Composer/Util/Http/CurlDownloader.phptmet!@vendor/composer/composer/src/Composer/Util/Http/CurlResponse.phpme1'ɤ?vendor/composer/composer/src/Composer/Util/MetadataMinifier.phpme#`>vendor/composer/composer/src/Composer/Util/ProcessExecutor.php/8me/8@_1vendor/composer/composer/src/Composer/Util/Hg.php me ˤ?vendor/composer/composer/src/Composer/Util/RemoteFilesystem.phpkmek;vendor/composer/composer/src/Composer/Util/ErrorHandler.php me cFޤ9vendor/composer/composer/src/Composer/Util/SyncHelper.php me ެpQ3vendor/composer/composer/src/Composer/Util/Loop.php2me2Avendor/composer/composer/src/Composer/Config/JsonConfigSource.php()me()Fvendor/composer/composer/src/Composer/Config/ConfigSourceInterface.phpfmef(פ=vendor/composer/composer/src/Composer/SelfUpdate/Versions.phpme.]!9vendor/composer/composer/src/Composer/SelfUpdate/Keys.phpme-*/vendor/composer/composer/src/Composer/Cache.php+me+2v?vendor/composer/composer/src/Composer/EventDispatcher/Event.phpme~kRvendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.phpme;;Ivendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.phpumeuUcRvendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.phpumeuh_}9vendor/composer/composer/src/Composer/PartialComposer.phpF meF =:vendor/composer/composer/src/Composer/Platform/Runtime.phpmeH.?vendor/composer/composer/src/Composer/Platform/HhvmDetector.phpme8 :vendor/composer/composer/src/Composer/Platform/Version.php me Ԥ5vendor/composer/composer/src/Composer/IO/BufferIO.php me /a6vendor/composer/composer/src/Composer/IO/ConsoleIO.phpK)meK)51 3vendor/composer/composer/src/Composer/IO/BaseIO.phpmef$)3vendor/composer/composer/src/Composer/IO/NullIO.php me {8vendor/composer/composer/src/Composer/IO/IOInterface.phpZ meZ =vendor/composer/composer/src/Composer/Plugin/PluginEvents.phpemee1+ˤCvendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php2me2:q@Kvendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.phpmed,Fvendor/composer/composer/src/Composer/Plugin/Capability/Capability.phpmeL kGvendor/composer/composer/src/Composer/Plugin/PluginBlockedException.phpmeNu=vendor/composer/composer/src/Composer/Plugin/CommandEvent.phpymey/@vendor/composer/composer/src/Composer/Plugin/PluginInterface.php?me?#?">vendor/composer/composer/src/Composer/Plugin/PluginManager.phpxmex̼Evendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php9me9Fvendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php} me} 8vendor/composer/composer/src/Composer/Plugin/Capable.phpmeuCvendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.phpmeHBvendor/composer/composer/src/Composer/Downloader/RarDownloader.php me Rvendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.phpCmeCHBvendor/composer/composer/src/Composer/Downloader/VcsDownloader.php3me3jHvendor/composer/composer/src/Composer/Downloader/FilesystemException.phpmeJDvendor/composer/composer/src/Composer/Downloader/DownloadManager.phpR<meR<HBvendor/composer/composer/src/Composer/Downloader/TarDownloader.phpmeK7JEvendor/composer/composer/src/Composer/Downloader/FossilDownloader.phpmei[Qvendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.phpme`ܤHvendor/composer/composer/src/Composer/Downloader/DownloaderInterface.phpmexAvendor/composer/composer/src/Composer/Downloader/XzDownloader.phpumeubAvendor/composer/composer/src/Composer/Downloader/HgDownloader.phpme|Bvendor/composer/composer/src/Composer/Downloader/SvnDownloader.php"me"-eCvendor/composer/composer/src/Composer/Downloader/FileDownloader.phptNmetNVadNJvendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php'me'}s$Gvendor/composer/composer/src/Composer/Downloader/TransportException.phpme;*sFvendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php!me! Lvendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php6me61N}Cvendor/composer/composer/src/Composer/Downloader/PharDownloader.phpme/&֤Cvendor/composer/composer/src/Composer/Downloader/PathDownloader.php2me2ȞOGvendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php me R.Bvendor/composer/composer/src/Composer/Downloader/GitDownloader.php9bme9bBvendor/composer/composer/src/Composer/Downloader/ZipDownloader.php4me4#ϤCvendor/composer/composer/src/Composer/Downloader/GzipDownloader.phpme~o 0vendor/composer/composer/src/Composer/Config.php_me_0M6vendor/composer/composer/src/Composer/Script/Event.php me D]=vendor/composer/composer/src/Composer/Script/ScriptEvents.phpmeKGꮤMvendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php me z22vendor/composer/composer/src/Composer/Composer.phpmew=TFvendor/composer/composer/src/Composer/Json/JsonValidationException.phplmelexˤ<vendor/composer/composer/src/Composer/Json/JsonFormatter.phpme곤7vendor/composer/composer/src/Composer/Json/JsonFile.php2me2<^=>vendor/composer/composer/src/Composer/Json/JsonManipulator.phpPmeP;vendor/composer/composer/src/Composer/InstalledVersions.php?me?FbWvendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.phpzmez=~ibvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.phpxmexnҤYvendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.phpme}|(̤Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.phpw mew c;Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.phpmeIwYvendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.phpbmeb&Yldvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php~me~Hvendor/composer/composer/src/Composer/DependencyResolver/Transaction.php7me7aTLvendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.phpxmex-]Qvendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php(me(tʺKvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.phpAmeA>Hvendor/composer/composer/src/Composer/DependencyResolver/GenericRule.phpme6|Lvendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.phpmeSiߤCvendor/composer/composer/src/Composer/DependencyResolver/Solver.phpgmegӂuAvendor/composer/composer/src/Composer/DependencyResolver/Rule.phpNmeNUGJvendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php me 7|KTvendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php^me^|!8Ovendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.phpme/dJvendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php'me'XJvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php me 'Dvendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php=me=_4Avendor/composer/composer/src/Composer/DependencyResolver/Pool.php me ?6lJNvendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php me _ Dvendor/composer/composer/src/Composer/DependencyResolver/Request.phpa!mea!jJvendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.phpLmeL5ߤMvendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php6me6NkǤFvendor/composer/composer/src/Composer/DependencyResolver/Decisions.phpme3NHvendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.phpCmeC!ADvendor/composer/composer/src/Composer/DependencyResolver/Problem.phprmerϒۤLvendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php@ me@ wKvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.phpme]Dvendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.phpme ̤>vendor/composer/composer/src/Composer/Autoload/ClassLoader.phpg>meg>vDvendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.phpmeI%c Avendor/composer/composer/src/Composer/Command/SuggestsCommand.php;me;:&|?vendor/composer/composer/src/Composer/Command/SearchCommand.phpme.H=vendor/composer/composer/src/Composer/Command/BumpCommand.php+&me+&Q\@vendor/composer/composer/src/Composer/Command/ArchiveCommand.php!me!+ä>vendor/composer/composer/src/Composer/Command/AuditCommand.php1 me1 TAvendor/composer/composer/src/Composer/Command/ValidateCommand.php$me$ Y=vendor/composer/composer/src/Composer/Command/FundCommand.phpme{Gvendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php Ome O) wDvendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php me \ Fvendor/composer/composer/src/Composer/Command/CreateProjectCommand.phpH\meH\zڤ=vendor/composer/composer/src/Composer/Command/BaseCommand.php=me=sAvendor/composer/composer/src/Composer/Command/OutdatedCommand.phpmeX†Evendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.phpJmeJC=vendor/composer/composer/src/Composer/Command/HomeCommand.phpameaTqBvendor/composer/composer/src/Composer/Command/ReinstallCommand.phpme?vendor/composer/composer/src/Composer/Command/RemoveCommand.php;me;1f=vendor/composer/composer/src/Composer/Command/ShowCommand.phpmeK@vendor/composer/composer/src/Composer/Command/InstallCommand.phpme q=vendor/composer/composer/src/Composer/Command/InitCommand.php_Xme_XbBvendor/composer/composer/src/Composer/Command/RunScriptCommand.phpmerm?vendor/composer/composer/src/Composer/Command/ConfigCommand.phpme`]Avendor/composer/composer/src/Composer/Command/LicensesCommand.phpme8$䂤?vendor/composer/composer/src/Composer/Command/UpdateCommand.phpq?meq?n?vendor/composer/composer/src/Composer/Command/StatusCommand.phpv mev y 0Gvendor/composer/composer/src/Composer/Command/BaseDependencyCommand.phpL-meL-@vendor/composer/composer/src/Composer/Command/DependsCommand.phpmevNBvendor/composer/composer/src/Composer/Command/ProhibitsCommand.phpme &Jvendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.phpmeYjCvendor/composer/composer/src/Composer/Command/SelfUpdateCommand.phphmehHW@vendor/composer/composer/src/Composer/Command/RequireCommand.phpsmes NLAvendor/composer/composer/src/Composer/Command/CompletionTrait.phpme|ޤCvendor/composer/composer/src/Composer/Command/ClearCacheCommand.php me QAvendor/composer/composer/src/Composer/Command/DiagnoseCommand.phpzmez?vendor/composer/composer/src/Composer/Command/GlobalCommand.php3me3 >vendor/composer/composer/src/Composer/Command/AboutCommand.phpme =vendor/composer/composer/src/Composer/Command/ExecCommand.php>me>Ƙi3vendor/composer/composer/src/Composer/Installer.phpmeƕmvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.phpmemvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.phpmeunvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php me f qvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.phpmeukvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.phpme$¤Avendor/composer/composer/src/Composer/Installer/PackageEvents.php"me"]mCvendor/composer/composer/src/Composer/Installer/BinaryInstaller.php7me7`Cvendor/composer/composer/src/Composer/Installer/PluginInstaller.phpwmew0\Kvendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.phpmeDvendor/composer/composer/src/Composer/Installer/LibraryInstaller.php&,me&,Fvendor/composer/composer/src/Composer/Installer/InstallerInterface.php1me1K;Hvendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php me ն٤Dvendor/composer/composer/src/Composer/Installer/ProjectInstaller.php me Mvendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.phpmevӤGvendor/composer/composer/src/Composer/Installer/InstallationManager.php4_me4_&Avendor/composer/composer/src/Composer/Installer/NoopInstaller.phpY meY ^.Cvendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.phpmeݭJvendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.phpmeu:vendor/composer/composer/src/Composer/Advisory/Auditor.php8me8nJvendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.phpkmekK`Rvendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.phpmeBvendor/composer/composer/src/Composer/Exception/NoSslException.phpme;P2vendor/composer/composer/src/Composer/Compiler.php)-me)-?9vendor/composer/composer/src/Composer/Package/Package.phpBmeB%96vendor/composer/composer/src/Composer/Package/Link.php me +HJvendor/composer/composer/src/Composer/Package/CompletePackageInterface.phpme}{+Ivendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php$me$kPvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php me  Gvendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php me ǁMLvendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.phpme@ݤPvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.phpmeߵKvendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.phpme^Pvendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpmeޣLvendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.phpme;wFvendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php me Fvendor/composer/composer/src/Composer/Package/CompleteAliasPackage.phpmelN(>vendor/composer/composer/src/Composer/Package/AliasPackage.php'me'/8vendor/composer/composer/src/Composer/Package/Locker.phpNmeN]+Bvendor/composer/composer/src/Composer/Package/PackageInterface.php-me-ɴk^Gvendor/composer/composer/src/Composer/Package/Version/VersionBumper.phpmeOCIvendor/composer/composer/src/Composer/Package/Version/StabilityFilter.phpQmeQTwHvendor/composer/composer/src/Composer/Package/Version/VersionGuesser.phppBmepBIvendor/composer/composer/src/Composer/Package/Version/VersionSelector.phps/mes/ӤGvendor/composer/composer/src/Composer/Package/Version/VersionParser.php me  ?Nvendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php`qme`qMnPvendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.phpomeo,ҤDvendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.phpFmeFlHvendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phpjmej7Jvendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php+me+JH$@Cvendor/composer/composer/src/Composer/Package/Loader/JsonLoader.phpcmecܝ@ =vendor/composer/composer/src/Composer/Package/BasePackage.phpmeգCvendor/composer/composer/src/Composer/Package/Comparer/Comparer.phpumeuL`Bvendor/composer/composer/src/Composer/Package/RootAliasPackage.phpmerAvendor/composer/composer/src/Composer/Package/CompletePackage.php@me@ewFvendor/composer/composer/src/Composer/Package/RootPackageInterface.phpme7=vendor/composer/composer/src/Composer/Package/RootPackage.php@ me@ B:Dvendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.phpZmeZ-]=vendor/composer/composer/src/Composer/Console/Application.phpsmesGΤCvendor/composer/composer/src/Composer/Console/GithubActionError.phplmelg`+Evendor/composer/composer/src/Composer/Console/Input/InputArgument.php me Pu=Cvendor/composer/composer/src/Composer/Console/Input/InputOption.php me j Evendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php1 me1 g?Ѥ*vendor/composer/composer/src/bootstrap.php7me7t(vendor/composer/ca-bundle/res/cacert.pemOumeOu !vendor/composer/ca-bundle/LICENSEme*!^`#vendor/composer/ca-bundle/README.md1me1>VuĤ'vendor/composer/ca-bundle/composer.jsonmeä*vendor/composer/ca-bundle/src/CaBundle.phpDmeDʪ!vendor/composer/autoload_psr4.php}me}CV%vendor/composer/autoload_classmap.phpFmeFy@6p"vendor/composer/platform_check.phpmeUԤ#vendor/composer/autoload_static.phpȚmeȚ^z!vendor/composer/autoload_real.phpmeRue,vendor/composer/semver/phpstan-baseline.neonmei%mvendor/composer/semver/LICENSEmeBh#vendor/composer/semver/CHANGELOG.md%me%5 vendor/composer/semver/README.mdH meH }Τ$vendor/composer/semver/composer.jsonme./vendor/composer/semver/src/CompilingMatcher.php me x'vendor/composer/semver/src/Interval.phpumeu)vendor/composer/semver/src/Comparator.php< me< !me>!"vendor/composer/pcre/composer.json1me1m"(vendor/composer/pcre/src/MatchResult.phpmeڇҤ"vendor/composer/pcre/src/Regex.phpme6vendor/composer/pcre/src/MatchAllWithOffsetsResult.phpme3vendor/composer/pcre/src/MatchWithOffsetsResult.phpmeo7vendor/composer/pcre/src/MatchAllStrictGroupsResult.phpxmexFA9vendor/composer/pcre/src/UnexpectedNullMatchException.php"me"_4vendor/composer/pcre/src/MatchStrictGroupsResult.phpme+vendor/composer/pcre/src/MatchAllResult.phpmeiIE*vendor/composer/pcre/src/ReplaceResult.phpme/!vendor/composer/pcre/src/Preg.phpHmeHԤ*vendor/composer/pcre/src/PcreException.phpmeg[/)vendor/composer/metadata-minifier/LICENSEmehg^+vendor/composer/metadata-minifier/README.mdLmeLMv<3vendor/composer/metadata-minifier/phpstan.neon.distBmeB#f/vendor/composer/metadata-minifier/composer.jsonmeD0פ:vendor/composer/metadata-minifier/src/MetadataMinifier.php me 6vendor/composer/spdx-licenses/res/spdx-exceptions.jsonme"m4vendor/composer/spdx-licenses/res/spdx-licenses.jsonme x %vendor/composer/spdx-licenses/LICENSEmeBh*vendor/composer/spdx-licenses/CHANGELOG.mdSmeSp+'vendor/composer/spdx-licenses/README.md<me<se/vendor/composer/spdx-licenses/phpstan.neon.distme-+vendor/composer/spdx-licenses/composer.jsonme'2vendor/composer/spdx-licenses/src/SpdxLicenses.php%me%]H &vendor/paragonie/random_compat/LICENSEJmeJ>=vendor/paragonie/random_compat/dist/random_compat.phar.pubkeyme*A|Avendor/paragonie/random_compat/dist/random_compat.phar.pubkey.ascme١i3vendor/paragonie/random_compat/other/build_phar.phpImeIc,vendor/paragonie/random_compat/build-phar.shmet8Q1vendor/paragonie/random_compat/psalm-autoload.phpme-vendor/paragonie/random_compat/lib/random.phpJmeJ.N,vendor/paragonie/random_compat/composer.jsonmeŀM(vendor/paragonie/random_compat/psalm.xmlTmeTDvendor/doctrine/lexer/LICENSE)me)`XQvendor/doctrine/lexer/README.mdomeo6q vendor/doctrine/lexer/UPGRADE.mdwmewoK#vendor/doctrine/lexer/composer.jsonmeVL¤#vendor/doctrine/lexer/src/Token.php me @D+vendor/doctrine/lexer/src/AbstractLexer.phpmex #vendor/doctrine/annotations/LICENSE)me)``%vendor/doctrine/annotations/README.mdme5פIvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php me b7Svendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.phpmeOvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php! me! W[cvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.phpme\[vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.phpmeז&ߤUvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php6me6:פOvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.phpEmeEZ'Tvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.phpumeuBʤQvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php me 5Svendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.phpmehfMvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php me 7KuFvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php me FJvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php me |ΤVvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.phpa mea Ovendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.phpmegIvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.phpmeCLvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.phpmeDP5)Kvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.phpmep#`vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php me MPvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php,me,(mRvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php me 'L{xHvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php me U)bvendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.phpimei! )vendor/doctrine/annotations/composer.jsonmeФ%vendor/doctrine/annotations/psalm.xmlme C,$vendor/doctrine/deprecations/LICENSE)me)"0&vendor/doctrine/deprecations/README.md$me$qU Uvendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.phpmeKFvendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php\$me\$L_*vendor/doctrine/deprecations/composer.json me ;R%vendor/firebase/php-jwt/composer.lock/me/ݝvendor/firebase/php-jwt/LICENSEme-nK#vendor/firebase/php-jwt/package.xml[me[2t!vendor/firebase/php-jwt/README.mdmeH%vendor/firebase/php-jwt/composer.jsonme@9vendor/firebase/php-jwt/src/SignatureInvalidException.phpumeuϽ4vendor/firebase/php-jwt/src/BeforeValidException.phppmep+Ȥ0vendor/firebase/php-jwt/src/ExpiredException.phplmelSֶ #vendor/firebase/php-jwt/src/JWT.php5me5(vendor/justinrainbow/json-schema/LICENSE me 2vendor/justinrainbow/json-schema/bin/validate-jsonmeFvendor/justinrainbow/json-schema/dist/schema/json-schema-draft-03.jsonme-aߤFvendor/justinrainbow/json-schema/dist/schema/json-schema-draft-04.jsonmeNߤ*vendor/justinrainbow/json-schema/README.mdzmez˱.vendor/justinrainbow/json-schema/composer.jsonmeKj_;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.phpme̤=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php me Ivendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.phpmehJPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php}me}ҍNGvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.phpme"9{Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php me iäNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.phpmeDNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php$me$A{Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.phpmeYhbPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.phpme s.Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php7me7J,=Tvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.phpmeq|Jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.phpmeyꎼPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php-#me-#yF¤\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.phpmeyYvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php:me:*9Xvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.phpmer(Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php<me<(Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php me 2eFvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php4 me4 Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php me "Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php me hlAvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.phpmeuuXvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.phpme-,Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.phpme_ǤGvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.phpmex(Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php^ me^ Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php me /8Dvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php$me$AqCvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.phpme_9<Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpbmeb`Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpXmeX[Nvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.phpomeoCrWvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.phpjmej*5Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.phpmeN^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.phpmeZHeQvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php'me'AjTvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php_me_PSvendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.phpmeϭTvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php\me\@S6Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpXmeXӔ]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpemeeU3$Jvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php.me.VԠg1vendor/crell/api-problem/docker/php/81/DockerfilemeL>1vendor/crell/api-problem/docker/php/81/xdebug.inilmelb0>vendor/crell/api-problem/docker/php/conf.d/error_reporting.inimelc¤!vendor/crell/api-problem/Makefile]me]Zib*vendor/crell/api-problem/phpstan.neon.dist0me0b+vendor/crell/api-problem/docker-compose.ymlbmebRuv&vendor/crell/api-problem/composer.jsonme@%vendor/crell/api-problem/default-.envme]h Q4vendor/crell/api-problem/src/JsonEncodeException.phpmeewF.vendor/crell/api-problem/src/HttpConverter.phpe mee ΊE3vendor/crell/api-problem/src/JsonParseException.phpHmeH%P(.vendor/crell/api-problem/src/JsonException.php me 5-+vendor/crell/api-problem/src/ApiProblem.phpHmeH|ovendor/psr/cache/CHANGELOG.mdme- Gvendor/psr/cache/README.mdmemvendor/psr/cache/LICENSE.txt8me8Dfvendor/psr/cache/composer.json3me3̥P/vendor/psr/cache/src/CacheItemPoolInterface.php+me+S%Ĥ+vendor/psr/cache/src/CacheItemInterface.phpmeJ1vendor/psr/cache/src/InvalidArgumentException.php:me:MY'vendor/psr/cache/src/CacheException.phpmew"'#vendor/psr/event-dispatcher/LICENSE(me(}]%vendor/psr/event-dispatcher/README.mdEmeES)vendor/psr/event-dispatcher/composer.jsonbmebq=vendor/psr/event-dispatcher/src/ListenerProviderInterface.phpmebh<vendor/psr/event-dispatcher/src/EventDispatcherInterface.phpme;vendor/psr/event-dispatcher/src/StoppableEventInterface.php!me!T>qvendor/psr/container/LICENSEymeyOpvendor/psr/container/README.mdBmeBg?"vendor/psr/container/composer.jsonumeug7vendor/psr/container/src/NotFoundExceptionInterface.phpmeB@/vendor/psr/container/src/ContainerInterface.phpmeM8vendor/psr/container/src/ContainerExceptionInterface.phpme&vendor/psr/log/LICENSE=me=pOvendor/psr/log/README.mdBmeB'/vendor/psr/log/Psr/Log/LoggerAwareInterface.php8me8"##vendor/psr/log/Psr/Log/LogLevel.phpHmeHu3vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php_me_ .*vendor/psr/log/Psr/Log/Test/TestLogger.phpme0)vendor/psr/log/Psr/Log/Test/DummyTest.php me 4B+vendor/psr/log/Psr/Log/LoggerAwareTrait.phpme3vendor/psr/log/Psr/Log/InvalidArgumentException.phpomeoMWd%vendor/psr/log/Psr/Log/NullLogger.phpme;*vendor/psr/log/Psr/Log/LoggerInterface.php1 me1 M&vendor/psr/log/Psr/Log/LoggerTrait.php^ me^ кڤ)vendor/psr/log/Psr/Log/AbstractLogger.php( me( Dvendor/psr/log/composer.jsonimeivendor/monolog/monolog/LICENSE'me'#vendor/monolog/monolog/CHANGELOG.mdԖmeԖZ vendor/monolog/monolog/README.mdHmeH\!vendor/monolog/monolog/UPGRADE.md_ me_ ƻ,$vendor/monolog/monolog/composer.json2 me2 gS>vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.phpY meY o@vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.phpme7i:vendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpmea;vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.phpwmewCHvendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.phpmezP<vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php/me/\6Cvendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.phpAmeA[oˤ:vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.phpme>u'<vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php`me`l<vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.phpme\=/=vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php me äDvendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php!me!CV>vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.phpymeyKY=vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.phpB meB T+"Yvendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.phpmefjZvendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.phpymey1\vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php me G/;vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php me Cvendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.phpme4|>vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.phpme/6vendor/monolog/monolog/src/Monolog/Handler/Handler.phpme5(>vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php me .ƤAvendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.phpme擝>vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php me ec9>vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.phpmeG/:vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.phpmeAvendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php+ me+ IPR@vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php)me)%>vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.phpK meK ?&@vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.phpme[eD>vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php me 0!ƤCvendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpme. X=vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.phpme K8vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpAmeAzEvendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.phpme;uĤ>vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php me uJvendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.phpkmek0ѤAvendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.phpameaC^a:vendor/monolog/monolog/src/Monolog/Handler/MailHandler.phpO meO j<vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpme%d<vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php?me?G<Bvendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpmeRj>vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php me r<Fvendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.phpmeNBJ?vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php9me9;@vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php'me'z]9vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php me K삤>vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.phpamea!Fvendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.phpme?vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php me ԍ&;vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.phpkmek$D?vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php me 9% :vendor/monolog/monolog/src/Monolog/Handler/NullHandler.phpdmedP6:vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php)me) <Avendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.phpme<>vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php me kHvendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.phpme<=vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.phpme`7;vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php me ):vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.phpmeݢŤBvendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.phpme%-vendor/monolog/monolog/src/Monolog/Logger.phpAXmeAX3ѤCvendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.phpme_ʏ8vendor/monolog/monolog/src/Monolog/DateTimeImmutable.phpJmeJs4vendor/monolog/monolog/src/Monolog/Test/TestCase.phpmeb#E>/vendor/monolog/monolog/src/Monolog/Registry.phpmegäBvendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.phpme3YGvendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php me cä=vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php me  Ivendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.phpRmeR@Cvendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.phpomeoФCvendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.phpbmeb9Ť=vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.phpnmen커=vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.phpme 2@vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php5me5y[*Gvendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.phpmeֱ=vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php7me7`Cvendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.phpmeLEvendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php?me?9٤:vendor/monolog/monolog/src/Monolog/ResettableInterface.phpmeI{ 4vendor/monolog/monolog/src/Monolog/SignalHandler.phpmer.Bvendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.phpmeʓh#Bvendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php1me1قȤ>vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.phpLmeL~:>vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpmeFɤBvendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php me Ϭ}Cvendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.phpme=cBvendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php& me& P@vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php'me'lNGvendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.phpMmeMJl @vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.phpmem"oAvendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php me yCvendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.phpme`2QAvendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.phpme5XEvendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.phpmeNBvendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php}me}ֻˤLvendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.phpmeDvendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php$ me$ vä>vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.phpme٨פ0vendor/monolog/monolog/src/Monolog/LogRecord.phpmeb 93vendor/monolog/monolog/src/Monolog/ErrorHandler.php9)me9)@L,vendor/monolog/monolog/src/Monolog/Utils.php%me%Fk!vendor/studio24/rotate/LICENSE.md>me>3ä"vendor/studio24/rotate/phpunit.xmlmegE vendor/studio24/rotate/README.mdme?/$vendor/studio24/rotate/composer.jsonmeWѤ.vendor/studio24/rotate/src/RotateException.php_me_ ;%vendor/studio24/rotate/src/Rotate.php#me#j-vendor/studio24/rotate/src/FilenameFormat.phpme=D-vendor/studio24/rotate/src/RotateAbstract.phpmeD6vendor/studio24/rotate/src/FilenameFormatException.phpgmegܙ%vendor/studio24/rotate/src/Delete.php|#me|#ؤ0vendor/studio24/rotate/src/DirectoryIterator.phpkmekmAGvendor/symfony/framework-bundle/DataCollector/AbstractDataCollector.php#me#NEvendor/symfony/framework-bundle/DataCollector/RouterDataCollector.phpmecvUvendor/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.phpUmeU~suܤ'vendor/symfony/framework-bundle/LICENSE,me,U7vendor/symfony/framework-bundle/Test/KernelTestCase.phptmetb0Bvendor/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.phpmeX,>vendor/symfony/framework-bundle/Test/MailerAssertionsTrait.phpmeЅ>IBvendor/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php me '69vendor/symfony/framework-bundle/Test/TestBrowserToken.phpqmeqE4vendor/symfony/framework-bundle/Test/WebTestCase.phpme?vendor/symfony/framework-bundle/Test/WebTestAssertionsTrait.phpmejo6vendor/symfony/framework-bundle/Test/TestContainer.phpmeC,vendor/symfony/framework-bundle/CHANGELOG.md1}me1}$:vendor/symfony/framework-bundle/Translation/Translator.php)me)&_7vendor/symfony/framework-bundle/Secrets/SodiumVault.php#me#B[z7vendor/symfony/framework-bundle/Secrets/DotenvVault.phpA meA Ȟ9vendor/symfony/framework-bundle/Secrets/AbstractVault.phpme,s>UIvendor/symfony/framework-bundle/Resources/bin/check-unused-known-tags.phpme18vendor/symfony/framework-bundle/Resources/config/esi.phpme@vendor/symfony/framework-bundle/Resources/config/translation.phpme=դ?vendor/symfony/framework-bundle/Resources/config/serializer.php$me$ V?vendor/symfony/framework-bundle/Resources/config/form_debug.phpme">vendor/symfony/framework-bundle/Resources/config/validator.php me ;@vendor/symfony/framework-bundle/Resources/config/annotations.phpw mew u]>vendor/symfony/framework-bundle/Resources/config/profiling.phpme+$;vendor/symfony/framework-bundle/Resources/config/mailer.php me "RFvendor/symfony/framework-bundle/Resources/config/fragment_listener.phpwmewVgFvendor/symfony/framework-bundle/Resources/config/translation_debug.phpme$Cvendor/symfony/framework-bundle/Resources/config/notifier_debug.phpme/T_2Avendor/symfony/framework-bundle/Resources/config/rate_limiter.phpme|s9vendor/symfony/framework-bundle/Resources/config/lock.phpmezHvendor/symfony/framework-bundle/Resources/config/identity_translator.php!me!@vendor/symfony/framework-bundle/Resources/config/http_client.phpme,Avendor/symfony/framework-bundle/Resources/config/mailer_debug.phpme`܄ =vendor/symfony/framework-bundle/Resources/config/services.phpme<Ƥ:vendor/symfony/framework-bundle/Resources/config/cache.php~ me~ Kg<vendor/symfony/framework-bundle/Resources/config/session.php>me>~*Fvendor/symfony/framework-bundle/Resources/config/mailer_transports.php7me7ЩJvendor/symfony/framework-bundle/Resources/config/translation_providers.php1me1ܩ:vendor/symfony/framework-bundle/Resources/config/debug.phpme"5VFvendor/symfony/framework-bundle/Resources/config/fragment_renderer.php me =vendor/symfony/framework-bundle/Resources/config/web_link.phpWmeW <vendor/symfony/framework-bundle/Resources/config/routing.phpmeD*sGvendor/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsdYmeYZKh=vendor/symfony/framework-bundle/Resources/config/notifier.phpAmeAIAFvendor/symfony/framework-bundle/Resources/config/http_client_debug.phpme"5$<vendor/symfony/framework-bundle/Resources/config/request.phpmekP?9vendor/symfony/framework-bundle/Resources/config/test.phpmeȧDvendor/symfony/framework-bundle/Resources/config/validator_debug.phpmeGUCvendor/symfony/framework-bundle/Resources/config/error_renderer.php/me/>vendor/symfony/framework-bundle/Resources/config/mime_type.php me H̤>vendor/symfony/framework-bundle/Resources/config/messenger.phpmePX<vendor/symfony/framework-bundle/Resources/config/console.phpN,meN,.=ߤ8vendor/symfony/framework-bundle/Resources/config/uid.php=me=SBvendor/symfony/framework-bundle/Resources/config/property_info.php me j8vendor/symfony/framework-bundle/Resources/config/ssi.phpme5{8vendor/symfony/framework-bundle/Resources/config/web.phpme]|Dvendor/symfony/framework-bundle/Resources/config/messenger_debug.phpmeo灤?vendor/symfony/framework-bundle/Resources/config/collectors.phpMmeM%fBCvendor/symfony/framework-bundle/Resources/config/routing/errors.xmlme><vendor/symfony/framework-bundle/Resources/config/secrets.phpme*n9vendor/symfony/framework-bundle/Resources/config/form.phpme`>vendor/symfony/framework-bundle/Resources/config/form_csrf.phpDmeDA3U=vendor/symfony/framework-bundle/Resources/config/workflow.phpmeV/Bvendor/symfony/framework-bundle/Resources/config/security_csrf.php}me}jnkHvendor/symfony/framework-bundle/Resources/config/notifier_transports.php9me9?vendor/symfony/framework-bundle/Resources/config/debug_prod.phpmeUDvendor/symfony/framework-bundle/Resources/config/property_access.phpmeBʤ;vendor/symfony/framework-bundle/Resources/config/assets.phpl mel Cd|<@vendor/symfony/framework-bundle/Resources/config/cache_debug.phpme%]1vendor/symfony/framework-bundle/KernelBrowser.phpmerI馤Dvendor/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php me WHvendor/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.phpN meN u8fJvendor/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php me Avendor/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php8me8Gvendor/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.phpmeA Evendor/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php! me! e{Kvendor/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.phpme #A Fvendor/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php<me<OYAvendor/symfony/framework-bundle/Controller/AbstractController.phpJmeJog0Avendor/symfony/framework-bundle/Controller/TemplateController.phpmehuAvendor/symfony/framework-bundle/Controller/ControllerResolver.phpGmeG,uߤAvendor/symfony/framework-bundle/Controller/RedirectController.phpnmenhs>)vendor/symfony/framework-bundle/README.mdme^)J7vendor/symfony/framework-bundle/HttpCache/HttpCache.php me @.F3vendor/symfony/framework-bundle/FrameworkBundle.phpI-meI->vendor/symfony/framework-bundle/Command/SecretsListCommand.php me _@vendor/symfony/framework-bundle/Command/SecretsRemoveCommand.php7 me7 C=vendor/symfony/framework-bundle/Command/CacheClearCommand.php_'me_'cQ?<vendor/symfony/framework-bundle/Command/XliffLintCommand.phpmeJL0Hvendor/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php me f`פAvendor/symfony/framework-bundle/Command/AbstractConfigCommand.phpmep>vendor/symfony/framework-bundle/Command/RouterDebugCommand.phpmehDvendor/symfony/framework-bundle/Command/TranslationUpdateCommand.phpsHmesHymFvendor/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.phpmekzE@vendor/symfony/framework-bundle/Command/AssetsInstallCommand.php(me(q8Avendor/symfony/framework-bundle/Command/CachePoolPruneCommand.phpme}=Avendor/symfony/framework-bundle/Command/CachePoolClearCommand.phpme͉f@vendor/symfony/framework-bundle/Command/ContainerLintCommand.phpPmePKAJvendor/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.phpme#EBvendor/symfony/framework-bundle/Command/DebugAutowiringCommand.phpmej%>vendor/symfony/framework-bundle/Command/CacheWarmupCommand.php\ me\ hӗ?vendor/symfony/framework-bundle/Command/WorkflowDumpCommand.phpme}[Gvendor/symfony/framework-bundle/Command/EventDispatcherDebugCommand.phphmeh/Avendor/symfony/framework-bundle/Command/ContainerDebugCommand.php12me12# Cvendor/symfony/framework-bundle/Command/TranslationDebugCommand.phpN<meN<5;vendor/symfony/framework-bundle/Command/YamlLintCommand.php<me<=@vendor/symfony/framework-bundle/Command/CachePoolListCommand.phpImeIzFvendor/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.phprmerNBvendor/symfony/framework-bundle/Command/CachePoolDeleteCommand.php me ]nMDvendor/symfony/framework-bundle/Command/BuildDebugContainerTrait.phpL meL )Hf8vendor/symfony/framework-bundle/Command/AboutCommand.phpme]>vendor/symfony/framework-bundle/Command/RouterMatchCommand.phpEmeEm`=vendor/symfony/framework-bundle/Command/SecretsSetCommand.phpmeI.Ҥ>vendor/symfony/framework-bundle/Command/ConfigDebugCommand.php`!me`!oK*Jvendor/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php6me6L9d@vendor/symfony/framework-bundle/Routing/RouteLoaderInterface.phpmeb 2vendor/symfony/framework-bundle/Routing/Router.phpme.<vendor/symfony/framework-bundle/Routing/DelegatingLoader.php me FޤJvendor/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.phpme@Evendor/symfony/framework-bundle/DependencyInjection/Configuration.phpmeRӤJvendor/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php me _ ^vendor/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.phpmebr`vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.phpmmemVcvendor/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.phpme\\bOvendor/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.phpMmeMZtRvendor/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.phpmeƄXvendor/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.phpme7Zvendor/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.phpmef`vendor/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php me PVvendor/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php me RLvendor/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php me 3 \vendor/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.phpmeל_vendor/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.phpme4FcԤMvendor/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php me 2ɤjvendor/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.phpme_nYvendor/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.phpme&Ƥ-vendor/symfony/framework-bundle/composer.jsonhmeh$w;vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php"me"E7vendor/symfony/framework-bundle/Console/Application.phpBmeB򚬤Evendor/symfony/framework-bundle/Console/Descriptor/TextDescriptor.phpUfmeUf}'Dvendor/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.phpamearaAvendor/symfony/framework-bundle/Console/Descriptor/Descriptor.phpQ3meQ3OEvendor/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php<me<$Ivendor/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.phpBmeB;B$Cvendor/symfony/framework-bundle/Console/Helper/DescriptorHelper.phpmepQvendor/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php me cTAvendor/symfony/framework-bundle/Session/ServiceSessionFactory.phpme pUۤDvendor/symfony/framework-bundle/Session/DeprecatedSessionFactory.php4me4MU%vendor/symfony/monolog-bundle/LICENSE)me)Ǥ*vendor/symfony/monolog-bundle/CHANGELOG.mdme:L:vendor/symfony/monolog-bundle/Resources/config/monolog.xml. me. 0yKEvendor/symfony/monolog-bundle/Resources/config/schema/monolog-1.0.xsd'me')X<vendor/symfony/monolog-bundle/SwiftMailer/MessageFactory.phpme'vendor/symfony/monolog-bundle/README.mdYmeY/vendor/symfony/monolog-bundle/MonologBundle.phpme@gFvendor/symfony/monolog-bundle/DependencyInjection/MonologExtension.phpimeiveCvendor/symfony/monolog-bundle/DependencyInjection/Configuration.phpmevrPvendor/symfony/monolog-bundle/DependencyInjection/Compiler/AddProcessorsPass.php me RPvendor/symfony/monolog-bundle/DependencyInjection/Compiler/LoggerChannelPass.phpmeOvendor/symfony/monolog-bundle/DependencyInjection/Compiler/DebugHandlerPass.php me HSQvendor/symfony/monolog-bundle/DependencyInjection/Compiler/FixEmptyLoggerPass.phpmeDNZvendor/symfony/monolog-bundle/DependencyInjection/Compiler/AddSwiftMailerTransportPass.phpme*Q +vendor/symfony/monolog-bundle/composer.jsonmeLJ%vendor/symfony/polyfill-php73/LICENSE,me,'vendor/symfony/polyfill-php73/Php73.phpsmesD\?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.phpEmeE8S+vendor/symfony/polyfill-php73/bootstrap.phpme|'vendor/symfony/polyfill-php73/README.md/me/m+vendor/symfony/polyfill-php73/composer.jsonme= Ǥ*vendor/symfony/polyfill-php80/PhpToken.phpmes’`%vendor/symfony/polyfill-php80/LICENSE,me, K:vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.phpwmew=7T8<vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php>me>g;vendor/symfony/polyfill-php80/Resources/stubs/Attribute.phpmeMK<Evendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.phpGmeGֈ+<vendor/symfony/polyfill-php80/Resources/stubs/Stringable.phpmet]\ڤ+vendor/symfony/polyfill-php80/bootstrap.phpme.Ĥ'vendor/symfony/polyfill-php80/README.mdme"tF'vendor/symfony/polyfill-php80/Php80.phpmeH+vendor/symfony/polyfill-php80/composer.jsonbmeb 7vendor/symfony/monolog-bridge/Handler/MailerHandler.phpmewI8vendor/symfony/monolog-bridge/Handler/ConsoleHandler.phpfmef Svendor/symfony/monolog-bridge/Handler/FingersCrossed/HttpCodeActivationStrategy.php me ESvendor/symfony/monolog-bridge/Handler/FingersCrossed/NotFoundActivationStrategy.phpmeA<vendor/symfony/monolog-bridge/Handler/SwiftMailerHandler.php me Yl8:vendor/symfony/monolog-bridge/Handler/ServerLogHandler.phpmmemW!9vendor/symfony/monolog-bridge/Handler/NotifierHandler.php^ me^ `Zj8vendor/symfony/monolog-bridge/Handler/FirePHPHandler.phpmeAĬ:vendor/symfony/monolog-bridge/Handler/ChromePhpHandler.phpYmeYFvendor/symfony/monolog-bridge/Handler/ElasticsearchLogstashHandler.phpmeh(vendor/symfony/monolog-bridge/Logger.php me , I%vendor/symfony/monolog-bridge/LICENSE,me,U*vendor/symfony/monolog-bridge/CHANGELOG.md me Ez:vendor/symfony/monolog-bridge/Processor/TokenProcessor.phpgmegVDvendor/symfony/monolog-bridge/Processor/SwitchUserTokenProcessor.php$me$xsz8vendor/symfony/monolog-bridge/Processor/WebProcessor.phpme\OL:vendor/symfony/monolog-bridge/Processor/RouteProcessor.php- me- pF7Cvendor/symfony/monolog-bridge/Processor/ConsoleCommandProcessor.phpnmen KBvendor/symfony/monolog-bridge/Processor/AbstractTokenProcessor.php4me42uƤ:vendor/symfony/monolog-bridge/Processor/DebugProcessor.php me  f'vendor/symfony/monolog-bridge/README.mdme=~<vendor/symfony/monolog-bridge/Formatter/ConsoleFormatter.phpme=>vendor/symfony/monolog-bridge/Formatter/VarDumperFormatter.phpme-[֞Hvendor/symfony/monolog-bridge/Messenger/ResetLoggersWorkerSubscriber.php_me_#wd:vendor/symfony/monolog-bridge/Command/ServerLogCommand.phpvmev[,+vendor/symfony/monolog-bridge/composer.json:me:o29vendor/symfony/cache/DataCollector/CacheDataCollector.phpmez"vendor/symfony/cache/CacheItem.php2me28r#vendor/symfony/cache/Psr16Cache.php "me "mNtvendor/symfony/cache/LICENSE,me,X+vendor/symfony/cache/PruneableInterface.phpmeU!vendor/symfony/cache/CHANGELOG.mdmeOF.5vendor/symfony/cache/Traits/RedisClusterNodeProxy.phpXmeXX*vendor/symfony/cache/Traits/RedisTrait.php.lme.l!*vendor/symfony/cache/Traits/ProxyTrait.phpZmeZnq5vendor/symfony/cache/Traits/FilesystemCommonTrait.phpmeu1vendor/symfony/cache/Traits/RedisClusterProxy.phpmePv*vendor/symfony/cache/Traits/RedisProxy.php,me,kp.vendor/symfony/cache/Traits/ContractsTrait.phpimeiG/vendor/symfony/cache/Traits/FilesystemTrait.php me C4vendor/symfony/cache/Traits/AbstractAdapterTrait.php1me1v)vendor/symfony/cache/DoctrineProvider.php me ;w-vendor/symfony/cache/Adapter/ProxyAdapter.php:!me:!hHФ,vendor/symfony/cache/Adapter/ApcuAdapter.phpme>i:;vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.phpqmeqP1vendor/symfony/cache/Adapter/TraceableAdapter.php|me|Vt+vendor/symfony/cache/Adapter/PdoAdapter.phppTmepTU4-vendor/symfony/cache/Adapter/ArrayAdapter.php,me,K5vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php1me1X57vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.phpTmeTώ,vendor/symfony/cache/Adapter/NullAdapter.php me s4vendor/symfony/cache/Adapter/ParameterNormalizer.phpmeoe0vendor/symfony/cache/Adapter/TagAwareAdapter.phpE,meE,N0vendor/symfony/cache/Adapter/PhpFilesAdapter.php(me((]ˤ4vendor/symfony/cache/Adapter/DoctrineDbalAdapter.phpCmeC`V9vendor/symfony/cache/Adapter/TagAwareAdapterInterface.phpme: 1vendor/symfony/cache/Adapter/MemcachedAdapter.php6me6-vendor/symfony/cache/Adapter/RedisAdapter.phpmeeR!=1vendor/symfony/cache/Adapter/AdapterInterface.php me ڑ:vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.phpHmeH!¤9vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.phpme;K-vendor/symfony/cache/Adapter/Psr16Adapter.phpmeÓ-vendor/symfony/cache/Adapter/ChainAdapter.php$me$-/2vendor/symfony/cache/Adapter/FilesystemAdapter.phpme(Acb0vendor/symfony/cache/Adapter/DoctrineAdapter.php* me* t0vendor/symfony/cache/Adapter/AbstractAdapter.phpz!mez!>֤8vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php?2me?2Jx|0vendor/symfony/cache/Adapter/PhpArrayAdapter.php1me1;դ%vendor/symfony/cache/LockRegistry.php@me@B',vendor/symfony/cache/ResettableInterface.phpmeNRvendor/symfony/cache/README.mdme`t:9vendor/symfony/cache/Messenger/EarlyExpirationHandler.php me I4t9vendor/symfony/cache/Messenger/EarlyExpirationMessage.php[ me[ g#<vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.phpf mef yOK5vendor/symfony/cache/Marshaller/DefaultMarshaller.phpme}ȵ5vendor/symfony/cache/Marshaller/DeflateMarshaller.phpmeUIf7vendor/symfony/cache/Marshaller/MarshallerInterface.phpGmeG|ѷ6vendor/symfony/cache/Marshaller/TagAwareMarshaller.php% me% = Ԥ4vendor/symfony/cache/Marshaller/SodiumMarshaller.php me 6:vendor/symfony/cache/DependencyInjection/CachePoolPass.phpy-mey-O@vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.phpme'?vendor/symfony/cache/DependencyInjection/CacheCollectorPass.phpmeAvendor/symfony/cache/DependencyInjection/CachePoolClearerPass.phpNmeNjc1vendor/symfony/cache/Exception/LogicException.phpme9d;vendor/symfony/cache/Exception/InvalidArgumentException.phpmej1vendor/symfony/cache/Exception/CacheException.phpmeQ4"vendor/symfony/cache/composer.jsonmeA.-vendor/symfony/polyfill-ctype/bootstrap80.phprmerF)%vendor/symfony/polyfill-ctype/LICENSE,me,+vendor/symfony/polyfill-ctype/bootstrap.php@me@jQ9'vendor/symfony/polyfill-ctype/README.md^me^lHk'vendor/symfony/polyfill-ctype/Ctype.phpme /+vendor/symfony/polyfill-ctype/composer.jsonmeӹ%vendor/symfony/polyfill-php81/LICENSE,me,0Fvendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.phpme5+@vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.phpmeX(P+vendor/symfony/polyfill-php81/bootstrap.phpme<P'vendor/symfony/polyfill-php81/README.mdme'vendor/symfony/polyfill-php81/Php81.phpme 3E+vendor/symfony/polyfill-php81/composer.jsonme0Fvendor/symfony/security-bundle/DataCollector/SecurityDataCollector.php5me5j&vendor/symfony/security-bundle/LICENSE,me,U+vendor/symfony/security-bundle/CHANGELOG.md6me6@EGvendor/symfony/security-bundle/Security/LegacyLogoutHandlerListener.php,me,i?vendor/symfony/security-bundle/Security/LazyFirewallContext.php me  ä=vendor/symfony/security-bundle/Security/UserAuthenticator.phpcmec1;vendor/symfony/security-bundle/Security/FirewallContext.phpkmek(RS7vendor/symfony/security-bundle/Security/FirewallMap.php8 me8  &:vendor/symfony/security-bundle/Security/FirewallConfig.php@me@;_>vendor/symfony/security-bundle/Security/FirewallAwareTrait.phpmetCvendor/symfony/security-bundle/Resources/config/templating_twig.phpme@']Fvendor/symfony/security-bundle/Resources/config/security_listeners.phpmeweUvendor/symfony/security-bundle/Resources/config/security_authenticator_login_link.php me +Gvendor/symfony/security-bundle/Resources/config/security_rememberme.php me 9vendor/symfony/security-bundle/Resources/config/guard.phpmeB{ˤCvendor/symfony/security-bundle/Resources/config/security_legacy.phpqmeq) Bvendor/symfony/security-bundle/Resources/config/security_debug.php3me36!H!Avendor/symfony/security-bundle/Resources/config/debug_console.phpmeGvendor/symfony/security-bundle/Resources/config/schema/security-1.0.xsdPmeP|A;vendor/symfony/security-bundle/Resources/config/console.phpme,gCvendor/symfony/security-bundle/Resources/config/password_hasher.phpmeް>vendor/symfony/security-bundle/Resources/config/collectors.phpmeu|Jvendor/symfony/security-bundle/Resources/config/security_authenticator.php.me.ΘL<vendor/symfony/security-bundle/Resources/config/security.phpI-meI-EVvendor/symfony/security-bundle/Resources/config/security_authenticator_remember_me.php8 me8 \&+Avendor/symfony/security-bundle/Resources/views/Collector/icon.svgmerKvendor/symfony/security-bundle/Resources/views/Collector/security.html.twig\me\O\Dvendor/symfony/security-bundle/CacheWarmer/ExpressionCacheWarmer.php3me3;Z3z1vendor/symfony/security-bundle/SecurityBundle.phpSmeS?(vendor/symfony/security-bundle/README.mdme[AHvendor/symfony/security-bundle/RememberMe/DecoratedRememberMeHandler.phpme=/gLvendor/symfony/security-bundle/RememberMe/FirewallAwareRememberMeHandler.php@me@G٤?vendor/symfony/security-bundle/Command/DebugFirewallCommand.phpR$meR$(D5Evendor/symfony/security-bundle/Command/UserPasswordEncoderCommand.phpme ◪Jvendor/symfony/security-bundle/LoginLink/FirewallAwareLoginLinkHandler.phpmeʤHvendor/symfony/security-bundle/DependencyInjection/MainConfiguration.phpBmeBSXvendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/LdapFactory.phpV meV J[\vendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php me qvivendor/symfony/security-bundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.phpme@bvendor/symfony/security-bundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.phpmeʖXvendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginLinkFactory.php@me@y\vendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php me )\n\vendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.phpE meE b`դXvendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginFactory.phpmeUO\vendor/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php me .bvendor/symfony/security-bundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.phpmeR`vendor/symfony/security-bundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.phpLmeLaѤYvendor/symfony/security-bundle/DependencyInjection/Security/Factory/RemoteUserFactory.phpM meM 1Ϥevendor/symfony/security-bundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.phpmeYvendor/symfony/security-bundle/DependencyInjection/Security/Factory/RememberMeFactory.phpV?meV?95Wvendor/symfony/security-bundle/DependencyInjection/Security/Factory/AbstractFactory.phpme$<Svendor/symfony/security-bundle/DependencyInjection/Security/Factory/X509Factory.php me #Shvendor/symfony/security-bundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.phpqmeq!eXvendor/symfony/security-bundle/DependencyInjection/Security/Factory/AnonymousFactory.php" me" 5IXvendor/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginFactory.phpmeXvendor/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicFactory.php> me> yC٤Xvendor/symfony/security-bundle/DependencyInjection/Security/Factory/LdapFactoryTrait.phpm mem 盤^vendor/symfony/security-bundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.phpmeVHvendor/symfony/security-bundle/DependencyInjection/SecurityExtension.phpWmeW6_ؤbvendor/symfony/security-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.phpemee8Ƥ^vendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.phpd med πΤjvendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php}me}{Wvendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.phpPmePa5 hvendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php me ϗYvendor/symfony/security-bundle/DependencyInjection/Compiler/SortFirewallListenersPass.php me \Unevendor/symfony/security-bundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php me A^vendor/symfony/security-bundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.phpmeUvendor/symfony/security-bundle/DependencyInjection/Compiler/AddSecurityVotersPass.php me qVvendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterEntryPointPass.phpme9paB[vendor/symfony/security-bundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.phpmeL2iXvendor/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.phpp mep e},vendor/symfony/security-bundle/composer.json# me# KBvendor/symfony/security-bundle/Debug/TraceableFirewallListener.phpmeA˖8vendor/symfony/security-bundle/Debug/WrappedListener.phpme(?vendor/symfony/security-bundle/Debug/TraceableListenerTrait.phpme<vendor/symfony/security-bundle/Debug/WrappedLazyListener.phpHmeHǪ`Avendor/symfony/security-bundle/EventListener/FirewallListener.phpme%=vendor/symfony/security-bundle/EventListener/VoteListener.phpomeog4vendor/symfony/config/ResourceCheckerConfigCache.php_me_Xvendor/symfony/config/LICENSE,me,U'vendor/symfony/config/Util/XmlUtils.php&me&{o<vendor/symfony/config/Util/Exception/XmlParsingException.phpme䢗<vendor/symfony/config/Util/Exception/InvalidXmlException.php4me4"vendor/symfony/config/CHANGELOG.mdmeQ yȤ.vendor/symfony/config/ConfigCacheInterface.phpmeO2.vendor/symfony/config/FileLocatorInterface.php?me?,c%vendor/symfony/config/ConfigCache.phpmeō5vendor/symfony/config/ConfigCacheFactoryInterface.phpmeEizĤ%vendor/symfony/config/FileLocator.php me 22vendor/symfony/config/ResourceCheckerInterface.phpmeåؤvendor/symfony/config/README.mdTmeT'vendor/symfony/config/Loader/Loader.phpmel+vendor/symfony/config/Loader/FileLoader.phpme =Ol2vendor/symfony/config/Loader/ParamConfigurator.phpjmejO30vendor/symfony/config/Loader/LoaderInterface.phpmeq78vendor/symfony/config/Loader/LoaderResolverInterface.phpmeo[ߤ/vendor/symfony/config/Loader/LoaderResolver.phpmeYM1vendor/symfony/config/Loader/DelegatingLoader.phpmex9/vendor/symfony/config/Loader/GlobFileLoader.phpme.ο.vendor/symfony/config/Definition/FloatNode.phpmeD-vendor/symfony/config/Definition/BaseNode.phpG?meG?(`0vendor/symfony/config/Definition/NumericNode.phpme70vendor/symfony/config/Definition/IntegerNode.phpvmevH*;vendor/symfony/config/Definition/PrototypeNodeInterface.phpUmeUbnB8vendor/symfony/config/Definition/PrototypedArrayNode.php|,me|,&,.vendor/symfony/config/Definition/Processor.php me 2vendor/symfony/config/Definition/NodeInterface.php me m)Ȥ-vendor/symfony/config/Definition/EnumNode.phpme#¤0vendor/symfony/config/Definition/BooleanNode.phpmeMޤ.vendor/symfony/config/Definition/ArrayNode.php.me.c٤/vendor/symfony/config/Definition/ScalarNode.phpmeݜͤ;vendor/symfony/config/Definition/ConfigurationInterface.phpme_+8vendor/symfony/config/Definition/Builder/ExprBuilder.phpme?vendor/symfony/config/Definition/Builder/EnumNodeDefinition.phpmen Ť9vendor/symfony/config/Definition/Builder/MergeBuilder.phpmeE#Bvendor/symfony/config/Definition/Builder/NumericNodeDefinition.phpme8vendor/symfony/config/Definition/Builder/NodeBuilder.phpmeQCvendor/symfony/config/Definition/Builder/VariableNodeDefinition.phpPmePq@vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php>me>#$Bvendor/symfony/config/Definition/Builder/BuilderAwareInterface.phpQmeQWج>vendor/symfony/config/Definition/Builder/ValidationBuilder.phpme"8vendor/symfony/config/Definition/Builder/TreeBuilder.phpmeAJBvendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php-me-ܩǤ@vendor/symfony/config/Definition/Builder/NodeParentInterface.phpme ʤAvendor/symfony/config/Definition/Builder/NormalizationBuilder.phpmewBvendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php4me4~֤Jvendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.phpmeWG֤Avendor/symfony/config/Definition/Builder/ScalarNodeDefinition.phpmeBڤ@vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php"me"Uf+;vendor/symfony/config/Definition/Builder/NodeDefinition.php"me"6$Dvendor/symfony/config/Definition/Exception/DuplicateKeyException.phpSmeSgIvendor/symfony/config/Definition/Exception/InvalidDefinitionException.phpmereCvendor/symfony/config/Definition/Exception/InvalidTypeException.phpme2@vendor/symfony/config/Definition/Exception/UnsetKeyException.php*me*%2dLvendor/symfony/config/Definition/Exception/InvalidConfigurationException.phpOmeO36A*Jvendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php_me_:8vendor/symfony/config/Definition/Exception/Exception.phpmeRrO1vendor/symfony/config/Definition/VariableNode.php me ͤ?vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php7!me7!>vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.phpK)meK)4^@vendor/symfony/config/Resource/SelfCheckingResourceInterface.phpme/vendor/symfony/config/Resource/FileResource.phpmeD[:vendor/symfony/config/Resource/ReflectionClassResource.php$me$?EU>vendor/symfony/config/Resource/SelfCheckingResourceChecker.phpme9ڤ3vendor/symfony/config/Resource/ComposerResource.phpDmeD> pf8vendor/symfony/config/Resource/FileExistenceResource.phpmeI4vendor/symfony/config/Resource/ResourceInterface.phpme(9vendor/symfony/config/Resource/ClassExistenceResource.phpPmePMW4vendor/symfony/config/Resource/DirectoryResource.php me gd/vendor/symfony/config/Resource/GlobResource.phpmeh28vendor/symfony/config/Builder/ConfigBuilderInterface.phpme{O(vendor/symfony/config/Builder/Method.phpme5_.vendor/symfony/config/Builder/ClassBuilder.phpme^NI*vendor/symfony/config/Builder/Property.phpme Avendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.phpme8vendor/symfony/config/Builder/ConfigBuilderGenerator.phpHmeHʤ7vendor/symfony/config/Exception/LoaderLoadException.phpnmenoNvendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php$me$2WSDvendor/symfony/config/Exception/FileLocatorFileNotFoundException.php me Ĥ#vendor/symfony/config/composer.jsonmea,vendor/symfony/config/ConfigCacheFactory.php\me\;vendor/symfony/config/ResourceCheckerConfigCacheFactory.php:me:4Dvendor/symfony/security-http/RateLimiter/DefaultLoginRateLimiter.php me #J6vendor/symfony/security-http/Attribute/CurrentUser.phpmeޤ$vendor/symfony/security-http/LICENSE,me,U5vendor/symfony/security-http/Util/TargetPathTrait.phpTmeTm)vendor/symfony/security-http/CHANGELOG.md5me5,vendor/symfony/security-http/FirewallMap.phpbmeby)vendor/symfony/security-http/Firewall.phpme,h>vendor/symfony/security-http/Logout/LogoutHandlerInterface.phpmep7Y<vendor/symfony/security-http/Logout/SessionLogoutHandler.phpmejEvendor/symfony/security-http/Logout/LogoutSuccessHandlerInterface.php me Z설Cvendor/symfony/security-http/Logout/CookieClearingLogoutHandler.phpme|V^Fvendor/symfony/security-http/Logout/CsrfTokenClearingLogoutHandler.phpme Ȥ:vendor/symfony/security-http/Logout/LogoutUrlGenerator.phpme'WwCvendor/symfony/security-http/Logout/DefaultLogoutSuccessHandler.phplmel/vendor/symfony/security-http/SecurityEvents.phpme78vendor/symfony/security-http/Firewall/AccessListener.phpme_訤9vendor/symfony/security-http/Firewall/ContextListener.php@me@Ivendor/symfony/security-http/Firewall/AnonymousAuthenticationListener.phpr mer +:vendor/symfony/security-http/Firewall/AbstractListener.phpjmej*Ȇ<vendor/symfony/security-http/Firewall/RememberMeListener.phpkmek. Fvendor/symfony/security-http/Firewall/AuthenticatorManagerListener.phpLmeLp ŤEvendor/symfony/security-http/Firewall/BasicAuthenticationListener.phpomeoCJJvendor/symfony/security-http/Firewall/AbstractPreAuthenticatedListener.phpme2;vendor/symfony/security-http/Firewall/ExceptionListener.php(me('7Cvendor/symfony/security-http/Firewall/FirewallListenerInterface.php"me"P2lI9vendor/symfony/security-http/Firewall/ChannelListener.phpme&z$Tvendor/symfony/security-http/Firewall/UsernamePasswordJsonAuthenticationListener.php'me'.E<vendor/symfony/security-http/Firewall/SwitchUserListener.php$me$H8vendor/symfony/security-http/Firewall/LogoutListener.phpmeHr5Tvendor/symfony/security-http/Firewall/UsernamePasswordFormAuthenticationListener.phpCmeCHvendor/symfony/security-http/Firewall/AbstractAuthenticationListener.phpz&mez&PqܤDvendor/symfony/security-http/Firewall/X509AuthenticationListener.php me T%Jvendor/symfony/security-http/Firewall/RemoteUserAuthenticationListener.phpme]@z =vendor/symfony/security-http/Controller/UserValueResolver.phpImeI8&&vendor/symfony/security-http/README.mdmeQT`IGvendor/symfony/security-http/RememberMe/RememberMeServicesInterface.php me x|iRvendor/symfony/security-http/RememberMe/PersistentTokenBasedRememberMeServices.php+me+WO<vendor/symfony/security-http/RememberMe/ResponseListener.phpmePS(Hvendor/symfony/security-http/RememberMe/TokenBasedRememberMeServices.phpmewT Gvendor/symfony/security-http/RememberMe/PersistentRememberMeHandler.phpvmev;=fFvendor/symfony/security-http/RememberMe/RememberMeHandlerInterface.php me  ~¤=vendor/symfony/security-http/RememberMe/RememberMeDetails.php8 me8 ϢEvendor/symfony/security-http/RememberMe/AbstractRememberMeHandler.php&me&&ZˤFvendor/symfony/security-http/RememberMe/AbstractRememberMeServices.php*me*YdFvendor/symfony/security-http/RememberMe/SignatureRememberMeHandler.php_ me_ CȤMvendor/symfony/security-http/EntryPoint/AuthenticationEntryPointInterface.phpme3iIvendor/symfony/security-http/EntryPoint/RetryAuthenticationEntryPoint.php6me6}]*-Ivendor/symfony/security-http/EntryPoint/BasicAuthenticationEntryPoint.phpmeyOQNvendor/symfony/security-http/EntryPoint/Exception/NotAnEntryPointException.php5me5aYܤHvendor/symfony/security-http/EntryPoint/FormAuthenticationEntryPoint.phpmex*vendor/symfony/security-http/HttpUtils.phpme\5vendor/symfony/security-http/FirewallMapInterface.phpme9JLvendor/symfony/security-http/Authenticator/Token/PostAuthenticationToken.phpme3ȤPvendor/symfony/security-http/Authenticator/InteractiveAuthenticatorInterface.phpme= Mvendor/symfony/security-http/Authenticator/AbstractLoginFormAuthenticator.php me J Evendor/symfony/security-http/Authenticator/AuthenticatorInterface.phpmeʰYDvendor/symfony/security-http/Authenticator/AbstractAuthenticator.php me Evendor/symfony/security-http/Authenticator/HttpBasicAuthenticator.php[me[/3Evendor/symfony/security-http/Authenticator/LoginLinkAuthenticator.phpme.Evendor/symfony/security-http/Authenticator/JsonLoginAuthenticator.php me ]oNvendor/symfony/security-http/Authenticator/Passport/SelfValidatingPassport.phpmeYWWIvendor/symfony/security-http/Authenticator/Passport/PassportInterface.php-me-C @vendor/symfony/security-http/Authenticator/Passport/Passport.php me Evendor/symfony/security-http/Authenticator/Passport/PassportTrait.phpEmeEMvendor/symfony/security-http/Authenticator/Passport/UserPassportInterface.phpGmeG׶aXvendor/symfony/security-http/Authenticator/Passport/Credentials/CredentialsInterface.phpmePUvendor/symfony/security-http/Authenticator/Passport/Credentials/CustomCredentials.phpme\qxWvendor/symfony/security-http/Authenticator/Passport/Credentials/PasswordCredentials.php+me+H#rWvendor/symfony/security-http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.phpmeyΤLvendor/symfony/security-http/Authenticator/Passport/Badge/CsrfTokenBadge.php>me>RۤRvendor/symfony/security-http/Authenticator/Passport/Badge/PasswordUpgradeBadge.phpme!RMgGvendor/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php4 me4 EhMvendor/symfony/security-http/Authenticator/Passport/Badge/RememberMeBadge.phpmei_$Lvendor/symfony/security-http/Authenticator/Passport/Badge/BadgeInterface.phpme˙Au@vendor/symfony/security-http/Authenticator/X509Authenticator.php.me.Fvendor/symfony/security-http/Authenticator/RemoteUserAuthenticator.phpme/q!Evendor/symfony/security-http/Authenticator/FormLoginAuthenticator.phpT!meT!mTvendor/symfony/security-http/Authenticator/AbstractPreAuthenticatedAuthenticator.phpmeKvendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticator.phpme@bZvendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php me wFvendor/symfony/security-http/Authenticator/RememberMeAuthenticator.phpImeIJ'[ԤKvendor/symfony/security-http/Authorization/AccessDeniedHandlerInterface.phpmebP3vendor/symfony/security-http/AccessMapInterface.phpameau;vendor/symfony/security-http/LoginLink/LoginLinkDetails.phpBmeBLZ ;vendor/symfony/security-http/LoginLink/LoginLinkHandler.phpmeZ@vendor/symfony/security-http/LoginLink/LoginLinkNotification.php me ;;dDvendor/symfony/security-http/LoginLink/LoginLinkHandlerInterface.phpme2AΤWvendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.phpmeHFNvendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkException.phpme\vendor/symfony/security-http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.phpmeMNvendor/symfony/security-http/LoginLink/Exception/ExpiredLoginLinkException.phpImeIZIvendor/symfony/security-http/Authentication/NoopAuthenticationManager.phpmeJpSvendor/symfony/security-http/Authentication/DefaultAuthenticationFailureHandler.php>me>C[Mvendor/symfony/security-http/Authentication/AuthenticatorManagerInterface.phpme1Rvendor/symfony/security-http/Authentication/CustomAuthenticationSuccessHandler.php~me~ ŤUvendor/symfony/security-http/Authentication/AuthenticationFailureHandlerInterface.phpme^Cvendor/symfony/security-http/Authentication/AuthenticationUtils.php+ me+ pqRvendor/symfony/security-http/Authentication/CustomAuthenticationFailureHandler.phpme9Uvendor/symfony/security-http/Authentication/AuthenticationSuccessHandlerInterface.phpme TJvendor/symfony/security-http/Authentication/UserAuthenticatorInterface.phpme >Dvendor/symfony/security-http/Authentication/AuthenticatorManager.php;me;Svendor/symfony/security-http/Authentication/DefaultAuthenticationSuccessHandler.php&me&zC*vendor/symfony/security-http/AccessMap.phpzmez/8vendor/symfony/security-http/Event/LoginFailureEvent.php me 8vendor/symfony/security-http/Event/LoginSuccessEvent.phpme9u_;vendor/symfony/security-http/Event/DeauthenticatedEvent.phpLmeLb8vendor/symfony/security-http/Event/LazyResponseEvent.phpme@6vendor/symfony/security-http/Event/SwitchUserEvent.php[me[8oӤFvendor/symfony/security-http/Event/AuthenticationTokenCreatedEvent.phpme9vendor/symfony/security-http/Event/CheckPassportEvent.phpme/aJ<vendor/symfony/security-http/Event/InteractiveLoginEvent.php0me0X2vendor/symfony/security-http/Event/LogoutEvent.phpme%@vendor/symfony/security-http/Event/TokenDeauthenticatedEvent.phpme"CDvendor/symfony/security-http/Impersonate/ImpersonateUrlGenerator.php" me" T2vendor/symfony/security-http/ParameterBagUtils.php% me% E*vendor/symfony/security-http/composer.json3me3Dvendor/symfony/security-http/EventListener/SessionLogoutListener.phpmetDvendor/symfony/security-http/EventListener/DefaultLogoutListener.php\me\O4HEHvendor/symfony/security-http/EventListener/PasswordMigratingListener.phpme-1Avendor/symfony/security-http/EventListener/RememberMeListener.php me xFvendor/symfony/security-http/EventListener/LoginThrottlingListener.phpme[%Cvendor/symfony/security-http/EventListener/UserProviderListener.phpqmeqp\Kvendor/symfony/security-http/EventListener/CookieClearingLogoutListener.phpmeh@Gvendor/symfony/security-http/EventListener/CheckCredentialsListener.phpme6+?Gvendor/symfony/security-http/EventListener/RememberMeLogoutListener.phpmeͤFvendor/symfony/security-http/EventListener/SessionStrategyListener.phpme1#6)Nvendor/symfony/security-http/EventListener/CsrfTokenClearingLogoutListener.phpimei='9Bvendor/symfony/security-http/EventListener/UserCheckerListener.phpmeӖEvendor/symfony/security-http/EventListener/CsrfProtectionListener.phpgmeg!zPvendor/symfony/security-http/EventListener/CheckRememberMeConditionsListener.php me Ovendor/symfony/security-http/Session/SessionAuthenticationStrategyInterface.php(me(ୃԤFvendor/symfony/security-http/Session/SessionAuthenticationStrategy.php7me7?5Ǵ<vendor/symfony/event-dispatcher/EventDispatcherInterface.phpmeʦv=vendor/symfony/event-dispatcher/Attribute/AsEventListener.phpme$'vendor/symfony/event-dispatcher/LICENSE,me,U,vendor/symfony/event-dispatcher/CHANGELOG.md, me, *פ>vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.phpme|23vendor/symfony/event-dispatcher/EventDispatcher.php$me$;N)vendor/symfony/event-dispatcher/README.mdWmeW,fB<vendor/symfony/event-dispatcher/EventSubscriberInterface.phpmeA{)<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.phpme4zS0vendor/symfony/event-dispatcher/GenericEvent.phpmewDMvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php%me%4Kvendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.phpmen-vendor/symfony/event-dispatcher/composer.jsonmeSBvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php.me.-X'9vendor/symfony/event-dispatcher/Debug/WrappedListener.php.me.Q䦌+vendor/symfony/var-dumper/Cloner/Cursor.phpHmeHcq.vendor/symfony/var-dumper/Cloner/VarCloner.php5me5ѵ4vendor/symfony/var-dumper/Cloner/ClonerInterface.phpme Mf4vendor/symfony/var-dumper/Cloner/DumperInterface.phpGmeGg3vendor/symfony/var-dumper/Cloner/AbstractCloner.php\me\))vendor/symfony/var-dumper/Cloner/Data.php6me6k)vendor/symfony/var-dumper/Cloner/Stub.phpmeJ&!vendor/symfony/var-dumper/LICENSE,me,.z5vendor/symfony/var-dumper/Test/VarDumperTestTrait.php me &vendor/symfony/var-dumper/CHANGELOG.mdme/h.vendor/symfony/var-dumper/Caster/GmpCaster.php me " 5vendor/symfony/var-dumper/Caster/ReflectionCaster.phpY9meY9{7{/vendor/symfony/var-dumper/Caster/StubCaster.phptmet ߤ2vendor/symfony/var-dumper/Caster/SymfonyCaster.php me /Q].vendor/symfony/var-dumper/Caster/FrameStub.phpmeaa7vendor/symfony/var-dumper/Caster/ProxyManagerCaster.phpme;/vendor/symfony/var-dumper/Caster/DateCaster.phpme6Y.vendor/symfony/var-dumper/Caster/PdoCaster.php me 96vendor/symfony/var-dumper/Caster/XmlResourceCaster.phpW meW =3vendor/symfony/var-dumper/Caster/ResourceCaster.phpc mec >*4vendor/symfony/var-dumper/Caster/ExceptionCaster.phpW?meW?W)sT-vendor/symfony/var-dumper/Caster/DsCaster.phpSmeSc-vendor/symfony/var-dumper/Caster/LinkStub.phpb meb (174vendor/symfony/var-dumper/Caster/MemcachedCaster.php me ֤3vendor/symfony/var-dumper/Caster/DoctrineCaster.phpmel2vendor/symfony/var-dumper/Caster/RdKafkaCaster.phpmeęs1vendor/symfony/var-dumper/Caster/MysqliCaster.phpme,vendor/symfony/var-dumper/Caster/ImgStub.phpmeͤ.vendor/symfony/var-dumper/Caster/ClassStub.php2me2+S.vendor/symfony/var-dumper/Caster/TraceStub.phpme_1vendor/symfony/var-dumper/Caster/CutArrayStub.phpmeQG0vendor/symfony/var-dumper/Caster/PgSqlCaster.phpmex+vendor/symfony/var-dumper/Caster/Caster.phpme; ߤ.vendor/symfony/var-dumper/Caster/SplCaster.phpme`.vendor/symfony/var-dumper/Caster/ConstStub.phpme}/vendor/symfony/var-dumper/Caster/DsPairStub.phpbmebP-/vendor/symfony/var-dumper/Caster/IntlCaster.php)me)˵J0vendor/symfony/var-dumper/Caster/RedisCaster.phpme("mt0vendor/symfony/var-dumper/Caster/FiberCaster.phpAmeA>zӤ2vendor/symfony/var-dumper/Caster/ImagineCaster.phpme|gdߤ/vendor/symfony/var-dumper/Caster/AmqpCaster.phpmeYR.vendor/symfony/var-dumper/Caster/DOMCaster.php!me!* /vendor/symfony/var-dumper/Caster/UuidCaster.phpmeE74vendor/symfony/var-dumper/Caster/XmlReaderCaster.php6 me6 z-vendor/symfony/var-dumper/Caster/ArgsStub.php me ͨ-vendor/symfony/var-dumper/Caster/EnumStub.phpme`",vendor/symfony/var-dumper/Caster/CutStub.phpme$N7vendor/symfony/var-dumper/Resources/bin/var-dump-serverme':vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css me Dva8vendor/symfony/var-dumper/Resources/js/htmlDescriptor.jsbmebh|6vendor/symfony/var-dumper/Resources/functions/dump.php+me+7/vendor/symfony/var-dumper/Server/Connection.php me SJn/vendor/symfony/var-dumper/Server/DumpServer.php me ڤ#vendor/symfony/var-dumper/README.md_me_Fq7vendor/symfony/var-dumper/Command/ServerDumpCommand.phpmel+Hvendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.phpmea*>vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php me F?vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.phpme"?vendor/symfony/var-dumper/Exception/ThrowingCasterException.phpmeQ k1vendor/symfony/var-dumper/Dumper/ServerDumper.php~me~NHΤ8vendor/symfony/var-dumper/Dumper/DataDumperInterface.phpme׌.Mvendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php(me(EFfKvendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.phpmepGvendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.phpmey9.LJvendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.phpmeC^)3vendor/symfony/var-dumper/Dumper/AbstractDumper.phpRmeR ܾQ.vendor/symfony/var-dumper/Dumper/CliDumper.phpRVmeRV8h9vendor/symfony/var-dumper/Dumper/ContextualizedDumper.phpmey5|/vendor/symfony/var-dumper/Dumper/HtmlDumper.phpcmecMs'vendor/symfony/var-dumper/composer.jsonqmeqT'vendor/symfony/var-dumper/VarDumper.phpme/:!vendor/symfony/filesystem/LICENSE,me,U&vendor/symfony/filesystem/CHANGELOG.mdmeI(vendor/symfony/filesystem/Filesystem.phpumeug"vendor/symfony/filesystem/Path.phpemee#vendor/symfony/filesystem/README.mdme<vendor/symfony/filesystem/Exception/IOExceptionInterface.phpmeyp:vendor/symfony/filesystem/Exception/ExceptionInterface.phpme(l`Ф8vendor/symfony/filesystem/Exception/RuntimeException.phpmeA3vendor/symfony/filesystem/Exception/IOException.phpme@vendor/symfony/filesystem/Exception/InvalidArgumentException.phpme$ =vendor/symfony/filesystem/Exception/FileNotFoundException.phpme[:'vendor/symfony/filesystem/composer.json`me`tӺGvendor/symfony/security-csrf/TokenStorage/NativeSessionTokenStorage.php me 'Avendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.phpme" Lvendor/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.phpme@MCvendor/symfony/security-csrf/TokenStorage/TokenStorageInterface.phpzmezW-m$vendor/symfony/security-csrf/LICENSE,me,U)vendor/symfony/security-csrf/CHANGELOG.mdme*vendor/symfony/security-csrf/CsrfToken.phpmeϗ&vendor/symfony/security-csrf/README.mdmeЪ:vendor/symfony/security-csrf/CsrfTokenManagerInterface.php me cEvendor/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.phpmebwxGvendor/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php&me&Hvܤ1vendor/symfony/security-csrf/CsrfTokenManager.phpmeAvendor/symfony/security-csrf/Exception/TokenNotFoundException.phpme U*vendor/symfony/security-csrf/composer.jsonlmelSˤDvendor/symfony/security-guard/Token/PostAuthenticationGuardToken.php me 9vCvendor/symfony/security-guard/Token/PreAuthenticationGuardToken.phpmmemQ;vendor/symfony/security-guard/Token/GuardTokenInterface.phpmenɚ8vendor/symfony/security-guard/AuthenticatorInterface.phpmeNn%vendor/symfony/security-guard/LICENSE,me,U<vendor/symfony/security-guard/AbstractGuardAuthenticator.phpmeȌ*vendor/symfony/security-guard/CHANGELOG.mdmeFvendor/symfony/security-guard/Provider/GuardAuthenticationProvider.phpK%meK%e@vendor/symfony/security-guard/PasswordAuthenticatedInterface.phpvmevQ:LFvendor/symfony/security-guard/Firewall/GuardAuthenticationListener.php]-me]-<'vendor/symfony/security-guard/README.md me @;vendor/symfony/security-guard/GuardAuthenticatorHandler.phpme$6Nvendor/symfony/security-guard/Authenticator/AbstractFormLoginAuthenticator.phpmed$Hvendor/symfony/security-guard/Authenticator/GuardBridgeAuthenticator.phpme-tM+vendor/symfony/security-guard/composer.jsonmez7vendor/symfony/polyfill-intl-normalizer/bootstrap80.phpme,/vendor/symfony/polyfill-intl-normalizer/LICENSE,me,H6vendor/symfony/polyfill-intl-normalizer/Normalizer.php8$me8$&(CFvendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.phpme%Lvendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php.me.CbTvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.phpxmexRvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php=me=V{Xvendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.phpme[ +5vendor/symfony/polyfill-intl-normalizer/bootstrap.phpme#p 1vendor/symfony/polyfill-intl-normalizer/README.mdme+tK5vendor/symfony/polyfill-intl-normalizer/composer.jsonxmex~OFvendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php me K}3vendor/symfony/event-dispatcher-contracts/Event.phpme%ߤ1vendor/symfony/event-dispatcher-contracts/LICENSE)me)5古6vendor/symfony/event-dispatcher-contracts/CHANGELOG.mdmeh{#3vendor/symfony/event-dispatcher-contracts/README.md\me\͗7vendor/symfony/event-dispatcher-contracts/composer.jsonme$>ɤ8vendor/symfony/property-access/PropertyPathInterface.phpamea,7vendor/symfony/property-access/PropertyPathIterator.phpmed&vendor/symfony/property-access/LICENSE,me,U@vendor/symfony/property-access/PropertyPathIteratorInterface.php$me$F8+vendor/symfony/property-access/CHANGELOG.mdme{r<vendor/symfony/property-access/PropertyAccessorInterface.phpme`ml:/vendor/symfony/property-access/PropertyPath.phpmeZ:vendor/symfony/property-access/PropertyAccessorBuilder.php me _ͤ(vendor/symfony/property-access/README.md1me1!Q3vendor/symfony/property-access/PropertyAccessor.phpN~meN~e1vendor/symfony/property-access/PropertyAccess.phpme6vendor/symfony/property-access/PropertyPathBuilder.php#me#1><vendor/symfony/property-access/Exception/AccessException.phpmeKWKvendor/symfony/property-access/Exception/UninitializedPropertyException.phpme,?vendor/symfony/property-access/Exception/ExceptionInterface.phpmelAvendor/symfony/property-access/Exception/OutOfBoundsException.php me  ؤ=vendor/symfony/property-access/Exception/RuntimeException.phpmeCnEvendor/symfony/property-access/Exception/InvalidArgumentException.phpmeIF[3Ivendor/symfony/property-access/Exception/InvalidPropertyPathException.phpmezޤDvendor/symfony/property-access/Exception/UnexpectedTypeException.phpsmes>rAvendor/symfony/property-access/Exception/NoSuchIndexException.phpmeNQpDvendor/symfony/property-access/Exception/NoSuchPropertyException.phpme?,vendor/symfony/property-access/composer.jsonmeS[%vendor/symfony/finder/SplFileInfo.php(me(a85vendor/symfony/finder/Comparator/NumberComparator.php me /vendor/symfony/finder/Comparator/Comparator.php me pe,3vendor/symfony/finder/Comparator/DateComparator.phpme vendor/symfony/finder/Finder.phpXmeX1prvendor/symfony/finder/LICENSE,me,U"vendor/symfony/finder/CHANGELOG.mdmesnvendor/symfony/finder/Glob.phpFmeF)hvendor/symfony/finder/README.mdmeC/vendor/symfony/finder/Iterator/LazyIterator.phpmezz;vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.phpmeK<vendor/symfony/finder/Iterator/FilecontentFilterIterator.phpme(=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php me %:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpme9vendor/symfony/finder/Iterator/FilenameFilterIterator.phpme:vendor/symfony/finder/Iterator/DateRangeFilterIterator.phpmeQ7vendor/symfony/finder/Iterator/CustomFilterIterator.php/me/~N<=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.phpme9ޤAvendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php me ܤ3vendor/symfony/finder/Iterator/SortableIterator.phpfmef򺩤;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.phpme`(d9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php}me}/u5vendor/symfony/finder/Iterator/PathFilterIterator.phpmezΤ#vendor/symfony/finder/Gitignore.php! me! HWkJ9vendor/symfony/finder/Exception/AccessDeniedException.phpme>vendor/symfony/finder/Exception/DirectoryNotFoundException.phpme)#vendor/symfony/finder/composer.jsonFmeFi>vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php&*me&*@vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php me aϤGvendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.phpme|7r@vendor/symfony/http-kernel/DataCollector/RouterDataCollector.phpr mer 6v>vendor/symfony/http-kernel/DataCollector/TimeDataCollector.phpmeCvendor/symfony/http-kernel/DataCollector/DataCollectorInterface.phpmeAvendor/symfony/http-kernel/DataCollector/RequestDataCollector.php;me;j>vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php^me^0:vendor/symfony/http-kernel/DataCollector/DataCollector.php me Rɓ@vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php)me)TFN@vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.phpQmeQF:ߤ?vendor/symfony/http-kernel/DataCollector/EventDataCollector.phpme ECvendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.phpmezP5vendor/symfony/http-kernel/Attribute/AsController.phpmeݤ:vendor/symfony/http-kernel/Attribute/ArgumentInterface.php&me&ߓ "vendor/symfony/http-kernel/LICENSE,me,URvendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.phpme)#Bvendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.phpme.TIvendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php me nz'vendor/symfony/http-kernel/CHANGELOG.mdeBmeeBy1vendor/symfony/http-kernel/Config/FileLocator.php}me}S/vendor/symfony/http-kernel/HttpClientKernel.phpmeWuL2vendor/symfony/http-kernel/TerminableInterface.phpme+Avendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.phpme炱=vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php_me_(. <vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.phpme5vendor/symfony/http-kernel/Resources/welcome.html.php}me}K@)vendor/symfony/http-kernel/HttpKernel.php)me) ;l?vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.phpBmeBw56vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.phpme t<vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.phprmer?vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.phpme{w]b,vendor/symfony/http-kernel/Bundle/Bundle.phpgmegta֤5vendor/symfony/http-kernel/Bundle/BundleInterface.phpLmeL'K.vendor/symfony/http-kernel/KernelInterface.php[me[ܵy:vendor/symfony/http-kernel/Controller/ArgumentResolver.phpme#S=vendor/symfony/http-kernel/Controller/ControllerReference.php5me5Ul9vendor/symfony/http-kernel/Controller/ErrorController.phpmeaoͤEvendor/symfony/http-kernel/Controller/ContainerControllerResolver.php me _c nPvendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.phpmeܵOvendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.phpS meS g%Ovendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.phpme{Ovendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.phpmeOvendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.phpmer7Xvendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.phpme:;)'[vendor/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php2 me2 Qvendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.phpme۵Cvendor/symfony/http-kernel/Controller/ArgumentResolverInterface.phpGmeG^MEvendor/symfony/http-kernel/Controller/ControllerResolverInterface.phphmeh3Cvendor/symfony/http-kernel/Controller/TraceableArgumentResolver.phpTmeT<vendor/symfony/http-kernel/Controller/ControllerResolver.php8me8Evendor/symfony/http-kernel/Controller/TraceableControllerResolver.php8me8܍4Hvendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.phpme~$vendor/symfony/http-kernel/README.mdme Evendor/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php;me;?>vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php7me7 ޤ;vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.phpAmeA,Ivendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.phpJmeJ`AAvendor/symfony/http-kernel/Fragment/FragmentRendererInterface.phpme䴀<vendor/symfony/http-kernel/Fragment/FragmentUriGenerator.php%me%~2\;vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.phpAmeAlY^$@vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php* me* ז{7vendor/symfony/http-kernel/Fragment/FragmentHandler.phpmef٤@vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.phpmeݤ+vendor/symfony/http-kernel/KernelEvents.phpdmedZ$,vendor/symfony/http-kernel/HttpCache/Esi.php6 me6 ד'2vendor/symfony/http-kernel/HttpCache/HttpCache.phpdmed}tQC.vendor/symfony/http-kernel/HttpCache/Store.php9me9J݉Ԥ;vendor/symfony/http-kernel/HttpCache/SurrogateInterface.phpmeGvendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.phpmeH>vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php(#me(# |7vendor/symfony/http-kernel/HttpCache/StoreInterface.phpmeȴ,vendor/symfony/http-kernel/HttpCache/Ssi.php me H:vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.phpme[:vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php1me1イ2vendor/symfony/http-kernel/HttpKernelInterface.phpme~8)vendor/symfony/http-kernel/Log/Logger.php(me(K_/7vendor/symfony/http-kernel/Log/DebugLoggerInterface.phpmeq<;vendor/symfony/http-kernel/Profiler/FileProfilerStorage.phpR"meR" /vendor/symfony/http-kernel/Profiler/Profile.phpme>P60vendor/symfony/http-kernel/Profiler/Profiler.php4me4Te@vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.phpme5gGvendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php= me= #Cvendor/symfony/http-kernel/DependencyInjection/ServicesResetter.phphmehQRvendor/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php`me`e`Hvendor/symfony/http-kernel/DependencyInjection/ResettableServicePass.php me Q:Rvendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.phpme)Qvendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.phpImeI ڤHvendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.phpme;t=vendor/symfony/http-kernel/DependencyInjection/LoggerPass.php0me0s-<vendor/symfony/http-kernel/DependencyInjection/Extension.phpRmeRP@Vvendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php me `PYvendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php'-me'-\vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php me >Mvendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.phpmePEvendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php me @b[Ť>vendor/symfony/http-kernel/Exception/ConflictHttpException.php#me#6uG6vendor/symfony/http-kernel/Exception/HttpException.php(me($Bvendor/symfony/http-kernel/Exception/UnauthorizedHttpException.phpmeO0FAvendor/symfony/http-kernel/Exception/InvalidMetadataException.phpjmej;녤Dvendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php)me) Ivendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php7me7 Hvendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php-me-pjϤ:vendor/symfony/http-kernel/Exception/GoneHttpException.phpmehJvendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php/me/x稤Hvendor/symfony/http-kernel/Exception/UnexpectedSessionUsageException.phpmeZisHvendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.phpme ?vendor/symfony/http-kernel/Exception/HttpExceptionInterface.phpme<ͯBvendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php\me\~&դ@vendor/symfony/http-kernel/Exception/BadRequestHttpException.php%me%˜4]Qvendor/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.phpEmeE:Cvendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php(me(/Jvendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php]me]!F3>vendor/symfony/http-kernel/Exception/NotFoundHttpException.php*me*Fvendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.phpmeZ(^4vendor/symfony/http-kernel/Event/ControllerEvent.phpme3vendor/symfony/http-kernel/Event/ExceptionEvent.phpme-ү7vendor/symfony/http-kernel/Event/FinishRequestEvent.phpmeTY2vendor/symfony/http-kernel/Event/ResponseEvent.phpme~3vendor/symfony/http-kernel/Event/TerminateEvent.php}me}i+=vendor/symfony/http-kernel/Event/ControllerArgumentsEvent.phpVmeV+Ƥ0vendor/symfony/http-kernel/Event/KernelEvent.phpS meS m1vendor/symfony/http-kernel/Event/RequestEvent.phpmegS.vendor/symfony/http-kernel/Event/ViewEvent.php:me:W<(vendor/symfony/http-kernel/composer.json me }_2vendor/symfony/http-kernel/RebootableInterface.php!me!1Ƥ%vendor/symfony/http-kernel/Kernel.phpsmesZ(vendor/symfony/http-kernel/UriSigner.php me Mu=vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php me ;֌6vendor/symfony/http-kernel/Debug/FileLinkFormatter.php me IH0vendor/symfony/http-kernel/HttpKernelBrowser.phpmeM֤Evendor/symfony/http-kernel/EventListener/StreamedResponseListener.phpmey=vendor/symfony/http-kernel/EventListener/ResponseListener.phpsmesb;vendor/symfony/http-kernel/EventListener/LocaleListener.php me '=vendor/symfony/http-kernel/EventListener/FragmentListener.php me TDvendor/symfony/http-kernel/EventListener/AbstractSessionListener.php3me3鑍0@vendor/symfony/http-kernel/EventListener/TestSessionListener.phpmeHbDvendor/symfony/http-kernel/EventListener/ValidateRequestListener.phpmeAKvendor/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.phpme"vFvendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.phpme ܤ=vendor/symfony/http-kernel/EventListener/ProfilerListener.phpme u>vendor/symfony/http-kernel/EventListener/SurrogateListener.php:me:*Bvendor/symfony/http-kernel/EventListener/DebugHandlersListener.php me &gHvendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.phpmeNg@vendor/symfony/http-kernel/EventListener/LocaleAwareListener.php me s9vendor/symfony/http-kernel/EventListener/DumpListener.php@me@:vendor/symfony/http-kernel/EventListener/ErrorListener.php$me$^}<vendor/symfony/http-kernel/EventListener/SessionListener.php8me8s;vendor/symfony/http-kernel/EventListener/RouterListener.php\me\tvendor/symfony/yaml/LICENSE,me,Uvendor/symfony/yaml/Parser.phpme^4  vendor/symfony/yaml/CHANGELOG.md6me6Wvendor/symfony/yaml/Escaper.php me +vendor/symfony/yaml/Resources/bin/yaml-lintme3VԈvendor/symfony/yaml/README.mdmeـT!vendor/symfony/yaml/Unescaper.phpme<Lvendor/symfony/yaml/Dumper.phpmeVvendor/symfony/yaml/Inline.php'me'̤vendor/symfony/yaml/Yaml.php me xw+vendor/symfony/yaml/Command/LintCommand.php=(me=(ُ/vendor/symfony/yaml/Exception/DumpException.phpme?<̤4vendor/symfony/yaml/Exception/ExceptionInterface.phpme,P2vendor/symfony/yaml/Exception/RuntimeException.phpme#OU0vendor/symfony/yaml/Exception/ParseException.phpq meq 8'vendor/symfony/yaml/Tag/TaggedValue.phpme.|!vendor/symfony/yaml/composer.json=me=K')vendor/symfony/string/CodePointString.phpme}Ѥvendor/symfony/string/LICENSE,me,զ_Ϥ"vendor/symfony/string/CHANGELOG.mdDmeD(vendor/symfony/string/AbstractString.phpOmeO,}'-vendor/symfony/string/Resources/functions.phplmel<vendor/symfony/string/Resources/data/wcswidth_table_zero.phpme°jj<vendor/symfony/string/Resources/data/wcswidth_table_wide.phpme}NM3vendor/symfony/string/Inflector/FrenchInflector.phpme.76vendor/symfony/string/Inflector/InflectorInterface.phpRmeR_4vendor/symfony/string/Inflector/EnglishInflector.php0@me0@7 vendor/symfony/string/README.md+me+L/vendor/symfony/string/AbstractUnicodeString.phpKimeKi7<$vendor/symfony/string/LazyString.phptmetΤ$vendor/symfony/string/ByteString.php<me<u֤'vendor/symfony/string/UnicodeString.php2me2cŤ6vendor/symfony/string/Exception/ExceptionInterface.php_me_ :4vendor/symfony/string/Exception/RuntimeException.php~me~)Ʉ<vendor/symfony/string/Exception/InvalidArgumentException.phpmeB;8#vendor/symfony/string/composer.jsonme~&2vendor/symfony/string/Slugger/SluggerInterface.phpme.vendor/symfony/string/Slugger/AsciiSlugger.php#me#6%4vendor/symfony/security-core/Role/SwitchUserRole.phpme'v<vendor/symfony/security-core/Role/RoleHierarchyInterface.php]me]Բ'*vendor/symfony/security-core/Role/Role.phpmei֜3vendor/symfony/security-core/Role/RoleHierarchy.phpmexwCvendor/symfony/security-core/Validator/Constraints/UserPassword.phpmeYLvendor/symfony/security-core/Validator/Constraints/UserPasswordValidator.phpPmeP*㏤$vendor/symfony/security-core/LICENSE,me,UDvendor/symfony/security-core/Test/AccessDecisionStrategyTestCase.php me Bvendor/symfony/security-core/Signature/ExpiredSignatureStorage.phpmeNvendor/symfony/security-core/Signature/Exception/InvalidSignatureException.phpme[.ܤNvendor/symfony/security-core/Signature/Exception/ExpiredSignatureException.phpme(::vendor/symfony/security-core/Signature/SignatureHasher.phpmei)vendor/symfony/security-core/CHANGELOG.mdmesڱ;vendor/symfony/security-core/Encoder/LegacyEncoderTrait.phpmeO%@vendor/symfony/security-core/Encoder/EncoderFactoryInterface.phpmeՅ<vendor/symfony/security-core/Encoder/BasePasswordEncoder.php me ;->vendor/symfony/security-core/Encoder/Pbkdf2PasswordEncoder.php-me-m9Ԥ7vendor/symfony/security-core/Encoder/EncoderFactory.phpK!meK!-Avendor/symfony/security-core/Encoder/PlaintextPasswordEncoder.phpme]䟤Avendor/symfony/security-core/Encoder/PasswordEncoderInterface.phpmeoM>vendor/symfony/security-core/Encoder/PasswordHasherAdapter.php*me*I>vendor/symfony/security-core/Encoder/SodiumPasswordEncoder.phpme+ Avendor/symfony/security-core/Encoder/MigratingPasswordEncoder.phpmelEvendor/symfony/security-core/Encoder/MessageDigestPasswordEncoder.php me KDvendor/symfony/security-core/Encoder/SelfSaltingEncoderInterface.phpmesDvendor/symfony/security-core/Encoder/LegacyPasswordHasherEncoder.php%me%Ah<vendor/symfony/security-core/Encoder/UserPasswordEncoder.php2 me2 Xh4>vendor/symfony/security-core/Encoder/PasswordHasherEncoder.phpFmeFڈդ>vendor/symfony/security-core/Encoder/NativePasswordEncoder.phpme>vendor/symfony/security-core/Encoder/EncoderAwareInterface.phpCmeCwEvendor/symfony/security-core/Encoder/UserPasswordEncoderInterface.php6me6'Cvendor/symfony/security-core/Resources/translations/security.sq.xlf"me"|99Cvendor/symfony/security-core/Resources/translations/security.ru.xlfwmew{T Cvendor/symfony/security-core/Resources/translations/security.hy.xlfme\UlCvendor/symfony/security-core/Resources/translations/security.fr.xlf9me9=ĤCvendor/symfony/security-core/Resources/translations/security.fa.xlfdmed7Cvendor/symfony/security-core/Resources/translations/security.pl.xlfmeMoCvendor/symfony/security-core/Resources/translations/security.sv.xlfmecuyCvendor/symfony/security-core/Resources/translations/security.el.xlfmen(Hvendor/symfony/security-core/Resources/translations/security.sr_Cyrl.xlf'me'$MCvendor/symfony/security-core/Resources/translations/security.en.xlfamea_<Cvendor/symfony/security-core/Resources/translations/security.ja.xlfmesFvendor/symfony/security-core/Resources/translations/security.pt_BR.xlfme;=Cvendor/symfony/security-core/Resources/translations/security.no.xlfme\Cvendor/symfony/security-core/Resources/translations/security.vi.xlfme?%Cvendor/symfony/security-core/Resources/translations/security.ur.xlflmelqsCvendor/symfony/security-core/Resources/translations/security.nn.xlfme8ICvendor/symfony/security-core/Resources/translations/security.nl.xlfmeatXCvendor/symfony/security-core/Resources/translations/security.cy.xlfmeRTCvendor/symfony/security-core/Resources/translations/security.af.xlfme!hCvendor/symfony/security-core/Resources/translations/security.lv.xlf-me-EeCvendor/symfony/security-core/Resources/translations/security.ar.xlfmeyRCvendor/symfony/security-core/Resources/translations/security.lb.xlfme|Cvendor/symfony/security-core/Resources/translations/security.tr.xlfmeʮCvendor/symfony/security-core/Resources/translations/security.lt.xlfmerFvendor/symfony/security-core/Resources/translations/security.zh_CN.xlfmeXCvendor/symfony/security-core/Resources/translations/security.ca.xlfmejmCvendor/symfony/security-core/Resources/translations/security.be.xlfkmekJCvendor/symfony/security-core/Resources/translations/security.th.xlfmejŤCvendor/symfony/security-core/Resources/translations/security.uz.xlfme:4ŤCvendor/symfony/security-core/Resources/translations/security.mk.xlfmegCvendor/symfony/security-core/Resources/translations/security.bs.xlf"me"_Cvendor/symfony/security-core/Resources/translations/security.bg.xlf/me/uۤHvendor/symfony/security-core/Resources/translations/security.sr_Latn.xlfme3GCvendor/symfony/security-core/Resources/translations/security.uk.xlf9me9WD;Fvendor/symfony/security-core/Resources/translations/security.zh_TW.xlfmeF:oCvendor/symfony/security-core/Resources/translations/security.cs.xlfme}Cvendor/symfony/security-core/Resources/translations/security.tl.xlf&me&c Cvendor/symfony/security-core/Resources/translations/security.my.xlf,me,OOCvendor/symfony/security-core/Resources/translations/security.nb.xlfme\Cvendor/symfony/security-core/Resources/translations/security.mn.xlfGmeGjyCvendor/symfony/security-core/Resources/translations/security.az.xlfme3Cvendor/symfony/security-core/Resources/translations/security.eu.xlfme Cvendor/symfony/security-core/Resources/translations/security.he.xlf me ?Cvendor/symfony/security-core/Resources/translations/security.hr.xlfme#Cvendor/symfony/security-core/Resources/translations/security.pt.xlfme?Cvendor/symfony/security-core/Resources/translations/security.et.xlfme]MUCvendor/symfony/security-core/Resources/translations/security.de.xlf/me/@ Cvendor/symfony/security-core/Resources/translations/security.sl.xlfmeQjCvendor/symfony/security-core/Resources/translations/security.it.xlfmeACvendor/symfony/security-core/Resources/translations/security.gl.xlfmeeȤCvendor/symfony/security-core/Resources/translations/security.es.xlfme-Cvendor/symfony/security-core/Resources/translations/security.hu.xlfmeqCvendor/symfony/security-core/Resources/translations/security.da.xlfzmez,ŤCvendor/symfony/security-core/Resources/translations/security.fi.xlfmeH|?Cvendor/symfony/security-core/Resources/translations/security.id.xlfmeYK{ӤCvendor/symfony/security-core/Resources/translations/security.sk.xlfmekCvendor/symfony/security-core/Resources/translations/security.ro.xlfmeY9vendor/symfony/security-core/User/InMemoryUserChecker.phpmeX$3vendor/symfony/security-core/User/UserInterface.php me f^;vendor/symfony/security-core/User/UserProviderInterface.php me %TX*vendor/symfony/security-core/User/User.phpmeb9vendor/symfony/security-core/User/MissingUserProvider.phpmeɺNvendor/symfony/security-core/User/LegacyPasswordAuthenticatedUserInterface.php1me1,?vendor/symfony/security-core/User/PasswordUpgraderInterface.phpme2vendor/symfony/security-core/User/InMemoryUser.phpmeܿ{:vendor/symfony/security-core/User/InMemoryUserProvider.php}me}$8vendor/symfony/security-core/User/EquatableInterface.phpme-\Hvendor/symfony/security-core/User/PasswordAuthenticatedUserInterface.phpmehΤ:vendor/symfony/security-core/User/UserCheckerInterface.php,me, 1vendor/symfony/security-core/User/UserChecker.phpmec'7vendor/symfony/security-core/User/ChainUserProvider.phpmee{&vendor/symfony/security-core/README.mdmeEKLvendor/symfony/security-core/Authorization/AuthorizationCheckerInterface.phpWmeW"Ivendor/symfony/security-core/Authorization/ExpressionLanguageProvider.php me `uMvendor/symfony/security-core/Authorization/TraceableAccessDecisionManager.phpme{0Dvendor/symfony/security-core/Authorization/AccessDecisionManager.php`me`CMvendor/symfony/security-core/Authorization/AccessDecisionManagerInterface.phpmeҟCvendor/symfony/security-core/Authorization/Voter/VoterInterface.phpme>Lvendor/symfony/security-core/Authorization/Voter/CacheableVoterInterface.php~me~9Dvendor/symfony/security-core/Authorization/Voter/ExpressionVoter.php me &Gvendor/symfony/security-core/Authorization/Voter/RoleHierarchyVoter.phpUmeU`FCvendor/symfony/security-core/Authorization/Voter/TraceableVoter.phpme^:vendor/symfony/security-core/Authorization/Voter/Voter.php me >vendor/symfony/security-core/Authorization/Voter/RoleVoter.phpmeGvendor/symfony/security-core/Authorization/Voter/AuthenticatedVoter.phpZmeZP㳤Avendor/symfony/security-core/Authorization/ExpressionLanguage.phpmeCvendor/symfony/security-core/Authorization/AuthorizationChecker.phpmetHvendor/symfony/security-core/Authorization/Strategy/PriorityStrategy.php/me/cRIvendor/symfony/security-core/Authorization/Strategy/UnanimousStrategy.phpOmeOȅ1٤Kvendor/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.phpVmeVIvendor/symfony/security-core/Authorization/Strategy/ConsensusStrategy.phpmeWvendor/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php]me]pf)vendor/symfony/security-core/Security.phpmeh}@vendor/symfony/security-core/Exception/AccessDeniedException.phpmeaDvendor/symfony/security-core/Exception/InvalidCsrfTokenException.phpme :vendor/symfony/security-core/Exception/LogoutException.phpmey'פ9vendor/symfony/security-core/Exception/LogicException.phpme$2@vendor/symfony/security-core/Exception/LazyResponseException.php me _KSvendor/symfony/security-core/Exception/CustomUserMessageAuthenticationException.phpme2+V=vendor/symfony/security-core/Exception/ExceptionInterface.phpme^ Bvendor/symfony/security-core/Exception/BadCredentialsException.phpmenh;vendor/symfony/security-core/Exception/RuntimeException.phpme> <vendor/symfony/security-core/Exception/DisabledException.phpme[ΩAvendor/symfony/security-core/Exception/TokenNotFoundException.phpme|jˤDvendor/symfony/security-core/Exception/ProviderNotFoundException.phpme=xFvendor/symfony/security-core/Exception/SessionUnavailableException.phpmeaECvendor/symfony/security-core/Exception/InvalidArgumentException.phpmeW.Bvendor/symfony/security-core/Exception/AccountExpiredException.phpmeMҤIvendor/symfony/security-core/Exception/AuthenticationExpiredException.phpcmec#mDvendor/symfony/security-core/Exception/UsernameNotFoundException.phpmeӯnVvendor/symfony/security-core/Exception/TooManyLoginAttemptsAuthenticationException.php me xܤ?vendor/symfony/security-core/Exception/CookieTheftException.phpmekIvendor/symfony/security-core/Exception/AuthenticationServiceException.php me 4!Cvendor/symfony/security-core/Exception/UnsupportedUserException.phpJmeJjۤAvendor/symfony/security-core/Exception/AccountStatusException.phpBmeBGRvendor/symfony/security-core/Exception/CustomUserMessageAccountStatusException.phpmeZ^Nvendor/symfony/security-core/Exception/InsufficientAuthenticationException.phpBmeB-:vendor/symfony/security-core/Exception/LockedException.phpme 8Bvendor/symfony/security-core/Exception/AuthenticationException.php me yP@vendor/symfony/security-core/Exception/UserNotFoundException.phpv mev Uvendor/symfony/security-core/Exception/AuthenticationCredentialsNotFoundException.phpmel.Fvendor/symfony/security-core/Exception/CredentialsExpiredException.phpmeEXΤCvendor/symfony/security-core/Authentication/Token/AbstractToken.php9'me9'ي?vendor/symfony/security-core/Authentication/Token/NullToken.php me SˤDvendor/symfony/security-core/Authentication/Token/AnonymousToken.phpmek{Wvendor/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php me Jvendor/symfony/security-core/Authentication/Token/Storage/TokenStorage.phpvmevd=Svendor/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.phpmekcZޤEvendor/symfony/security-core/Authentication/Token/RememberMeToken.php me [Evendor/symfony/security-core/Authentication/Token/SwitchUserToken.phpi mei 8Kvendor/symfony/security-core/Authentication/Token/PreAuthenticatedToken.php me ڎ~Dvendor/symfony/security-core/Authentication/Token/TokenInterface.phpz mez ʒKvendor/symfony/security-core/Authentication/Token/UsernamePasswordToken.php me qTvendor/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.phpwmew6Kvendor/symfony/security-core/Authentication/AuthenticationTrustResolver.phpmeCSvendor/symfony/security-core/Authentication/Provider/UserAuthenticationProvider.phpFmeFjʞRvendor/symfony/security-core/Authentication/Provider/DaoAuthenticationProvider.phpmeR{Wvendor/symfony/security-core/Authentication/Provider/LdapBindAuthenticationProvider.phpmeI Xvendor/symfony/security-core/Authentication/Provider/AuthenticationProviderInterface.phpYmeY'Xs_vendor/symfony/security-core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php me %JYvendor/symfony/security-core/Authentication/Provider/RememberMeAuthenticationProvider.php! me! MXvendor/symfony/security-core/Authentication/Provider/AnonymousAuthenticationProvider.phpme`Mvendor/symfony/security-core/Authentication/AuthenticationProviderManager.phpmeԱSvendor/symfony/security-core/Authentication/RememberMe/PersistentTokenInterface.php me ӤPvendor/symfony/security-core/Authentication/RememberMe/InMemoryTokenProvider.phpme Mvendor/symfony/security-core/Authentication/RememberMe/CacheTokenVerifier.php me JpJvendor/symfony/security-core/Authentication/RememberMe/PersistentToken.phpme0@fQvendor/symfony/security-core/Authentication/RememberMe/TokenVerifierInterface.phpmeäQvendor/symfony/security-core/Authentication/RememberMe/TokenProviderInterface.phpmeGU=Nvendor/symfony/security-core/Authentication/AuthenticationManagerInterface.phpme{<Avendor/symfony/security-core/Event/AuthenticationSuccessEvent.phpumeu:vendor/symfony/security-core/Event/AuthenticationEvent.phpXmeX+YAvendor/symfony/security-core/Event/AuthenticationFailureEvent.phpsmesKGM0vendor/symfony/security-core/Event/VoteEvent.phpme(͐*vendor/symfony/security-core/composer.jsonumeu95vendor/symfony/security-core/AuthenticationEvents.phpmeE,vendor/symfony/deprecation-contracts/LICENSE)me)21vendor/symfony/deprecation-contracts/CHANGELOG.mdmeh{#1vendor/symfony/deprecation-contracts/function.phpmerg.vendor/symfony/deprecation-contracts/README.mdme32vendor/symfony/deprecation-contracts/composer.jsonSmeSIN>(vendor/symfony/routing/CompiledRoute.php}me}_vendor/symfony/routing/LICENSE,me,U(vendor/symfony/routing/RouteCompiler.phpB9meB9c#vendor/symfony/routing/CHANGELOG.md/me/ )vendor/symfony/routing/RequestContext.phpmetK+vendor/symfony/routing/Annotation/Route.phpymey#L:vendor/symfony/routing/Matcher/RequestMatcherInterface.php?me?С=vendor/symfony/routing/Matcher/ExpressionLanguageProvider.phpme-vendor/symfony/routing/Matcher/UrlMatcher.phpW$meW$YBvendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.phpmeۤ6vendor/symfony/routing/Matcher/UrlMatcherInterface.phpme 5vendor/symfony/routing/Matcher/CompiledUrlMatcher.phpme*g6vendor/symfony/routing/Matcher/TraceableUrlMatcher.phpme6Bvendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php1Jme1JmK.7vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php!me!!Ҵ@vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.phpmeSQAvendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php;me;6H@vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.phpme79vendor/symfony/routing/Matcher/RedirectableUrlMatcher.phpamea|: vendor/symfony/routing/README.mdme.9vendor/symfony/routing/Generator/CompiledUrlGenerator.php me PgZ1vendor/symfony/routing/Generator/UrlGenerator.phpZ=meZ=5Fvendor/symfony/routing/Generator/ConfigurableRequirementsInterface.phpme):vendor/symfony/routing/Generator/UrlGeneratorInterface.php me #Ѵ%Dvendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.phpme)_Fvendor/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.phpme9ݥ;vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php'me'V6*vendor/symfony/routing/RouteCollection.php,me,n7vendor/symfony/routing/RequestContextAwareInterface.php/me/h37vendor/symfony/routing/Loader/AnnotationClassLoader.phpO4meO4&ϮR0vendor/symfony/routing/Loader/YamlFileLoader.php0me0{!6vendor/symfony/routing/Loader/AnnotationFileLoader.php me # <vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd6me6Vܤ/vendor/symfony/routing/Loader/PhpFileLoader.phpJ meJ |%/vendor/symfony/routing/Loader/XmlFileLoader.phpqEmeqE}[ .vendor/symfony/routing/Loader/ObjectLoader.php me _ʲҤ?vendor/symfony/routing/Loader/Configurator/Traits/HostTrait.phpmeEs2Avendor/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php me $Ivendor/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php& me& peݤ@vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php me =/' >vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php9me9@vendor/symfony/routing/Loader/Configurator/RouteConfigurator.phpme/7Evendor/symfony/routing/Loader/Configurator/CollectionConfigurator.phpmevŤ@vendor/symfony/routing/Loader/Configurator/AliasConfigurator.phpme}Bvendor/symfony/routing/Loader/Configurator/RoutingConfigurator.phpme蝤Avendor/symfony/routing/Loader/Configurator/ImportConfigurator.phpmeWw1vendor/symfony/routing/Loader/ContainerLoader.phpme{)1vendor/symfony/routing/Loader/DirectoryLoader.php&me& \0vendor/symfony/routing/Loader/GlobFileLoader.phpKmeKx/vendor/symfony/routing/Loader/ClosureLoader.phpRmeRf:+;vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php me JT vendor/symfony/routing/Route.php<0me<0֩1vendor/symfony/routing/RouteCollectionBuilder.php%me%YM*vendor/symfony/routing/RouterInterface.php"me"WF!vendor/symfony/routing/Router.php0me0x1vendor/symfony/routing/RouteCompilerInterface.phpme vendor/symfony/routing/Alias.php me Bvendor/symfony/routing/DependencyInjection/RoutingResolverPass.phpmeg<>vendor/symfony/routing/Exception/ResourceNotFoundException.php2me2FL"17vendor/symfony/routing/Exception/ExceptionInterface.phpme&;vendor/symfony/routing/Exception/RouteNotFoundException.phpme C5vendor/symfony/routing/Exception/RuntimeException.phpme$>GHvendor/symfony/routing/Exception/MissingMandatoryParametersException.php@me@Tv=vendor/symfony/routing/Exception/InvalidArgumentException.phpme֤>vendor/symfony/routing/Exception/MethodNotAllowedException.phpJmeJu#=vendor/symfony/routing/Exception/NoConfigurationException.phpmerDvendor/symfony/routing/Exception/RouteCircularReferenceException.php>me>\T>vendor/symfony/routing/Exception/InvalidParameterException.phpme!N$vendor/symfony/routing/composer.jsonme?vendor/symfony/service-contracts/ServiceSubscriberInterface.phpmeL@vendor/symfony/service-contracts/Attribute/SubscribedService.php2me2UȤ7vendor/symfony/service-contracts/Attribute/Required.phpme`e(vendor/symfony/service-contracts/LICENSE)me)5古<vendor/symfony/service-contracts/Test/ServiceLocatorTest.php me b'L-vendor/symfony/service-contracts/CHANGELOG.mdmeh{#*vendor/symfony/service-contracts/README.mdLmeLȤ3vendor/symfony/service-contracts/ResetInterface.phpme{Ф=vendor/symfony/service-contracts/ServiceProviderInterface.phpmeyl;vendor/symfony/service-contracts/ServiceSubscriberTrait.phpme8vendor/symfony/service-contracts/ServiceLocatorTrait.phpme0"i.vendor/symfony/service-contracts/composer.jsonmex#S9vendor/symfony/property-info/PhpStan/NameScopeFactory.phpmew2vendor/symfony/property-info/PhpStan/NameScope.phpme~w?vendor/symfony/property-info/PropertyInfoExtractorInterface.phpmeU-R%vendor/symfony/property-info/Type.php!me!B$vendor/symfony/property-info/LICENSE,me,H6vendor/symfony/property-info/Util/PhpDocTypeHelper.php(me(7ׅo7vendor/symfony/property-info/Util/PhpStanTypeHelper.php^me^Zⶳ)vendor/symfony/property-info/CHANGELOG.mdme:;vendor/symfony/property-info/PropertyInfoCacheExtractor.php me KaiDvendor/symfony/property-info/PropertyWriteInfoExtractorInterface.phpmexBy\Avendor/symfony/property-info/PropertyAccessExtractorInterface.phpme2$C?vendor/symfony/property-info/PropertyTypeExtractorInterface.phpQmeQ1vendor/symfony/property-info/PropertyReadInfo.phpmeKΤ;vendor/symfony/property-info/Extractor/PhpStanExtractor.php(me(rc>vendor/symfony/property-info/Extractor/SerializerExtractor.phpme8?vendor/symfony/property-info/Extractor/ConstructorExtractor.phpmeX5Tvendor/symfony/property-info/Extractor/ConstructorArgumentTypeExtractorInterface.phpmejv\>vendor/symfony/property-info/Extractor/ReflectionExtractor.php&me&b:vendor/symfony/property-info/Extractor/PhpDocExtractor.php/me/ZFvendor/symfony/property-info/PropertyDescriptionExtractorInterface.phpPmeP&2vendor/symfony/property-info/PropertyWriteInfo.phpc mec 4b(}&vendor/symfony/property-info/README.mdme'7Cvendor/symfony/property-info/PropertyReadInfoExtractorInterface.phpme:|`դHvendor/symfony/property-info/PropertyInitializableExtractorInterface.phpmeOW6vendor/symfony/property-info/PropertyInfoExtractor.php^me^ji?vendor/symfony/property-info/PropertyListExtractorInterface.phpmeJ$Pvendor/symfony/property-info/DependencyInjection/PropertyInfoConstructorPass.phpmeEEvendor/symfony/property-info/DependencyInjection/PropertyInfoPass.php me ֤*vendor/symfony/property-info/composer.jsonmeX)3vendor/symfony/http-foundation/StreamedResponse.phpE meE 񠈤:vendor/symfony/http-foundation/RequestMatcherInterface.phpmeXY5vendor/symfony/http-foundation/BinaryFileResponse.php6me6C+vendor/symfony/http-foundation/InputBag.phpAmeAmIvendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.phpmecJvendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php me ¤&vendor/symfony/http-foundation/LICENSE,me,UJvendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.phpmeiDvendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.phpme*Ivendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.phpUmeU QGvendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.phpme-Jvendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.phpmeFGvendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.phpmeƤDvendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.phpmeYLvendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.phpmef,Evendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.phpbmebt$ؤEvendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php:me:0W,vendor/symfony/http-foundation/ServerBag.phpme(+vendor/symfony/http-foundation/CHANGELOG.mdD=meD=N|,vendor/symfony/http-foundation/File/File.php7me7qsF.vendor/symfony/http-foundation/File/Stream.phpEmeEZ4vendor/symfony/http-foundation/File/UploadedFile.phpa(mea(i$pGvendor/symfony/http-foundation/File/Exception/AccessDeniedException.phpimeiYAvendor/symfony/http-foundation/File/Exception/NoFileException.phpme-AFvendor/symfony/http-foundation/File/Exception/IniSizeFileException.phpme IJvendor/symfony/http-foundation/File/Exception/CannotWriteFileException.phpme :ݤHvendor/symfony/http-foundation/File/Exception/ExtensionFileException.phpme)?vendor/symfony/http-foundation/File/Exception/FileException.phpmej2Avendor/symfony/http-foundation/File/Exception/UploadException.phpmeSIvendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php3me3t*Gvendor/symfony/http-foundation/File/Exception/FormSizeFileException.phpmem;Gvendor/symfony/http-foundation/File/Exception/FileNotFoundException.phpYmeY{WɈFvendor/symfony/http-foundation/File/Exception/PartialFileException.phpme*Gvendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.phpmen +vendor/symfony/http-foundation/Response.phpHmeHN")vendor/symfony/http-foundation/Cookie.php-me-Cw/vendor/symfony/http-foundation/AcceptHeader.php<me<d1vendor/symfony/http-foundation/RequestMatcher.php:me:&4vendor/symfony/http-foundation/ResponseHeaderBag.phpme&G.vendor/symfony/http-foundation/HeaderUtils.php$me$p/vendor/symfony/http-foundation/RequestStack.phps mes ˾'3vendor/symfony/http-foundation/RedirectResponse.php me Ek/vendor/symfony/http-foundation/JsonResponse.phpwmew(v(vendor/symfony/http-foundation/README.mdme%*vendor/symfony/http-foundation/Request.php me ;vendor/symfony/http-foundation/ExpressionRequestMatcher.phpmeL/vendor/symfony/http-foundation/ParameterBag.php me Dd*vendor/symfony/http-foundation/IpUtils.phpmeάͤ3vendor/symfony/http-foundation/AcceptHeaderItem.phpQ meQ 1,vendor/symfony/http-foundation/UrlHelper.phpme{,vendor/symfony/http-foundation/HeaderBag.php0me0ȥ_*vendor/symfony/http-foundation/FileBag.php(me(ɿHvendor/symfony/http-foundation/Exception/ConflictingHeadersException.phpme.>+wFvendor/symfony/http-foundation/Exception/RequestExceptionInterface.phpmeR@vendor/symfony/http-foundation/Exception/BadRequestException.phpmeդIvendor/symfony/http-foundation/Exception/SuspiciousOperationException.phpme㝜:vendor/symfony/http-foundation/Exception/JsonException.phpme~|mWEvendor/symfony/http-foundation/Exception/SessionNotFoundException.phpYmeY1,vendor/symfony/http-foundation/composer.jsonDmeDb1Jvendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.phpmeKvendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.phpme"Avendor/symfony/http-foundation/Session/Attribute/AttributeBag.php me z<Bvendor/symfony/http-foundation/Session/SessionFactoryInterface.phpmef*2vendor/symfony/http-foundation/Session/Session.phpmeU/67vendor/symfony/http-foundation/Session/SessionUtils.phptmetOYMvendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.phpmeUޤQvendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php=me=Pvendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.phpme'Lvendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.phpme3Tvendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.phpm mem 8Svendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.phpme:^Rvendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.phpJmeJ]EqMvendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.phpmeR=pPvendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.phpmeDTNvendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.phpmedOvendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php me oqФRvendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.phpW meW RjGvendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php?me?Jvendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php=me=ʒPvendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.phpme TҤLvendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.phpy mey sFvendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php me &.Nvendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.phpcmecJvendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.phpme 3Qvendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.phpwmew]nHvendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.phpme}Ivendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.phpmeBQvendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.phpme*5>>vendor/symfony/http-foundation/Session/Storage/MetadataBag.phpme9 1Jvendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.phpmeC;vendor/symfony/http-foundation/Session/SessionInterface.phpqmeq teCvendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php me Ճ5Bvendor/symfony/http-foundation/Session/Flash/FlashBagInterface.phpfmef39vendor/symfony/http-foundation/Session/Flash/FlashBag.php me ۖV9vendor/symfony/http-foundation/Session/SessionFactory.phpmebE;:vendor/symfony/http-foundation/Session/SessionBagProxy.php2me2W@>vendor/symfony/http-foundation/Session/SessionBagInterface.php\me\hI,vendor/symfony/var-exporter/Instantiator.phpmeYd#vendor/symfony/var-exporter/LICENSE,me,(vendor/symfony/var-exporter/CHANGELOG.mdme/vendor/symfony/var-exporter/Internal/Values.phpme`ǿ1vendor/symfony/var-exporter/Internal/Hydrator.phprmer:c1vendor/symfony/var-exporter/Internal/Registry.php>me>M,2vendor/symfony/var-exporter/Internal/Reference.php;me;*Q/ˤ1vendor/symfony/var-exporter/Internal/Exporter.phpAmeAh'%vendor/symfony/var-exporter/README.mdme8+vendor/symfony/var-exporter/VarExporter.phpme <vendor/symfony/var-exporter/Exception/ExceptionInterface.phpdmed@vendor/symfony/var-exporter/Exception/ClassNotFoundException.php1me1wwFvendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php>me>})vendor/symfony/var-exporter/composer.jsonmeҽeEvendor/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php me ޺Ivendor/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.phpmepn[Mvendor/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.phpK meK -Kvendor/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.phpmea$vendor/symfony/error-handler/LICENSE,me,զ_Ϥ)vendor/symfony/error-handler/CHANGELOG.md|me|n:tBvendor/symfony/error-handler/Resources/bin/patch-type-declarations me RMvendor/symfony/error-handler/Resources/bin/extract-tentative-return-types.phpmemE?vendor/symfony/error-handler/Resources/views/exception.html.php>me>˚<vendor/symfony/error-handler/Resources/views/traces.html.php] me] m}Avendor/symfony/error-handler/Resources/views/traces_text.html.phpmej+2:vendor/symfony/error-handler/Resources/views/logs.html.php) me) _&;vendor/symfony/error-handler/Resources/views/trace.html.phpU meU 4K';vendor/symfony/error-handler/Resources/views/error.html.phpme< zDvendor/symfony/error-handler/Resources/views/exception_full.html.phpme ?vendor/symfony/error-handler/Resources/assets/css/exception.css@7me@7|vNDvendor/symfony/error-handler/Resources/assets/css/exception_full.css me *bc;vendor/symfony/error-handler/Resources/assets/css/error.cssme%7Jvendor/symfony/error-handler/Resources/assets/images/icon-minus-square.svgQmeQpBvendor/symfony/error-handler/Resources/assets/images/icon-book.svgmeEvendor/symfony/error-handler/Resources/assets/images/icon-support.svgzmezS̤Fvendor/symfony/error-handler/Resources/assets/images/chevron-right.svgme Jvendor/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php me 9꒤Ivendor/symfony/error-handler/Resources/assets/images/icon-plus-square.svgmeUEvendor/symfony/error-handler/Resources/assets/images/symfony-logo.svgme%0ɤBvendor/symfony/error-handler/Resources/assets/images/icon-copy.svg me "ȖGvendor/symfony/error-handler/Resources/assets/images/favicon.png.base64me"i^Kvendor/symfony/error-handler/Resources/assets/images/icon-plus-square-o.svgme-Lvendor/symfony/error-handler/Resources/assets/images/icon-minus-square-o.svgmes0=vendor/symfony/error-handler/Resources/assets/js/exception.js4me42d8vendor/symfony/error-handler/Internal/TentativeTypes.phpme&vendor/symfony/error-handler/Debug.phpBmeB;~Fvendor/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php7me7Ƥ?vendor/symfony/error-handler/ErrorRenderer/CliErrorRenderer.phpme0Evendor/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.phpme5g@vendor/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php`me`j&vendor/symfony/error-handler/README.md=me=\1vendor/symfony/error-handler/DebugClassLoader.phpqmeqrP0vendor/symfony/error-handler/BufferingLogger.php"me"i9--vendor/symfony/error-handler/ErrorHandler.phptmet/vendor/symfony/error-handler/ThrowableUtils.phpme~+G7vendor/symfony/error-handler/Error/OutOfMemoryError.php[me[N;vendor/symfony/error-handler/Error/UndefinedMethodError.php^me^+3 1vendor/symfony/error-handler/Error/FatalError.php me ڐڤ=vendor/symfony/error-handler/Error/UndefinedFunctionError.php`me`]W,9vendor/symfony/error-handler/Error/ClassNotFoundError.php\me\3Xޤ;vendor/symfony/error-handler/Exception/FlattenException.php(me(i ?vendor/symfony/error-handler/Exception/SilencedErrorContext.php]me]SƤ*vendor/symfony/error-handler/composer.json:me:P4vendor/symfony/cache-contracts/CallbackInterface.phpGmeG0vendor/symfony/cache-contracts/ItemInterface.php$me$&vendor/symfony/cache-contracts/LICENSE)me)5古+vendor/symfony/cache-contracts/CHANGELOG.mdmeh{#1vendor/symfony/cache-contracts/CacheInterface.php me C(vendor/symfony/cache-contracts/README.mdHmeHZ#-vendor/symfony/cache-contracts/CacheTrait.php) me) _9vendor/symfony/cache-contracts/TagAwareCacheInterface.php me ,vendor/symfony/cache-contracts/composer.jsongmeg(7vendor/symfony/dependency-injection/EnvVarProcessor.php*me*Y;vendor/symfony/dependency-injection/ContainerAwareTrait.phpjmejNBvendor/symfony/dependency-injection/ExpressionLanguageProvider.phpmes1vendor/symfony/dependency-injection/Container.php(7me(7XE8vendor/symfony/dependency-injection/ReverseContainer.php me 0vendor/symfony/dependency-injection/Variable.phpmeVB#Dvendor/symfony/dependency-injection/Extension/ExtensionInterface.phpmeƤ;vendor/symfony/dependency-injection/Extension/Extension.phpmesjKvendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php8me8%iQvendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.phpSmeS)?vendor/symfony/dependency-injection/Attribute/Autoconfigure.php\me\SH>vendor/symfony/dependency-injection/Attribute/AsTaggedItem.phpmek8vendor/symfony/dependency-injection/Attribute/Target.phpme S6vendor/symfony/dependency-injection/Attribute/When.phpmeXˤ@vendor/symfony/dependency-injection/Attribute/TaggedIterator.php=me=*"Bvendor/symfony/dependency-injection/Attribute/AutoconfigureTag.phpme ?vendor/symfony/dependency-injection/Attribute/TaggedLocator.php<me<Eͤ+vendor/symfony/dependency-injection/LICENSE,me,U0vendor/symfony/dependency-injection/CHANGELOG.md9me9Qvendor/symfony/dependency-injection/Config/ContainerParametersResourceChecker.phpumeuʔs Jvendor/symfony/dependency-injection/Config/ContainerParametersResource.phpmeJ`Jvendor/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.phpFmeFk[Avendor/symfony/dependency-injection/Argument/AbstractArgument.phpmevDvendor/symfony/dependency-injection/Argument/RewindableGenerator.phpme?~\Gvendor/symfony/dependency-injection/Argument/ServiceLocatorArgument.phpBmeBp3>vendor/symfony/dependency-injection/Argument/BoundArgument.phpmeaAvendor/symfony/dependency-injection/Argument/IteratorArgument.phpmebaGvendor/symfony/dependency-injection/Argument/TaggedIteratorArgument.php me 2CBvendor/symfony/dependency-injection/Argument/ArgumentInterface.phpGmeGﺔ?vendor/symfony/dependency-injection/Argument/ServiceLocator.php9me9͞$Gvendor/symfony/dependency-injection/Argument/ServiceClosureArgument.phpmevA8vendor/symfony/dependency-injection/ContainerBuilder.phpPmeP Q7vendor/symfony/dependency-injection/ChildDefinition.php me g1vendor/symfony/dependency-injection/Reference.phpme^].Jvendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php: me: ˒-Ovendor/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php me +L{Avendor/symfony/dependency-injection/ParameterBag/ParameterBag.php{me{݇Jvendor/symfony/dependency-injection/ParameterBag/ContainerBagInterface.phpwmewsAvendor/symfony/dependency-injection/ParameterBag/ContainerBag.phpmeBSGvendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php_me_nȤ2vendor/symfony/dependency-injection/Definition.phpWmeW$Sɤ@vendor/symfony/dependency-injection/EnvVarProcessorInterface.phpme}o>X-vendor/symfony/dependency-injection/README.mdCmeC<:vendor/symfony/dependency-injection/ContainerInterface.php# me# a |9vendor/symfony/dependency-injection/Loader/FileLoader.php5)me5)*=vendor/symfony/dependency-injection/Loader/YamlFileLoader.phpme_ӤOvendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsdr9mer9-0<vendor/symfony/dependency-injection/Loader/PhpFileLoader.phpY!meY!E<vendor/symfony/dependency-injection/Loader/XmlFileLoader.phpmeD<vendor/symfony/dependency-injection/Loader/IniFileLoader.php me uq7Pvendor/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.phpmeF$jPvendor/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.phpmeƭbQvendor/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.phpme*bMvendor/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php/me/݅Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.phpLmeLQvendor/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.phpmePvendor/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.phpdmedkPvendor/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.phpme萯Tvendor/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.phpmeސNvendor/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.phpmeOvendor/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.phpme֍Kvendor/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.phpmesNvendor/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.phpmeZ$ѪPvendor/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.phpymeyл*/Uvendor/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.phpCmeCVQvendor/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.phpRmeR]*Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.phpSmeS# Lvendor/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php6me6~qLvendor/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.phpEmeELPvendor/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.phpmeF-Mvendor/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.phpJmeJ@ Pvendor/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.phpKmeKPvendor/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.phpme'0Xvendor/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.phpme#yKvendor/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php0me0peWvendor/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php me CrڠRvendor/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.phpmeBрYQvendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.phpmeƔMvendor/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.phpmeRF,bUvendor/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.phpme(ЁQvendor/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php me hRvendor/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.phpmeo9-Ovendor/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php#me#s˜ۤ>vendor/symfony/dependency-injection/Loader/DirectoryLoader.phpameaǤ=vendor/symfony/dependency-injection/Loader/GlobFileLoader.phpme9$<vendor/symfony/dependency-injection/Loader/ClosureLoader.phpme[=vendor/symfony/dependency-injection/LazyProxy/ProxyHelper.php me <Kvendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.phpmelݴFvendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.phpmeaYVvendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.phpme5'פTvendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.phpxmex9|:vendor/symfony/dependency-injection/ExpressionLanguage.phpdmedb6vendor/symfony/dependency-injection/ServiceLocator.php>me>J6vendor/symfony/dependency-injection/TypedReference.phpme2)-vendor/symfony/dependency-injection/Alias.phpmev%Uvendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.phpme?u@vendor/symfony/dependency-injection/Exception/LogicException.phpme}|ȤFvendor/symfony/dependency-injection/Exception/EnvNotFoundException.phpme˜&Lvendor/symfony/dependency-injection/Exception/ParameterNotFoundException.phpQ meQ \Dvendor/symfony/dependency-injection/Exception/ExceptionInterface.php~me~Fvendor/symfony/dependency-injection/Exception/OutOfBoundsException.phpmeOnSvendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.phpme +Jvendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php$me$ rBvendor/symfony/dependency-injection/Exception/RuntimeException.php me  Jvendor/symfony/dependency-injection/Exception/InvalidArgumentException.php'me'Hvendor/symfony/dependency-injection/Exception/BadMethodCallException.phpme,wKvendor/symfony/dependency-injection/Exception/AutowiringFailedException.phpmeeOvendor/symfony/dependency-injection/Exception/InvalidParameterTypeException.php0me0IGvendor/symfony/dependency-injection/Exception/EnvParameterException.phpme=&)@vendor/symfony/dependency-injection/TaggedContainerInterface.phpmeǝ1vendor/symfony/dependency-injection/Parameter.php|me|nJEvendor/symfony/dependency-injection/Compiler/DecoratorServicePass.phpme'Mvendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.phpKmeKALvendor/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.phpmeAfMvendor/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php me S/Tvendor/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php me Lvendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.phpme_9;vendor/symfony/dependency-injection/Compiler/PassConfig.phpme`ZFvendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php~ me~ FAaäEvendor/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.phpX meX L_;Jvendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php me HJvendor/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php1me11FʤMvendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php)me)<`Hvendor/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.phpme/ODvendor/symfony/dependency-injection/Compiler/ResolveBindingsPass.php&me&eKvendor/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.phpmef*Mvendor/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php me Fvendor/symfony/dependency-injection/Compiler/AbstractRecursivePass.php3%me3%xIRvendor/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php me Ls]Rvendor/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.phpvmevvWOvendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php me pKvendor/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.phpTmeTLOvendor/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.phpmeBuDvendor/symfony/dependency-injection/Compiler/ResolvePrivatesPass.phpmeפOvendor/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php& me& K.Jvendor/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php,me,t}ȤIvendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.phpvmevx#Pvendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.phpR!meR!L6kJvendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.phpme\Mvendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php7 me7 Lvendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.phpT meT =vendor/symfony/dependency-injection/Compiler/AutowirePass.phpkbmekbLRFvendor/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.phpMmeM\hTRvendor/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.phpmeD>ϤKvendor/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.phpmmemI2aLvendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php me b4]vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.phpme7Evendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.phpme #Qvendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php me Avendor/symfony/dependency-injection/Compiler/ResolveClassPass.php^me^j'Nvendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.phpmeV𿐤Kvendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.phpme ⅤOvendor/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.phpme1[Cvendor/symfony/dependency-injection/Compiler/ResolveHotPathPass.php9 me9 1f\Jvendor/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.phpmecǤNvendor/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.phpme[{Fvendor/symfony/dependency-injection/Compiler/CompilerPassInterface.phpme@Svendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.phpme,vendor/symfony/dependency-injection/Dumper/DumperInterface.phpjmejm5vendor/symfony/dependency-injection/Dumper/Dumper.phpmeU;a8vendor/symfony/dependency-injection/Dumper/PhpDumper.phpdgmedg:ä1vendor/symfony/dependency-injection/composer.jsonxmexEd=vendor/symfony/dependency-injection/EnvVarLoaderInterface.phpmeAar?vendor/symfony/dependency-injection/ContainerAwareInterface.php]me]雤:vendor/symfony/password-hasher/PasswordHasherInterface.phpmek>>vendor/symfony/password-hasher/Hasher/SodiumPasswordHasher.phpmeUBvendor/symfony/password-hasher/Hasher/CheckPasswordLengthTrait.phpwmew#1<vendor/symfony/password-hasher/Hasher/UserPasswordHasher.phpmerπ>vendor/symfony/password-hasher/Hasher/Pbkdf2PasswordHasher.php me (SAFvendor/symfony/password-hasher/Hasher/PasswordHasherAwareInterface.phpmeϡ*>vendor/symfony/password-hasher/Hasher/NativePasswordHasher.phpmeEvendor/symfony/password-hasher/Hasher/MessageDigestPasswordHasher.php me xEvendor/symfony/password-hasher/Hasher/UserPasswordHasherInterface.phpmeVkTAvendor/symfony/password-hasher/Hasher/PlaintextPasswordHasher.php6 me6 juqHvendor/symfony/password-hasher/Hasher/PasswordHasherFactoryInterface.php1me1|<Avendor/symfony/password-hasher/Hasher/MigratingPasswordHasher.phpLmeLSG?vendor/symfony/password-hasher/Hasher/PasswordHasherFactory.php#me#vӫ&vendor/symfony/password-hasher/LICENSE,me,U+vendor/symfony/password-hasher/CHANGELOG.md]me]{>I@vendor/symfony/password-hasher/LegacyPasswordHasherInterface.phpvmevV(vendor/symfony/password-hasher/README.mdme!Bvendor/symfony/password-hasher/Command/UserPasswordHashCommand.php)me)Ud;vendor/symfony/password-hasher/Exception/LogicException.phpmeA?vendor/symfony/password-hasher/Exception/ExceptionInterface.phpmeEWLEvendor/symfony/password-hasher/Exception/InvalidPasswordException.phpmeL,vendor/symfony/password-hasher/composer.jsonmeuB$6vendor/symfony/process/LICENSE,me,U+vendor/symfony/process/ExecutableFinder.php me #vendor/symfony/process/CHANGELOG.mdme(C vendor/symfony/process/README.mdmeH!X*vendor/symfony/process/Pipes/UnixPipes.phpmebU-vendor/symfony/process/Pipes/WindowsPipes.phpcmec$ޙ.vendor/symfony/process/Pipes/AbstractPipes.phpme#/vendor/symfony/process/Pipes/PipesInterface.phpme&vendor/symfony/process/InputStream.php me D_%vendor/symfony/process/PhpProcess.php me nq.vendor/symfony/process/PhpExecutableFinder.php] me] "!Τ=vendor/symfony/process/Exception/ProcessSignaledException.phpmeV3vendor/symfony/process/Exception/LogicException.phpmeiUQ7vendor/symfony/process/Exception/ExceptionInterface.phpme j%W;vendor/symfony/process/Exception/ProcessFailedException.phpAmeAlk5vendor/symfony/process/Exception/RuntimeException.phpmeP=vendor/symfony/process/Exception/ProcessTimedOutException.phptmet/=vendor/symfony/process/Exception/InvalidArgumentException.phpmeڴ$vendor/symfony/process/composer.jsonmeSg'vendor/symfony/process/ProcessUtils.phpTmeT"vendor/symfony/process/Process.phpme}B=(vendor/symfony/console/ConsoleEvents.phpme. !vendor/symfony/console/Cursor.php7me7E &vendor/symfony/console/Application.phppmep(;/vendor/symfony/console/Logger/ConsoleLogger.phpme2.vendor/symfony/console/Attribute/AsCommand.phpJmeJcvendor/symfony/console/LICENSE,me,U#vendor/symfony/console/CHANGELOG.md me P9vendor/symfony/console/Input/StreamableInputInterface.phpvmev@+vendor/symfony/console/Input/ArrayInput.phpme$*vendor/symfony/console/Input/ArgvInput.php0me0.vendor/symfony/console/Input/InputArgument.php me 24vendor/symfony/console/Input/InputAwareInterface.phpHmeHEa,vendor/symfony/console/Input/InputOption.php<me<'0vendor/symfony/console/Input/InputDefinition.phpH.meH.ʄ=s,vendor/symfony/console/Input/StringInput.php me JYc&vendor/symfony/console/Input/Input.php1me1m/vendor/symfony/console/Input/InputInterface.phpme +#vendor/symfony/console/Terminal.phpimei5wt82vendor/symfony/console/CI/GithubActionReporter.php me s0vendor/symfony/console/Resources/completion.bash me z(4vendor/symfony/console/Resources/bin/hiddeninput.exe$me$v5vendor/symfony/console/Output/TrimmedBufferOutput.phpcmec8vendor/symfony/console/Output/ConsoleOutputInterface.php-me-U0vendor/symfony/console/Output/BufferedOutput.php_me_PR11vendor/symfony/console/Output/OutputInterface.phpl mel -I(vendor/symfony/console/Output/Output.php[me[466vendor/symfony/console/Output/ConsoleSectionOutput.phpmeɤ/vendor/symfony/console/Output/ConsoleOutput.php!me!-i¤,vendor/symfony/console/Output/NullOutput.php, me, GPJ.vendor/symfony/console/Output/StreamOutput.phpmec(Avendor/symfony/console/Completion/Output/BashCompletionOutput.php me Q2Fvendor/symfony/console/Completion/Output/CompletionOutputInterface.phpme:]0vendor/symfony/console/Completion/Suggestion.phpme s;vendor/symfony/console/Completion/CompletionSuggestions.phpfmef\5vendor/symfony/console/Completion/CompletionInput.phpG meG Fx,vendor/symfony/console/Style/OutputStyle.php8 me8 '-vendor/symfony/console/Style/SymfonyStyle.php9me9y/vendor/symfony/console/Style/StyleInterface.phpN meN ͎)2vendor/symfony/console/Question/ChoiceQuestion.phpme.!ܤ,vendor/symfony/console/Question/Question.phprmerl8vendor/symfony/console/Question/ConfirmationQuestion.php%me%䦤 vendor/symfony/console/README.mdme=NP9vendor/symfony/console/Formatter/OutputFormatterStyle.php me .}2Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.phpemee5ޤ=vendor/symfony/console/Formatter/NullOutputFormatterStyle.phpme]*=vendor/symfony/console/Formatter/OutputFormatterInterface.php@me@?&4vendor/symfony/console/Formatter/OutputFormatter.php me d8vendor/symfony/console/Formatter/NullOutputFormatter.phpkmekNlFvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.phpmepޤ>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php me 0 vendor/symfony/console/Color.phpRmeRP8vendor/symfony/console/SignalRegistry/SignalRegistry.php@me@ 3vendor/symfony/console/SingleCommandApplication.php)me) 6^?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpvmevh?vendor/symfony/console/CommandLoader/ContainerCommandLoader.php me 7!=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php]me]j74vendor/symfony/console/Descriptor/TextDescriptor.php1me1X<vendor/symfony/console/Descriptor/ApplicationDescription.phpme5x3vendor/symfony/console/Descriptor/XmlDescriptor.php'me'fʕ'9vendor/symfony/console/Descriptor/DescriptorInterface.phpImeI N)0vendor/symfony/console/Descriptor/Descriptor.php me =4vendor/symfony/console/Descriptor/JsonDescriptor.phpmek/68vendor/symfony/console/Descriptor/MarkdownDescriptor.phpKmeKѤ/vendor/symfony/console/Tester/CommandTester.php> me> ڑc-vendor/symfony/console/Tester/TesterTrait.phpmé3vendor/symfony/console/Tester/ApplicationTester.php me $Z9vendor/symfony/console/Tester/CommandCompletionTester.php<me<y@vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.phpmeLd.vendor/symfony/console/Command/LazyCommand.php&me&i0vendor/symfony/console/Command/LockableTrait.php me ̤.vendor/symfony/console/Command/HelpCommand.php me 2vendor/symfony/console/Command/CompleteCommand.php me gQ@.vendor/symfony/console/Command/ListCommand.php me 8MZ8vendor/symfony/console/Command/DumpCompletionCommand.phpmeDә=vendor/symfony/console/Command/SignalableCommandInterface.phpme,*vendor/symfony/console/Command/Command.phpPmePvK,vendor/symfony/console/Helper/TableStyle.php0me0?V+vendor/symfony/console/Helper/TableRows.phpRmeRHD7vendor/symfony/console/Helper/SymfonyQuestionHelper.php+ me+ ݙ 1vendor/symfony/console/Helper/HelperInterface.phpZmeZ6+vendor/symfony/console/Helper/TableCell.php me ~3/vendor/symfony/console/Helper/ProcessHelper.phpmeIʤ0vendor/symfony/console/Helper/TableSeparator.php!me!n21vendor/symfony/console/Helper/FormatterHelper.phpx mex @d Ϥ0vendor/symfony/console/Helper/TableCellStyle.php\me\9|S-vendor/symfony/console/Helper/ProgressBar.phpHmeHEX6vendor/symfony/console/Helper/DebugFormatterHelper.phpX meX 젤+vendor/symfony/console/Helper/HelperSet.php; me; J>~(vendor/symfony/console/Helper/Dumper.phpDmeDz2vendor/symfony/console/Helper/InputAwareHelper.phpmeE0vendor/symfony/console/Helper/QuestionHelper.php}Nme}N(vendor/symfony/console/Helper/Helper.phpmeI 0'vendor/symfony/console/Helper/Table.phpPrmePrm63vendor/symfony/console/Helper/ProgressIndicator.phpme:2vendor/symfony/console/Helper/DescriptorHelper.php me NqDvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpmeS3vendor/symfony/console/Exception/LogicException.phpme5;vendor/symfony/console/Exception/InvalidOptionException.phpme%:7vendor/symfony/console/Exception/ExceptionInterface.phpmeoOȤ5vendor/symfony/console/Exception/RuntimeException.phpme ?vendor/symfony/console/Exception/NamespaceNotFoundException.phpmexD=vendor/symfony/console/Exception/InvalidArgumentException.phpmex Τ:vendor/symfony/console/Exception/MissingInputException.phpmeL=vendor/symfony/console/Exception/CommandNotFoundException.phpme k g6vendor/symfony/console/Event/ConsoleTerminateEvent.phpZmeZTJ2vendor/symfony/console/Event/ConsoleErrorEvent.phpmeo}4Ϥ4vendor/symfony/console/Event/ConsoleCommandEvent.php9me9tp-vendor/symfony/console/Event/ConsoleEvent.phpme.bF3vendor/symfony/console/Event/ConsoleSignalEvent.phpme#4pԤ$vendor/symfony/console/composer.jsonmeS(6vendor/symfony/console/EventListener/ErrorListener.phpB meB a0vendor/symfony/polyfill-mbstring/bootstrap80.phpa#mea#,(vendor/symfony/polyfill-mbstring/LICENSE,me,HBvendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.phpme]SC@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpUmeUD׎Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.phpmeoቤ@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpO[meO[(-vendor/symfony/polyfill-mbstring/Mbstring.phpi|mei|fe%.vendor/symfony/polyfill-mbstring/bootstrap.phpVmeVm<*vendor/symfony/polyfill-mbstring/README.mdrmerA`.vendor/symfony/polyfill-mbstring/composer.json8me8.5vendor/symfony/polyfill-intl-grapheme/bootstrap80.phpg meg E{-vendor/symfony/polyfill-intl-grapheme/LICENSE,me,H2vendor/symfony/polyfill-intl-grapheme/Grapheme.php &me &83vendor/symfony/polyfill-intl-grapheme/bootstrap.phpme/vendor/symfony/polyfill-intl-grapheme/README.mdKmeKC>3vendor/symfony/polyfill-intl-grapheme/composer.json me vendor/ramsey/uuid/LICENSEDmeDD¤vendor/ramsey/uuid/README.mdhmehg vendor/ramsey/uuid/composer.json7me7J$vendor/ramsey/uuid/src/functions.phpj mej ":vendor/ramsey/uuid/src/Converter/Time/PhpTimeConverter.phpme濤?vendor/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.phpme:@vendor/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php me lCvendor/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.phpme>vendor/ramsey/uuid/src/Converter/Number/BigNumberConverter.phpme;vendor/ramsey/uuid/src/Converter/TimeConverterInterface.phpDmeDx=vendor/ramsey/uuid/src/Converter/NumberConverterInterface.phpmeuѢ8vendor/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php me o^1vendor/ramsey/uuid/src/Codec/OrderedTimeCodec.php"me"а/vendor/ramsey/uuid/src/Codec/CodecInterface.phpmeEΤ0vendor/ramsey/uuid/src/Codec/GuidStringCodec.php me U`,vendor/ramsey/uuid/src/Codec/StringCodec.phpRmeR 7vendor/ramsey/uuid/src/Codec/TimestampLastCombCodec.phpme0vendor/ramsey/uuid/src/Uuid.php0Zme0Zx9vendor/ramsey/uuid/src/Provider/NodeProviderInterface.phpmeڤ;vendor/ramsey/uuid/src/Provider/Time/SystemTimeProvider.phpme:vendor/ramsey/uuid/src/Provider/Time/FixedTimeProvider.phpmeUtC9vendor/ramsey/uuid/src/Provider/TimeProviderInterface.php^me^ۤ;vendor/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php]me]An=vendor/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.phpme[I;vendor/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php me CKd%vendor/ramsey/uuid/src/FeatureSet.php$me$3/t(vendor/ramsey/uuid/src/UuidInterface.php%me%He"L/vendor/ramsey/uuid/src/UuidFactoryInterface.phprmerV+X9vendor/ramsey/uuid/src/Generator/RandomBytesGenerator.phpmecߤ5vendor/ramsey/uuid/src/Generator/OpenSslGenerator.phpme4$;vendor/ramsey/uuid/src/Generator/RandomGeneratorFactory.phpqmeq)Q!9vendor/ramsey/uuid/src/Generator/TimeGeneratorFactory.phpmeZFzN9vendor/ramsey/uuid/src/Generator/DefaultTimeGenerator.php*me*p5vendor/ramsey/uuid/src/Generator/RandomLibAdapter.php(me(<;vendor/ramsey/uuid/src/Generator/TimeGeneratorInterface.phpme^ܟ"2vendor/ramsey/uuid/src/Generator/CombGenerator.php me y?$=vendor/ramsey/uuid/src/Generator/RandomGeneratorInterface.phpmeȔ<vendor/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.phpomeo(4vendor/ramsey/uuid/src/Generator/MtRandGenerator.phpmeg:vendor/ramsey/uuid/src/Generator/SodiumRandomGenerator.phpme:L:vendor/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.phpmeA&vendor/ramsey/uuid/src/BinaryUtils.php4me4F@ۤ&vendor/ramsey/uuid/src/UuidFactory.phpE!meE!Jji'vendor/ramsey/uuid/src/DegradedUuid.php me @{6vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.phpmeL7vendor/ramsey/uuid/src/Builder/UuidBuilderInterface.phpme5vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.phpmeBvendor/ramsey/uuid/src/Exception/UnsupportedOperationException.phpmez{Cvendor/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.phpme佢?vendor/ramsey/uuid/src/Exception/InvalidUuidStringException.phpmeEp8vendor/react/promise/LICENSEgmegF!vendor/react/promise/CHANGELOG.mdmeB<Τvendor/react/promise/README.mdQYmeQY@wn"vendor/react/promise/composer.jsonmeNjB-vendor/react/promise/src/PromiseInterface.phpmeMFx8&vendor/react/promise/src/functions.php1-me1-t.vendor/react/promise/src/functions_include.phpmedx\$vendor/react/promise/src/Promise.php(me(A%vendor/react/promise/src/Deferred.php/me/-ߤ5vendor/react/promise/src/Internal/RejectedPromise.phpme6vendor/react/promise/src/Internal/FulfilledPromise.php1 me1 RKbФ7vendor/react/promise/src/Internal/CancellationQueue.phpmer26vendor/react/promise/src/Exception/LengthException.php^me^?q9vendor/react/promise/src/Exception/CompositeException.phpamea'3vendor/terminal42/service-annotation-bundle/LICENSE#me#~ke03vendor/terminal42/service-annotation-bundle/ecs.phprmer=055vendor/terminal42/service-annotation-bundle/README.md me #9vendor/terminal42/service-annotation-bundle/composer.jsonme幉Uvendor/terminal42/service-annotation-bundle/src/Terminal42ServiceAnnotationBundle.phpnmenENvendor/terminal42/service-annotation-bundle/src/ServiceAnnotationInterface.phpmeojIvendor/terminal42/service-annotation-bundle/src/Annotation/ServiceTag.phpmeHƧRvendor/terminal42/service-annotation-bundle/src/Annotation/ServiceTagInterface.phpmeFJ"fvendor/terminal42/service-annotation-bundle/src/DependencyInjection/Compiler/ServiceAnnotationPass.php me =! array ( 'type' => 'php', 'condition' => '^7.2.5 || ^8.0', 'message' => 'The application requires the version "^7.2.5 || ^8.0" or greater.', 'helpMessage' => 'The application requires the version "^7.2.5 || ^8.0" or greater.', ), 1 => array ( 'type' => 'extension', 'condition' => 'json', 'message' => 'The application requires the extension "json". Enable it or install a polyfill.', 'helpMessage' => 'The application requires the extension "json".', ), 2 => array ( 'type' => 'extension', 'condition' => 'json', 'message' => 'The package "ramsey/uuid" requires the extension "json". Enable it or install a polyfill.', 'helpMessage' => 'The package "ramsey/uuid" requires the extension "json".', ), 3 => array ( 'type' => 'extension', 'condition' => 'zip', 'message' => 'The application requires the extension "zip". Enable it or install a polyfill.', 'helpMessage' => 'The application requires the extension "zip".', ), 4 => array ( 'type' => 'extension', 'condition' => 'openssl', 'message' => 'The package "composer/ca-bundle" requires the extension "openssl". Enable it or install a polyfill.', 'helpMessage' => 'The package "composer/ca-bundle" requires the extension "openssl".', ), 5 => array ( 'type' => 'extension', 'condition' => 'pcre', 'message' => 'The package "composer/ca-bundle" requires the extension "pcre". Enable it or install a polyfill.', 'helpMessage' => 'The package "composer/ca-bundle" requires the extension "pcre".', ), 6 => array ( 'type' => 'extension', 'condition' => 'tokenizer', 'message' => 'The package "doctrine/annotations" requires the extension "tokenizer". Enable it or install a polyfill.', 'helpMessage' => 'The package "doctrine/annotations" requires the extension "tokenizer".', ), 7 => array ( 'type' => 'extension', 'condition' => 'xml', 'message' => 'The package "symfony/framework-bundle" requires the extension "xml". Enable it or install a polyfill.', 'helpMessage' => 'The package "symfony/framework-bundle" requires the extension "xml".', ), 8 => array ( 'type' => 'extension', 'condition' => 'xml', 'message' => 'The package "symfony/security-bundle" requires the extension "xml". Enable it or install a polyfill.', 'helpMessage' => 'The package "symfony/security-bundle" requires the extension "xml".', ), ); * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var ?string */ private $vendorDir; // PSR-4 /** * @var array[] * @psalm-var array> */ private $prefixLengthsPsr4 = array(); /** * @var array[] * @psalm-var array> */ private $prefixDirsPsr4 = array(); /** * @var array[] * @psalm-var array */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * @var array[] * @psalm-var array> */ private $prefixesPsr0 = array(); /** * @var array[] * @psalm-var array */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var string[] * @psalm-var array */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var bool[] * @psalm-var array */ private $missingClasses = array(); /** @var ?string */ private $apcuPrefix; /** * @var self[] */ private static $registeredLoaders = array(); /** * @param ?string $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } /** * @return string[] */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array[] * @psalm-return array> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return array[] * @psalm-return array */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return array[] * @psalm-return array */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return string[] Array of classname => path * @psalm-return array */ public function getClassMap() { return $this->classMap; } /** * @param string[] $classMap Class to filename map * @psalm-param array $classMap * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param string[]|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( (array) $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, (array) $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( (array) $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], (array) $paths ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param string[]|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param string[]|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } /** * Returns the currently registered loaders indexed by their corresponding vendor directories. * * @return self[] */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void * @private */ function includeFile($file) { include $file; } array($baseDir . '/src'), 'HumbugBox3150\\Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), ); $vendorDir . '/composer/InstalledVersions.php', 'HumbugBox3150\\Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php', 'HumbugBox3150\\Composer\\Semver\\CompilingMatcher' => $vendorDir . '/composer/semver/src/CompilingMatcher.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\Bound' => $vendorDir . '/composer/semver/src/Constraint/Bound.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\MatchAllConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchAllConstraint.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\MatchNoneConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchNoneConstraint.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php', 'HumbugBox3150\\Composer\\Semver\\Interval' => $vendorDir . '/composer/semver/src/Interval.php', 'HumbugBox3150\\Composer\\Semver\\Intervals' => $vendorDir . '/composer/semver/src/Intervals.php', 'HumbugBox3150\\Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php', 'HumbugBox3150\\Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Checker' => $baseDir . '/src/Checker.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IO' => $baseDir . '/src/IO.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IsExtensionFulfilled' => $baseDir . '/src/IsExtensionFulfilled.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IsFulfilled' => $baseDir . '/src/IsFulfilled.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IsPhpVersionFulfilled' => $baseDir . '/src/IsPhpVersionFulfilled.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Printer' => $baseDir . '/src/Printer.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Requirement' => $baseDir . '/src/Requirement.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\RequirementCollection' => $baseDir . '/src/RequirementCollection.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Terminal' => $baseDir . '/src/Terminal.php', ); array ( 'HumbugBox3150\\KevinGH\\RequirementChecker\\' => 41, 'HumbugBox3150\\Composer\\Semver\\' => 30, ), ); public static $prefixDirsPsr4 = array ( 'HumbugBox3150\\KevinGH\\RequirementChecker\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), 'HumbugBox3150\\Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), ); public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'HumbugBox3150\\Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php', 'HumbugBox3150\\Composer\\Semver\\CompilingMatcher' => __DIR__ . '/..' . '/composer/semver/src/CompilingMatcher.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\Bound' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Bound.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\MatchAllConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchAllConstraint.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\MatchNoneConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchNoneConstraint.php', 'HumbugBox3150\\Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php', 'HumbugBox3150\\Composer\\Semver\\Interval' => __DIR__ . '/..' . '/composer/semver/src/Interval.php', 'HumbugBox3150\\Composer\\Semver\\Intervals' => __DIR__ . '/..' . '/composer/semver/src/Intervals.php', 'HumbugBox3150\\Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php', 'HumbugBox3150\\Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Checker' => __DIR__ . '/../..' . '/src/Checker.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IO' => __DIR__ . '/../..' . '/src/IO.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IsExtensionFulfilled' => __DIR__ . '/../..' . '/src/IsExtensionFulfilled.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IsFulfilled' => __DIR__ . '/../..' . '/src/IsFulfilled.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\IsPhpVersionFulfilled' => __DIR__ . '/../..' . '/src/IsPhpVersionFulfilled.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Printer' => __DIR__ . '/../..' . '/src/Printer.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Requirement' => __DIR__ . '/../..' . '/src/Requirement.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\RequirementCollection' => __DIR__ . '/../..' . '/src/RequirementCollection.php', 'HumbugBox3150\\KevinGH\\RequirementChecker\\Terminal' => __DIR__ . '/../..' . '/src/Terminal.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit7b1918d23e69dc64db20ca1f98396893::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit7b1918d23e69dc64db20ca1f98396893::$prefixDirsPsr4; $loader->classMap = ComposerStaticInit7b1918d23e69dc64db20ca1f98396893::$classMap; }, null, ClassLoader::class); } } = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit7b1918d23e69dc64db20ca1f98396893::getInitializer($loader)); } else { $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->setClassMapAuthoritative(true); $loader->register(true); return $loader; } } ', $version2); } public static function greaterThanOrEqualTo($version1, $version2) { return self::compare($version1, '>=', $version2); } public static function lessThan($version1, $version2) { return self::compare($version1, '<', $version2); } public static function lessThanOrEqualTo($version1, $version2) { return self::compare($version1, '<=', $version2); } public static function equalTo($version1, $version2) { return self::compare($version1, '==', $version2); } public static function notEqualTo($version1, $version2) { return self::compare($version1, '!=', $version2); } public static function compare($version1, $operator, $version2) { $constraint = new Constraint($operator, $version2); return $constraint->matchSpecific(new Constraint('==', $version1), \true); } } normalize($version)); $parsedConstraints = $versionParser->parseConstraints($constraints); return $parsedConstraints->matches($provider); } public static function satisfiedBy(array $versions, $constraints) { $versions = \array_filter($versions, function ($version) use($constraints) { return Semver::satisfies($version, $constraints); }); return \array_values($versions); } public static function sort(array $versions) { return self::usort($versions, self::SORT_ASC); } public static function rsort(array $versions) { return self::usort($versions, self::SORT_DESC); } private static function usort(array $versions, $direction) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $normalized = array(); foreach ($versions as $key => $version) { $normalizedVersion = $versionParser->normalize($version); $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion); $normalized[] = array($normalizedVersion, $key); } \usort($normalized, function (array $left, array $right) use($direction) { if ($left[0] === $right[0]) { return 0; } if (Comparator::lessThan($left[0], $right[0])) { return -$direction; } return $direction; }); $sorted = array(); foreach ($normalized as $item) { $sorted[] = $versions[$item[1]]; } return $sorted; } } expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? \ltrim($matches[$index + 1], '.-') : ''); } if (!empty($matches[$index + 2])) { $version .= '-dev'; } return $version; } if (\preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { try { $normalized = $this->normalizeBranch($match[1]); if (\strpos($normalized, 'dev-') === \false) { return $normalized; } } catch (\Exception $e) { } } $extraMessage = ''; if (\preg_match('{ +as +' . \preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))?$}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; } elseif (\preg_match('{^' . \preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))? +as +}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; } throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); } public function parseNumericAliasPrefix($branch) { if (\preg_match('{^(?P(\\d++\\.)*\\d++)(?:\\.x)?-dev$}i', $branch, $matches)) { return $matches['version'] . '.'; } return \false; } public function normalizeBranch($name) { $name = \trim($name); if (\preg_match('{^v?(\\d++)(\\.(?:\\d++|[xX*]))?(\\.(?:\\d++|[xX*]))?(\\.(?:\\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; for ($i = 1; $i < 5; ++$i) { $version .= isset($matches[$i]) ? \str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; } return \str_replace('x', '9999999', $version) . '-dev'; } return 'dev-' . $name; } public function normalizeDefaultBranch($name) { if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') { return '9999999-dev'; } return $name; } public function parseConstraints($constraints) { $prettyConstraint = $constraints; $orConstraints = \preg_split('{\\s*\\|\\|?\\s*}', \trim($constraints)); $orGroups = array(); foreach ($orConstraints as $constraints) { $andConstraints = \preg_split('{(?< ,]) *(? 1) { $constraintObjects = array(); foreach ($andConstraints as $constraint) { foreach ($this->parseConstraint($constraint) as $parsedConstraint) { $constraintObjects[] = $parsedConstraint; } } } else { $constraintObjects = $this->parseConstraint($andConstraints[0]); } if (1 === \count($constraintObjects)) { $constraint = $constraintObjects[0]; } else { $constraint = new MultiConstraint($constraintObjects); } $orGroups[] = $constraint; } $constraint = MultiConstraint::create($orGroups, \false); $constraint->setPrettyString($prettyConstraint); return $constraint; } private function parseConstraint($constraint) { if (\preg_match('{^([^,\\s]++) ++as ++([^,\\s]++)$}', $constraint, $match)) { $constraint = $match[1]; } if (\preg_match('{^([^,\\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { $constraint = '' !== $match[1] ? $match[1] : '*'; if ($match[2] !== 'stable') { $stabilityModifier = $match[2]; } } if (\preg_match('{^(dev-[^,\\s@]+?|[^,\\s@]+?\\.x-dev)#.+$}i', $constraint, $match)) { $constraint = $match[1]; } if (\preg_match('{^(v)?[xX*](\\.[xX*])*$}i', $constraint, $match)) { if (!empty($match[1]) || !empty($match[2])) { return array(new Constraint('>=', '0.0.0.0-dev')); } return array(new MatchAllConstraint()); } $versionRegex = 'v?(\\d++)(?:\\.(\\d++|[xX*]))?(?:\\.(\\d++|[xX*]))?(?:\\.(\\d++|[xX*]))?' . self::$modifierRegex . '(?:\\+[^\\s]+)?'; if (\preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { if (\strpos($constraint, '~>') === 0) { throw new \UnexpectedValueException('Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator'); } if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { $position = 4; } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } for ($i = $position; $i >= 0; $i--) { if ($matches[$i] === 'x' || $matches[$i] === 'X' || $matches[$i] === '*') { $matches[$i] = '9999999'; } } $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(\substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); $highPosition = \max(1, $position - 1); $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array($lowerBound, $upperBound); } if (\preg_match('{^\\^' . $versionRegex . '($)}i', $constraint, $matches)) { if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { $position = 1; } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { $position = 2; } else { $position = 3; } if ($position === 2 && ($matches[2] === 'x' || $matches[2] === 'X' || $matches[2] === '*')) { $position = 1; } $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(\substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array($lowerBound, $upperBound); } if (\preg_match('{^v?(\\d++)(?:\\.(\\d++))?(?:\\.(\\d++))?(?:\\.[xX*])++$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; if ($lowVersion === '0.0.0.0-dev') { return array(new Constraint('<', $highVersion)); } return array(new Constraint('>=', $lowVersion), new Constraint('<', $highVersion)); } if (\preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { $lowStabilitySuffix = ''; if (empty($matches[6]) && empty($matches[8])) { $lowStabilitySuffix = '-dev'; } $lowVersion = $this->normalize($matches['from']); $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); $empty = function ($x) { return $x === 0 || $x === '0' ? \false : empty($x); }; if (!$empty($matches[11]) && !$empty($matches[12]) || !empty($matches[14]) || !empty($matches[16])) { $highVersion = $this->normalize($matches['to']); $upperBound = new Constraint('<=', $highVersion); } else { $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]); $this->normalize($matches['to']); $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); } return array($lowerBound, $upperBound); } if (\preg_match('{^(<>|!=|>=?|<=?|==?)?\\s*(.*)}', $constraint, $matches)) { try { try { $version = $this->normalize($matches[2]); } catch (\UnexpectedValueException $e) { if (\substr($matches[2], -4) === '-dev') { $version = $this->normalize('dev-' . \substr($matches[2], 0, -4)); } else { throw $e; } } $op = $matches[1] ?: '='; if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { $version .= '-' . $stabilityModifier; } elseif ('<' === $op || '>=' === $op) { if (!\preg_match('/-' . self::$modifierRegex . '$/', \strtolower($matches[2]))) { if (\strpos($matches[2], 'dev-') !== 0) { $version .= '-dev'; } } } return array(new Constraint($matches[1] ?: '=', $version)); } catch (\Exception $e) { } } $message = 'Could not parse version constraint ' . $constraint; if (isset($e)) { $message .= ': ' . $e->getMessage(); } throw new \UnexpectedValueException($message); } private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') { for ($i = 4; $i > 0; --$i) { if ($i > $position) { $matches[$i] = $pad; } elseif ($i === $position && $increment) { $matches[$i] += $increment; if ($matches[$i] < 0) { $matches[$i] = $pad; --$position; if ($i === 1) { return null; } } } } return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; } private function expandStability($stability) { $stability = \strtolower($stability); switch ($stability) { case 'a': return 'alpha'; case 'b': return 'beta'; case 'p': case 'pl': return 'patch'; case 'rc': return 'RC'; default: return $stability; } } } constraints = $constraints; $this->conjunctive = $conjunctive; } public function getConstraints() { return $this->constraints; } public function isConjunctive() { return $this->conjunctive; } public function isDisjunctive() { return !$this->conjunctive; } public function compile($otherOperator) { $parts = array(); foreach ($this->constraints as $constraint) { $code = $constraint->compile($otherOperator); if ($code === 'true') { if (!$this->conjunctive) { return 'true'; } } elseif ($code === 'false') { if ($this->conjunctive) { return 'false'; } } else { $parts[] = '(' . $code . ')'; } } if (!$parts) { return $this->conjunctive ? 'true' : 'false'; } return $this->conjunctive ? \implode('&&', $parts) : \implode('||', $parts); } public function matches(ConstraintInterface $provider) { if (\false === $this->conjunctive) { foreach ($this->constraints as $constraint) { if ($provider->matches($constraint)) { return \true; } } return \false; } foreach ($this->constraints as $constraint) { if (!$provider->matches($constraint)) { return \false; } } return \true; } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } public function __toString() { if ($this->string !== null) { return $this->string; } $constraints = array(); foreach ($this->constraints as $constraint) { $constraints[] = (string) $constraint; } return $this->string = '[' . \implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; } public function getLowerBound() { $this->extractBounds(); return $this->lowerBound; } public function getUpperBound() { $this->extractBounds(); return $this->upperBound; } public static function create(array $constraints, $conjunctive = \true) { if (0 === \count($constraints)) { return new MatchAllConstraint(); } if (1 === \count($constraints)) { return $constraints[0]; } $optimized = self::optimizeConstraints($constraints, $conjunctive); if ($optimized !== null) { list($constraints, $conjunctive) = $optimized; if (\count($constraints) === 1) { return $constraints[0]; } } return new self($constraints, $conjunctive); } private static function optimizeConstraints(array $constraints, $conjunctive) { if (!$conjunctive) { $left = $constraints[0]; $mergedConstraints = array(); $optimized = \false; for ($i = 1, $l = \count($constraints); $i < $l; $i++) { $right = $constraints[$i]; if ($left instanceof MultiConstraint && $left->conjunctive && $right instanceof MultiConstraint && $right->conjunctive && ($left0 = (string) $left->constraints[0]) && $left0[0] === '>' && $left0[1] === '=' && ($left1 = (string) $left->constraints[1]) && $left1[0] === '<' && ($right0 = (string) $right->constraints[0]) && $right0[0] === '>' && $right0[1] === '=' && ($right1 = (string) $right->constraints[1]) && $right1[0] === '<' && \substr($left1, 2) === \substr($right0, 3)) { $optimized = \true; $left = new MultiConstraint(\array_merge(array($left->constraints[0], $right->constraints[1]), \array_slice($left->constraints, 2), \array_slice($right->constraints, 2)), \true); } else { $mergedConstraints[] = $left; $left = $right; } } if ($optimized) { $mergedConstraints[] = $left; return array($mergedConstraints, \false); } } return null; } private function extractBounds() { if (null !== $this->lowerBound) { return; } foreach ($this->constraints as $constraint) { if (null === $this->lowerBound && null === $this->upperBound) { $this->lowerBound = $constraint->getLowerBound(); $this->upperBound = $constraint->getUpperBound(); continue; } if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) { $this->lowerBound = $constraint->getLowerBound(); } if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) { $this->upperBound = $constraint->getUpperBound(); } } } } self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE); /** @phpstan-var */ private static $transOpInt = array(self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!='); /** @phpstan-var */ protected $operator; protected $version; protected $prettyString; protected $lowerBound; protected $upperBound; public function __construct($operator, $version) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(\sprintf('Invalid operator "%s" given, expected one of: %s', $operator, \implode(', ', self::getSupportedOperators()))); } $this->operator = self::$transOpStr[$operator]; $this->version = $version; } public function getVersion() { return $this->version; } public function getOperator() { return self::$transOpInt[$this->operator]; } public function matches(ConstraintInterface $provider) { if ($provider instanceof self) { return $this->matchSpecific($provider); } return $provider->matches($this); } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } public static function getSupportedOperators() { return \array_keys(self::$transOpStr); } /** @phpstan-return */ public static function getOperatorConstant($operator) { return self::$transOpStr[$operator]; } public function versionCompare($a, $b, $operator, $compareBranches = \false) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(\sprintf('Invalid operator "%s" given, expected one of: %s', $operator, \implode(', ', self::getSupportedOperators()))); } $aIsBranch = 'dev-' === \substr($a, 0, 4); $bIsBranch = 'dev-' === \substr($b, 0, 4); if ($operator === '!=' && ($aIsBranch || $bIsBranch)) { return $a !== $b; } if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return \false; } return \version_compare($a, $b, $operator); } public function compile($otherOperator) { if ($this->version[0] === 'd' && 'dev-' === \substr($this->version, 0, 4)) { if (self::OP_EQ === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('$b && $v === %s', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return \sprintf('!$b || $v !== %s', \var_export($this->version, \true)); } return 'false'; } if (self::OP_NE === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('!$b || $v !== %s', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return 'true'; } return '!$b'; } return 'false'; } if (self::OP_EQ === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('\\version_compare($v, %s, \'==\')', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return \sprintf('$b || \\version_compare($v, %s, \'!=\')', \var_export($this->version, \true)); } return \sprintf('!$b && \\version_compare(%s, $v, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$otherOperator]); } if (self::OP_NE === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('$b || (!$b && \\version_compare($v, %s, \'!=\'))', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return 'true'; } return '!$b'; } if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) { if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) { return '!$b'; } } elseif (self::OP_GT === $this->operator || self::OP_GE === $this->operator) { if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) { return '!$b'; } } if (self::OP_NE === $otherOperator) { return 'true'; } $codeComparison = \sprintf('\\version_compare($v, %s, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$this->operator]); if ($this->operator === self::OP_LE) { if ($otherOperator === self::OP_GT) { return \sprintf('!$b && \\version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison; } } elseif ($this->operator === self::OP_GE) { if ($otherOperator === self::OP_LT) { return \sprintf('!$b && \\version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison; } } return \sprintf('!$b && %s', $codeComparison); } public function matchSpecific(Constraint $provider, $compareBranches = \false) { $noEqualOp = \str_replace('=', '', self::$transOpInt[$this->operator]); $providerNoEqualOp = \str_replace('=', '', self::$transOpInt[$provider->operator]); $isEqualOp = self::OP_EQ === $this->operator; $isNonEqualOp = self::OP_NE === $this->operator; $isProviderEqualOp = self::OP_EQ === $provider->operator; $isProviderNonEqualOp = self::OP_NE === $provider->operator; if ($isNonEqualOp || $isProviderNonEqualOp) { if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && 'dev-' === \substr($provider->version, 0, 4)) { return \false; } if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && 'dev-' === \substr($this->version, 0, 4)) { return \false; } if (!$isEqualOp && !$isProviderEqualOp) { return \true; } return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); } if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { if ('dev-' === \substr($this->version, 0, 4) || 'dev-' === \substr($provider->version, 0, 4)) { return \false; } return \true; } $version1 = $isEqualOp ? $this->version : $provider->version; $version2 = $isEqualOp ? $provider->version : $this->version; $operator = $isEqualOp ? $provider->operator : $this->operator; if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) { return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp && \version_compare($provider->version, $this->version, '==')); } return \false; } public function __toString() { return self::$transOpInt[$this->operator] . ' ' . $this->version; } public function getLowerBound() { $this->extractBounds(); return $this->lowerBound; } public function getUpperBound() { $this->extractBounds(); return $this->upperBound; } private function extractBounds() { if (null !== $this->lowerBound) { return; } if (\strpos($this->version, 'dev-') === 0) { $this->lowerBound = Bound::zero(); $this->upperBound = Bound::positiveInfinity(); return; } switch ($this->operator) { case self::OP_EQ: $this->lowerBound = new Bound($this->version, \true); $this->upperBound = new Bound($this->version, \true); break; case self::OP_LT: $this->lowerBound = Bound::zero(); $this->upperBound = new Bound($this->version, \false); break; case self::OP_LE: $this->lowerBound = Bound::zero(); $this->upperBound = new Bound($this->version, \true); break; case self::OP_GT: $this->lowerBound = new Bound($this->version, \false); $this->upperBound = Bound::positiveInfinity(); break; case self::OP_GE: $this->lowerBound = new Bound($this->version, \true); $this->upperBound = Bound::positiveInfinity(); break; case self::OP_NE: $this->lowerBound = Bound::zero(); $this->upperBound = Bound::positiveInfinity(); break; } } } &1', $output, $exitcode); return self::$stty = 0 === $exitcode; } private static function initDimensions() { if ('\\' === \DIRECTORY_SEPARATOR) { if (\preg_match('/^(\\d+)x(\\d+)(?: \\((\\d+)x(\\d+)\\))?$/', \trim(\getenv('ANSICON')), $matches)) { self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { self::initDimensionsUsingStty(); } elseif (null !== ($dimensions = self::getConsoleMode())) { self::$width = (int) $dimensions[0]; self::$height = (int) $dimensions[1]; } } else { self::initDimensionsUsingStty(); } } private static function hasVt100Support() { return \function_exists('sapi_windows_vt100_support') && \sapi_windows_vt100_support(\fopen('php://stdout', 'wb')); } private static function initDimensionsUsingStty() { if ($sttyString = self::getSttyColumns()) { if (\preg_match('/rows.(\\d+);.columns.(\\d+);/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } elseif (\preg_match('/;.(\\d+).rows;.(\\d+).columns/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } } } private static function getConsoleMode() { $info = self::readFromProcess('mode CON'); if (null === $info || !\preg_match('/--------+\\r?\\n.+?(\\d+)\\r?\\n.+?(\\d+)\\r?\\n/', $info, $matches)) { return null; } return array((int) $matches[2], (int) $matches[1]); } private static function getSttyColumns() { return self::readFromProcess('stty -a | grep columns'); } private static function readFromProcess($command) { if (!\function_exists('proc_open')) { return null; } $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = \proc_open($command, $descriptorspec, $pipes, null, null, array('suppress_errors' => \true)); if (!\is_resource($process)) { return null; } $info = \stream_get_contents($pipes[1]); \fclose($pipes[1]); \fclose($pipes[2]); \proc_close($process); return $info; } } options = \implode(' ', $_SERVER['argv']); $shellVerbosity = $this->configureVerbosity(); $this->interactive = $this->checkInteractivity($shellVerbosity); $this->colorSupport = $this->checkColorSupport(); } public function isInteractive() { return $this->interactive; } public function getVerbosity() { return $this->verbosity; } public function hasColorSupport() { return $this->colorSupport; } public function hasParameter($values) { $values = (array) $values; foreach ($values as $value) { $regexp = \sprintf('/\\s%s\\b/', \str_replace(' ', '\\s+', \preg_quote($value, '/'))); if (1 === \preg_match($regexp, $this->options)) { return \true; } } return \false; } private function checkInteractivity($shellVerbosity) { if (-1 === $shellVerbosity) { return \false; } if (\true === $this->hasParameter(array('--no-interaction', '-n'))) { return \false; } if (\function_exists('posix_isatty') && !@\posix_isatty(\STDOUT) && \false === \getenv('SHELL_INTERACTIVE')) { return \false; } return \true; } private function configureVerbosity() { switch ($shellVerbosity = (int) \getenv('SHELL_VERBOSITY')) { case -1: $this->verbosity = self::VERBOSITY_QUIET; break; case 1: $this->verbosity = self::VERBOSITY_VERBOSE; break; case 2: $this->verbosity = self::VERBOSITY_VERY_VERBOSE; break; case 3: $this->verbosity = self::VERBOSITY_DEBUG; break; default: $shellVerbosity = 0; break; } if ($this->hasParameter(array('--quiet', '-q'))) { $this->verbosity = self::VERBOSITY_QUIET; $shellVerbosity = -1; } elseif ($this->hasParameter(array('-vvv', '--verbose=3', '--verbose 3'))) { $this->verbosity = self::VERBOSITY_DEBUG; $shellVerbosity = 3; } elseif ($this->hasParameter(array('-vv', '--verbose=2', '--verbose 2'))) { $this->verbosity = self::VERBOSITY_VERY_VERBOSE; $shellVerbosity = 2; } elseif ($this->hasParameter(array('-v', '--verbose=1', '--verbose 1', '--verbose'))) { $this->verbosity = self::VERBOSITY_VERBOSE; $shellVerbosity = 1; } return $shellVerbosity; } private function checkColorSupport() { if ($this->hasParameter(array('--ansi'))) { return \true; } if ($this->hasParameter(array('--no-ansi'))) { return \false; } if (\DIRECTORY_SEPARATOR === '\\') { return \function_exists('sapi_windows_vt100_support') && \sapi_windows_vt100_support(\STDOUT) || \false !== \getenv('ANSICON') || 'ON' === \getenv('ConEmuANSI') || 'xterm' === \getenv('TERM'); } if (\function_exists('stream_isatty')) { return \stream_isatty(\STDOUT); } if (\function_exists('posix_isatty')) { return \posix_isatty(\STDOUT); } $stat = \fstat(\STDOUT); return $stat ? 020000 === ($stat['mode'] & 0170000) : \false; } } requiredExtension = $requiredExtension; } public function __invoke() { return \extension_loaded($this->requiredExtension); } } evaluateRequirements(); $io = new IO(); self::printCheck($checkPassed, new Printer($io->getVerbosity(), $io->hasColorSupport()), $requirements); return $checkPassed; } public static function printCheck($checkPassed, Printer $printer, RequirementCollection $requirements) { if (\false === $checkPassed && IO::VERBOSITY_VERY_VERBOSE > $printer->getVerbosity()) { $printer->setVerbosity(IO::VERBOSITY_VERY_VERBOSE); } $verbosity = IO::VERBOSITY_VERY_VERBOSE; $iniPath = $requirements->getPhpIniPath(); $printer->title('Box Requirements Checker', $verbosity); $printer->printv('> Using PHP ', $verbosity); $printer->printvln(\PHP_VERSION, $verbosity, 'green'); $printer->printvln('> PHP is using the following php.ini file:', $verbosity); if ($iniPath) { $printer->printvln(' ' . $iniPath, $verbosity, 'green'); } else { $printer->printvln(' WARNING: No configuration file (php.ini) used by PHP!', $verbosity, 'yellow'); } $printer->printvln('', $verbosity); if (\count($requirements) > 0) { $printer->printvln('> Checking Box requirements:', $verbosity); $printer->printv(' ', $verbosity); } else { $printer->printvln('> No requirements found.', $verbosity); } $errorMessages = array(); foreach ($requirements->getRequirements() as $requirement) { if ($errorMessage = $printer->getRequirementErrorMessage($requirement)) { if (IO::VERBOSITY_DEBUG === $printer->getVerbosity()) { $printer->printvln('✘ ' . $requirement->getTestMessage(), IO::VERBOSITY_DEBUG, 'red'); $printer->printv(' ', IO::VERBOSITY_DEBUG); $errorMessages[] = $errorMessage; } else { $printer->printv('E', $verbosity, 'red'); $errorMessages[] = $errorMessage; } continue; } if (IO::VERBOSITY_DEBUG === $printer->getVerbosity()) { $printer->printvln('✔ ' . $requirement->getHelpText(), IO::VERBOSITY_DEBUG, 'green'); $printer->printv(' ', IO::VERBOSITY_DEBUG); } else { $printer->printv('.', $verbosity, 'green'); } } if (IO::VERBOSITY_DEBUG !== $printer->getVerbosity() && \count($requirements) > 0) { $printer->printvln('', $verbosity); } if ($requirements->evaluateRequirements()) { $printer->block('OK', 'Your system is ready to run the application.', $verbosity, 'success'); } else { $printer->block('ERROR', 'Your system is not ready to run the application.', $verbosity, 'error'); $printer->title('Fix the following mandatory requirements:', $verbosity, 'red'); foreach ($errorMessages as $errorMessage) { $printer->printv(' * ' . $errorMessage, $verbosity); } } $printer->printvln('', $verbosity); } private static function retrieveRequirements() { if (null === self::$requirementsConfig) { self::$requirementsConfig = __DIR__ . '/../.requirements.php'; } $config = (require self::$requirementsConfig); $requirements = new RequirementCollection(); foreach ($config as $constraint) { $requirements->addRequirement('php' === $constraint['type'] ? new IsPhpVersionFulfilled($constraint['condition']) : new IsExtensionFulfilled($constraint['condition']), $constraint['message'], $constraint['helpMessage']); } return $requirements; } } requiredPhpVersion = $requiredPhpVersion; } public function __invoke() { return Semver::satisfies(\sprintf('%d.%d.%d', \PHP_MAJOR_VERSION, \PHP_MINOR_VERSION, \PHP_RELEASE_VERSION), $this->requiredPhpVersion); } } requirements); } #[\ReturnTypeWillChange] public function count() { return \count($this->requirements); } public function add(Requirement $requirement) { $this->requirements[] = $requirement; } public function addRequirement($checkIsFulfilled, $testMessage, $helpText) { $this->add(new Requirement($checkIsFulfilled, $testMessage, $helpText)); } public function getRequirements() { return $this->requirements; } public function getPhpIniPath() { return \get_cfg_var('cfg_file_path'); } public function evaluateRequirements() { return \array_reduce($this->requirements, function ($checkPassed, Requirement $requirement) { return $checkPassed && $requirement->isFulfilled(); }, \true); } } "\x1b[0m", 'red' => "\x1b[31m", 'green' => "\x1b[32m", 'yellow' => "\x1b[33m", 'title' => "\x1b[33m", 'error' => "\x1b[37;41m", 'success' => "\x1b[30;42m"); private $verbosity; private $supportColors; private $width; public function __construct($verbosity, $supportColors, $width = null) { if (null === $width) { $terminal = new Terminal(); $width = $terminal->getWidth(); } $this->verbosity = $verbosity; $this->supportColors = $supportColors; $this->width = $width ?: 80; } public function getVerbosity() { return $this->verbosity; } public function setVerbosity($verbosity) { $this->verbosity = $verbosity; } public function title($title, $verbosity, $style = null) { if (null === $style) { $style = 'title'; } $this->printvln('', $verbosity, $style); $this->printvln($title, $verbosity, $style); $this->printvln(\str_repeat('=', \min(\strlen($title), $this->width)), $verbosity, $style); $this->printvln('', $verbosity, $style); } public function getRequirementErrorMessage(Requirement $requirement) { if ($requirement->isFulfilled()) { return null; } $errorMessage = \wordwrap($requirement->getTestMessage(), $this->width - 3, \PHP_EOL . ' ') . \PHP_EOL; return $errorMessage; } public function block($title, $message, $verbosity, $style = null) { $prefix = ' [' . $title . '] '; $lineLength = $this->width - \strlen($prefix) - 1; if ($lineLength < 0) { $lineLength = 0; } $message = $prefix . \trim($message); $lines = array(); $remainingMessage = $message; $wrapped = \wordwrap($remainingMessage, $lineLength, '¬'); $wrapped = \explode('¬', $wrapped); do { $line = \array_shift($wrapped); if ($lines && $lineLength > 0) { $line = \str_repeat(' ', \strlen($prefix)) . \ltrim($line); } $lines[] = \str_pad($line, $this->width, ' ', \STR_PAD_RIGHT); } while (\count($wrapped)); $this->printvln('', $verbosity); $this->printvln(\str_repeat(' ', $this->width), $verbosity, $style); foreach ($lines as $line) { $this->printvln($line, $verbosity, $style); } $this->printv(\str_repeat(' ', $this->width), $verbosity, $style); $this->printvln('', $verbosity); } public function printvln($message, $verbosity, $style = null) { $this->printv($message, $verbosity, $style); $this->printv(\PHP_EOL, $verbosity, null); } public function printv($message, $verbosity, $style = null) { if ($verbosity > $this->verbosity) { return; } $message = \wordwrap($message, $this->width); $message = \sprintf('%s%s%s', $this->supportColors && isset($this->styles[$style]) ? $this->styles[$style] : '', $message, $this->supportColors ? $this->styles['reset'] : ''); echo $message; } } checkIsFulfilled = $checkIsFulfilled; $this->testMessage = $testMessage; $this->helpText = $helpText; } public function isFulfilled() { if (null === $this->fulfilled) { $this->fulfilled = $this->checkIsFulfilled->__invoke(); } return (bool) $this->fulfilled; } public function getIsFullfilledChecker() { return $this->checkIsFulfilled; } public function getTestMessage() { return $this->testMessage; } public function getHelpText() { return $this->helpText; } } GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. Contao Manager
handle($request); $response->send(); $kernel->terminate($request, $response); } catch (Throwable $e) { ApiProblemResponse::createFromException($e, '@symfony_env@' !== 'prod')->send(); } body.nav-active{overflow:hidden!important}#app{-webkit-transition:-webkit-transform .4s cubic-bezier(.55,0,.1,1);transition:-webkit-transform .4s cubic-bezier(.55,0,.1,1);transition:transform .4s cubic-bezier(.55,0,.1,1);transition:transform .4s cubic-bezier(.55,0,.1,1),-webkit-transform .4s cubic-bezier(.55,0,.1,1)}.nav-active #app{overflow-y:visible;-webkit-transform:translateX(-250px);transform:translateX(-250px)}@media(min-width:1024px){.nav-active #app{-webkit-transform:none;transform:none}}.navigation{float:right}.navigation__toggle{display:block;float:right;position:relative;-webkit-transition:-webkit-transform .4s cubic-bezier(.55,0,.1,1);transition:-webkit-transform .4s cubic-bezier(.55,0,.1,1);transition:transform .4s cubic-bezier(.55,0,.1,1);transition:transform .4s cubic-bezier(.55,0,.1,1),-webkit-transform .4s cubic-bezier(.55,0,.1,1);margin:5px 15px;padding:0;width:30px;height:30px;cursor:pointer;z-index:20}@media(min-width:1024px){.navigation__toggle{display:none}}.nav-active .navigation__toggle{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.navigation__toggle span{display:block;height:3px;width:25px;margin:5px auto;background:#535353;pointer-events:none}.navigation__group,.navigation__item{list-style-type:none;margin:0;padding:0}.navigation__group--main{position:fixed;top:0;bottom:0;right:-250px;width:250px;padding:20px;overflow-y:auto;overflow-scrolling:touch;background:#fff;-webkit-box-shadow:-1px 0 #ccbfa2;box-shadow:-1px 0 #ccbfa2;z-index:10}.navigation__item a{display:block;padding:12px 10px;font-size:16px;color:#535353;white-space:pre}.navigation__item a:hover{text-decoration:none}.navigation__item a[href]:hover{color:#f47c00}.navigation__item--main>a{text-transform:uppercase}.navigation__item--sub>a{margin-left:15px}.navigation__item--icon svg{display:none}.navigation__item-badge{position:relative;top:-2px;margin-left:8px;padding:2px 5px;font-size:10px;color:#fff;font-weight:600;background:#f47c00;border-radius:5px}@media(min-width:1024px){.navigation__group--main{position:inherit;top:auto;bottom:auto;right:auto;width:auto;padding:0;overflow:visible;background:none;-webkit-box-shadow:none;box-shadow:none;-webkit-transform:none;transform:none;-webkit-transition:none;transition:none}.navigation__group--sub{display:none;position:absolute;left:50%;min-width:180px;margin-top:-3px;text-align:center;background:#fff;border-top:3px solid #f47c00;-webkit-transform:translateX(-50%);transform:translateX(-50%);z-index:100;-webkit-box-shadow:0 1px 2px #ccbfa2;box-shadow:0 1px 2px #ccbfa2}.navigation__group--sub:before{position:absolute;left:50%;top:-7px;width:0;height:0;margin-left:-4px;border-style:solid;border-width:0 3.5px 4px 3.5px;border-color:transparent transparent #f47c00 transparent;content:""}.navigation__group--right{left:auto;right:7px;-webkit-transform:translateX(0);transform:translateX(0)}.navigation__group--right:before{left:auto;right:18px}.navigation__item{position:relative;display:inline-block;padding:0 8px}.navigation__item.router-link-active>a,.navigation__item:hover>a{color:#f47c00!important;border-bottom:3px solid #f47c00}.navigation__item:hover>.navigation__group--sub{display:block}.navigation__item--sub{display:block;border-top:1px solid #e5dfd0}.navigation__item--sub a{margin:0;border:none!important}.navigation__item--sub a:hover{color:#000!important}.navigation__item--sub:first-child{border-top:none}.navigation__item--icon>a{padding-top:7px}.navigation__item--icon>a svg{display:inline;position:relative;top:4px;width:22px;height:22px;fill:#535353}.navigation__item--icon>a:hover svg{fill:#f47c00}.navigation__item--icon>a span{display:none}.navigation:hover li>a{border:none}.navigation:hover li:hover>a{border-bottom:3px solid #f47c00}.navigation:hover li:hover>a svg{fill:#f47c00}}.logout-warning{position:fixed;display:block;top:20%;left:50%;width:500px;max-width:90%;text-align:center;background:#fff;z-index:10;opacity:1;-webkit-transform:translateX(-50%);transform:translateX(-50%);border-bottom:2px solid #ddd3bc;border-radius:2px}.logout-warning__headline{position:relative;background:#f47c00;color:#fff;font-weight:300;line-height:40px;border-radius:2px 2px 0 0}.logout-warning__headline--complete{background-color:#31a64b}.logout-warning__headline--error{background-color:#db5041}.logout-warning__text{margin:2em 40px}.logout-warning__countdown{margin:-20px 0 20px;font:600 4em/1.6 SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;color:#db8c41}.logout-warning .widget-button{width:auto;height:35px;margin:0 5px 2em 5px;padding:0 30px;line-height:35px}.fragment-footer{clear:both;width:250px;margin:10px auto 0;padding:15px 0;font-size:12px;text-align:center;border-top:1px solid #eee}.fragment-footer--main{width:auto;margin-top:52px!important;padding:20px 0;border-top:1px solid #bbb}.fragment-footer:before{content:"";display:table;clear:both}.fragment-footer__product{font-weight:300}.fragment-footer__links{margin:5px 0 0;padding:0;list-style-type:none}.fragment-footer__links li{display:inline-block}.fragment-footer__links li:not(:first-child):before{content:"|";padding:0 10px 0 8px}.fragment-footer__links a{display:inline!important;color:#2a7887}.fragment-footer__language{position:relative;display:inline-block;margin-left:5px}.fragment-footer__language button{width:auto;height:auto;padding:0 0 0 25px;margin-top:10px!important;background:transparent;color:#535353;font-size:12px;font-weight:300;line-height:20px;background:url(../img/language.95d0a00b.svg) 0 no-repeat;background-size:20px 20px;border:none}.fragment-footer__language button:hover{color:#000}.fragment-footer__language ul{position:absolute;display:block;width:350px;left:50%;bottom:30px;margin:0;padding:0;text-align:left;list-style-type:none;white-space:nowrap;background:#fff;border-bottom:3px solid #f47c00;-webkit-transform:translateX(-50%);transform:translateX(-50%);z-index:100;-webkit-box-shadow:0 -1px 2px #ccbfa2;box-shadow:0 -1px 2px #ccbfa2}.fragment-footer__language ul:after{position:absolute;left:50%;bottom:-7px;width:0;height:0;margin-left:-4px;border-style:solid;border-width:4px 3.5px 0 3.5px;border-color:#f47c00 transparent transparent transparent;content:""}.fragment-footer__language li{float:left;width:50%;margin:0;padding:0;border-top:1px solid #e5dfd0}.fragment-footer__language li a{display:block;margin:0;padding:5px 10px;color:#535353;cursor:pointer}.fragment-footer__language li a.active{font-weight:600}.fragment-footer__language li a:hover{color:#000;text-decoration:none}.fragment-footer__language li:first-child,.fragment-footer__language li:nth-child(2){border-top:none}@media(min-width:960px){.fragment-footer--boxed .fragment-footer__product,.fragment-footer--main .fragment-footer__product{float:left}.fragment-footer--boxed .fragment-footer__links,.fragment-footer--main .fragment-footer__links{float:right;margin:0}.fragment-footer--boxed .fragment-footer__language button,.fragment-footer--main .fragment-footer__language button{margin-top:0!important}.fragment-footer--boxed{width:840px}}.layout-main{overflow:hidden;min-height:100vh}.layout-main__header{height:56px;padding:8px;background:#fff;-webkit-box-shadow:0 1px #ccbfa2;box-shadow:0 1px #ccbfa2}.layout-main__header--margin{margin-bottom:30px}.layout-main__subheader{margin:30px 0 45px;padding:20px 0;background:#e5dfcf;border-bottom:1px solid #dcd8cc}.layout-main__subheader-inside{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.layout-main__news{width:320px;height:50px;margin-bottom:20px}.layout-main .search-bar{width:100%;margin:0}.layout-main__logo{display:inline;color:#535353;text-decoration:none;font-weight:100;font-size:27px;line-height:40px}.layout-main__logo img{float:left;margin:0 10px 0 12px}@media(min-width:1024px){.layout-main__logo img{margin-left:0}}.layout-main footer,.layout-main__content,.layout-main__subheader-inside{position:relative;margin:0 20px}@media(min-width:700px){.layout-main__subheader-inside{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.layout-main__news{margin:0 20px 0 0}}@media(min-width:1024px){.layout-main footer,.layout-main__content,.layout-main__subheader-inside{max-width:960px;margin:0 auto}}@media(min-width:1200px){.layout-main footer,.layout-main__content,.layout-main__subheader-inside{max-width:1180px}}.confirm-button{position:relative}.confirm-button__icon{display:none;position:absolute;opacity:0;z-index:100}.confirm-button__icon--confirm{display:block;-webkit-animation:confirm_button .5s ease-out 0s;animation:confirm_button .5s ease-out 0s}.confirm-button__icon svg{fill:#31a64b;width:100%;height:100%}@-webkit-keyframes confirm_button{0%{opacity:1;height:10px;width:10px;left:calc(50% - 5px);top:calc(50% - 5px)}to{opacity:0;height:150px;width:150px;left:calc(50% - 75px);top:calc(50% - 75px)}}@keyframes confirm_button{0%{opacity:1;height:10px;width:10px;left:calc(50% - 5px);top:calc(50% - 5px)}to{opacity:0;height:150px;width:150px;left:calc(50% - 75px);top:calc(50% - 75px)}}.package-tools{position:relative;clear:both;text-align:center}@media(min-width:800px){.package-tools{margin-bottom:40px}}.package-tools__button.widget-button{margin-bottom:10px}@media(min-width:800px){.package-tools{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.package-tools__button.widget-button{width:auto;margin:0 15px;padding:0 15px}}.package-actions{position:fixed;left:0;right:0;bottom:0;max-height:0;background:#000;background:rgba(0,0,0,.8);color:#fff;-webkit-transition:max-height .4s ease;transition:max-height .4s ease;z-index:100}.package-actions--active{max-height:200px}.package-actions__inner{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0;padding:12px;text-align:right}@media(min-width:1024px){.package-actions__inner{max-width:976px;margin:0 auto;padding-left:0;padding-right:0}}@media(min-width:1200px){.package-actions__inner{max-width:1196px}}.package-actions__text{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:initial;margin:0 8px;font-weight:600}.package-actions__button{display:block;padding:0 15px!important;margin:8px}.package-actions__button--dryRun{width:auto!important;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}@media(min-width:600px){.package-actions__button{width:auto!important}.package-actions__button--dryRun{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}}.package-actions__button-group{display:block;width:100%;margin:8px}.package-actions__button-group>.button-group__primary{padding:0 15px!important}@media(min-width:600px){.package-actions__button-group{width:auto!important}}.package{margin-bottom:14px;background:#fff;border-bottom:3px solid #ddd3bc;border-radius:2px}.package--contao{border-bottom-color:#f47c00}.package__hint{position:relative;padding:8px 20px 8px 20px;background:#e8c8bc;font-weight:400;font-size:12px;line-height:1.8;border-radius:2px 2px 0 0}@media(min-width:800px){.package__hint{padding-left:56px;background:#e8c8bc url(../img/hint.ba2ac97e.svg) 20px 5px no-repeat;background-size:28px 28px}}.package__hint p a{display:inline-block;padding-right:10px}.package__hint p a:first-child{margin-left:10px}.package__hint p a:not(:first-child):before{padding-right:10px;content:"|"}.package__hint-close{float:right;padding-left:18px;color:#bd2e20;background:url(../img/close.88e95867.svg) 0 no-repeat;background-size:14px 14px}.package__inside{padding:10px 20px 25px}.package__inside:after{display:table;clear:both;content:""}@media(min-width:1024px){.package__inside{padding:25px 20px}}.package__icon{display:none}.package__icon img{width:100%;height:100%}@media(min-width:1024px){.package__icon{display:block;float:left;width:90px;height:90px;margin-right:20px}}.package__about{margin-bottom:20px}@media(min-width:1024px){.package__about{float:left;width:370px;margin-bottom:0}}@media(min-width:1200px){.package__about{width:590px}}.package__headline{position:relative;margin-bottom:5px}.package__headline em{background-color:#ff0;font-style:normal}.package__headline--badge{padding-right:100px}@media(min-width:1024px){.package__headline--badge{padding-right:0}}.package__title{margin-right:10px}.package__badge{position:absolute;top:6px;right:0;padding:0 8px;background:#db5041;border-radius:2px;font-size:12px;line-height:19px;color:#fff;cursor:help}@media(min-width:800px){.package__badge{position:relative;display:inline-block;top:-3px;right:auto}}.package__description{display:-webkit-box;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;margin-bottom:1em}.package__description em{background-color:#ff0;font-style:normal}.package__additional{margin-top:-5px}.package .package__release{display:none;text-align:right;margin-bottom:10px}@media(min-width:600px){.package .package__release{display:block;float:left;width:50%}}@media(min-width:1024px){.package .package__release{width:180px;margin-left:40px;margin-bottom:0}}.package__version--additional strong{margin-right:10px}@media(min-width:1024px){.package__version--additional{display:none}}.package__version--release{display:none}@media(min-width:1024px){.package__version--release{display:block;margin-top:20px;text-align:center}}.package__version--release time{display:block}.package__version--missing{padding:4px 8px;background:#db5041;border-radius:2px;color:#fff;font-weight:700}.package__version-update{display:inline-block;margin:0 0 2px;padding:1px 8px;color:#fff}.package__version-update--available{background:#31a64b}.package__version-update--error{background:#db5041}.package__version-update--none{background:#ccc}@media(min-width:1024px){.package__version-update{display:block;margin:2px 0 0}}.package__version-latest{float:right;position:relative;right:-7px;width:24px;height:20px;background:#31a64b url(../img/button-update.c63bd5a5.svg) 50%/20px 20px no-repeat}@media(min-width:600px){.package__actions{float:right;width:50%;max-width:500px;margin-top:-5px;padding-left:40px;text-align:right}}@media(min-width:1024px){.package__actions{width:180px;margin-left:40px;padding-left:0}}.package__actions .button-group:not(:last-child),.package__actions .widget-button:not(:last-child){margin-bottom:5px}.package__actions .button-group button{margin-bottom:0!important}.package__features{padding:0 0 10px 0;margin:-20px 0 0}@media(min-width:1024px){.package__features{margin-top:-10px}}@media(min-width:960px){.package__hint{overflow:hidden;height:37px;-webkit-transition:height .4s ease;transition:height .4s ease}.package__hint-enter,.package__hint-leave-to{height:0}}.progress-bar{position:relative;width:100%;height:30px;background:#fff;border:2px solid #db8c41;color:#000;font-weight:600;text-align:center;line-height:26px}.progress-bar__bar{position:absolute;overflow:hidden;left:0;right:0;top:0;bottom:0;background:#db8c41}.progress-bar__bar span{display:block;color:#fff;text-align:center}.feature-package{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-top:4px;margin:4px 20px 4px;border-top:1px solid #e9eef1}.feature-package:last-child{padding-bottom:0;margin-bottom:-4px}.feature-package__name{font-weight:600;white-space:nowrap}.feature-package__name:after{content:": "}.feature-package__text{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;overflow:hidden;-webkit-line-clamp:1;-webkit-box-orient:vertical;margin-right:.5em;padding:4px 0;line-height:20px}.feature-package__text--hint{display:inline;-webkit-line-clamp:none}.feature-package__badge{margin-left:5px;padding:2px 8px;background:#db5041;border-radius:2px;font-size:12px;font-weight:600;line-height:19px;color:#fff;cursor:help}.feature-package__hint{line-height:1.2;padding:2px 5px;background:#e8c8bc;font-size:12px}.feature-package__actions{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0 -4px 0 0}.feature-package__actions>*{margin:0 4px}.feature-package__restore{padding-left:18px;font-size:12px;color:#bd2e20;background:url(../img/close.88e95867.svg) 0 no-repeat;background-size:14px 14px;border:none;outline:none;cursor:pointer}.feature-package__restore:hover{text-decoration:underline}@media(min-width:800px){.feature-package{-ms-flex-wrap:nowrap;flex-wrap:nowrap}}@media(min-width:1024px){.feature-package__hint{padding:8px 10px 8px 36px;background:#e8c8bc url(../img/hint.ba2ac97e.svg) 10px 5px no-repeat;background-size:20px 20px}.feature-package__actions{margin:0 -4px 0 0}}.package-constraint input[type=text][data-v-5cd7bdbd]{height:30px;margin-right:2px;background:#fff;border:2px solid #db8c41;color:#000;font-weight:600;text-align:center;line-height:30px}.package-constraint input[type=text][data-v-5cd7bdbd]::-webkit-input-placeholder{color:#fff;opacity:1}.package-constraint input[type=text][data-v-5cd7bdbd]::-moz-placeholder{color:#fff;opacity:1}.package-constraint input[type=text][data-v-5cd7bdbd]:-ms-input-placeholder{color:#fff;opacity:1}.package-constraint input[type=text][data-v-5cd7bdbd]::-ms-input-placeholder{color:#fff;opacity:1}.package-constraint input[type=text][data-v-5cd7bdbd]::placeholder{color:#fff;opacity:1}.package-constraint input[type=text][data-v-5cd7bdbd]:disabled{color:#fff;opacity:1;background:#db8c41;-webkit-text-fill-color:#fff}.package-constraint input[type=text].disabled[data-v-5cd7bdbd]{background:#ccc;border-color:#ccc}.package-constraint input[type=text].error[data-v-5cd7bdbd]{-webkit-animation:input-error .15s linear 3;animation:input-error .15s linear 3}.package-constraint>input[type=text][data-v-5cd7bdbd],.package-constraint>input[type=text][data-v-5cd7bdbd]:disabled{float:left;width:calc(100% - 32px)}.package-constraint button[data-v-5cd7bdbd]{position:relative;width:30px;height:30px;background:#db8c41;line-height:20px;text-indent:-999em}.package-constraint button[data-v-5cd7bdbd]:hover{background:#d77f2c;border-color:#c47225}.package-constraint button[data-v-5cd7bdbd]:before{position:absolute;left:50%;top:50%;margin:-10px 0 0 -10px}.package-constraint button.rotate[data-v-5cd7bdbd]:before{-webkit-animation:release-validating-5cd7bdbd 2s linear infinite;animation:release-validating-5cd7bdbd 2s linear infinite}@-webkit-keyframes release-validating-5cd7bdbd{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes release-validating-5cd7bdbd{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.button-group{position:relative}.button-group__primary.widget-button{float:left;width:calc(100% - 39px);border-top-right-radius:0;border-bottom-right-radius:0}.button-group__more.widget-button{float:right;width:38px;padding:7px;border-top-left-radius:0;border-bottom-left-radius:0}.button-group__more.widget-button svg{width:24px;height:24px}.button-group__group{position:absolute;top:39px;width:100%;z-index:100}.button-group__group:focus{outline:none}.button-group__group .widget-button{margin-top:1px}.button-group__group .link-menu{margin-top:3px}.button-group__group--top{top:auto;bottom:39px}.button-group__group--top .link-menu{margin-top:0;margin-bottom:3px}.composer-package__stats{display:inline-block;margin-right:15px;padding-left:18px;font-size:13px;background-position:0 50%;background-repeat:no-repeat;background-size:13px 13px}.composer-package__stats--license{padding-left:0}.composer-package__stats--downloads{background-image:url(../img/downloads.aa84cdf1.svg)}.composer-package__stats--favers{background-image:url(../img/favers.31587387.svg)}.composer-package__stats--funding{width:16px;background-image:url(../img/funding.8a3c0e0e.svg);background-size:16px 16px;background-repeat:no-repeat;text-decoration:none!important}.package-uploads__overlay{top:0;bottom:0;right:0;left:0;position:fixed;z-index:9999;opacity:.6;text-align:center;background:#000}.package-uploads__overlay div{margin:-.5em 0 0;position:absolute;top:50%;left:0;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:40px;color:#fff;padding:0}.cloud-status{margin-left:8px;position:relative}.cloud-status__button{margin-left:0;margin-right:0;padding-left:8px;cursor:help!important}.cloud-status__popup{position:absolute;text-align:left;left:0;bottom:55px;margin:0;padding:0 0 15px;outline:none;background:#fff;color:#535353;border-bottom:3px solid #f47c00;-webkit-box-shadow:0 -1px 2px #ccbfa2;box-shadow:0 -1px 2px #ccbfa2;z-index:100}.cloud-status__popup:after{position:absolute;left:38px;bottom:-7px;width:0;height:0;margin-left:-4px;border-style:solid;border-width:4px 3.5px 0 3.5px;border-color:#f47c00 transparent transparent transparent;content:""}.cloud-status__popup--error{color:#fff;text-align:center;background-color:#db5041;border-color:#db5041}.cloud-status__popup--error:after{left:27px;border-color:#db5041 transparent transparent transparent}.cloud-status__headline{padding:15px 20px 0;font-size:16px;white-space:pre}.cloud-status__version{text-align:center;margin:0 0 8px;font-size:12px}.cloud-status__link{display:block;margin:15px 10px 0;text-align:center}.cloud-status__error{padding:8px 20px 8px;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.cloud-status table{width:100%;margin-top:12px;border-spacing:0;border-collapse:collapse}.cloud-status th{padding:3px 5px 3px 20px}.cloud-status td{padding:3px 20px 3px 0}.cloud-status tr:nth-child(odd){background:#f0f0f0}.package-list{position:relative}.package-list__status{margin:100px 0;text-align:center;font-size:20px;line-height:1.5em}.package-list__status .sk-circle{width:100px;height:100px;margin:0 auto 40px}.package-list__headline{font-size:18px;font-weight:300;margin:30px 0 10px}.layout-boxed{display:table;width:100%;height:100%}.layout-boxed__cell{display:table-cell;overflow:hidden;vertical-align:middle;padding:10px 0}.layout-boxed__container{position:relative;max-width:380px;margin:0 auto;background:#fff}.layout-boxed__container:after{display:table;clear:both}.layout-boxed__container:before{left:-100px;-webkit-box-shadow:inset 15px 0 60px 25px #ebe6db,inset -8px 0 8px -8px rgba(0,0,0,.8);box-shadow:inset 15px 0 60px 25px #ebe6db,inset -8px 0 8px -8px rgba(0,0,0,.8)}.layout-boxed__container:after,.layout-boxed__container:before{position:absolute;display:block;width:100px;top:0;bottom:0;content:""}.layout-boxed__container:after{right:-100px;-webkit-box-shadow:inset -15px 0 60px 25px #ebe6db,inset 8px 0 8px -8px rgba(0,0,0,.8);box-shadow:inset -15px 0 60px 25px #ebe6db,inset 8px 0 8px -8px rgba(0,0,0,.8)}@media(min-width:960px){.layout-boxed__container{margin-top:20px;margin-bottom:20px}.layout-boxed__container--wide{max-width:940px}}.view-oauth__header{max-width:280px;margin:0 auto 60px;padding-top:40px;text-align:center}.view-oauth__product{margin-top:15px;font-size:36px;font-weight:100;line-height:1}.view-oauth__form{position:relative;max-width:250px;margin:0 auto 80px;text-align:center}.view-oauth__form input,.view-oauth__form select{margin:5px 0 10px}.view-oauth__headline{margin-bottom:0}.view-oauth__description{margin-top:.5em;margin-bottom:.5em}.view-oauth__client{margin:1em 0;font-size:32px}.view-oauth__warning{color:#db5041;margin-top:.5em;margin-bottom:2em}.view-oauth__button{margin-top:20px}.view-oauth__button .sk-circle{color:#fff;text-align:center}.message-overlay{position:relative}.message-overlay__blur{opacity:.75;-webkit-filter:blur(2px);filter:blur(2px);-webkit-transition:opacity .5s,-webkit-filter .5s;transition:opacity .5s,-webkit-filter .5s;transition:opacity .5s,filter .5s;transition:opacity .5s,filter .5s,-webkit-filter .5s}.message-overlay__overlay{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;left:0;right:0;top:0;bottom:0}.message-overlay__message{padding:10px;font-size:2em;font-weight:400;text-align:center}.maintenance{margin-bottom:14px;background:#fff;border-bottom:3px solid #ddd3bc;border-radius:2px}.maintenance__inside{padding:10px 20px 20px}.maintenance__inside:after{display:table;clear:both;content:""}@media(min-width:1024px){.maintenance__inside{padding:25px 20px}}.maintenance__image{display:none}.maintenance__image img{width:100%;height:100%}@media(min-width:1024px){.maintenance__image{display:block;float:left;width:90px;height:90px;margin-right:20px}}.maintenance__about{margin-bottom:20px}@media(min-width:1024px){.maintenance__about{float:left;width:510px;margin-bottom:0}}.maintenance__about h1{position:relative;margin-bottom:5px}.maintenance__about p{margin:0 0 1em;display:inline}.maintenance__error,.maintenance__warning{position:relative;top:-2px;margin-left:.5em;padding:2px 8px;font-size:14px;line-height:1em;font-weight:300;background:#db8c41;color:#fff}.maintenance__error{background:#db5041}@media(min-width:600px){.maintenance__actions{margin:0 -10px}}@media(min-width:1024px){.maintenance__actions{float:right;width:250px;margin:0 0 0 40px}}.maintenance__actions>.button-group,.maintenance__actions>button{width:100%;margin-bottom:10px}@media(min-width:600px){.maintenance__actions>.button-group,.maintenance__actions>button{float:right;width:calc(50% - 20px);margin:0 10px}}@media(min-width:1024px){.maintenance__actions>.button-group,.maintenance__actions>button{width:100%;margin:0 0 10px}}.maintenance__loader{width:50px;margin:0 auto}.maintenance__loader .sk-circle{width:50px;height:50px}.log-viewer__status{margin:100px 0;text-align:center;font-size:20px;line-height:1.5em}.log-viewer__status--empty{padding-top:140px;background:url(../img/warning.a0297242.svg) top no-repeat;background-size:100px 100px}.log-viewer__status--loader .sk-circle{width:100px;height:100px;margin:0 auto 40px}.log-viewer__status button{margin-top:2em}.log-viewer__loading{width:30px;margin:40px auto}.log-viewer__loading .sk-circle{width:30px;height:30px}.log-viewer__filters{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-wrap:wrap;flex-wrap:wrap}.log-viewer__filters,.log-viewer__filters>div{display:-webkit-box;display:-ms-flexbox;display:flex;gap:20px}.log-viewer__filters>div{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.log-viewer__list{margin-top:2em;padding-bottom:1px;background:#fff;border-radius:2px}@media(min-width:600px){.log-viewer__list{overflow-y:scroll;max-height:calc(100vh - 300px)}}.log-viewer__line{position:relative;padding:10px 0;border-bottom:1px solid #e9eef1}@media(min-width:600px){.log-viewer__line{display:-webkit-box;display:-ms-flexbox;display:flex}}.log-viewer__line:hover{background:#f9f9f9}.log-viewer__line:last-child{border-bottom:none}.log-viewer__line--header{display:none}@media(min-width:600px){.log-viewer__line--header{display:-webkit-box;display:-ms-flexbox;display:flex;position:sticky;top:0;z-index:1;font-weight:600;background:#ccbfa2;color:#fff;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-color:#fff}.log-viewer__line--header:hover{background:#ccbfa2}}.log-viewer__line--alert:before,.log-viewer__line--critical:before,.log-viewer__line--emergency:before,.log-viewer__line--error:before,.log-viewer__line--warning:before{content:"";position:absolute;left:0;top:-1px;bottom:-1px;width:4px;background:#db5041}.log-viewer__line--warning:before{background:#db8c41}.log-viewer__line--raw{padding:5px 10px;background:#24292e;border-bottom:none;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;color:#f6f8fa;font-size:.8em;line-height:1.5}.log-viewer__line--raw:hover{background:#2f363d}.log-viewer__more{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:20px}.log-viewer__content,.log-viewer__meta{padding:10px 20px}.log-viewer__content--header,.log-viewer__meta--header{padding:0 20px!important}.log-viewer__meta{padding-bottom:0;-ms-flex-negative:0;flex-shrink:0;font-style:italic}.log-viewer__meta--header{font-style:normal}@media(min-width:600px){.log-viewer__meta{width:220px;padding-bottom:10px}}.log-viewer__content{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.log-viewer__datetime{display:block}@media(min-width:600px){.log-viewer__datetime{margin-bottom:1em}}.log-viewer__badge{display:inline-block;margin-right:10px;padding:1px 4px;background:#ccc;border-radius:2px;font-size:.9em;font-weight:400;text-transform:lowercase}.log-viewer__badge--desktop{display:none}@media(min-width:600px){.log-viewer__badge--desktop{display:inline-block}.log-viewer__badge--mobile{display:none}}.log-viewer__badge--channel{padding-top:0;padding-bottom:0;border:1px solid #ccc;background:#fff}.log-viewer__badge--level-warning{background:#db8c41;color:#fff}.log-viewer__badge--level-alert,.log-viewer__badge--level-critical,.log-viewer__badge--level-emergency,.log-viewer__badge--level-error{background:#db5041;color:#fff}.log-viewer__message span:nth-child(2n){font-weight:600;color:#000}.log-viewer__details{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:1em}.log-viewer__toggle{margin-right:10px;padding:0;border:none;background:none;color:#f47c00;text-decoration:none;cursor:pointer}.log-viewer__toggle:hover{text-decoration:underline}.log-viewer__json{margin:10px 0 0}.package-popup__installed strong{margin-right:5px}@media(min-width:600px){.package-popup__installed{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:center}.package-popup__installed strong{display:block;margin:0}}.package-popup__update{margin:0 0 20px;padding:10px 20px 10px 50px;color:#fff;background:#31a64b url(../img/button-update.c63bd5a5.svg) 15px 50% no-repeat;background-size:23px 23px}.view-error{position:fixed;left:0;right:0;top:0;bottom:0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:10px;color:#e8e8e8;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:13px;line-height:1.2;background-color:rgba(0,0,0,.851);background-position:0 0;background-repeat:repeat;z-index:9998}.view-error,.view-error__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.view-error__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;max-width:800px;max-height:100vh;line-height:1.5;text-align:center}.view-error__icon{display:block;height:100px;margin:2em 0;fill:#fff}.view-error__status{margin-bottom:1em;padding:2px 4px;background-color:#e36049;border-radius:2px}.view-error__headline{margin:0;font-size:1em;line-height:1.5}.view-error__status a{color:#e8e8e8;text-decoration:underline}.view-error__details{display:block;margin-top:2em;white-space:pre-line}.view-error__debug{-ms-flex-item-align:start;align-self:flex-start;max-height:60vh;overflow-y:auto;margin-top:2em;text-align:left;white-space:pre-line}.view-error__actions{margin:4em 0;text-align:center}.view-error__link{margin:10px;padding:10px 20px;border:1px solid #fff;border-radius:4px;color:#fff}.widget-text--password input{padding-right:40px!important}.widget__password-toggle{position:absolute;right:8px;bottom:2px;padding:0;margin:0;background:none;border:none;cursor:pointer}.widget__password-toggle--hidden svg{fill:#31a64b}.widget__password-toggle--visible svg{fill:#737373}.view-account__header{max-width:280px;margin-left:auto;margin-right:auto;padding:40px 0;text-align:center}.view-account__product{margin-top:15px;margin-bottom:40px;font-weight:600}.view-account__product strong{display:block;margin-bottom:10px;font-size:54px;font-weight:100;line-height:1}.view-account__headline{margin-bottom:.5em;font-size:18px;font-weight:600;line-height:30px}.view-account__description{margin-bottom:1em;text-align:justify}.view-account__form{position:relative;max-width:280px;margin:0 auto}.view-account__form .widget-text{margin-top:10px}.view-account__form .widget-text label{display:block;padding-bottom:5px}.view-account__form .widget-button{margin-top:1.5em}.view-account__contribute{max-width:250px;margin:80px auto 0;font-size:12px;text-align:center}.view-account__contribute br{display:none}@media(min-width:960px){.view-account{padding-top:100px}.view-account__header{float:left;width:470px;max-width:none;padding:0 60px}.view-account__form{float:left;width:370px;max-width:none;margin:20px 50px 0}.view-account__form .widget-text label{float:left;width:120px;padding-top:10px;font-weight:400}.view-account__form input[type=password],.view-account__form input[type=text],.view-account__form select{width:250px!important}.view-account__form .widget-button{width:250px;margin-left:120px}.view-account__contribute{max-width:840px}.view-account__contribute br{display:block}}.view-login__header{max-width:280px;margin:0 auto 60px;padding-top:40px;text-align:center}.view-login__product{margin-top:15px;font-size:36px;font-weight:100;line-height:1}.view-login__form{position:relative;max-width:250px;margin:0 auto 80px}.view-login__form input{padding-right:30px;margin:5px 0 10px}.view-login__locked{max-width:290px;margin:-20px auto 60px;padding:20px;background:#db5041;color:#fff;text-align:center}.view-login__locked strong{white-space:pre}.view-login__headline{margin-bottom:0}.view-login__description{margin-top:.5em;margin-bottom:30px}.view-login label{position:absolute;text-indent:-999em}.view-login label[for=ctrl_username]{top:17px;right:13px;width:16px;height:16px;background:url(../img/person.00d78897.svg) 0 0 no-repeat;background-size:16px 16px;z-index:10}.view-login label[for=ctrl_password]{top:17px;right:12px;width:16px;height:16px;background:url(../img/lock.3c42a55f.svg) 0 0 no-repeat;background-size:14px 14px;z-index:10}.view-login .widget-text--password button{display:none}.view-login__link{display:block;font-size:12px}.view-login__button{margin-top:20px}.view-login__button .sk-circle{color:#fff;text-align:center}.button-menu{position:relative}.button-menu__primary.widget-button{float:left;width:calc(100% - 39px);border-top-right-radius:0;border-bottom-right-radius:0}.button-menu__more.widget-button{float:right;width:38px;padding:7px;border-top-left-radius:0;border-bottom-left-radius:0}.button-menu__more.widget-button svg{width:24px;height:24px}.button-menu__menu{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;position:absolute;top:38px;right:0;width:auto;z-index:100;background:#fff;border-radius:2px}.button-menu__menu:before{content:"";position:absolute;top:-5px;right:15px;width:0;height:0;border-right:none;border-bottom:none;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #fff}.button-menu__menu:focus{outline:none}.button-menu__menu button{padding:8px 16px;background:none;border:none;text-align:left;white-space:nowrap;border-bottom:1px solid #ccc;cursor:pointer}.button-menu__menu button:hover{background:#f0f0f0}.button-menu__menu button:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.button-menu__menu button:last-child{border-bottom:none;border-bottom-left-radius:2px;border-bottom-right-radius:2px}.button-menu__menu .link-menu{margin-top:3px}.console-operation{position:relative;padding:0 16px;text-align:left;font-size:12px;color:#959da5}.console-operation__summary{margin-left:13px;padding:8px;-webkit-box-sizing:border-box;box-sizing:border-box;outline:none}.console-operation__summary--console{margin-left:0}.console-operation summary{cursor:pointer}.console-operation__status{display:inline-block;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;padding-right:8px;width:24px;height:18px;text-align:center;vertical-align:middle}.console-operation__icon{position:absolute;left:0;top:0}.console-operation__icon--skipped{fill:#666b71}.console-operation__icon--pending{fill:#dbab0a}.console-operation__icon--active{fill:#dbab0a;-webkit-animation:console-active 1s linear infinite;animation:console-active 1s linear infinite}@-webkit-keyframes console-active{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}50%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes console-active{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}50%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.console-operation__icon--success{fill:#31a64b}.console-operation__icon--error{fill:#db5041}.console-operation__label{display:inline-block;overflow:hidden;max-width:750px;vertical-align:top}.console-operation__title{display:inline;margin:0;color:#fff}.console-operation__title--disabled{text-decoration:line-through}.console-operation__description{display:inline;margin:0 0 0 10px}.console-operation__console{position:relative}.console-operation__lines{overflow-y:auto;max-height:250px;padding:8px 0 16px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;color:#f6f8fa;line-height:1.5;white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word}.console-operation__line{display:-webkit-box;display:-ms-flexbox;display:flex}.console-operation__line:hover{background-color:#2f363d}.console-operation__line-number{display:inline-block;overflow:hidden;width:48px;min-width:48px;color:#959da5;text-align:right;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.console-operation__line-content{display:inline-block;margin-left:16px;vertical-align:middle}.console-operation__scroll{position:absolute;left:0;right:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end;width:100%;height:30px;padding:0;border:none;cursor:pointer}.console-operation__scroll svg{fill:#fff;width:16px;height:16px}.console-operation__scroll--top{top:0;background:-webkit-gradient(linear,left top,left bottom,from(#24292e),color-stop(50%,rgba(36,41,46,.502)));background:linear-gradient(#24292e,rgba(36,41,46,.502) 50%)}.console-operation__scroll--top svg{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.console-operation__scroll--bottom{bottom:0;background:-webkit-gradient(linear,left top,left bottom,from(rgba(36,41,46,.502)),color-stop(50%,#24292e));background:linear-gradient(rgba(36,41,46,.502),#24292e 50%)}.console-operation__scroll--bottom svg{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.console{background:#24292e}.console__header{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:12px 12px 12px 24px;border-bottom:1px solid #444d56}.console__headline{margin:0;font-size:inherit;line-height:1.5;color:#fff}.console__description{color:#959da5;font-size:12px}.console__actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.console__action,.console__action>button{height:30px!important;line-height:30px!important;width:auto!important;min-width:0;margin:0 2px;padding:0 5px!important;border:none!important}.console__action:hover,.console__action>button:hover{background-color:#2f363d!important}.console__action--active,.console__action>button--active{background-color:#586069!important}.console__operations{padding:20px 0}.widget-checkbox input{position:absolute;visibility:hidden}.widget-checkbox label{display:block;padding-left:25px;background:url(../img/widget-checkbox--off.73856538.svg) 0 0 no-repeat;background-size:20px 20px;text-align:left}.widget-checkbox input:checked+label{background-image:url(../img/widget-checkbox--on.c05d996c.svg)}.widget-checkbox input:disabled+label{opacity:.5}.widget-checkbox__description{padding-left:25px}.widget-checkbox__description--disabled{opacity:.5}.view-task__header{margin-left:auto;margin-right:auto;padding:40px 0;text-align:center}.view-task__icon{background:#f47c00;border-radius:10px;padding:10px}.view-task__headline{margin-top:15px;font-size:36px;font-weight:100;line-height:1}.view-task__description{margin:0;font-weight:600}.view-task__actions{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:2em}@media(min-width:960px){.view-task__actions{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}}.view-task .widget-button{width:250px;height:35px;margin:5px;padding:0 30px;line-height:35px}@media(min-width:960px){.view-task .widget-button{width:auto}}.view-task__main{margin:0 50px 50px;background:#24292e}.view-task__loading{width:30px;margin:40px auto}.view-task__loading .sk-circle{width:30px;height:30px}.view-task__sponsor{margin:-30px 50px 50px;text-align:center}@media(min-width:960px){.view-task__sponsor br{display:none}}.view-task__donate{position:relative;top:5px;margin-left:.5em;line-height:0}.boot-check{padding:10px}.boot-check:after{display:table;clear:both;content:""}.boot-check__icon{float:left}.boot-check__icon .sk-circle{width:34px;height:34px;margin:3px}.boot-check__icon svg{display:block;width:40px;height:40px}.boot-check__icon--success svg{fill:#31a64b}.boot-check__icon--info svg,.boot-check__icon--warning svg{fill:#db8c41}.boot-check__icon--error svg{fill:#db5041}.boot-check__label{margin-left:50px}.boot-check__description,.boot-check__detail,.boot-check__title{margin:0;line-height:inherit;overflow:hidden;text-overflow:ellipsis}.boot-check__detail{margin-top:5px;font-size:12px}.boot-check__action{margin-left:50px}.boot-check__action button{margin:15px 0 10px;height:33px;line-height:33px}@media(min-width:960px){.boot-check__label{float:left;width:540px;margin-left:10px}.boot-check__action{float:right;margin:0 10px;width:140px;text-align:center}.boot-check__action button{margin:3px 0}.boot-check__action a[target=_blank]{display:inline-block;margin:10px 0;padding-left:20px;background:url(../img/link-blank.c018a22c.svg) 0 no-repeat;background-size:16px 16px}}.config-check__header{max-width:280px;margin-left:auto;margin-right:auto;padding:40px 0;text-align:center}.config-check__icon{background:#f47c00;border-radius:10px;padding:10px}.config-check__headline{margin-top:20px;margin-bottom:25px;font-size:36px;font-weight:100;line-height:1}.config-check__description{text-align:justify}.config-check__form{position:relative;max-width:280px;margin:0 auto 50px}.config-check__form .widget-select,.config-check__form .widget-text{margin-top:20px}.config-check__form .widget-select label,.config-check__form .widget-text label{display:block;margin-bottom:5px;font-weight:400}.config-check__fields{margin-bottom:2em}.config-check__fieldtitle{margin-bottom:.5em;font-size:18px;font-weight:600;line-height:30px}.config-check__fielddesc{margin-bottom:1em}.config-check__issues{margin-bottom:1em;color:#db5041}.config-check__issues p{font-weight:600}.config-check__issues ul{margin:0;padding:0}.config-check__issues li{margin:.5em 0 0 25px;padding:0}.config-check .widget-button{margin-bottom:.5em}@media(min-width:960px){.config-check{padding-top:100px}.config-check__header{float:left;width:470px;max-width:none;padding:0 60px 100px}.config-check__form{float:left;width:370px;max-width:none;margin:20px 50px 0;padding-bottom:100px}.config-check__form .widget-select label,.config-check__form .widget-text label{display:block;float:left;width:120px;padding-top:10px;font-weight:400}.config-check__form .widget-select input[type=text],.config-check__form .widget-select select,.config-check__form .widget-text input[type=text],.config-check__form .widget-text select{width:250px}.config-check__form .widget-button{width:250px;margin-left:120px}}.view-boot__header{margin-left:auto;margin-right:auto;padding:40px 0;text-align:center}.view-boot__icon{background:#f47c00;border-radius:10px;padding:10px}.view-boot__headline{margin-top:15px;font-size:36px;font-weight:100;line-height:1}.view-boot__description{margin:0;font-weight:600}.view-boot__loading{width:30px;margin:0 auto 40px}.view-boot__loading .sk-circle{width:30px;height:30px}.view-boot__checks{margin:0 20px 50px}.view-boot__checks .boot-check:nth-child(odd){background:#f5f9fa}.view-boot__summary{margin:50px 0 0}.view-boot__summary--error svg{width:100%;height:40px;fill:#db5041}.view-boot__issue{max-width:60%;margin:10px auto;text-align:center;color:#db5041;line-height:1.2em}.view-boot__safeMode{margin:2em auto 0}.view-boot__continue,.view-boot__safeMode{clear:both;display:block!important;width:220px!important}.view-boot__continue{margin:0 auto}@media(min-width:960px){.view-boot__checks{margin:0 80px 50px}}.widget-radio-button legend{margin-bottom:2px}.widget-radio-button>div{position:relative;margin:.25em 0}.widget-radio-button input{position:absolute;visibility:hidden}.widget-radio-button label{display:block;padding-left:25px;background:url(../img/widget-radio--off.4e93f443.svg) 0 -1px no-repeat;background-size:20px 20px}.widget-radio-button input:checked+label{background-image:url(../img/widget-radio--on.181461b6.svg)}.widget-radio-button input:disabled+label{opacity:.5}.setup__directories{margin-top:2em}.setup__directories>dt{margin-top:1em;font-weight:600}.setup__directories>dd{margin:0;word-break:break-all}.setup__directories>dd span{background-color:#ff0;font-weight:400}.theme-details__constraint{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.file-tree{margin:0;padding:0;list-style:none}.file-tree__folder{position:relative;padding-left:20px}.file-tree__folder:before{content:"";position:absolute;left:10px;top:6px;width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #535353;-webkit-transition:-webkit-transform .1s ease-in-out;transition:-webkit-transform .1s ease-in-out;transition:transform .1s ease-in-out;transition:transform .1s ease-in-out,-webkit-transform .1s ease-in-out}.file-tree__folder--open:before{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.file-tree__file{padding-left:20px}.file-tree button{background:none;border:none;font-weight:400;cursor:pointer}.setup__versions{margin:0;padding:0 0 0 15px}.setup__version{margin:.5em 0;text-align:left}.setup__version--warning{color:#db5041}.setup__releaseplan{margin-top:1.5em}.setup__fielddesc--version{margin-bottom:-1em!important}.setup__core-features{margin:5px 0 0 5px;font-size:12px}.setup__theme-image{max-width:100%;height:auto;border:1px solid #ccc}.setup__theme p{margin:1em 0}.setup__theme-upload{position:absolute!important;visibility:hidden}.setup__themes{padding:0 14px}.setup__themes-item{border:1px solid #ddd3bc}.setup__themes-more{margin-top:20px;text-align:center}@media(min-width:960px){.setup__themes{padding:20px 12px 0}.setup__themes-results{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.setup__themes-item{-ms-flex-preferred-size:calc(50% - 16px);flex-basis:calc(50% - 16px);margin-left:8px;margin-right:8px}}.setup__theme-search{margin:20px 0 0;text-align:center}.setup__theme-search--empty{padding-top:60px;background:url(../img/sad.c9a06488.svg) top no-repeat;background-size:50px 50px}.setup__theme-search--offline{padding-top:60px;background:url(../img/offline.1f95ae24.svg) top no-repeat;background-size:50px 50px}.setup__theme-search--loader .sk-circle{width:50px;height:50px;margin:0 auto 20px}.setup__theme-search button{margin-top:2em}.setup__fielddesc--warning{padding:10px 10px 10px 40px;background:#e8c8bc url(../img/hint.ba2ac97e.svg) 10px 10px no-repeat}.setup__tabs{margin:1em 0}.setup__tab-controls{display:-webkit-box;display:-ms-flexbox;display:flex}.setup__tab-control{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;padding:4px 10px;border:none;border-top:1px solid #ccc;border-left:1px solid #ccc;background:none;cursor:pointer}.setup__tab-control:last-child{border-right:1px solid #ccc}.setup__tab-control--active{border-color:#737373;background:#737373;color:#fff}.setup__tab{border:1px solid #ccc}.setup__tab--files{white-space:pre;overflow:scroll;height:200px}.setup__requires{width:100%;border-collapse:collapse}.setup__requires td,.setup__requires th{margin:0;padding:3px 10px;text-align:start;vertical-align:top}.setup__requires th{background:#737373;color:#fff}.setup__requires td{border-bottom:1px solid #ccc}.setup__requires tr:last-child td{border-bottom:none}.setup__requires tr:nth-child(odd) td{background:#f5f5f5}.setup__or{position:relative;overflow:hidden;margin:1em 0;text-align:center}.setup__or:before{content:"";position:absolute;top:.8em;left:0;right:0;display:block;height:1px;background:#ccc;z-index:1}.setup__or span{position:relative;padding:0 10px;background:#fff;z-index:2}.view-setup{padding-top:40px}.view-setup footer{margin-top:40px}.view-setup__steps{padding:0 0 50px 0}.view-setup__steps ul{list-style-type:none}.view-setup__steps li,.view-setup__steps ul{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0;padding:0}.view-setup__steps li{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;position:relative;height:6px}.view-setup__steps li:before{content:"";position:absolute;left:-50%;right:50%;background:#ccc;height:5px}.view-setup__steps li:after{content:"";position:absolute;top:-18px;left:50%;margin-left:-21px;width:42px;height:42px;color:#fff;text-indent:0;text-align:center;line-height:35px;background:#ccc;border-radius:50%;z-index:1}.view-setup__steps li:first-child:before{content:none}.view-setup__steps li.active:after,.view-setup__steps li.active:before{background:#f47c00}.view-setup__steps button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:transparent;border:none;z-index:10;cursor:pointer}.view-setup__steps button:disabled{cursor:default}.view-setup__steps svg{fill:#fff}.view-setup__main{text-align:center}.view-setup__party{font-size:64px}.view-setup__headline{margin:10px 0 20px;padding:15px 0;font-size:42px;font-weight:100}.view-setup__description{max-width:500px;margin:1em 25px}.view-setup__start.widget-button{height:50px;margin:30px 10px 0;padding:0 50px;font-size:1.2em;line-height:50px}.view-setup__continue.widget-button{width:80%!important;margin:10px 0 0}.view-setup__funding{width:80%;margin:50px auto 0;padding:20px 25px;border:2px solid #ea4aaa}.view-setup__funding figure{margin-bottom:1em}.view-setup__funding p{margin:0 0 .5em 0}.view-setup__funding-link{margin:1em 0 0!important}@media(min-width:960px){.view-setup{padding-top:80px}.view-setup footer{margin-top:80px}.view-setup__steps{padding-bottom:80px}.view-setup__headline{margin:20px 0 40px;font-size:64px}.view-setup__continue.widget-button{width:auto!important;margin:25px 10px 0;padding:0 20px}.view-setup__description{max-width:550px;margin:1em auto;font-size:1.2em}.view-setup__funding{display:-webkit-box;display:-ms-flexbox;display:flex;margin:60px auto -20px;text-align:left}.view-setup__funding figure{margin-right:25px}}.setup__header{max-width:280px;margin-left:auto;margin-right:auto;padding:40px 0;text-align:center}.setup__header .widget-button{margin-top:1em}.setup__icon{background:#f47c00;border-radius:10px;padding:10px}.setup__headline{margin-top:20px;margin-bottom:25px;font-size:36px;font-weight:100;line-height:1}.setup__description,.setup__warning{margin:1em 0;text-align:justify}.setup__warning{color:#db5041;font-weight:600}.setup__form{position:relative;max-width:280px;margin:0 auto 50px;opacity:1}.setup__form svg.setup__check{display:block;width:80px;height:80px;margin:0 auto 2em;fill:#31a64b}.setup__form .widget-select,.setup__form .widget-text{margin-top:10px}.setup__form .widget-select label,.setup__form .widget-text label{display:block;margin-bottom:5px;font-weight:400}.setup__form .widget-checkbox{margin-top:20px;font-weight:400}.setup__form .widget-radio-button{margin-top:20px}.setup__fields{margin-bottom:2em}.setup__fields--center{text-align:center}.setup__fields .button-group .widget-button{margin-bottom:1px}.setup__fieldtitle{margin-bottom:.5em;font-size:18px;font-weight:600;line-height:30px}.setup__fielddesc{margin-bottom:1em;text-align:left}.setup__fielddesc code{word-break:break-word}.setup__actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:10px}.setup__actions--center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.setup__actions .button-group,.setup__actions .widget-button{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.setup__actions .button-group--inline,.setup__actions .widget-button--inline{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}@media(min-width:960px){.setup{padding-top:100px}.setup__header{float:left;width:470px;max-width:none;padding:0 60px}.setup__form{float:left;width:370px;max-width:none;margin:0 50px}.setup__form .widget-select label,.setup__form .widget-text label{display:block;float:left;width:120px;padding-top:10px;font-weight:400}.setup__form .widget-select input,.setup__form .widget-select select,.setup__form .widget-text input,.setup__form .widget-text select{width:250px!important}}.view-recovery__header{max-width:280px;margin-left:auto;margin-right:auto;padding:40px 0 10px;text-align:center}.view-recovery__icon{background:#f47c00;border-radius:10px;padding:10px}.view-recovery__headline{margin-top:15px;font-size:36px;font-weight:100;line-height:1}.view-recovery__content{margin:0 30px 50px}@media(min-width:960px){.view-recovery__content{margin-left:50px;margin-right:50px}}.view-recovery__description{font-weight:600;max-width:600px;margin:0 auto;text-align:center}.view-recovery__console{margin:30px 0 60px}.view-recovery__option{margin:50px 0 0;padding:20px 20px 30px;background:#f5f9fa;text-align:center}.view-recovery__option h3{position:relative;top:-40px;margin-bottom:-25px;font-size:2em;font-weight:300}.view-recovery__option button{margin-top:1.5em}.view-recovery__failed{margin:10px 0;color:#db5041;font-weight:600}.database-migration__header{margin-left:auto;margin-right:auto;padding:40px 0;text-align:center}.database-migration__icon{background:#f47c00;border-radius:10px;padding:10px}.database-migration__headline{margin-top:.5em;margin-bottom:.5em;font-size:36px;font-weight:100;line-height:1}.database-migration__description{margin:0 50px;font-weight:600}.database-migration__actions{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-top:2em;padding:0 50px}@media(min-width:960px){.database-migration__actions{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}}.database-migration .widget-button{width:250px;height:35px;margin:5px;padding:0 30px;line-height:35px}@media(min-width:960px){.database-migration .widget-button{width:auto}}.database-migration__main{margin:0 50px 50px;background:#24292e}.database-migration__loading{width:30px;margin:40px auto}.database-migration__loading .sk-circle{width:30px;height:30px}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}#app,body,html{height:100%}blockquote,body,figure,form,p{margin:0;padding:0}article,aside,figcaption,figure,footer,header,main,nav,section{display:block}body,div,fieldset,form,h1,h2,h3,h4,h5,h6,html,p{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}body{background:#ebe6db;overflow-y:hidden}#app{overflow-y:scroll}fieldset{border:none;margin:0;padding:0}legend{-webkit-padding-start:0;-webkit-padding-end:0}body,button,input,textarea{font:300 14px/1.4 -apple-system,system-ui,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;color:#535353}strong{font-weight:600}code{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}input,select,textarea{font-size:99%}input:disabled,select:disabled,textarea:disabled{color:#535353;-webkit-text-fill-color:#535353;cursor:text}input::-ms-clear,input::-ms-reveal,select::-ms-clear,select::-ms-reveal,textarea::-ms-clear,textarea::-ms-reveal{display:none}.invisible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-size:inherit;line-height:inherit;font-weight:600;margin:0}a{color:#f47c00;text-decoration:none}a:hover{text-decoration:underline}h1{font-size:18px;line-height:30px;margin-bottom:10px}@-webkit-keyframes input-error{0%{left:0}25%{left:-5px}75%{left:5px}to{left:0}}@keyframes input-error{0%{left:0}25%{left:-5px}75%{left:5px}to{left:0}}.widget{position:relative}.widget__error{display:none;position:absolute;bottom:-6px;left:0;right:0;margin:0;padding:4px 10px;color:#fff;background:#db5041;border-radius:2px;-webkit-transform:translateY(100%);transform:translateY(100%);z-index:10;white-space:pre-line}.widget__error:after,.widget__error:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.widget__error:after{border-bottom-color:#db5041;border-width:3px;margin-left:-3px}.widget__error:before{border-bottom-color:#db5041;border-width:5px;margin-left:-5px}input:focus+.widget__error,input:hover+.widget__error,select:hover+.widget__error{display:block}input:not([type=checkbox]):not([type=radio]),select{position:relative;width:100%;height:38px;padding:0 10px 0 10px;background:#fff;border:1px solid #ccc;border-radius:2px;color:#535353;-webkit-appearance:none;-moz-appearance:none;appearance:none}input:not([type=checkbox]):not([type=radio]):focus,select:focus{outline:none;background-color:#f9f9f9}.widget--validate input:not([type=checkbox]):not([type=radio]):not(:-moz-placeholder-shown):valid,.widget--validate select:not(:-moz-placeholder-shown):valid{border-color:#31a64b}.widget--validate input:not([type=checkbox]):not([type=radio]):not(:-ms-input-placeholder):valid,.widget--validate select:not(:-ms-input-placeholder):valid{border-color:#31a64b}.widget--validate input:not([type=checkbox]):not([type=radio]):not(:placeholder-shown):valid,.widget--validate select:not(:placeholder-shown):valid{border-color:#31a64b}.widget--validate input:not([type=checkbox]):not([type=radio]):not(:-moz-placeholder-shown):invalid,.widget--validate select:not(:-moz-placeholder-shown):invalid{border-color:#db5041}.widget--validate input:not([type=checkbox]):not([type=radio]):not(:-ms-input-placeholder):invalid,.widget--validate select:not(:-ms-input-placeholder):invalid{border-color:#db5041}.widget--validate input:not([type=checkbox]):not([type=radio]):not(:placeholder-shown):invalid,.widget--validate select:not(:placeholder-shown):invalid{border-color:#db5041}.widget--error input:not([type=checkbox]):not([type=radio]),.widget--error select{border-color:#db5041;-webkit-animation:input-error .15s linear 3;animation:input-error .15s linear 3}select{padding-right:30px}.widget-select:after{position:absolute;right:12px;bottom:16px;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #ccc;content:"";pointer-events:none}.widget-button{display:inline-block;width:100%;height:38px;padding:0;border:none;background-color:#737373;color:#fff;font-weight:600;line-height:38px;text-decoration:none;text-align:center;white-space:nowrap;cursor:pointer;border-bottom:1px solid inherit;border-radius:2px}.widget-button:active,.widget-button:hover{background-color:#666;border-bottom:1px solid #5a5a5a}.widget-button--inline{width:auto!important;min-width:100px;padding:0 20px}.widget-button--transparent{background:transparent}.widget-button--small{height:28px;width:auto!important;min-width:0;padding:0 15px;font-size:13px;line-height:28px;font-weight:300;border-radius:1px}.widget-button--small:before,.widget-button--small>:before{top:3px!important;width:15px!important;height:15px!important;margin-right:4px!important;background-size:15px 15px!important}.widget-button--primary{background-color:#31a64b}.widget-button--primary:active,.widget-button--primary:hover{background-color:#2b9242;border-bottom:1px solid #257f39}.widget-button--warning{background-color:#db8c41}.widget-button--warning:active,.widget-button--warning:hover{background-color:#d77f2c;border-bottom:1px solid #c47225}.widget-button--alert{background-color:#db5041}.widget-button--alert:active,.widget-button--alert:hover{background-color:#d73c2c;border-bottom:1px solid #c43525}.widget-button--info{background-color:#6a8ca6}.widget-button--info:active,.widget-button--info:hover{background-color:#5c7f9a;border-bottom:1px solid #53728a}.widget-button--funding{background-color:#ea4aaa}.widget-button--funding:active,.widget-button--funding:hover{background-color:#e7339f;border-bottom:1px solid #e51c95}.widget-button--add:before{background-image:url(../img/button-add.759df12e.svg)}.widget-button--check:before{background-image:url(../img/button-check.8170bc92.svg)}.widget-button--cloud:before{background-image:url(../img/button-cloud.5015c41d.svg)}.widget-button--cloud-off:before{background-image:url(../img/button-cloud-off.37842942.svg)}.widget-button--console:before{background-image:url(../img/button-console.93e68609.svg)}.widget-button--database:before{background-image:url(../img/button-database.1f267fe1.svg)}.widget-button--download:before{background-image:url(../img/button-download.ce5cd0d3.svg)}.widget-button--edit:before{background-image:url(../img/button-edit.d4a8737e.svg)}.widget-button--gear:before{background-image:url(../img/button-gear.75fa79f0.svg)}.widget-button--hide:before{background-image:url(../img/button-hide.6a3eaaf7.svg)}.widget-button--details:before{background-image:url(../img/button-details.78532630.svg)}.widget-button--link:before{background-image:url(../img/button-link.e4488c1f.svg)}.widget-button--lock:before{background-image:url(../img/button-lock.98988f08.svg)}.widget-button--maintenance:before{background-image:url(../img/button-maintenance.18fa2ce6.svg)}.widget-button--more:before{background-image:url(../img/button-more.e3eb2622.svg)}.widget-button--power:before{background-image:url(../img/button-power.6b957f3a.svg)}.widget-button--run:before{background-image:url(../img/button-run.5c7703ae.svg)}.widget-button--save:before{background-image:url(../img/button-save.7704614c.svg)}.widget-button--search:before{background-image:url(../img/button-search.6f2edfbf.svg)}.widget-button--show:before{background-image:url(../img/button-show.2336e1d9.svg)}.widget-button--trash:before{background-image:url(../img/button-trash.a2f11028.svg)}.widget-button--unlock:before{background-image:url(../img/button-unlock.51b76e07.svg)}.widget-button--update:before{background-image:url(../img/button-update.c63bd5a5.svg)}.widget-button--upload:before{background-image:url(../img/button-upload.b489ceec.svg)}.widget-button .icon-selector:before,.widget-button--add:before,.widget-button--check:before,.widget-button--cloud-off:before,.widget-button--cloud:before,.widget-button--console:before,.widget-button--database:before,.widget-button--details:before,.widget-button--download:before,.widget-button--edit:before,.widget-button--gear:before,.widget-button--hide:before,.widget-button--link:before,.widget-button--lock:before,.widget-button--maintenance:before,.widget-button--more:before,.widget-button--power:before,.widget-button--run:before,.widget-button--save:before,.widget-button--search:before,.widget-button--show:before,.widget-button--trash:before,.widget-button--unlock:before,.widget-button--update:before,.widget-button--upload:before{position:relative;display:inline-block;top:5px;width:20px;height:20px;margin-right:8px;background-position:50%;background-repeat:no-repeat;background-size:20px 20px;content:""}.widget-button .icon-selector:empty:before,.widget-button--add:empty:before,.widget-button--check:empty:before,.widget-button--cloud-off:empty:before,.widget-button--cloud:empty:before,.widget-button--console:empty:before,.widget-button--database:empty:before,.widget-button--details:empty:before,.widget-button--download:empty:before,.widget-button--edit:empty:before,.widget-button--gear:empty:before,.widget-button--hide:empty:before,.widget-button--link:empty:before,.widget-button--lock:empty:before,.widget-button--maintenance:empty:before,.widget-button--more:empty:before,.widget-button--power:empty:before,.widget-button--run:empty:before,.widget-button--save:empty:before,.widget-button--search:empty:before,.widget-button--show:empty:before,.widget-button--trash:empty:before,.widget-button--unlock:empty:before,.widget-button--update:empty:before,.widget-button--upload:empty:before{margin-right:0!important}.widget-button:empty{min-width:auto;padding:0 10px}.widget-button:empty:before{margin-right:0!important}.widget-button:hover{text-decoration:none}.widget-button.disabled,.widget-button:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.widget-button.disabled{pointer-events:none}label{padding:0}.widget--required label:after{margin-left:2px;content:"*";color:#db5041}.animate-initializing{-webkit-animation:initializing 1s linear infinite;animation:initializing 1s linear infinite}@-webkit-keyframes initializing{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}@keyframes initializing{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.animate-blur-in{z-index:-1;opacity:.5;-webkit-filter:blur(4px);filter:blur(4px);-webkit-transition:opacity .5s,-webkit-filter .5s;transition:opacity .5s,-webkit-filter .5s;transition:opacity .5s,filter .5s;transition:opacity .5s,filter .5s,-webkit-filter .5s}.animate-blur-out{opacity:1;-webkit-transition:opacity .5s;transition:opacity .5s}.animate-fade-enter-active,.animate-fade-leave-active{-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:ease;transition-timing-function:ease}.animate-fade-enter,.animate-fade-leave-active{opacity:0}.animate-flip-enter-active,.animate-flip-leave-active{-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition-duration:.5s;transition-duration:.5s;-webkit-transition-property:opacity,-webkit-transform;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform}.animate-flip-leave-active{-webkit-transform:perspective(600px) rotateY(0deg);transform:perspective(600px) rotateY(0deg);opacity:1}.animate-flip-leave-to{-webkit-transform:perspective(600px) rotateY(90deg);transform:perspective(600px) rotateY(90deg);-webkit-transition-timing-function:ease-in;transition-timing-function:ease-in;opacity:0}.animate-flip-enter-active{-webkit-transform:perspective(400px) rotateY(270deg);transform:perspective(400px) rotateY(270deg);opacity:0}.animate-flip-enter-to{-webkit-transform:perspective(400px) rotateY(1turn);transform:perspective(400px) rotateY(1turn);-webkit-transition-timing-function:ease-out;transition-timing-function:ease-out;opacity:1}.https-warning{position:absolute;top:0;left:0;right:0;height:27px;padding:4px 8px;background:#db8c41;color:#fff;text-align:center;z-index:100}.https-warning__description{display:none}@media(min-width:600px){.https-warning__description{display:inline}}.https-warning__link{color:#fff;text-decoration:underline}.https-warning+div{padding-top:25px}.safe-mode{position:absolute;top:0;left:0;right:0;height:27px;padding:4px 8px;background:#db5041;color:#fff;text-align:center;z-index:100}.safe-mode__description{display:none}@media(min-width:600px){.safe-mode__description{display:inline}}.safe-mode__link{color:#fff;text-decoration:underline}.safe-mode+div{padding-top:25px}.view-init{display:table;width:100%;height:100%}.view-init__cell{display:table-cell;font-size:1.5em;text-align:center;vertical-align:middle}.popup-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000}@media(min-width:960px){.popup-overlay{padding:20px 0;overflow-y:auto}}.loader__item{float:left;width:16px;height:16px;margin-right:1px;background-color:#f47c00;-webkit-animation:loading 1.4s ease-in-out infinite both;animation:loading 1.4s ease-in-out infinite both}.loader__item--20{-webkit-animation-delay:-.64s;animation-delay:-.64s}.loader__item--40{-webkit-animation-delay:-.48s;animation-delay:-.48s}.loader__item--60{-webkit-animation-delay:-.32s;animation-delay:-.32s}.loader__item--80{-webkit-animation-delay:-.16s;animation-delay:-.16s}@-webkit-keyframes loading{0%,90%,to{opacity:0}20%{opacity:1}}@keyframes loading{0%,90%,to{opacity:0}20%{opacity:1}}.loader__text{float:left;width:40px}.sk-circle{width:25px;height:25px;position:relative}.sk-circle .sk-child{width:100%;height:100%;position:absolute;left:0;top:0}.sk-circle .sk-child:before{content:"";display:block;margin:0 auto;width:15%;height:15%;background-color:#535353;border-radius:100%;-webkit-animation:sk-circleBounceDelay 1.2s ease-in-out infinite both;animation:sk-circleBounceDelay 1.2s ease-in-out infinite both}.sk-circle .sk-circle2{-webkit-transform:rotate(30deg);transform:rotate(30deg)}.sk-circle .sk-circle3{-webkit-transform:rotate(60deg);transform:rotate(60deg)}.sk-circle .sk-circle4{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.sk-circle .sk-circle5{-webkit-transform:rotate(120deg);transform:rotate(120deg)}.sk-circle .sk-circle6{-webkit-transform:rotate(150deg);transform:rotate(150deg)}.sk-circle .sk-circle7{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.sk-circle .sk-circle8{-webkit-transform:rotate(210deg);transform:rotate(210deg)}.sk-circle .sk-circle9{-webkit-transform:rotate(240deg);transform:rotate(240deg)}.sk-circle .sk-circle10{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.sk-circle .sk-circle11{-webkit-transform:rotate(300deg);transform:rotate(300deg)}.sk-circle .sk-circle12{-webkit-transform:rotate(330deg);transform:rotate(330deg)}.sk-circle .sk-circle2:before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-circle .sk-circle3:before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-circle .sk-circle4:before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-circle .sk-circle5:before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-circle .sk-circle6:before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-circle .sk-circle7:before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-circle .sk-circle8:before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-circle .sk-circle9:before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-circle .sk-circle10:before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-circle .sk-circle11:before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-circle .sk-circle12:before{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes sk-circleBounceDelay{0%,80%,to{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes sk-circleBounceDelay{0%,80%,to{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}.widget-button .loader{width:25px;margin:0 auto}.widget-button .sk-circle .sk-child:before{background-color:#fff}.loading-button{position:relative}.loading-button>.loader{position:absolute;left:calc(50% - 12.5px);top:calc(50% - 12.5px)}.loading-button>.loading{visibility:hidden}.package-logo--fallback[data-v-622648b4]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:#e9eef1}svg[data-v-622648b4]{width:80%;height:80%;fill:#babec1}.discover-package{position:relative;overflow:hidden;margin-bottom:14px;padding:20px;background:#fff;border-bottom:3px solid #ddd3bc;border-radius:2px}.discover-package__abandoned{display:inline-block;margin-bottom:1em;padding:2px 5px;color:#fff;font-size:12px;font-weight:600;background:#db5041;cursor:help;z-index:10}@media(min-width:600px){.discover-package__abandoned{position:absolute;top:20px;left:-25px;padding:2px 30px;border-top:1px solid #c43525;border-bottom:1px solid #99291d;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}}.discover-package__icon{position:absolute;right:20px;height:42px;width:42px;margin-left:1em}.discover-package__icon--fallback{display:none}.discover-package__icon img{position:absolute;width:100%;height:auto;max-height:100%}@media(min-width:600px){.discover-package__icon{position:relative;right:auto;display:block;float:left;width:90px;height:90px;margin-left:0;margin-right:20px}}@media(min-width:1024px){.discover-package__icon{position:absolute;right:20px;height:50px;width:50px;margin-left:1em;margin-right:0}.discover-package__icon--fallback{display:none}}@media(min-width:1200px){.discover-package__icon{position:relative;right:auto;display:block;float:left;width:90px;height:90px;margin-left:0;margin-right:20px}}.discover-package__details{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media(min-width:600px){.discover-package__details{height:100%;min-height:90px}}.discover-package__headline{margin-bottom:.2em;line-height:1;margin-right:50px;overflow-wrap:break-word}.discover-package__headline--fallback{margin-right:0}@media(min-width:600px){.discover-package__headline{margin-right:0}}@media(min-width:1024px){.discover-package__headline{margin-right:60px}.discover-package__headline--fallback{margin-right:0}}@media(min-width:1200px){.discover-package__headline{margin-right:0}}.discover-package__headline em{background-color:#ff0;font-style:normal}.discover-package__description{display:-webkit-box;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;margin-bottom:.5em;margin-right:50px}.discover-package__description--fallback{margin-right:0}@media(min-width:600px){.discover-package__description{margin-right:0}}@media(min-width:1024px){.discover-package__description{margin-right:60px}.discover-package__description--fallback{margin-right:0}}@media(min-width:1200px){.discover-package__description{margin-right:0}}.discover-package__description em{background-color:#ff0;font-style:normal}.discover-package__more{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end;margin-bottom:-.5em;line-height:28px}.discover-package__counts,.discover-package__more{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.discover-package__counts{margin-bottom:.5em}@media(min-width:600px){.discover-package__counts{margin-bottom:0}}.discover-package__count{display:inline-block;margin-right:15px;padding-left:18px;font-size:13px;background-position:0 50%;background-repeat:no-repeat;background-size:13px 13px}.discover-package__count--private{background-image:url(../img/private.c66d3582.svg)}.discover-package__count--updated{background-image:url(../img/updated.455f42dc.svg)}.discover-package__count--downloads{background-image:url(../img/downloads.aa84cdf1.svg)}.discover-package__count--favers{background-image:url(../img/favers.31587387.svg)}.discover-package__actions{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0 -4px .5em}.discover-package__actions>*{margin:0 4px}@media(min-width:600px){.discover-package__actions{margin-bottom:0}}.vueperslide{white-space:normal;background-size:cover;-ms-flex-negative:0;flex-shrink:0;display:block;width:100%;position:relative}.vueperslide--clone-1{position:absolute;top:0;bottom:0;right:100%}.vueperslides--rtl .vueperslide--clone-1{right:auto;left:100%}.vueperslide[href]{-webkit-user-drag:none}.vueperslide__image{background-size:cover}.vueperslide__image,.vueperslide__loader{position:absolute;top:0;left:0;right:0;bottom:0}.vueperslide__loader{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.vueperslide__content-wrapper:not(.vueperslide__content-wrapper--outside-top):not(.vueperslide__content-wrapper--outside-bottom){height:100%;margin:auto}.vueperslides--fade .vueperslide{position:absolute;top:0;left:0;right:0;bottom:0;opacity:0;-webkit-transition:opacity ease-in-out;transition:opacity ease-in-out;-webkit-transition-duration:inherit;transition-duration:inherit}.vueperslides--fade .vueperslide--active,.vueperslides--fade .vueperslide--visible{z-index:1;opacity:1}.vueperslides--slide-image-inside .vueperslide{overflow:hidden}.vueperslides--3d .vueperslide{position:absolute;z-index:-1;height:100%}.vueperslides--3d .vueperslide--active,.vueperslides--3d .vueperslide--next-slide,.vueperslides--3d .vueperslide--previous-slide{z-index:0}.vueperslides--3d .vueperslide--active{z-index:1}.vueperslides--3d .vueperslide[face=front]{-webkit-transform:rotateY(90deg) translateX(-50%) rotateY(-90deg);transform:rotateY(90deg) translateX(-50%) rotateY(-90deg)}.vueperslides--3d .vueperslide[face=right]{-webkit-transform:rotateY(90deg) translateX(50%);transform:rotateY(90deg) translateX(50%);-webkit-transform-origin:100% 0;transform-origin:100% 0}.vueperslides--3d .vueperslide[face=back]{-webkit-transform:rotateY(270deg) translateX(-50%) rotateY(-90deg);transform:rotateY(270deg) translateX(-50%) rotateY(-90deg)}.vueperslides--3d .vueperslide[face=left]{-webkit-transform:rotateY(270deg) translateX(-50%);transform:rotateY(270deg) translateX(-50%);-webkit-transform-origin:0 0;transform-origin:0 0}.vueperslides:not(.no-shadow):not(.vueperslides--3d) .vueperslides__parallax-wrapper:after,.vueperslides:not(.no-shadow):not(.vueperslides--3d) .vueperslides__parallax-wrapper:before{content:"";position:absolute;bottom:100%;left:-1em;right:-1em;height:2em;-webkit-box-shadow:0 0 20px rgba(0,0,0,.25);box-shadow:0 0 20px rgba(0,0,0,.25);z-index:2}.vueperslides:not(.no-shadow):not(.vueperslides--3d) .vueperslides__parallax-wrapper:after{top:100%;bottom:auto}.vueperslides__arrows{color:#fff}.vueperslides__arrows--outside{color:currentColor}.vueperslides__arrow{top:50%;background-color:transparent;border:none;opacity:.7}.vueperslides--rtl .vueperslides__arrow--next,.vueperslides__arrow--prev{right:auto;left:.5em}.vueperslides--rtl .vueperslides__arrow--prev,.vueperslides__arrow--next{left:auto;right:.5em}.vueperslides__arrow:hover{opacity:1}.vueperslides--rtl .vueperslides__arrows--outside .vueperslides__arrow--next,.vueperslides__arrows--outside .vueperslides__arrow--prev{right:auto;left:-3.5em}.vueperslides--rtl .vueperslides__arrows--outside .vueperslides__arrow--prev,.vueperslides__arrows--outside .vueperslides__arrow--next{left:auto;right:-3.5em}.vueperslides__paused{top:.7em;right:.7em;opacity:0;text-shadow:0 0 3px rgba(0,0,0,.4);z-index:1}.vueperslides:hover .vueperslides__paused{opacity:1}.vueperslides__bullets:not(.vueperslides__bullets--outside){color:#fff}.vueperslides__bullet{margin:1.5em .6em;padding:0;border:none;background:none}.vueperslides__bullet .default{width:12px;height:12px;border-radius:12px;border:1px solid currentColor;background-color:transparent;-webkit-box-shadow:0 0 1px rgba(0,0,0,.5),0 0 3px rgba(0,0,0,.3);box-shadow:0 0 1px rgba(0,0,0,.5),0 0 3px rgba(0,0,0,.3);-webkit-transition:.4s ease-in-out;transition:.4s ease-in-out;-webkit-box-sizing:border-box;box-sizing:border-box}.vueperslides__bullet .default span{display:none}.vueperslides__bullet--active .default{border-width:6px}.vueperslide,.vueperslide__image{background-position:50%}.vueperslide__video{outline:none}.vueperslide--no-pointer-events:before{content:"";position:absolute;top:0;bottom:0;left:0;right:0}.vueperslide__content-wrapper:not(.vueperslide__content-wrapper--outside-top):not(.vueperslide__content-wrapper--outside-bottom){display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.vueperslide--has-image-inside .vueperslide__content-wrapper,.vueperslide--has-video .vueperslide__content-wrapper,.vueperslide__content-wrapper.parallax-fixed-content{position:absolute;z-index:2;top:0;bottom:0;left:0;right:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;pointer-events:none}.vueperslides{position:relative}.vueperslides--fixed-height .vueperslide,.vueperslides--fixed-height .vueperslides__inner,.vueperslides--fixed-height .vueperslides__parallax-wrapper{height:inherit}.vueperslides--fixed-height .vueperslides__parallax-wrapper{padding-bottom:0!important}.vueperslides--fixed-height.vueperslides--bullets-outside{margin-bottom:4em}.vueperslides__inner{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.vueperslides__parallax-wrapper{position:relative;overflow:hidden}.vueperslides--3d .vueperslides__parallax-wrapper{overflow:visible}.vueperslides__track{position:absolute;top:0;height:100%;left:0;right:0;overflow:hidden;z-index:1}.vueperslides--parallax .vueperslides__track{height:200%;-webkit-transform:translateY(0);transform:translateY(0)}.vueperslides--touchable .vueperslides__track{cursor:ew-resize;cursor:-webkit-grab;cursor:grab}.vueperslides--touchable .vueperslides__track--dragging,.vueperslides--touchable .vueperslides__track--mousedown{cursor:-webkit-grabbing;cursor:grabbing}.vueperslides--3d .vueperslides__track{overflow:visible;-webkit-perspective:100em;perspective:100em}.vueperslides__track-inner{white-space:nowrap;-webkit-transition:transform .5s ease-in-out;-webkit-transition:-webkit-transform .5s ease-in-out;transition:-webkit-transform .5s ease-in-out;transition:transform .5s ease-in-out;transition:transform .5s ease-in-out,-webkit-transform .5s ease-in-out;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.vueperslides--no-animation .vueperslides__track-inner{-webkit-transition-duration:0s!important;transition-duration:0s!important}.vueperslides--fade .vueperslides__track-inner{white-space:normal;-webkit-transition:none;transition:none}.vueperslides--3d .vueperslides__track-inner{-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.vueperslides__track--mousedown .vueperslides__track-inner{-webkit-transition:transform .25s ease-in-out!important;-webkit-transition:-webkit-transform .25s ease-in-out!important;transition:-webkit-transform .25s ease-in-out!important;transition:transform .25s ease-in-out!important;transition:transform .25s ease-in-out,-webkit-transform .25s ease-in-out!important}.vueperslides__track--dragging .vueperslides__track-inner{-webkit-transition:none;transition:none}.vueperslides__arrow{position:absolute;font-size:inherit;color:inherit;text-align:center;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:none;z-index:2;line-height:1}.vueperslides__arrow,.vueperslides__arrow svg{-webkit-transition:.3s ease-in-out;transition:.3s ease-in-out}.vueperslides__arrow svg{vertical-align:middle;stroke:currentColor;fill:none;width:3.5em;padding:1em;stroke-width:1;-webkit-box-sizing:border-box;box-sizing:border-box}.vueperslides__arrow svg:hover{stroke-width:1.3}.vueperslides__paused{position:absolute;-webkit-transition:.3s ease-in-out;transition:.3s ease-in-out}.vueperslides__bullets{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:absolute;bottom:0;left:0;right:0}.vueperslides__bullets--outside{position:relative}.vueperslides__bullet,.vueperslides__bullets button{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:none;z-index:2;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:inherit}.vueperslides__bullet::-moz-focus-inner,.vueperslides__bullets button::-moz-focus-inner{border:0}.vueperslides__fractions{position:absolute;top:.8em;left:.5em;z-index:2;padding:.2em 1em;border:1px solid hsla(0,0%,100%,.5);border-radius:2em;background:hsla(0,0%,100%,.2);color:#fff}.vueperslides__progress{position:absolute;top:0;left:0;right:0;z-index:2;height:6px;color:rgba(0,0,0,.7)}.vueperslides__progress>*{position:absolute;top:0;bottom:0;left:0;background:currentColor;-webkit-transition:.3s ease-in-out;transition:.3s ease-in-out}img[data-v-5f0fe3e2]{width:100%}.ads[data-v-5f0fe3e2]{margin:59px 0 69px}.link[data-v-5f0fe3e2]{padding-top:5px;text-align:right}.link a[data-v-5f0fe3e2]{padding-left:16px;font-size:.8em;color:inherit;background:url(../img/link-blank.d5149b7c.svg) 0 no-repeat;background-size:13px 13px}.container[data-v-5f0fe3e2]{position:relative;background:#fff;-webkit-box-shadow:0 1px 5px 1px rgba(0,0,0,.2);box-shadow:0 1px 5px 1px rgba(0,0,0,.2)}.package-sorting{margin:20px 0 15px;text-align:right}.package-sorting__label{display:inline-block;text-transform:uppercase}.package-sorting__label:after{content:":"}.package-sorting__group{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0;padding:0 15px 0 0;list-style-type:none;text-align:left}.package-sorting__group:after{content:"";position:absolute;top:.8em;right:0;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #535353}.package-sorting__item{display:none;margin:0 0 0 10px;padding:3px 0;text-transform:uppercase;cursor:pointer;border-bottom:2px solid transparent}.package-sorting__item:hover{color:#f47c00}.package-sorting__item--open{display:inline}.package-sorting__item--active{display:inline;color:#f47c00;border-bottom:2px solid #f47c00}@media(min-width:600px){.package-sorting__group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:0}.package-sorting__group:after{content:none}.package-sorting__item{display:inline}}.search-bar{position:relative}.search-bar__input{height:50px!important;padding-right:40px!important}.search-bar__button{position:absolute;top:0;right:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:38px;height:50px;margin:0;padding:7px;line-height:36px;border:none;background:none;outline:none}.package-search{position:relative}.package-search__input{max-width:400px;margin:0 20px}.package-search__headline{font-size:18px;font-weight:300;margin:30px 0 10px}@media(min-width:1024px){.package-search__results{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0 -8px}.package-search__item{-ms-flex-preferred-size:calc(50% - 16px);flex-basis:calc(50% - 16px);margin-left:8px;margin-right:8px}}.package-search__status{margin:100px 0;text-align:center;font-size:20px;line-height:1.5em}.package-search__status--empty{padding-top:140px;background:url(../img/sad.c9a06488.svg) top no-repeat;background-size:100px 100px}.package-search__status--offline{padding-top:140px;background:url(../img/offline.1f95ae24.svg) top no-repeat;background-size:100px 100px}.package-search__status--loader .sk-circle{width:100px;height:100px;margin:0 auto 40px}.package-search__status button{margin-top:2em}.package-search__explain{font-size:16px}.package-search__more{margin:10px 0 30px;text-align:center}.package-search__more-button{display:inline-block;margin:0 auto;padding:0;text-transform:uppercase;background:none;border:none;cursor:pointer}.package-search__more-button:hover{text-decoration:underline}.package-search__algolia{display:block;width:200px;margin:50px auto 0}.link-menu{position:absolute;display:block;left:50%;margin:0;padding:0;text-align:center;list-style-type:none;white-space:nowrap;background:#fff;border-top:3px solid #535353;z-index:100;-webkit-box-shadow:0 1px 2px #ccbfa2;box-shadow:0 1px 2px #ccbfa2}.link-menu:before{position:absolute;left:50%;top:-7px;width:0;height:0;margin-left:-4px;border-style:solid;border-width:0 3.5px 4px 3.5px;border-color:transparent transparent #535353 transparent;content:""}.link-menu--align-left{left:0;right:auto}.link-menu--align-left:before{left:17px;right:auto}.link-menu--align-right{left:auto;right:0}.link-menu--align-right:before{left:auto;right:17px}.link-menu--valign-top{bottom:0;border-top:none;border-bottom:3px solid #535353;-webkit-box-shadow:0 0 2px #ccbfa2;box-shadow:0 0 2px #ccbfa2}.link-menu--valign-top:before{top:auto;bottom:-7px;border-width:4px 3.5px 0 3.5px;border-color:#535353 transparent transparent transparent}.link-menu--contao{border-color:#f47c00}.link-menu--contao:before{border-bottom-color:#f47c00}.link-menu--contao.link-menu--valign-top:before{border-bottom-color:transparent;border-top-color:#f47c00}.link-menu--primary{border-color:#31a64b}.link-menu--primary:before{border-bottom-color:#31a64b}.link-menu--primary.link-menu--valign-top:before{border-bottom-color:transparent;border-top-color:#31a64b}.link-menu--warning{border-color:#db8c41}.link-menu--warning:before{border-bottom-color:#db8c41}.link-menu--warning.link-menu--valign-top:before{border-bottom-color:transparent;border-top-color:#db8c41}.link-menu--alert{border-color:#db5041}.link-menu--alert:before{border-bottom-color:#db5041}.link-menu--alert.link-menu--valign-top:before{border-bottom-color:transparent;border-top-color:#db5041}.link-menu__item{margin:0;padding:0;display:block;border-top:1px solid #e5dfd0}.link-menu__item:first-child{border-top:none}.link-menu__action{display:block;margin:0;padding:10px 20px;color:#535353;cursor:pointer}.link-menu__action:hover{color:#000;text-decoration:none}.vjs-tree-brackets{cursor:pointer}.vjs-tree-brackets:hover{color:#1890ff}.vjs-check-controller{position:absolute;left:0}.vjs-check-controller.is-checked .vjs-check-controller-inner{background-color:#1890ff;border-color:#0076e4}.vjs-check-controller.is-checked .vjs-check-controller-inner.is-checkbox:after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.vjs-check-controller.is-checked .vjs-check-controller-inner.is-radio:after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.vjs-check-controller .vjs-check-controller-inner{display:inline-block;position:relative;border:1px solid #bfcbd9;border-radius:2px;vertical-align:middle;-webkit-box-sizing:border-box;box-sizing:border-box;width:16px;height:16px;background-color:#fff;z-index:1;cursor:pointer;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.vjs-check-controller .vjs-check-controller-inner:after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:2px solid #fff;border-left:0;border-top:0;height:8px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:4px;-webkit-transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) .05s;transition:-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) .05s;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) .05s;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) .05s,-webkit-transform .15s cubic-bezier(.71,-.46,.88,.6) .05s;-webkit-transform-origin:center;transform-origin:center}.vjs-check-controller .vjs-check-controller-inner.is-radio{border-radius:100%}.vjs-check-controller .vjs-check-controller-inner.is-radio:after{border-radius:100%;height:4px;background-color:#fff;left:50%;top:50%}.vjs-check-controller .vjs-check-controller-original{opacity:0;outline:none;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.vjs-carets{position:absolute;right:0;cursor:pointer}.vjs-carets svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.vjs-carets:hover{color:#1890ff}.vjs-carets-close{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.vjs-tree-node{display:-webkit-box;display:-ms-flexbox;display:flex;position:relative;line-height:20px}.vjs-tree-node.has-carets{padding-left:15px}.vjs-tree-node.has-carets.has-selector,.vjs-tree-node.has-selector{padding-left:30px}.vjs-tree-node.is-highlight,.vjs-tree-node:hover{background-color:#e6f7ff}.vjs-tree-node .vjs-indent{display:-webkit-box;display:-ms-flexbox;display:flex;position:relative}.vjs-tree-node .vjs-indent-unit{width:1em}.vjs-tree-node .vjs-indent-unit.has-line{border-left:1px dashed #bfcbd9}.vjs-node-index{position:absolute;right:100%;margin-right:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.vjs-colon{white-space:pre}.vjs-comment{color:#bfcbd9}.vjs-value{word-break:break-word}.vjs-value-null,.vjs-value-undefined{color:#d55fde}.vjs-value-boolean,.vjs-value-number{color:#1d8ce0}.vjs-value-string{color:#13ce66}.vjs-tree{font-family:Monaco,Menlo,Consolas,Bitstream Vera Sans Mono,monospace;font-size:14px;text-align:left}.vjs-tree.is-virtual{overflow:auto}.vjs-tree.is-virtual .vjs-tree-node{white-space:nowrap}.link-more{position:relative;display:inline-block}p:empty+.link-more{margin-left:0}.link-more button{width:auto;height:auto;padding:0 0 5px;background:transparent;color:#f47c00;font-size:13px;font-weight:300;line-height:inherit;border:none;cursor:pointer}.link-more button:hover{text-decoration:underline}.link-more__menu{outline:none}.link-more ul{-webkit-transform:translateX(-50%);transform:translateX(-50%)}.package-link{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%;padding-bottom:4px;margin-bottom:4px;border-bottom:1px solid #e9eef1}.package-link:last-child{padding-bottom:0;margin-bottom:0;border-bottom:none}.package-link__details{padding:5px 0;line-height:18px}.package-link__name{display:inline;font-weight:600}.package-link__name:after{content:": "}.package-link__text{display:inline}.package-link__actions{display:-webkit-box;display:-ms-flexbox;display:flex;margin-left:20px;margin:0 -4px}.package-link__actions>*{margin:0 4px}.package-link--limit .package-link__details{display:-webkit-box;display:-ms-flexbox;display:flex}.package-link--limit .package-link__name{white-space:nowrap}.package-link--limit .package-link__text{display:-webkit-box;overflow:hidden;-webkit-line-clamp:1;-webkit-box-orient:vertical;padding:0 10px 0 5px}div[data-v-7ac04620]{padding:10px 20px 10px 50px;font-weight:400;font-size:12px;line-height:1.8;background:rgba(234,74,170,.025) url(../img/funding.8a3c0e0e.svg) 15px 50% no-repeat;background-size:23px 23px;border:1px solid rgba(234,74,170,.5)}span[data-v-7ac04620]{margin-right:15px}a[data-v-7ac04620]{display:inline-block;padding-left:16px;color:#ea4aaa;background:url(../img/link-funding.c73bb011.svg) 0 50% no-repeat;background-size:13px 13px}a[data-v-7ac04620]:after{content:"|";margin:0 10px}a[data-v-7ac04620]:last-child:after{content:none}.package-popup{position:fixed;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;width:100%;height:100%;background:#fff;z-index:10;opacity:1}.package-popup>*{-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.package-popup__loader{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:50px 0}.package-popup__loader p{margin:1em}.package-popup__headline{position:relative;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:1;flex-shrink:1;padding:7px 30px 6px;background:#f47c00;color:#fff;font-size:18px;font-weight:300;line-height:1.5;text-align:center;border-radius:2px 2px 0 0}.package-popup__headline--complete{background-color:#31a64b}.package-popup__headline--error{background-color:#db5041}.package-popup__button{display:block;float:right;position:absolute;top:0;margin:4px 0;padding:4px;background:none;border:1px solid transparent;border-radius:1px;cursor:pointer}.package-popup__button--previous{left:4px}.package-popup__button--close{right:4px}.package-popup__button svg{display:block;width:22px;height:22px}.package-popup__button:hover{background-color:#db6f00;border-color:#c16200}.package-popup__headline--complete .package-popup__button:hover{background-color:#2b9242;border-color:#257f39}.package-popup__headline--error .package-popup__button:hover{background-color:#db5041;border-color:#c43525}.package-popup__summary{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding:25px 35px}@media(min-width:600px){.package-popup__summary{display:-webkit-box;display:-ms-flexbox;display:flex}}.package-popup__icon{float:right;height:42px;width:42px;margin-left:1em}.package-popup__icon--fallback{display:none}.package-popup__icon img{width:100%;height:auto;max-height:100%}@media(min-width:600px){.package-popup__icon{display:block;float:left;width:90px;height:90px;margin-left:0;margin-right:20px;margin-bottom:-4px}}.package-popup__text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}@media(min-width:600px){.package-popup__text{width:200px}}.package-popup__title{margin:0;line-height:1.4;overflow-wrap:break-word}.package-popup__authors{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;font-size:13px;margin-bottom:.5em}.package-popup__author{display:inline-block;margin-right:2px}.package-popup__author:after{color:#535353;content:", "}.package-popup__author:last-child:after{content:none}.package-popup__stats{display:inline-block;margin-right:15px;margin-top:.5em;padding-left:18px;font-size:13px;background-position:0 50%;background-repeat:no-repeat;background-size:13px 13px}.package-popup__stats--private{padding-left:20px;background-image:url(../img/private.c66d3582.svg);background-size:15px 15px}.package-popup__stats--updated{background-image:url(../img/updated.455f42dc.svg)}.package-popup__stats--downloads{background-image:url(../img/downloads.aa84cdf1.svg)}.package-popup__stats--favers{background-image:url(../img/favers.31587387.svg)}.package-popup__actions{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:10px;margin-top:1em}@media(min-width:600px){.package-popup__actions{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 0 0 25px;min-width:200px}}.package-popup__installed{margin-top:1em}.package-popup__abandoned{margin:0 0 20px;padding:10px 20px 10px 50px;font-weight:400;font-size:12px;line-height:1.8;background:hsla(16,49%,82%,.3) url(../img/hint.ba2ac97e.svg) 15px no-repeat;background-size:23px 23px;border:1px solid #bd2e20}.package-popup__funding{margin:0 0 20px}.package-popup__tabs{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;clear:both;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;width:100%;margin:0;padding:0;list-style-type:none}.package-popup__tab{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;margin:0;padding:0;height:39px;line-height:39px;text-align:center;border-top:1px solid #e9eef1;border-right:1px solid #e9eef1;border-bottom:1px solid #e9eef1}.package-popup__tab:last-child{border-right:none}.package-popup__tab--active{font-weight:600;background:#f8f9fb;border-bottom:1px solid #f8f9fb}.package-popup__tab button{width:100%;height:100%;margin:0;padding:0 10px;border:none;background:transparent;cursor:pointer;outline:none}.package-popup__tab button:disabled{color:#ccc!important;cursor:not-allowed}.package-popup__pill{position:relative;top:-2px;display:inline-block;margin-left:5px;padding:2px 5px;font-size:10px;font-weight:400;background:#e9eef1;border-radius:40%}.package-popup__pill--highlight{color:#fff;background:#31a64b}.package-popup__tabcontent{position:relative;padding:25px 35px;overflow-y:auto;background:#f8f9fb}@media(min-width:960px)and (min-height:700px){.package-popup__tabcontent{height:450px}}.package-popup__description{margin:1em 0;white-space:pre-wrap}@media(min-width:960px){.package-popup{position:relative;display:block;top:0;left:50%;width:750px;margin-left:-375px;height:auto;border-bottom:2px solid #ddd3bc;border-radius:2px}}@media(min-width:960px)and (min-height:700px){.package-popup{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}}"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[716],{3716:function(e){e.exports=JSON.parse('{"ui.app.title":"Rozszerzenia Contao","ui.app.loading":"Ładowanie listy rozszerzeń …","ui.discover.advertisement":"Reklama w liście rozszerzeń","ui.discover.loading":"Ładowanie …","ui.discover.offline":"Nie znaleziono żadnych wyników.","ui.discover.offlineExplain":"Sprawdź swoje połączenie internetowe i wyłącz narzędzia blokujące JavaScript w przeglądarce.","ui.discover.offlineButton":"Spróbuj ponownie","ui.discover.searchPlaceholder":"Szukaj w {count} rozszerzeniach ...","ui.discover.empty":"Brak wyników dla {query}","ui.discover.more":"Więcej Wyników","ui.discover.sortBy":"Sortuj według","ui.discover.sortReleased":"Released","ui.discover.sortReleasedTitle":"Sort results by release date","ui.discover.sortLatest":"Data aktualizacji","ui.discover.sortLatestTitle":"Sort results by last updated","ui.discover.sortDownloads":"Pobrania","ui.discover.sortDownloadsTitle":"Sort results by number of downloads","ui.discover.sortFavers":"Ocena","ui.discover.sortFaversTitle":"Sort results by rating","ui.discover.detailsButton":"Szczegóły","ui.discover.latestPackages":"Najnowsze i zaktualizowane rozszerzenia","ui.discover.faversPackages":"Najwyżej oceniane rozszerzenia","ui.discover.downloadsPackages":"Najczęściej pobierane rozszerzenia","ui.package.homepage":"Strona Projektu","ui.package.private":"Prywatny Pakiet","ui.package.privateTitle":"Prywatne pakiety są dostępne tylko bezpośrednio od dostawcy (np. jako plik ZIP). Odwiedź stronę po więcej informacji.","ui.package.abandoned":"porzucony","ui.package.abandonedText":"Ten pakiet jest porzucony i nie jest już wspierany.","ui.package.abandonedReplace":"Ten pakiet jest porzucony i nie jest już wspierany. Autor sugeruje użycie pakietu {replacement} zamiast niego.","ui.package-details.previous":"Szczegóły poprzedniego rozszerzenia","ui.package-details.close":"Zamknij szczegóły rozszerzenia","ui.package-details.loading":"Ładowanie …","ui.package-details.tabDescription":"Opis","ui.package-details.tabRequire":"Wymagania","ui.package-details.tabFeatures":"Funkcjonalności","ui.package-details.tabSuggest":"Sugestie","ui.package-details.tabConflict":"Konflikty","ui.package-details.tabDependents":"Zależności","ui.package-details.linkRequires":"wymaga","ui.package-details.linkReplaces":"zastępuje","ui.package-details.linkProvides":"dostarcza","ui.package-details.linkConflicts":"koliduje","ui.package-details.funding":"Finansuj rozwój pakietu!","ui.package-details.latest":"Ostatnia wersja","ui.package-details.released":"data wydania","ui.package-details.license":"Licencje","ui.package-details.authors":"od","ui.package-details.more":"Więcej","ui.package-details.packagist":"Szczegóły Pakietu","ui.package-details.metadata":"Edytuj metadane","ui.package-details.support_docs":"Dokumentacja","ui.package-details.support_wiki":"Wiki","ui.package-details.support_forum":"Forum","ui.package-details.support_issues":"Issues / Bug Report","ui.package-details.support_source":"Kod źródłowy","ui.package-details.support_irc":"IRC / Chat","ui.package-details.support_email":"Wsparcie E-mail","ui.package-details.support_rss":"Kanał RSS"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[486],{8486:function(e){e.exports=JSON.parse('{"ui.app.title":"Contao eklentileri","ui.app.loading":"Eklenti listesi yükleniyor…","ui.discover.advertisement":"Eklenti listesinde duyuru","ui.discover.loading":"Yükleniyor…","ui.discover.offline":"Herhangi bir sonuç alınamadı.","ui.discover.offlineExplain":"İnternet bağlantınızı denetleyin ve tarayıcınızdaki JavaScript engelleyicileri etkisizleştirin.","ui.discover.offlineButton":"Yeniden dene","ui.discover.searchPlaceholder":"{count} eklentide ara…","ui.discover.empty":"{query} ile eşleşen bir sonuç bulunamadı","ui.discover.more":"Diğer sonuçlar","ui.discover.sortBy":"Sıralama","ui.discover.sortReleased":"Yayınlandı","ui.discover.sortReleasedTitle":"Sonuçlar yayınlanma tarihine göre sıralansın","ui.discover.sortLatest":"Güncellendi","ui.discover.sortLatestTitle":"Sonuçlar güncellenme tarihine göre sıralansın","ui.discover.sortDownloads":"İndirmeler","ui.discover.sortDownloadsTitle":"Sonuçlar indirme sayılarına göre sıralansın","ui.discover.sortFavers":"Değerlendirme","ui.discover.sortFaversTitle":"Sonuçlar değerlendirmeye göre sıralansın","ui.discover.detailsButton":"Ayrıntılar","ui.discover.latestPackages":"Yeni ve güncellenmiş eklentiler","ui.discover.faversPackages":"İyi değerlendirilmiş eklentiler","ui.discover.downloadsPackages":"En çok indirilen eklentiler","ui.package.homepage":"Proje sitesi","ui.package.private":"Özel paket","ui.package.privateTitle":"Özel paketler yalnızca üretici tarafından sağlanabilir (ZIP indirmesi gibi). Ayrıntılı bilgi almak için siteye bakabilirsiniz.","ui.package.abandoned":"kullanımdan kaldırıldı","ui.package.abandonedText":"Bu paket kullanımdan kaldırıldı ve artık geliştirilmiyor.","ui.package.abandonedReplace":"Bu paket kullanımdan kaldırıldı ve artık geliştirilmiyor. Geliştirici bunun yerine {replacement} paketinin kullanılmasını öneriyor.","ui.package-details.previous":"Önceki eklenti bilgileri","ui.package-details.close":"Eklenti bilgilerini kapat","ui.package-details.loading":"Yükleniyor…","ui.package-details.tabDescription":"Açıklama","ui.package-details.tabRequire":"Gereksinimler","ui.package-details.tabFeatures":"Özellikler","ui.package-details.tabSuggest":"Öneriler","ui.package-details.tabConflict":"Çakışmalar","ui.package-details.tabDependents":"Bağımlılar","ui.package-details.linkRequires":"gereksinimi","ui.package-details.linkReplaces":"değişikliği","ui.package-details.linkProvides":"sundukları","ui.package-details.linkConflicts":"çakışması","ui.package-details.funding":"Paketin geliştirilmesine destek olun!","ui.package-details.latest":"Son sürüm","ui.package-details.released":"yayınlanma tarihi","ui.package-details.license":"Lisanslar","ui.package-details.authors":"geliştiren","ui.package-details.more":"Ayrıntılar","ui.package-details.packagist":"Paket bilgileri","ui.package-details.metadata":"Üst verileri düzenle","ui.package-details.support_docs":"Belgeler","ui.package-details.support_wiki":"Viki","ui.package-details.support_forum":"Destek forumu","ui.package-details.support_issues":"Sorunlar / Hata bildirimi","ui.package-details.support_source":"Kaynak kodu","ui.package-details.support_irc":"IRC / Sohbet","ui.package-details.support_email":"Destek e-postası","ui.package-details.support_rss":"RSS akışı"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[606],{2225:function(e){e.exports=JSON.parse('{"ui.app.httpsHeadline":"!! Ligação Insegura !!","ui.app.httpsDescription":"Sem recurso a HTTPS os seus dados confidenciais serão transferidos sem encriptação.","ui.app.httpsLink":"Mais info","ui.app.httpsHref":"https://https.cio.gov/everything/","ui.app.safeModeHeadline":"!! Modo Guardar activado !!","ui.app.safeModeDescription":"Algumas características do Gestor de Contacto não estão disponíveis.","ui.app.safeModeExit":"Sair do modo seguro","ui.app.loading":"A iniciar o Contao Manager ...","ui.app.apiError":"API status não esperado -erro","ui.app.configSecurity1":"ALERTA DE SEGURANÇA!!! Directório de configuração sem protecção detectado","ui.app.configSecurity2":"O Contao Manager detetou que os seus ficheiros de configuração estão acessíveis publicamente. Todas as operações estão desativadas até que o directório esteja seguro, caso contrário um atacante poderá aceder a informações sensíveis sobre a sua instalação.\\n\\nPara corrigir esta situação, certifique-se que previne o acesso ao directório \\"contao-manager\\" no seu servidor. Para saber como o fazer, consulte o manual do seu webserver ou contacte o fornecedor de alojamento web.","ui.account.welcome":"Bem Vindo","ui.account.intro1":"Bem-vindo ao Contao Manager, uma ferramenta universal para instalar e gerir o Contao Open Source CMS. Se é novo nele, por favor {readTheManualToGetStarted}.","ui.account.introGetStarted":"{readTheManual} para começar","ui.account.introManual":"ler o manual","ui.account.intro2":"Se encontrar algum problema, verifique {ourGithubIssues} e sinta-se à vontade para criar um novo para qualquer coisa que ainda não tenha sido relatada.","ui.account.introIssues":"as nossas edições GitHub","ui.account.headline":"Utilizador","ui.account.description":"Para gerir a sua instalação, por favor crie uma conta para utilizar no Contao Manager. Certifique-se que é distinta da conta utilizada no Contao back e front end.","ui.account.username":"Utilizador","ui.account.password":"Senha","ui.account.passwordPlaceholder":"min. 8 caracteres","ui.account.passwordLength":"Por favor utilize o minimo de 8 caracteres.","ui.account.submit":"Criar Conta","ui.account.contribute1":"O Contao e o Contao Manager são patrocinados pela Associação Contao sem fins lucrativos.","ui.account.contribute2":"Por favor, considere contribuir para o código aberto por {donate}.","ui.account.contributeDonate":"fazer um donativo","ui.login.headline":"Iniciar sessão","ui.login.description":"Iniciar sessão para gerir a sua instalação.","ui.login.username":"Utilizador","ui.login.password":"Senha","ui.login.forgotPassword":"Esqueceu a sua senha?","ui.login.button":"Iniciar sessão","ui.login.locked":"O acesso foi negado porque o Gestor de Contacto está bloqueado. Para desbloquear, apagar o ficheiro {lockFile} no directório raiz do seu Contao.","ui.logout.headline":"Tempo de Sessão expirado.","ui.logout.warning":"Esteve inactivo por mais de 25 minutos. Por razões de segurança a sua sessão será terminada em breve.","ui.logout.expired":"A sua sessão foi terminada automaticamente porque esteve inactivo por mais de 30 minutos.","ui.logout.renew":"Manter sessão ativa","ui.logout.logout":"Terminar Sessão","ui.logout.login":"Voltar para inicio de sessão","ui.oauth.error":"Tentativa OAuth inválida. Verifique os parâmetros pedidos.","ui.oauth.https":"O redireccionamento do URI DEVE utilizar um protocolo seguro (https:) para evitar que o símbolo de autenticação seja transmitido em texto claro.","ui.oauth.headline":"Autenticação remota","ui.oauth.description":"A seguinte aplicação ou serviço está a solicitar acesso remoto ao Contao Manager.","ui.oauth.domain":"Antes de permitir o acesso, certifique-se de que conhece este URL e confie no seu proprietário!","ui.oauth.allow":"Permitir Acesso","ui.oauth.deny":"Negar Acesso","ui.boot.headline":"Verificar Sistema","ui.boot.description":"Por favor espere, estamos a analisar o seu servidor...","ui.boot.issue1":"Problemas com a instalação detectados","ui.boot.issue2":"A sua instalação tem problemas que têm de ser resolvidos antes que o Gestor de Contacto possa ser utilizado.","ui.boot.run":"Iniciar o Contao Manager","ui.boot.safeMode":"Executar em Modo de Segurança","ui.recovery.headline":"Recuperação de Sistema","ui.recovery.description":"O Contao Manager detectou ficheiros que podem pertencer ao Contao, mas a linha de comandos não funciona como previsto.","ui.recovery.console":"Saída da Consola","ui.recovery.repairOptions":"Por favor escolha uma opção para reparar a sua instalação.","ui.recovery.repairHeadline":"Reparação Automática","ui.recovery.repairDescription":"Efectua uma tentativa de reparar automaticamente a instalação ao reconstruir a cache da aplicação e reinstalando os pacotes Composer.","ui.recovery.repairWarning":"Quaisquer modificações aos ficheiros vendor poderão ser apagadas durante o processo!","ui.recovery.repairFailed":"A reparação automática não teve sucesso. Usar o modo de segurança para reparar manualmente a instalação poderá resolver o problema.","ui.recovery.repairButton":"Executar Reparação de Sistema","ui.recovery.safeModeHeadline":"Modo de Segurança ","ui.recovery.safeModeDescription":"Executar o Contao Manager em Modo de Segurança permite gerir pacotes e correr tarefas de manutenção específicas, mas implementações que dependam de uma instalação Contao funcional não estarão disponíveis.","ui.recovery.safeModeButton":"Executar em Modo de Segurança","ui.server.pending":"Aguarde ...","ui.server.running":"A analisar ...","ui.server.error":"A verificação falhou devido a uma resposta inesperada do servidor.","ui.server.details":"Detalhes","ui.server.prerequisite":"A verificação falhou devido a um pré-requisito em falta.","ui.server.selfUpdate.title":"Actualizações Contao Manager","ui.server.selfUpdate.update":"Uma nova versão do Contao Manager {latest} está disponivel.","ui.server.selfUpdate.manualUpdate":"Está disponível uma nova versão do Contao Manager {latest}. O seu servidor não suporta actualizações automáticas, por favor descarregue a nova versão a partir de {download}.","ui.server.selfUpdate.latest":"Está a usar a versão mais recente {current}.","ui.server.selfUpdate.dev":"Versões de testes não suportam actualizações automáticas.","ui.server.selfUpdate.unsupported":"Uma nova versão está disponível mas não é compatível com a sua versão de PHP.","ui.server.selfUpdate.button":"Executar Auto-Update","ui.server.selfUpdate.continue":"Continuar","ui.server.config.title":"Configuração do Servidor","ui.server.config.setup":"Configurar","ui.server.config.change":"Alterar","ui.server.config.save":"Guardar","ui.server.config.cancel":"Cancelar","ui.server.config.customOption":"Outros ...","ui.server.config.description":"Para executar correctamente as tarefas de fundo, o Gestor de Contacto precisa de saber onde encontrar o binário de linha de comando PHP e como executar comandos separados do processo web.","ui.server.config.formTitle":"Configuração do Servidor","ui.server.config.formText":"Por favor introduza o caminho para o seu binário PHP. Certifique-se que o seu binário usa a mesma versao PHP que o seu processo web.","ui.server.config.cloudTitle":"Composer Resolver Cloud","ui.server.config.cloudText":"A Composer Resolver Cloud permite instalar dependências Composer mesmo se o seu servidor não possuir memória local suficiente. Tenha em consideração que a sua informação de pacotes será transmitida para um servidor em nuvem mantido pela Contao Association.","ui.server.config.cloud":"Usar a Composer Resolver Cloud","ui.server.config.cli":"Binário PHP","ui.server.config.stateErrorCli":"Nenhum binário PHP válido foi encontrado no servidor. ","ui.server.config.stateErrorCloud":"A Composer Resolver Cloud não é suportada.","ui.server.config.stateSuccess":"Binário PHP em {php_cli}.","ui.server.php_web.title":"Processo Web PHP","ui.server.php_web.below7":"Encontrada a versão {version} PHP. Actualize para a versão PHP 7 assim que possivel!","ui.server.php_web.success":"Encontrada a versão PHP {version}, nenhum problema a reportar.","ui.server.php_cli.title":"Interface PHP Linha de Comandos","ui.server.php_cli.success":"Encontrada a versão PHP {version}, nenhum problema a reportar.","ui.server.composer.title":"Ambiente Composer","ui.server.composer.success":"Nenhum problema encontrado.","ui.server.composer.install":"Dependências Composer não instaladas.","ui.server.composer.button":"Instalar","ui.server.contao.title":"Instalação Contao","ui.server.contao.setup":"Configuração","ui.server.contao.check":"Check database","ui.server.contao.empty":"Nenhuma instalação Contao foi encontrada.","ui.server.contao.old":"A versão {version} do Contao não é compatível com o Contao Manager, actualize a sua instalação manualmente.","ui.server.contao.found":"Encontrado Contao com versão {version} (Versão API {api}).","ui.server.contao.connectionError":"Incapaz de se ligar ao servidor da base de dados.","ui.server.contao.connectionProblem":"Database problem found.","ui.server.contao.missingUser":"Conta administrativa não encontrada.","ui.setup.continue":"Continuar","ui.setup.manager":"Iniciar o Contao Manager","ui.setup.cancel":"Cancelar","ui.setup.welcome":"Bem Vindo","ui.setup.welcome1":"Este assistente irá levá-lo através dos passos necessários para configurar a sua instalação do CMS Contao Open Source.","ui.setup.welcome2":"If you have any questions, please find documentation, forums, a Slack channel and more on the {support} page.","ui.setup.support":"Apoio à Comunidade","ui.setup.start":"ComeceComece","ui.setup.complete":"Parabéns!","ui.setup.complete1":"Contao {version} has been installed successfully.","ui.setup.complete2":"To finish the setup process, please open the install tool to configure the database connection and create a back end user.","ui.setup.complete3":"You can now start to create your website in the Contao back end. If you need additional extensions, continue to the Contao Manager.","ui.setup.installTool":"Open the Install Tool","ui.setup.login":"Login to Contao","ui.setup.funding":"Free software is \\"free\\" as in \\"free speech\\", not as in \\"free beer\\". An Open Source project like Contao requires amounts of money that can\'t be raised by a single person or company.\\nIf you have a website or sell websites built with Contao, we would love to see you contribute back financially to the product your business relies upon.","ui.setup.fundingLink":"Learn more","ui.setup.document-root.headline":"Configuração do Webserver","ui.setup.document-root.warning":"Para instalar o Contao através do Contao Manager, é necessário fixar a raiz do documento no servidor web.","ui.setup.document-root.description1":"Contao uses a separate folder for public files, application files are installed in its parent folder. Contao cannot be installed if the folder structure is not correct or the folders are not empty.","ui.setup.document-root.description2":"Se não souber como configurar a raiz do seu documento, leia a documentação do Contacto ou contacte o seu fornecedor de alojamento.","ui.setup.document-root.documentation":"Ler a Documentação","ui.setup.document-root.conflictsTitle":"Directório de instalação não vazio","ui.setup.document-root.conflictsDirectory":"The root directory of your future Contao installation is not empty, we have found {count} file(s) that might be overwritten by the installation process. It is recommended to create an empty directory structure for Contao, but you can also remove the following files and check again if you are sure they are unused.","ui.setup.document-root.ignoreConflicts":"Quero instalar o Contao no directório dos não vazios. Compreendo que isto pode substituir quaisquer ficheiros existentes no meu espaço web.","ui.setup.document-root.check":"Check again","ui.setup.document-root.create":"Criar directórios","ui.setup.document-root.change":"Change directories","ui.setup.document-root.formTitle":"Configuração de directório","ui.setup.document-root.formText1":"O Contao Manager pode criar automaticamente uma nova estrutura de directório no servidor.","ui.setup.document-root.formText2":"Será necessário configurar manualmente a raiz do novo documento (por exemplo, através de um painel de administração de alojamento).","ui.setup.document-root.autoconfig":"Compreendo que tenho de alterar a configuração do meu servidor. Não configurar a raiz do documento irá quebrar o Contao Manager e expor os ficheiros de configuração (incluindo detalhes de conta e palavras-passe)!","ui.setup.document-root.directory":"Novo Directório","ui.setup.document-root.currentRoot":"Raiz do documento actual","ui.setup.document-root.newRoot":"Novo Documento Raiz","ui.setup.document-root.finish":"Directórios de configuração","ui.setup.document-root.publicDir":"Utilizar {dir} para ficheiros públicos (para Contao {version})","ui.setup.document-root.directoryInvalid":"Por favor, introduza um nome de directório válido.","ui.setup.document-root.directoryExists":"O directório alvo já existe. Por favor, introduza um nome diferente.","ui.setup.document-root.confirmation":"O Contao Manager criou com sucesso o directório necessário para a sua instalação de Contao. Tem agora de configurar a raiz do documento no seu servidor web. Não volte a carregar esta página até lá.","ui.setup.document-root.reload":"Recarregar Página","ui.setup.document-root.success":"The directory structure on your web server is set up correctly!","ui.setup.document-root.installingProjectDir":"Application files will be installed to {dir}.","ui.setup.document-root.installingPublicDir":"Public files will be installed to {dir}.","ui.setup.document-root.installedProjectDir":"Application files are installed in {dir}.","ui.setup.document-root.installedPublicDir":"Public files are installed in {dir}.","ui.setup.create-project.headline":"Instalação Contao","ui.setup.create-project.description":"Contao development follows the principle of {semver}, a new minor version is released every six months. The currently supported releases are:","ui.setup.create-project.semver":"Semantic Versioning","ui.setup.create-project.latestTitle":"Mais Recente","ui.setup.create-project.ltsTitle":"Suporte a Longo Prazo (LTS)","ui.setup.create-project.latestQ1":"Our latest version, offers the most features with support until February {year}.","ui.setup.create-project.latestQ3":"Our latest version, offers the most features with support until August {year}.","ui.setup.create-project.ltsText":"Our current LTS version, if you focus on stability. Offers long term support until February {year}.","ui.setup.create-project.pltsText":"The previous LTS version, still has long term support until February {year}.","ui.setup.create-project.requiresPHP":"Requires at least PHP {version}, you have PHP {current}.","ui.setup.create-project.requiresDocroot":"The document root must be \\"{folder}\\".","ui.setup.create-project.releaseplan":"Ver o {contaoReleasePlan} para informações detalhadas.","ui.setup.create-project.releaseplanLink":"Plano de lançamento do Contao","ui.setup.create-project.installed":"Contao {version} is successfully installed on the server. Continue to set up your database or launch the Contao Manager to install a different version.","ui.setup.create-project.formTitle":"Select a distribution","ui.setup.create-project.formText":"Please choose which version should be installed.","ui.setup.create-project.version":"Versão","ui.setup.create-project.demo":"Install the Contao demo website","ui.setup.create-project.demoDescription":"The demo website helps you to get familiar with Contao and all of its core features. More themes can be found in the {store}.","ui.setup.create-project.coreOnly":"Instalação Mínima ( Apenas o \\"core\\")","ui.setup.create-project.noUpdate":"Ignorar Instalação (Utilizador avançado!)","ui.setup.create-project.theme":"Contao Theme","ui.setup.create-project.themeInstall":"To install a Contao theme, use the search input or upload a theme file (.cto/.zip) that supports installation through the Contao Manager.","ui.setup.create-project.themeBuy":"Make sure to visit the official {store}.","ui.setup.create-project.themeStore":"Contao Themes Store","ui.setup.create-project.themeUpload":"Upload theme file (.cto/.zip)","ui.setup.create-project.themeInvalid":"The uploaded file is not a Contao theme or does not support the Contao Manager.","ui.setup.create-project.themeWarning":"The Contao Manager cannot tell whether this theme is compatible with your server. Please check with the theme vendor if you have any questions.","ui.setup.create-project.themeTitle":"Review theme details","ui.setup.create-project.themeDetails":"The following dependencies and files will be installed with this theme.","ui.setup.create-project.themeRequire":"{count} Dependencies | {count} Dependencies","ui.setup.create-project.themeFiles":"{count} File | {count} Files","ui.setup.create-project.theme.or":"or search public themes","ui.setup.create-project.theme.search":"Search themes","ui.setup.create-project.theme.more":"More themes","ui.setup.create-project.theme.empty":"No themes matching {query}","ui.setup.create-project.theme.uploaded":"The theme file was uploaded successfully.","ui.setup.create-project.theme.packageName":"Package name","ui.setup.create-project.theme.version":"Versão","ui.setup.create-project.theme.authors":"Author(s)","ui.setup.create-project.install":"Instalar","ui.setup.create-project.cancel":"Cancelar","ui.setup.database-connection.headline":"Database Connection","ui.setup.database-connection.description":"Contao requires a MySQL database (or a compatible fork like MariaDB) to store pages, content, users and other relational data. Connection parameters are stored in the {env} file in the project root of your Contao installation.","ui.setup.database-connection.formTitle":"Connection Parameters","ui.setup.database-connection.formText":"Enter a database URL or fill in the username, password, server and database fields separately.","ui.setup.database-connection.url":"Database URL","ui.setup.database-connection.validUrl":"Database URL is invalid or connection to server failed.","ui.setup.database-connection.or":"or","ui.setup.database-connection.user":"Utilizador","ui.setup.database-connection.password":"Senha","ui.setup.database-connection.server":"Server (:Port)","ui.setup.database-connection.database":"Database Name","ui.setup.database-connection.connected":"Successfully connected to database {database} on {server}.","ui.setup.database-connection.error":"Error connecting to the database.","ui.setup.database-connection.problem":"Contao has detected a problem with your database server.","ui.setup.database-connection.schemaTitle":"Database Schema","ui.setup.database-connection.migration":"There is one pending migration. | There are {count} pending migrations.","ui.setup.database-connection.schema":"There is one pending schema update. | There are {count} pending schema updates.","ui.setup.database-connection.noChanges":"Your database schema is up to date.","ui.setup.database-connection.check":"Check database","ui.setup.database-connection.skip":"Skip","ui.setup.database-connection.save":"Guardar","ui.setup.database-connection.change":"Change credentials","ui.setup.database-connection.restoreTitle":"Database Import","ui.setup.database-connection.restoreText":"The theme you just installed contains a database backup. Restore the database to import theme data or skip this step to start with a blank Contao installation. | The theme you just installed contains multiple database backups. Select a backup file to import theme data or skip this step to start with a blank Contao installation.","ui.setup.database-connection.backup":"Backup current database before import","ui.setup.database-connection.backupWarning":"All data in database will be overwritten on import! Create a backup first if the database is not empty.","ui.setup.database-connection.restore":"Import theme database","ui.setup.database-connection.restoreOption":"Backup from {date} ({size})","ui.setup.database-connection.restored":"Your theme database was successfully imported. Continue to validate your database schema.","ui.setup.backend-user.success":"An admin account for the Contao back end was found in your database. Use the Contao back end to add more users.","ui.setup.backend-user.error":"Unable to retrieve user list. Check the console output for details.","ui.setup.backend-user.headline":"Backend Account","ui.setup.backend-user.description":"To manage your website, you need to have at least one admin account for the Contao back end. Be aware that this account is not related to the Contao Manager.","ui.setup.backend-user.formTitle":"Criar Conta","ui.setup.backend-user.formText":"Please enter the details for the new back end account.","ui.setup.backend-user.username":"Utilizador","ui.setup.backend-user.name":"Name","ui.setup.backend-user.email":"E-mail address","ui.setup.backend-user.emailInvalid":"Please enter a valid e-mail address","ui.setup.backend-user.password":"Senha","ui.setup.backend-user.passwordPlaceholder":"min. 8 caracteres","ui.setup.backend-user.passwordLength":"Por favor utilize o minimo de 8 caracteres.","ui.setup.backend-user.create":"Add account","ui.task.headline":"Tarefa de fundo","ui.task.loading":"Carregamento de detalhes ...","ui.task.created":"Carregamento de detalhes ...","ui.task.active":"Por favor aguarde enquanto o Gestor de Contacto está a executar operações de tarefas em segundo plano.","ui.task.complete":"Todas as operações são concluídas com sucesso. Verifique a saída da consola para mais detalhes.","ui.task.aborting":"Por favor aguarde enquanto as operações de fundo estão a ser canceladas.","ui.task.stopped":"Algumas operações de fundo foram canceladas. Por favor, verifique a saída da consola.","ui.task.error":"Uma operação de fundo parou inesperadamente. Por favor, verifique a saída da consola.","ui.task.failed":"O Gestor de Contao não conseguiu iniciar uma tarefa de fundo!","ui.task.failedDescription1":"Alguma coisa correu mal ao tentar executar operações em segundo plano.","ui.task.failedDescription2":"Se isto acontecer novamente, o seu servidor poderá não ser suportado.","ui.task.reportProblem":"Reportar um Problema","ui.task.sponsor":"Composer Cloud sponsored by {sponsor}","ui.task.buttonAudit":"Actualizar base de dados ","ui.task.buttonClose":"Fechar","ui.task.buttonConfirm":"Confirmar e Fechar","ui.task.buttonCancel":"Cancelar","ui.task.confirmCancel":"Tem a certeza que pretende interromper esta tarefa? Poderá afectar a instalação do Contao negativamente!","ui.task.autoclose":"Detalhes sobre o sucesso da tarefa","ui.console.toggle":"Mostrar/Esconder saída de Consola","ui.console.showLog":"Mostrar registo completo da consola","ui.console.copyLog":"Copiar log para prancheta","ui.migrate.headline":"Database Updates","ui.migrate.migrationsOnly":"(migrations only)","ui.migrate.schemaOnly":"(schema only)","ui.migrate.loading":"Please wait, we are checking your database …","ui.migrate.empty":"No pending migrations or schema updates found. Your database is up to date.","ui.migrate.emptyMigrations":"No pending migrations found. Make sure to also check for schema updates.","ui.migrate.emptySchema":"No pending schema updates found. Make sure to also check for migrations.","ui.migrate.pending":"Your database is not up to date. Please review the console output below and execute the changes.","ui.migrate.previousChanges":"A previous database migration was not confirmed.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.previousComplete":"A previous database migration was not confirmed, please review the console output below.\\nThere are no more pending changes.","ui.migrate.appliedChanges":"Database updates have been applied.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.appliedComplete":"Database updates have been applied.\\nThere are no more pending migrations or schema updates. Your database is up to date.","ui.migrate.problem":"Contao has detected a problem with your database server.\\nPlease review the console output below to find out what needs to be fixed. | Contao has detected problems with your database server.\\nPlease review the console output below to find out what needs to be fixed.","ui.migrate.warning":"Contao has detected a misconfiguration of your database server.\\nWarnings can be skipped temporarily, but should be fixed for optimal performance and data integrity.","ui.migrate.error":"The changes could not be applied. Your database might have been changed, please check again to retry.","ui.migrate.execute":"Execute","ui.migrate.close":"Fechar","ui.migrate.confirm":"Confirmar e Fechar","ui.migrate.cancel":"Cancelar","ui.migrate.continue":"Continuar","ui.migrate.setup":"Configuração","ui.migrate.skip":"Skip","ui.migrate.retry":"Check again","ui.migrate.retryAll":"Check all","ui.migrate.withDeletes":"Execute all database changes including DROP queries.","ui.migrate.migrationTitle":"Database Migrations","ui.migrate.schemaTitle":"Schema Updates","ui.migrate.problemTitle":"Database Problems","ui.migrate.warningTitle":"Database Warnings","ui.migrate.addTable":"Add table {table}","ui.migrate.dropTable":"Drop table {table}","ui.migrate.addField":"Add field {table}.{field}","ui.migrate.changeField":"Change field {table}.{field}","ui.migrate.dropField":"Drop field {table}.{field}","ui.migrate.createIndex":"Create index \\"{name}\\" on {table}","ui.migrate.dropIndex":"Drop index \\"{name}\\" on {table}","ui.widget.mandatory":"Este campo não pode estar vazio.","ui.widget.blankOption":"Por favor seleccione ...","ui.widget.showPassword":"Show password","ui.widget.hidePassword":"Hide password","ui.error.title":"O pedido HTTP de \\"{method} {url}\\" falhou.","ui.error.server500":"Parece que ocorreu um erro inesperado no seu servidor. Por favor verifique os ficheiros de registo do seu servidor web (Apache/Nginx) e os registos do Contao Manager em \\"Contao-manager/logs\\".","ui.error.response":"O servidor devolveu uma resposta com o código de estado {status}.","ui.error.moreLink":"Mais informação ","ui.error.support":"Contao Support","ui.footer.help":"Ajuda","ui.footer.reportProblem":"Reportar um Problema","ui.navigation.discover":"Descubra","ui.navigation.packages":"Pacotes","ui.navigation.tools":"Ferramentas","ui.navigation.installTool":"Ferramenta de Instalação","ui.navigation.backend":"Backend Contao","ui.navigation.debug":"Depuração Contao","ui.navigation.logViewer":"Log Viewer","ui.navigation.phpinfo":"Informação PHP","ui.navigation.phpinfoLoading":"Carregamento de informação PHP","ui.navigation.maintenance":"Manutenção","ui.navigation.rebuildCache":"Reconstruir Cache","ui.navigation.systemCheck":"Verificar Sistema","ui.navigation.advanced":"Avançado","ui.navigation.logout":"Terminar Sessão","ui.maintenance.database.title":"Database Migrations and Backups","ui.maintenance.database.description":"Database migrations ensure consistent data and table schemas.","ui.maintenance.database.migrations":"One pending database migration | {count} pending database migrations","ui.maintenance.database.schemaUpdates":"One pending schema update | {count} pending schema updates","ui.maintenance.database.error":"Database problem found.","ui.maintenance.database.warning":"Database warnings found.","ui.maintenance.database.button":"Check database","ui.maintenance.database.migrationOnly":"Check migrations only","ui.maintenance.database.schemaOnly":"Check schema only","ui.maintenance.database.installTool":"Open Install Tool","ui.maintenance.database.createBackup":"Create Backup","ui.maintenance.database.backupUnsupported":"Database backups are not supported by your Contao version.","ui.maintenance.database.backupList":"You have one database backup, created on {date}. | You have {count} database backups, the latest one was created on {date}.","ui.maintenance.database.backupEmpty":"You currently have no database backups.","ui.maintenance.rebuildCache.title":"Cache de Aplicação","ui.maintenance.rebuildCache.description":"Reconstruir a cache da aplicação é necessário após modificar quaisquer dos ficheiros de configuração.","ui.maintenance.rebuildCache.rebuildProd":"Reconstruir Cache de Produção","ui.maintenance.rebuildCache.rebuildDev":"Reconstruir Cache Desenvolvimento","ui.maintenance.rebuildCache.clearProd":"Limpar Cache Produção","ui.maintenance.rebuildCache.clearDev":"Limpar Cache Desenvolvimento","ui.maintenance.installTool.title":"Ferramenta de Instalação do Contao","ui.maintenance.installTool.description":"A Ferramenta de Instalação do Contao bloqueia automaticamente caso a senha seja introduzida erradamente três vezes.","ui.maintenance.installTool.unlock":"Desbloquear ferramenta de instalação","ui.maintenance.installTool.lock":"Bloquear ferramenta de instalação","ui.maintenance.dumpAutoload.title":"Carregador de Classes Composer","ui.maintenance.dumpAutoload.description":"O Composer autoloader é responsável por carregar classes PHP. O autoloader tem que ser \\"dumped\\" após adicionar namespaces personalizados ao root compser.json .","ui.maintenance.dumpAutoload.button":"Dump Autoloader","ui.maintenance.composerInstall.title":"Dependências Composer ","ui.maintenance.composerInstall.description":"As dependências dos Composer estão localizadas na pasta {vendor} na raiz da sua aplicação. A reinstalação das dependências pode ser necessária após manipulação ou carregamento manual do ficheiro {composerLock}.","ui.maintenance.composerInstall.button":"Executar Instalador","ui.maintenance.composerInstall.update":"Executar Actualizador Composer","ui.maintenance.composerCache.title":"Cache Composer","ui.maintenance.composerCache.description":"O Composer guarda cache de pacotes provenientes de download para melhor performance. Caso tenha problemas com ficheiros não funcionais, apagar a Composer cache para forçar um novo download poderá resolver o problema.","ui.maintenance.composerCache.button":"Apagar Cache","ui.maintenance.maintenanceMode.title":"Modo Manutenção","ui.maintenance.maintenanceMode.description":"Colocar o Contao em modo de manutenção exibirá um modelo \\"503 Serviço Indisponível\\" para o website.","ui.maintenance.maintenanceMode.enable":"Activar","ui.maintenance.maintenanceMode.disable":"Desactivar","ui.maintenance.debugMode.title":"Modo Depuração","ui.maintenance.debugMode.description":"Activar o modo de depuração definindo um utilizador e uma palavra-passe para o ponto de entrada {appDevPhp}.","ui.maintenance.debugMode.descriptionJwt":"Active o modo de depuração ao definir o cookie de depuração para o domínio corrente.","ui.maintenance.debugMode.activate":"Activar","ui.maintenance.debugMode.deactivate":"Desactivar","ui.maintenance.debugMode.credentials":"Credenciais","ui.maintenance.debugMode.user":"Por favor introduza um nome de utilizador para o modo de depuração.","ui.maintenance.debugMode.password":"Por favor introduza uma senha para o modo de depuração.","ui.maintenance.opcodeCache.title":"Cache Opcode","ui.maintenance.opcodeCache.description":"O Opcode efectua cache de ficheiros PHP no processo web para mais rápida execução. Deverá ser apagado dentro de certas circunstâncias caso ficheiros não sejam reconhecidos após edição.","ui.maintenance.opcodeCache.button":"Truncar Cache","ui.maintenance.safeMode":"Nao disponível em Modo Segurança","ui.maintenance.unsupported":"Não suportado pela sua versão Contao","ui.packages.updateButton":"Actualizar Pacotes","ui.packages.searchButton":"Procurar Pacotes","ui.packages.searchPlaceholder":"Procurar Pacotes ...","ui.packages.uploadOverlay":"Arrastar e largar ficheiros para upload","ui.packages.uploadButton":"Carregar Packages","ui.packages.uploadMessage":"Tem um carregamento não confirmado. | Tem {count} uploads não confirmados.","ui.packages.uploadApply":"Confirmar Uploads","ui.packages.uploadReset":"Apagar Uploads","ui.packages.uploadIncomplete":"Este ficheiro não foi uploaded completamente. Por favor remova-o e tente novamente.","ui.packages.uploadDuplicate":"Este ficheiro aparenta ter sido uploaded várias vezes. Por favor remova os duplicados.","ui.packages.uploadInstalled":"Este ficheiro já se encontra instalado. Por favor remova os duplicados.","ui.packages.uploadUnsupported":"Uploads are not supported in your installation. Please make sure that the PHP ZIP extension is installed and update your dependencies.","ui.packages.changesMessage":"Tem uma alteração não confirmada. | Tem {count} alterações não confirmadas.","ui.packages.changesDryrun":"Dry Run","ui.packages.changesApply":"Aplicar Alterações","ui.packages.changesApplyAll":"Actualizar todos os pacotes","ui.packages.changesDryrunAll":"Funcionamento a seco todos os pacotes","ui.packages.changesReset":"Reverter Alterações","ui.packages.changesReview":"Rever Alterações","ui.packagelist.loading":"A Carregar ...","ui.packagelist.uploads":"Uploads","ui.packagelist.added":"Novos pacotes","ui.packagelist.installed":"Pacotes instalados","ui.package.hintRevert":"Reverter Alterações","ui.package.hintNoupdate":"Não actualizar","ui.package.hintConstraint":" Este pacote será instalado com limitações de entrada {constraint} quando aplicar alterações.","ui.package.hintConstraintBest":"Este pacote será instalado com a melhor versão disponível quando aplicar alterações.","ui.package.hintConstraintChange":"As limitações de entrada para este pacote serão alteradas de \\"{from}\\" para \\"{to}\\" quando aplicar alterações.","ui.package.hintConstraintUpdate":"Este pacote será actualizado quando aplicar alterações. ","ui.package.hintAdded":"Este pacote vai ser instalado quando aplicar as alterações.","ui.package.hintRemoved":"Este pacote será removido quando aplicar alterações.","ui.package.requiredTitle":"manualmente adicionado","ui.package.requiredText":"Este pacote é requerido pelo seu composer.json mas não está instalado.","ui.package.removedTitle":"manualmente removido","ui.package.removedText":"Este pacote vai ser removido do seu composer.json.","ui.package.installed":"Actualmente instalado:","ui.package.version":"Versão {version}","ui.package.additionalDownloads":"{count} Descarregar | {count} Descarregar","ui.package.additionalStars":"{count} Estrela | {count} Estrelas","ui.package.editConstraint":"Editar","ui.package.uploadConstraint":"Esta restrição está definida pelo pacote que foi uploaded.","ui.package.updateButton":"Actualizar","ui.package.removeButton":"Remover","ui.package.installButton":"Adicionar Pacote","ui.package.installButtonShort":"Adicionar","ui.package.detailsButton":"Detalhes","ui.package.latestConstraint":"versão mais recente","ui.package.update":"Actualização disponível","ui.package.updateLatest":"versão mais recente","ui.package.updateAvailable":"{version} disponível","ui.package.updateUnknown":"versão desconhecida","ui.package.updateConstraint":"A newer version outside your version constraint is available.","ui.cloudStatus.headline":"Composer Resolver Cloud","ui.cloudStatus.version":"Versão {version}","ui.cloudStatus.waitingTime":"Tempo de Espera","ui.cloudStatus.jobs":"Tarefas Actuais","ui.cloudStatus.workers":"Trabalhadores","ui.cloudStatus.approx":"{minutes} min","ui.cloudStatus.none":"nenhum","ui.cloudStatus.short":"ca. {minutes} min","ui.cloudStatus.long":"ca. {minutes} min {seconds} seg","ui.cloudStatus.error":"Incapaz de obter o estatuto de Composer Resolver Cloud. Pode ser para questões de manutenção ou de experiência.","ui.cloudStatus.button":"Estado das nuvens","ui.cloudStatus.refresh":"Actualizar o estado das nuvens","ui.log-viewer.loading":"A Carregar ...","ui.log-viewer.empty":"There are no log files on your server.","ui.log-viewer.reload":"Reload","ui.log-viewer.file":"Log file","ui.log-viewer.channel":"Channel","ui.log-viewer.channelTitle":"The channel this message was logged to.","ui.log-viewer.level":"Level","ui.log-viewer.levelTitle":"Severity of the log message.","ui.log-viewer.timeHeader":"Time","ui.log-viewer.messageHeader":"Message","ui.log-viewer.showContext":"Show Context","ui.log-viewer.hideContext":"Hide Context","ui.log-viewer.showExtra":"Show Extra","ui.log-viewer.hideExtra":"Hide Extra","ui.log-viewer.more":"Load more …","ui.log-viewer.download":"Download","ui.log-viewer.downloadTitle":"Download file \\"{file}\\"","ui.log-viewer.prodEnvironment":"Production Environment","ui.log-viewer.devEnvironment":"Development Environment (Debug Mode)"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[477],{6477:function(e){e.exports=JSON.parse('{"ui.app.httpsHeadline":"!! Unsichere Verbindung !!","ui.app.httpsDescription":"Ohne HTTPS werden deine vertraulichen Daten unverschlüsselt übertragen.","ui.app.httpsLink":"Weitere Informationen","ui.app.httpsHref":"https://https.cio.gov/everything/","ui.app.safeModeHeadline":"!! Abgesicherter Modus !!","ui.app.safeModeDescription":"Einige Funktionen des Contao Managers sind nicht verfügbar.","ui.app.safeModeExit":"Beenden","ui.app.loading":"Contao Manager wird geladen …","ui.app.apiError":"Unerwarteter API-Status","ui.app.configSecurity1":"SICHERHEITSWARNUNG !!! Das Konfigurationsverzeichnis ist ungeschützt","ui.app.configSecurity2":"Der Contao Manager hat erkannt, dass seine Konfigurationsdateien öffentlich erreichbar sind. Du musst diese Dateien schützen, bevor der Manager verwendet werden kann, da ein Angreifer sonst auf sensible Daten zugreifen könnte.\\n\\nUm dieses Problem zu beheben, schütze das Verzeichnis \\"contao-manager\\" auf deinem Server. Bei Fragen lies das Handbuch deines Webservers oder wende dich an deinen Hosting-Anbieter.","ui.account.welcome":"Willkommen","ui.account.intro1":"Willkommen zum Contao Manager, einem universellen Werkzeug, um das Contao Open Source CMS zu installieren, zu konfigurieren und zu warten. Wenn du den Manager zum ersten Mal einsetzen, {readTheManualToGetStarted}.","ui.account.introGetStarted":"{readTheManual} bevor du beginnst","ui.account.introManual":"lies die Dokumentation","ui.account.intro2":"Sollten Probleme auftreten, dann prüfe {ourGithubIssues}. Falls für dein Anliegen noch kein Ticket existiert, kannst du gern ein Neues erstellen.","ui.account.introIssues":"die Tickets auf GitHub","ui.account.headline":"Benutzerkonto","ui.account.description":"Erstelle ein Benutzerkonto, um deine Installation zu verwalten. Beachte dass dieses Konto in keinem Zusammenhang mit dem Contao Backend oder Frontend steht.","ui.account.username":"Benutzername","ui.account.password":"Passwort","ui.account.passwordPlaceholder":"min. 8 Zeichen","ui.account.passwordLength":"Bitte gib mindestens 8 Zeichen ein.","ui.account.submit":"Benutzerkonto erstellen","ui.account.contribute1":"Contao und der Contao Manager werden durch die Contao Association gefördert.","ui.account.contribute2":"{donate} und leiste deinen Beitrag zu Open Source!","ui.account.contributeDonate":"Mach eine Spende","ui.login.headline":"Anmelden","ui.login.description":"Melde dich an, um deine Installation zu verwalten.","ui.login.username":"Benutzername","ui.login.password":"Passwort","ui.login.forgotPassword":"Passwort vergessen?","ui.login.button":"Anmelden","ui.login.locked":"Der Zugriff wurde verweigert, da der Contao Manager gesperrt ist. Zum entsperren lösche die Datei {lockFile} in deinem Contao Hauptverzeichnis.","ui.logout.headline":"Session-Timeout","ui.logout.warning":"Du warst mehr als 25 Minuten inaktiv. Aus Sicherheitsgründen wird deine Sitzung in Kürze beendet.","ui.logout.expired":"Deine Sitzung wurde automatisch beendet, da du mehr als 30 Minuten inaktiv warst.","ui.logout.renew":"Angemeldet bleiben","ui.logout.logout":"Abmelden","ui.logout.login":"Zurück zum Login","ui.oauth.error":"Ungültiger OAuth-Versuch. Bitte überprüfe die Request-Parameter.","ui.oauth.https":"Die Rückleitungs-URL MUSS eine sichere Verbindung (https:) nutzen, um die Übertragung der Anmeldedaten im Klartext zu verhindern.","ui.oauth.headline":"Remote-Anmeldung","ui.oauth.description":"Die folgende Anwendung oder der folgende Dienst fordert den Fernzugriff auf deine Contao Manager-Instanz an.","ui.oauth.domain":"Bevor du diesen Zugriff erlaubst, stelle sicher dass du die URL kennst und ihr vertraust!","ui.oauth.allow":"Zugriff erlauben","ui.oauth.deny":"Zugriff verweigern","ui.boot.headline":"Systemprüfung","ui.boot.description":"Bitte warten, dein Server wird überprüft …","ui.boot.issue1":"Installationsprobleme erkannt","ui.boot.issue2":"Du musst die Installationsprobleme beheben, bevor der Contao Manager verwendet werden kann.","ui.boot.run":"Contao Manager starten","ui.boot.safeMode":"Abgesicherten Modus starten","ui.recovery.headline":"Systemwiederherstellung","ui.recovery.description":"Es wurden Dateien erkannt, die nach einer Contao-Installation aussehen, aber die Kommandozeile scheint nicht korrekt zu funktionieren.","ui.recovery.console":"Konsolenausgabe","ui.recovery.repairOptions":"Wähle eine Option, um das System zu reparieren.","ui.recovery.repairHeadline":"Automatische Reparatur","ui.recovery.repairDescription":"Versucht eine automatische Systemreparatur, indem der Cache neu aufgebaut und die Composer-Pakete neu installiert werden.","ui.recovery.repairWarning":"Manuelle Änderungen an den vendor-Dateien können dabei verloren gehen!","ui.recovery.repairFailed":"Die automatische Reparatur ist fehlgeschlagen. Versuche den abgesicherten Modus, um die Installation von Hand zu reparieren.","ui.recovery.repairButton":"Reparatur ausführen","ui.recovery.safeModeHeadline":"Abgesicherter Modus","ui.recovery.safeModeDescription":"Der abgesicherte Modus erlaubt die Paketverwaltung und gewisse Wartungsaufgaben, aber Funktionen, welche ein funktionierendes Contao benötigen, sind nicht verfügbar.","ui.recovery.safeModeButton":"Abgesicherten Modus starten","ui.server.pending":"Warten …","ui.server.running":"Analysiere …","ui.server.error":"Die Prüfung ist aufgrund einer unerwarteten Server-Antwort fehlgeschlagen.","ui.server.details":"Details","ui.server.prerequisite":"Prüfung aufgrund fehlender Abhängigkeiten abgebrochen.","ui.server.selfUpdate.title":"Updates für den Contao Manager","ui.server.selfUpdate.update":"Die neue Version {latest} des Contao Managers ist verfügbar.","ui.server.selfUpdate.manualUpdate":"Eine neue Version {latest} des Contao Manager ist verfügbar. Dein Server unterstützt keine automatischen Updates, bitte lade die neue Version von {download} herunter.","ui.server.selfUpdate.latest":"Du verwendest die aktuelle Version {current}.","ui.server.selfUpdate.dev":"Entwicklungs-Versionen können nicht automatisch aktualisiert werden.","ui.server.selfUpdate.unsupported":"Eine neue Version ist verfügbar, aber diese funktioniert nicht mit deiner PHP-Version.","ui.server.selfUpdate.button":"Aktualisieren","ui.server.selfUpdate.continue":"Weiter","ui.server.config.title":"Serverkonfiguration","ui.server.config.setup":"Konfigurieren","ui.server.config.change":"Ändern","ui.server.config.save":"Speichern","ui.server.config.cancel":"Abbrechen","ui.server.config.customOption":"Andere …","ui.server.config.description":"Der Contao Manager benötigt den Pfad zum PHP-Binary und weitere Server-Informationen, um Hintergrund-Prozesse korrekt auszuführen.","ui.server.config.formTitle":"Serverkonfiguration","ui.server.config.formText":"Bitte gib den Pfad zu deinem PHP-Binary ein. Das Binary muss dieselbe PHP-Version sein wie der Webprozess.","ui.server.config.cloudTitle":"Composer Resolver Cloud","ui.server.config.cloudText":"Die Composer Resolver Cloud erlaubt die Installation von Composer-Abhängigkeiten, selbst wenn der lokale Server nicht über genug Arbeitsspeicher verfügt. Beachte dass deine Paketinformationen an einen Cloud-Dienst der Contao Association übermittelt werden.","ui.server.config.cloud":"Die Composer Resolver Cloud verwenden","ui.server.config.cli":"PHP-Binary","ui.server.config.stateErrorCli":"Es wurde kein valides PHP-Programm auf dem Server gefunden.","ui.server.config.stateErrorCloud":"Die Composer Resolver Cloud wird nicht unterstützt.","ui.server.config.stateSuccess":"PHP-Binary in {php_cli}.","ui.server.php_web.title":"PHP-Webprozess","ui.server.php_web.below7":"PHP Version {version} gefunden. Bitte wechsle baldmöglichst auf PHP 7!","ui.server.php_web.success":"PHP Version {version}, keine bekannten Probleme gefunden.","ui.server.php_cli.title":"PHP Kommandozeilen-Programm","ui.server.php_cli.success":"PHP Version {version}, keine bekannten Probleme gefunden.","ui.server.composer.title":"Composer-Umgebung","ui.server.composer.success":"Keine bekannten Probleme gefunden.","ui.server.composer.install":"Composer-Abhängigkeiten sind nicht installiert.","ui.server.composer.button":"Installieren","ui.server.contao.title":"Contao-Installation","ui.server.contao.setup":"Einrichtung","ui.server.contao.check":"Datenbank überprüfen","ui.server.contao.empty":"Es wurde keine Contao-Installation gefunden.","ui.server.contao.old":"Contao {version} ist nicht kompatibel mit dem Contao Manager; bitte aktualisiere deine Installation manuell.","ui.server.contao.found":"Contao {version} (API-Version {api}) gefunden.","ui.server.contao.connectionError":"Verbindung zum Datenbank-Server fehlgeschlagen.","ui.server.contao.connectionProblem":"Datenbank-Problem gefunden.","ui.server.contao.missingUser":"Administratorkonto nicht gefunden.","ui.setup.continue":"Weiter","ui.setup.manager":"Contao Manager starten","ui.setup.cancel":"Abbrechen","ui.setup.welcome":"Willkommen","ui.setup.welcome1":"Dieser Assistent führt dich durch die Installation und Einrichtung deines Contao Open Source CMS.","ui.setup.welcome2":"Solltest du Fragen haben, findest du die Dokumentation, das Forum, den Slack Kanal und weiteres auf der {support}-Seite.","ui.setup.support":"Contao-Support","ui.setup.start":"Los geht\'s","ui.setup.complete":"Geschafft!","ui.setup.complete1":"Contao {version} wurde erfolgreich installiert.","ui.setup.complete2":"Um die Installation fertig zu stellen, öffne bitte das Install-Tool um die Datenbank zu verbinden und ein Backend-Konto hinzuzufügen.","ui.setup.complete3":"Du kannst nun deine Webseite im Contao Backend einrichten. Willst du Erweiterungen installieren fahre mit dem Contao Manager fort.","ui.setup.installTool":"Install-Tool öffnen","ui.setup.login":"Contao Login","ui.setup.funding":"Ohne seine Community wäre Contao nicht das, was es heute ist. Wenn du professionell Webseiten mit Contao erstellst, sind wir auf deine Unterstützung angewiesen. Mit einer Investition in das Projekt sicherst du die Entwicklung für die Zukunft, damit dein Unternehmen weiterhin von Contao profitieren kann.","ui.setup.fundingLink":"Weitere Informationen","ui.setup.document-root.headline":"Webserver-Einrichtung","ui.setup.document-root.warning":"Um Contao über den Contao Manager zu installieren, muss das Wurzelverzeichnis des Webservers angepasst werden.","ui.setup.document-root.description1":"Contao verwendet einen Unterordner für öffentliche Dateien, die Systemdaten werden im übergeordneten Ordner installiert. Contao kann nicht installiert werden, wenn die Struktur nicht stimmt oder die Ordner nicht leer sind.","ui.setup.document-root.description2":"Falls du nicht weisst, wie dein Wurzelverzeichnis konfiguriert werden kann, lies die Contao Dokumentation oder kontaktiere deinen Hosting-Anbieter.","ui.setup.document-root.documentation":"Dokumentation lesen","ui.setup.document-root.conflictsTitle":"Installationsverzeichnis nicht leer","ui.setup.document-root.conflictsDirectory":"Das Wurzelverzeichnis deiner zukünftigen Contao-Installation ist nicht leer, es wurden {count} Datei(en) gefunden, die bei der Installation überschrieben werden könnten. Es wird empfohlen Contao in ein leeres Verzeichnis zu installieren, aber du kannst auch die Dateien löschen und erneut prüfen lassen wenn du sicher bist dass diese nicht benötigt werden.","ui.setup.document-root.ignoreConflicts":"Ich will Contao in ein nicht-leeres Verzeichnis installieren. Ich verstehe dass dadurch bestehende Dateien auf meinem Server möglicherweise überschrieben werden.","ui.setup.document-root.check":"Erneut prüfen","ui.setup.document-root.create":"Verzeichnisse erstellen","ui.setup.document-root.change":"Verzeichnisse ändern","ui.setup.document-root.formTitle":"Verzeichnisstruktur einrichten","ui.setup.document-root.formText1":"Der Contao Manager kann automatisch eine neue Verzeichnisstruktur auf dem Server einrichten.","ui.setup.document-root.formText2":"Du musst das Wurzelverzeichnis danach manuell umkonfigurieren (z. B. über die Hosting-Administration).","ui.setup.document-root.autoconfig":"Ich habe verstanden dass meine Server-Konfiguration geändert werden muss. Wenn ich dies nicht tue, funktioniert der Contao Manager nicht mehr und meine Konfigurationsdateien (inklusive Benutzer & Passwörter) werden öffentlich erreichbar!","ui.setup.document-root.directory":"Neuer Ordner","ui.setup.document-root.currentRoot":"Aktuelles Wurzelverzeichnis","ui.setup.document-root.newRoot":"Neues Wurzelverzeichnis","ui.setup.document-root.finish":"Ordner erstellen","ui.setup.document-root.publicDir":"{dir} für öffentliche Dateien nutzen (Contao {version})","ui.setup.document-root.directoryInvalid":"Bitte gib einen gültigen Ordnernamen ein.","ui.setup.document-root.directoryExists":"Das Zielverzeichnis ist bereits vorhanden. Bitte gibt einen anderen Namen ein.","ui.setup.document-root.confirmation":"Der Contao Manager hat die benötigten Verzeichnisse erfolgreich angelegt. Nun musst du dein Wurzelverzeichnis anpassen. Lade die Seite nicht neu bis dies erledigt ist.","ui.setup.document-root.reload":"Neu laden","ui.setup.document-root.success":"Die Verzeichnisstruktur auf deinem Server ist korrekt eingerichtet!","ui.setup.document-root.installingProjectDir":"Systemdateien werden nach {dir} installiert.","ui.setup.document-root.installingPublicDir":"Öffentliche Dateien werden nach {dir} installiert.","ui.setup.document-root.installedProjectDir":"Systemdateien sind in {dir} installiert.","ui.setup.document-root.installedPublicDir":"Öffentliche Dateien sind in {dir} installiert.","ui.setup.create-project.headline":"Contao-Installation","ui.setup.create-project.description":"Die Contao-Entwicklung folgt dem Prinzip von {semver}, eine neue Minor-Version wird alles sechs Monate veröffentlicht. Die aktuell unterstützten Versionen sind:","ui.setup.create-project.semver":"Semantic Versioning","ui.setup.create-project.latestTitle":"Neuste","ui.setup.create-project.ltsTitle":"Langzeit-Support","ui.setup.create-project.latestQ1":"Unsere neuste Version mit den meisten Funktionen, wird bis Februar {year} unterstützt.","ui.setup.create-project.latestQ3":"Unsere neuste Version mit den meisten Funktionen, wird bis August {year} unterstützt.","ui.setup.create-project.ltsText":"Unsere aktuelle LTS-Version mit Fokus auf Stabilität. Bietet einen verlängerten Supportzeitraum bis Februar {year}.","ui.setup.create-project.pltsText":"Die vorherige LTS-Version, hat einen verlängerten Supportzeitraum bis Februar {year}","ui.setup.create-project.requiresPHP":"Benötigt mindestens PHP {version}, du hast PHP {current}.","ui.setup.create-project.requiresDocroot":"Das Wurzelverzeichnis muss \\"{folder}\\" sein.","ui.setup.create-project.releaseplan":"Weitere Details findest du im {contaoReleasePlan}.","ui.setup.create-project.releaseplanLink":"Contao Release-Plan","ui.setup.create-project.installed":"Contao {version} wurde erfolgreich auf dem Server installiert. Fahre weiter um die Datenbank einzurichten, oder gehe zum Contao Manager um eine andere Version zu installieren.","ui.setup.create-project.formTitle":"Wähle eine Distribution","ui.setup.create-project.formText":"Bitte wähle die zu installierende Version.","ui.setup.create-project.version":"Version","ui.setup.create-project.demo":"Beispiel-Webseite installieren","ui.setup.create-project.demoDescription":"Die Beispiel-Webseite von Contao hilft dir dabei, dich mit dem System und seinen Hauptfunktionen vertraut zu machen. Weitere Themes findest du im {store}.","ui.setup.create-project.coreOnly":"Minimale Installation (nur Core)","ui.setup.create-project.noUpdate":"Installation überspringen (Expertenmodus!)","ui.setup.create-project.theme":"Theme für Contao","ui.setup.create-project.themeInstall":"Um ein Theme für Contao zu installieren, benutze die Sucheingabe oder lade eine Theme-Datei (.cto/.zip) hoch, die den Contao Manager unterstützt.","ui.setup.create-project.themeBuy":"Besuche auch den {store}.","ui.setup.create-project.themeStore":"Contao Themes-Store","ui.setup.create-project.themeUpload":"Theme-Datei (.cto/.zip) hochladen","ui.setup.create-project.themeInvalid":"Die hochgeladene Datei ist kein Theme für Contao oder unterstützt die Installation im Contao Manager nicht.","ui.setup.create-project.themeWarning":"Der Contao Manager kann nicht beurteilen ob dieses Theme mit deinem Server kompatibel ist. Wende dich bei Fragen bitte direkt an den Theme-Hersteller.","ui.setup.create-project.themeTitle":"Theme-Details überprüfen","ui.setup.create-project.themeDetails":"Die nachfolgenden Abhängigkeiten und Dateien werden mit diesem Theme installiert.","ui.setup.create-project.themeRequire":"{count} Abhängigkeit | {count} Abhängigkeiten","ui.setup.create-project.themeFiles":"{count} Datei | {count} Dateien","ui.setup.create-project.theme.or":"oder suche öffentliche Themes","ui.setup.create-project.theme.search":"Themes durchsuchen","ui.setup.create-project.theme.more":"Weitere Themes","ui.setup.create-project.theme.empty":"Keine Theme für {query} gefunden","ui.setup.create-project.theme.uploaded":"Die Theme-Datei wurde erfolgreich hochgeladen.","ui.setup.create-project.theme.packageName":"Paketname","ui.setup.create-project.theme.version":"Version","ui.setup.create-project.theme.authors":"Autor(en)","ui.setup.create-project.install":"Installieren","ui.setup.create-project.cancel":"Abbrechen","ui.setup.database-connection.headline":"Datenbank-Verbindung","ui.setup.database-connection.description":"Contao benötigt eine MySQL Datenbank (oder ein kompatibler Fork wie MariaDB) um Seiten, Inhalte und andere Daten zu speichern. Die Verbindungsdaten werden in der Datei {env} im Systemverzeichnis von Contao gespeichert werden.","ui.setup.database-connection.formTitle":"Verbindungs-Parameter","ui.setup.database-connection.formText":"Gib eine Datenbank-URL ein, oder fülle die Felder für Benutzername, Passwort, Server und Datenbank separat aus.","ui.setup.database-connection.url":"Datenbank-URL","ui.setup.database-connection.validUrl":"Datenbank-URL ungültig oder Verbindung zum Server fehlgeschlagen.","ui.setup.database-connection.or":"oder","ui.setup.database-connection.user":"Benutzername","ui.setup.database-connection.password":"Passwort","ui.setup.database-connection.server":"Server (:Port)","ui.setup.database-connection.database":"Datenbankname","ui.setup.database-connection.connected":"Erfolgreich verbunden mit Datenbank {database} auf {server}.","ui.setup.database-connection.error":"Fehler beim verbinden mit der Datenbank.","ui.setup.database-connection.problem":"Contao hat ein Problem mit dem Datenbank-Server gefunden.","ui.setup.database-connection.schemaTitle":"Datenbank-Schema","ui.setup.database-connection.migration":"Es gibt eine ausstehende Migration. | Es gibt {count} ausstehende Migrationen.","ui.setup.database-connection.schema":"Es gibt eine ausstehende Schema-Änderung. | Es gibt {count} ausstehende Schema-Änderungen.","ui.setup.database-connection.noChanges":"Dein Datenbank-Schema ist aktuell.","ui.setup.database-connection.check":"Datenbank überprüfen","ui.setup.database-connection.skip":"Überspringen","ui.setup.database-connection.save":"Speichern","ui.setup.database-connection.change":"Zugangsdaten ändern","ui.setup.database-connection.restoreTitle":"Datenbank-Import","ui.setup.database-connection.restoreText":"Das soeben installierte Theme enthält ein Datenbank-Backup. Du kannst diese Theme-Daten in die Datenbank importieren oder diesen Schritt überspringen und mit einem leeren Contao beginnen. | Das soeben installierte Theme enthält mehrere Datenbank-Backups. Wähle eine Backup-Datei für den Import oder überspringe diesen Schritt, um mit einem leeren Contao zu beginnen.","ui.setup.database-connection.backup":"Datenbank vor dem Import sichern","ui.setup.database-connection.backupWarning":"Alle Daten in der Datenbank werden beim Import überschrieben! Erstelle zuerst ein Backup, falls die Datenbank nicht leer ist.","ui.setup.database-connection.restore":"Theme importieren","ui.setup.database-connection.restoreOption":"Backup vom {date} ({size})","ui.setup.database-connection.restored":"Die Theme-Datenbank wurde erfolgreich importiert. Fahre fort um das Datenbank-Schema zu überprüfen.","ui.setup.backend-user.success":"Es ist bereits ein Benutzerkonto in der Datenbank vorhanden. Weitere Konten können über das Backend hinzugefügt werden.","ui.setup.backend-user.error":"Die Benutzerliste konnte nicht gelesen werden. Weitere Details findest du in der Konsolen-Ausgabe.","ui.setup.backend-user.headline":"Backend-Konto","ui.setup.backend-user.description":"Um deine Webseite zu verwalten benötigst du mindestens ein Administratorkonto für das Contao Backend. Beachte dass dieses Konto nicht mit dem Contao Manager zusammenhängt.","ui.setup.backend-user.formTitle":"Benutzerkonto erstellen","ui.setup.backend-user.formText":"Bitte gib die Details für das neue Backend-Konto ein.","ui.setup.backend-user.username":"Benutzername","ui.setup.backend-user.name":"Name","ui.setup.backend-user.email":"E-Mail Adresse","ui.setup.backend-user.emailInvalid":"Bitte gib eine gültige E-Mail Adresse ein","ui.setup.backend-user.password":"Passwort","ui.setup.backend-user.passwordPlaceholder":"min. 8 Zeichen","ui.setup.backend-user.passwordLength":"Bitte gib mindestens 8 Zeichen ein.","ui.setup.backend-user.create":"Konto hinzufügen","ui.task.headline":"Hintergrund-Prozess","ui.task.loading":"Lade Details …","ui.task.created":"Lade Details …","ui.task.active":"Bitte warte, während der Contao Manager die nötigen Operationen im Hintergrund ausführt.","ui.task.complete":"Alle Operationen erfolgreich abgeschlossen. Weitere Details findest du in der Konsolen-Ausgabe.","ui.task.aborting":"Bitte warte während der Hintergrund-Prozess abgebrochen wird.","ui.task.stopped":"Einige Operationen wurden abgebrochen. Bitte prüfe die Konsolen-Ausgabe.","ui.task.error":"Ein Hintergrund-Prozess wurde unerwartet beendet. Bitte prüfe die Konsolen-Ausgabe.","ui.task.failed":"Der Contao Manager konnte den Hintergrund-Prozess nicht starten.","ui.task.failedDescription1":"Die Ausführung der Aufgabe ist fehlgeschlagen.","ui.task.failedDescription2":"Sollte dies wiederholt geschehen, wird dein Server möglicherweise nicht unterstützt.","ui.task.reportProblem":"Probleme melden","ui.task.sponsor":"Composer Cloud gesponsert von {sponsor}","ui.task.buttonAudit":"Datenbank aktualisieren","ui.task.buttonClose":"Schließen","ui.task.buttonConfirm":"Bestätigen & Schließen","ui.task.buttonCancel":"Abbrechen","ui.task.confirmCancel":"Möchtest du diesen Prozess wirklich abbrechen? Deine Contao-Installation könnte in einem defekten Zustand zurückbleiben!","ui.task.autoclose":"Fenster bei Erfolg schließen","ui.console.toggle":"Konsolenausgabe anzeigen/verstecken","ui.console.showLog":"Vollständige Konsole anzeigen","ui.console.copyLog":"Konsole in Zwischenablage kopieren","ui.migrate.headline":"Datenbank-Änderungen","ui.migrate.migrationsOnly":"(nur Migrationen)","ui.migrate.schemaOnly":"(nur Schema)","ui.migrate.loading":"Bitte warten, die Datenbank wird überprüft …","ui.migrate.empty":"Keine ausstehenden Migrationen oder Schema-Änderungen gefunden. Deine Datenbank ist auf dem aktuellsten Stand.","ui.migrate.emptyMigrations":"Keine ausstehenden Migrationen gefunden. Bitte prüfe auch die Schema-Änderungen.","ui.migrate.emptySchema":"Keine ausstehenden Schema-Änderungen gefunden. Bitte prüfe auch die Migrationen.","ui.migrate.pending":"Die Datenbank ist nicht aktuell. Bitte prüfe die untenstehende Konsolenausgabe und führe die Änderungen aus.","ui.migrate.previousChanges":"Eine vorherige Datenbankmigration wurde nicht bestätigt.\\nBitte prüfen die untenstehende Konsolenausgabe und klicke weiter, um die nächsten Änderungen zu sehen.","ui.migrate.previousComplete":"Eine vorherige Datenbankmigration wurde nicht bestätigt, bitte prüfe die untenstehende Konsolenausgabe.\\nEs gibt keine weiteren ausstehenden Änderungen.","ui.migrate.appliedChanges":"Die Datenbankänderungen wurden übernommen.\\nBitte prüfe die untenstehende Konsolenausgabe, dann klicke weiter, um die nächsten Änderungen zu sehen.","ui.migrate.appliedComplete":"Die Datenbankänderungen wurden übernommen.\\nEs gibt keine weiteren Migrationen oder Schema-Änderungen. Deine Datenbank ist auf dem aktuellen Stand.","ui.migrate.problem":"Contao hat ein Problem mit dem Datenbank-Server gefunden.\\nBitte prüfe die Konsolen-Ausgabe unten um zu sehen was angepasst werden muss. | Contao hat Probleme mit dem Datenbank-Server gefunden.\\nBitte prüfe die Konsolen-Ausgabe unten um zu sehen was angepasst werden muss.","ui.migrate.warning":"Contao hat eine Fehlkonfiguration des Datenbank-Servers gefunden.\\nDie unten stehenden Warnungen sollten für optimalen Betrieb und Datensicherheit behoben werden.","ui.migrate.error":"Die Änderungen konnten nicht angewendet werden. Möglicherweise wurde die Datenbank geändert, bitte prüfe nochmals und versuche es erneut.","ui.migrate.execute":"Ausführen","ui.migrate.close":"Schließen","ui.migrate.confirm":"Bestätigen & Schließen","ui.migrate.cancel":"Abbrechen","ui.migrate.continue":"Weiter","ui.migrate.setup":"Einrichtung","ui.migrate.skip":"Überspringen","ui.migrate.retry":"Erneut prüfen","ui.migrate.retryAll":"Alles überprüfen","ui.migrate.withDeletes":"Alle Änderungen inklusive Löschungen anwenden.","ui.migrate.migrationTitle":"Datenbank-Migrationen","ui.migrate.schemaTitle":"Schema-Änderungen","ui.migrate.problemTitle":"Datenbank-Probleme","ui.migrate.warningTitle":"Datenbank-Warnungen","ui.migrate.addTable":"Tabelle {table} hinzufügen","ui.migrate.dropTable":"Tabelle {table} löschen","ui.migrate.addField":"Feld {table}.{field} hinzufügen","ui.migrate.changeField":"Feld {table}.{field} ändern","ui.migrate.dropField":"Feld {table}.{field} löschen","ui.migrate.createIndex":"Index \\"{name}\\" zu {table} hinzufügen","ui.migrate.dropIndex":"Index \\"{name}\\" aus {table} löschen","ui.widget.mandatory":"Dieses Feld darf nicht leer sein.","ui.widget.blankOption":"Bitte wählen …","ui.widget.showPassword":"Passwort anzeigen","ui.widget.hidePassword":"Passwort ausblenden","ui.error.title":"HTTP-Anfrage für \\"{method} {url}\\" fehlgeschlagen.","ui.error.server500":"Es scheint ein unbekannter Fehler aufgetreten zu sein. Prüfe die Log-Dateien deines Webservers (Apache/Nginx) und des Contao Managers im Ordner \\"contao-manager/logs\\".","ui.error.response":"Der Server hat eine Antwort mit Status-Code {status} gesendet.","ui.error.moreLink":"Weitere Informationen","ui.error.support":"Contao Support","ui.footer.help":"Hilfe","ui.footer.reportProblem":"Probleme melden","ui.navigation.discover":"Entdecken","ui.navigation.packages":"Pakete","ui.navigation.tools":"Tools","ui.navigation.installTool":"Installtool","ui.navigation.backend":"Contao-Backend","ui.navigation.debug":"Contao-Debug-Modus","ui.navigation.logViewer":"System-Log","ui.navigation.phpinfo":"PHP-Informationen","ui.navigation.phpinfoLoading":"Lade PHP-Informationen…","ui.navigation.maintenance":"Systemwartung","ui.navigation.rebuildCache":"Cache erneuern","ui.navigation.systemCheck":"Systemprüfung","ui.navigation.advanced":"Fortgeschritten","ui.navigation.logout":"Abmelden","ui.maintenance.database.title":"Datenbank-Migrationen und -Backups","ui.maintenance.database.description":"Datenbankmigrationen stellen konsistente Daten und Tabellenschemas sicher.","ui.maintenance.database.migrations":"Eine ausstehende Datenbank-Migration | {count} ausstehende Datenbank-Migrationen","ui.maintenance.database.schemaUpdates":"Eine ausstehende Schema-Änderung | {count} ausstehende Schema-Änderungen","ui.maintenance.database.error":"Datenbank-Problem gefunden.","ui.maintenance.database.warning":"Datenbank-Warnung gefunden.","ui.maintenance.database.button":"Datenbank überprüfen","ui.maintenance.database.migrationOnly":"Nur Migrationen prüfen","ui.maintenance.database.schemaOnly":"Nur Schema prüfen","ui.maintenance.database.installTool":"Install-Tool öffnen","ui.maintenance.database.createBackup":"Backup erstellen","ui.maintenance.database.backupUnsupported":"Datenbank-Backups werden von deiner Contao-Version nicht unterstützt.","ui.maintenance.database.backupList":"Du hast ein Datenbank-Backup, erstellt am {date}. | Du hast {count} Datenbank-Backups, das neuste wurde am {date} erstellt.","ui.maintenance.database.backupEmpty":"Du hast noch keine Datenbank-Backups.","ui.maintenance.rebuildCache.title":"Anwendungs-Cache","ui.maintenance.rebuildCache.description":"Nach dem Ändern einer der Konfigurationsdateien muss der Anwendungs-Cache neu aufgebaut werden.","ui.maintenance.rebuildCache.rebuildProd":"Prod.-Cache erneuern","ui.maintenance.rebuildCache.rebuildDev":"Dev.-Cache erneuern","ui.maintenance.rebuildCache.clearProd":"Prod.-Cache leeren","ui.maintenance.rebuildCache.clearDev":"Dev.-Cache leeren","ui.maintenance.installTool.title":"Contao-Installtool","ui.maintenance.installTool.description":"Das Contao-Installtool wird automatisch gesperrt, wenn das Passwort drei Mal falsch eingegeben wird.","ui.maintenance.installTool.unlock":"Installtool entsperren","ui.maintenance.installTool.lock":"Installtool sperren","ui.maintenance.dumpAutoload.title":"Composer Class Loader","ui.maintenance.dumpAutoload.description":"Der Composer-Autoloader ist für das Laden der PHP-Klassen verantwortlich. Der Autoloader muss nach dem Hinzufügen von eigenen Namespaces in die composer.json neu geschrieben werden.","ui.maintenance.dumpAutoload.button":"Datei aktualisieren","ui.maintenance.composerInstall.title":"Composer-Abhängigkeiten","ui.maintenance.composerInstall.description":"Composer-Abhängigkeiten befinden sich im Ordner {vendor} im Hauptverzeichnis deiner Anwendung. Eine Neuinstallation der Abhängigkeiten kann nach der Bearbeitung oder dem manuellen Hochladen der Datei {composerLock} notwendig sein.","ui.maintenance.composerInstall.button":"Installer ausführen","ui.maintenance.composerInstall.update":"Composer Update ausführen","ui.maintenance.composerCache.title":"Composer-Cache","ui.maintenance.composerCache.description":"Composer speichert heruntergeladene Pakete im Cache, um die Performance zu verbessern. Wenn Du z. B. Probleme mit korrupten Dateien hast, kannst du den Composer-Cache leeren, um einen neuen Download zu erzwingen.","ui.maintenance.composerCache.button":"Cache leeren","ui.maintenance.maintenanceMode.title":"Wartungsmodus","ui.maintenance.maintenanceMode.description":"Im Wartungsmodus zeigt Contao das \\"503 Dienst nicht verfügbar\\"-Template anstelle der Webseite an.","ui.maintenance.maintenanceMode.enable":"Aktivieren","ui.maintenance.maintenanceMode.disable":"Deaktivieren","ui.maintenance.debugMode.title":"Debug-Modus","ui.maintenance.debugMode.description":"Aktiviere den Debug-Modus, indem du einen Benutzer und ein Passwort für den Einstiegspunkt {appDevPhp} festlegen.","ui.maintenance.debugMode.descriptionJwt":"Aktiviert den Debug-Modus, indem für die aktuelle Domain ein entsprechendes Cookie gesetzt wird.","ui.maintenance.debugMode.activate":"Aktivieren","ui.maintenance.debugMode.deactivate":"Deaktivieren","ui.maintenance.debugMode.credentials":"Anmeldedaten","ui.maintenance.debugMode.user":"Bitte gib einen Benutzernamen für den Debug-Modus ein.","ui.maintenance.debugMode.password":"Bitte gib ein Passwort für den Debug-Modus ein.","ui.maintenance.opcodeCache.title":"Opcode-Cache","ui.maintenance.opcodeCache.description":"Der Opcode-Cache speichert PHP-Dateien im Webprozess für eine schnellere Ausführung. Er muss unter Umständen gelöscht werden, wenn Dateien nach dem Ändern nicht erkannt werden.","ui.maintenance.opcodeCache.button":"Cache leeren","ui.maintenance.safeMode":"Deaktiviert im abgesicherten Modus","ui.maintenance.unsupported":"Von deiner Contao-Version nicht unterstützt","ui.packages.updateButton":"Pakete aktualisieren","ui.packages.searchButton":"Pakete suchen","ui.packages.searchPlaceholder":"Pakete suchen …","ui.packages.uploadOverlay":"Lege Dateien hier ab, um sie hochzuladen","ui.packages.uploadButton":"Pakete hochladen","ui.packages.uploadMessage":"Du hast einen unbestätigten Upload. | Du hast {count} unbestätigte Uploads.","ui.packages.uploadApply":"Uploads bestätigen","ui.packages.uploadReset":"Uploads löschen","ui.packages.uploadIncomplete":"Diese Datei wurde nicht vollständig hochgeladen. Bitte entferne sie und versuchen es noch einmal.","ui.packages.uploadDuplicate":"Diese Datei scheint mehrfach hochgeladen worden zu sein. Bitte entferne die Duplikate.","ui.packages.uploadInstalled":"Diese Datei ist bereits installiert. Bitte entferne die Duplikate.","ui.packages.uploadUnsupported":"Uploads werden in deiner Installation nicht unterstützt. Stelle sicher dass die PHP ZIP-Erweiterung installiert ist und aktualisiere die Abhängigkeiten.","ui.packages.changesMessage":"Du hast eine unbestätigte Änderung. | Du hast {count} unbestätigte Änderungen.","ui.packages.changesDryrun":"Testlauf","ui.packages.changesApply":"Änderungen anwenden","ui.packages.changesApplyAll":"Alle Pakete aktualisieren","ui.packages.changesDryrunAll":"Testlauf mit allen Paketen","ui.packages.changesReset":"Änderungen verwerfen","ui.packages.changesReview":"Änderungen prüfen","ui.packagelist.loading":"Laden …","ui.packagelist.uploads":"Uploads","ui.packagelist.added":"Neue Pakete","ui.packagelist.installed":"Installierte Pakete","ui.package.hintRevert":"Änderung verwerfen","ui.package.hintNoupdate":"Nicht aktualisieren","ui.package.hintConstraint":"Dieses Paket wird mit der Versionsbedingung {constraint} installiert, wenn du die Änderungen anwendest.","ui.package.hintConstraintBest":"Dieses Paket wird in der besten verfügbaren Version installiert, wenn du die Änderungen anwendest.","ui.package.hintConstraintChange":"Die Versionsbedingung dieses Pakets wird von \\"{from}\\" in \\"{to}\\" geändert, wenn du die Änderungen anwendest.","ui.package.hintConstraintUpdate":"Dieses Paket wird aktualisiert, wenn du die Änderungen anwendest.","ui.package.hintAdded":"Dieses Paket wird installiert, wenn du die Änderungen anwendest.","ui.package.hintRemoved":"Dieses Paket wird entfernt, wenn du die Änderungen anwendest.","ui.package.requiredTitle":"manuell hinzugefügt","ui.package.requiredText":"Dieses Paket wurde in der composer.json hinzugefügt, ist aber nicht installiert.","ui.package.removedTitle":"manuell entfernt","ui.package.removedText":"Dieses Paket wurde aus der composer.json entfernt.","ui.package.installed":"Aktuell installiert:","ui.package.version":"Version {version}","ui.package.additionalDownloads":"{count} Download | {count} Downloads","ui.package.additionalStars":"{count} Stern | {count} Sterne","ui.package.editConstraint":"Bearbeiten","ui.package.uploadConstraint":"Diese Versionsbedingung wird von dem hochgeladenen Paket definiert.","ui.package.updateButton":"Aktualisieren","ui.package.removeButton":"Entfernen","ui.package.installButton":"Paket hinzufügen","ui.package.installButtonShort":"Hinzufügen","ui.package.detailsButton":"Details","ui.package.latestConstraint":"neuste Version","ui.package.update":"Update verfügbar","ui.package.updateLatest":"neuste Version","ui.package.updateAvailable":"{version} verfügbar","ui.package.updateUnknown":"unbekannte Version","ui.package.updateConstraint":"Es ist eine neuere Version ausserhalb der Versionsbedingung verfügbar.","ui.cloudStatus.headline":"Composer Resolver Cloud","ui.cloudStatus.version":"Version {version}","ui.cloudStatus.waitingTime":"Wartezeit","ui.cloudStatus.jobs":"Aktive Aufgaben","ui.cloudStatus.workers":"Server","ui.cloudStatus.approx":"{minutes} min","ui.cloudStatus.none":"keine","ui.cloudStatus.short":"ca. {minutes} min","ui.cloudStatus.long":"ca. {minutes} min {seconds} sek","ui.cloudStatus.error":"Status der Composer Resolver Cloud konnte nicht abgerufen werden. Möglicherweise gibt es ein Wartungsfenster oder Systemprobleme.","ui.cloudStatus.button":"Cloud-Status","ui.cloudStatus.refresh":"Cloud-Status aktualisieren","ui.log-viewer.loading":"Laden …","ui.log-viewer.empty":"Auf dem Server sind keine Logdateien vorhanden.","ui.log-viewer.reload":"Neu laden","ui.log-viewer.file":"Logdatei","ui.log-viewer.channel":"Kanal","ui.log-viewer.channelTitle":"Kanal in welcher die Nachricht geschrieben wurde.","ui.log-viewer.level":"Stufe","ui.log-viewer.levelTitle":"Wichtigkeit der Log-Nachricht.","ui.log-viewer.timeHeader":"Uhrzeit","ui.log-viewer.messageHeader":"Nachricht","ui.log-viewer.showContext":"Kontext anzeigen","ui.log-viewer.hideContext":"Kontext ausblenden","ui.log-viewer.showExtra":"Extras anzeigen","ui.log-viewer.hideExtra":"Extras ausblenden","ui.log-viewer.more":"Mehr laden …","ui.log-viewer.download":"Herunterladen","ui.log-viewer.downloadTitle":"Datei \\"{file}\\" herunterladen","ui.log-viewer.prodEnvironment":"Produktivumgebung","ui.log-viewer.devEnvironment":"Entwicklungsumgebung (Debug Modus)"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[606],{4606:function(e){e.exports=JSON.parse('{"ui.app.httpsHeadline":"!! Ligação Insegura !!","ui.app.httpsDescription":"Sem recurso a HTTPS os seus dados confidenciais serão transferidos sem encriptação.","ui.app.httpsLink":"Mais info","ui.app.httpsHref":"https://https.cio.gov/everything/","ui.app.safeModeHeadline":"!! Modo Guardar activado !!","ui.app.safeModeDescription":"Algumas características do Gestor de Contacto não estão disponíveis.","ui.app.safeModeExit":"Sair do modo seguro","ui.app.loading":"A iniciar o Contao Manager ...","ui.app.apiError":"API status não esperado -erro","ui.app.configSecurity1":"ALERTA DE SEGURANÇA!!! Directório de configuração sem protecção detectado","ui.app.configSecurity2":"O Contao Manager detetou que os seus ficheiros de configuração estão acessíveis publicamente. Todas as operações estão desativadas até que o directório esteja seguro, caso contrário um atacante poderá aceder a informações sensíveis sobre a sua instalação.\\n\\nPara corrigir esta situação, certifique-se que previne o acesso ao directório \\"contao-manager\\" no seu servidor. Para saber como o fazer, consulte o manual do seu webserver ou contacte o fornecedor de alojamento web.","ui.account.welcome":"Bem Vindo","ui.account.intro1":"Bem-vindo ao Contao Manager, uma ferramenta universal para instalar e gerir o Contao Open Source CMS. Se é novo nele, por favor {readTheManualToGetStarted}.","ui.account.introGetStarted":"{readTheManual} para começar","ui.account.introManual":"ler o manual","ui.account.intro2":"Se encontrar algum problema, verifique {ourGithubIssues} e sinta-se à vontade para criar um novo para qualquer coisa que ainda não tenha sido relatada.","ui.account.introIssues":"as nossas edições GitHub","ui.account.headline":"Utilizador","ui.account.description":"Para gerir a sua instalação, por favor crie uma conta para utilizar no Contao Manager. Certifique-se que é distinta da conta utilizada no Contao back e front end.","ui.account.username":"Utilizador","ui.account.password":"Senha","ui.account.passwordPlaceholder":"min. 8 caracteres","ui.account.passwordLength":"Por favor utilize o minimo de 8 caracteres.","ui.account.submit":"Criar Conta","ui.account.contribute1":"O Contao e o Contao Manager são patrocinados pela Associação Contao sem fins lucrativos.","ui.account.contribute2":"Por favor, considere contribuir para o código aberto por {donate}.","ui.account.contributeDonate":"fazer um donativo","ui.login.headline":"Iniciar sessão","ui.login.description":"Iniciar sessão para gerir a sua instalação.","ui.login.username":"Utilizador","ui.login.password":"Senha","ui.login.forgotPassword":"Esqueceu a sua senha?","ui.login.button":"Iniciar sessão","ui.login.locked":"O acesso foi negado porque o Gestor de Contacto está bloqueado. Para desbloquear, apagar o ficheiro {lockFile} no directório raiz do seu Contao.","ui.logout.headline":"Tempo de Sessão expirado.","ui.logout.warning":"Esteve inactivo por mais de 25 minutos. Por razões de segurança a sua sessão será terminada em breve.","ui.logout.expired":"A sua sessão foi terminada automaticamente porque esteve inactivo por mais de 30 minutos.","ui.logout.renew":"Manter sessão ativa","ui.logout.logout":"Terminar Sessão","ui.logout.login":"Voltar para inicio de sessão","ui.oauth.error":"Tentativa OAuth inválida. Verifique os parâmetros pedidos.","ui.oauth.https":"O redireccionamento do URI DEVE utilizar um protocolo seguro (https:) para evitar que o símbolo de autenticação seja transmitido em texto claro.","ui.oauth.headline":"Autenticação remota","ui.oauth.description":"A seguinte aplicação ou serviço está a solicitar acesso remoto ao Contao Manager.","ui.oauth.domain":"Antes de permitir o acesso, certifique-se de que conhece este URL e confie no seu proprietário!","ui.oauth.allow":"Permitir Acesso","ui.oauth.deny":"Negar Acesso","ui.boot.headline":"Verificar Sistema","ui.boot.description":"Por favor espere, estamos a analisar o seu servidor...","ui.boot.issue1":"Problemas com a instalação detectados","ui.boot.issue2":"A sua instalação tem problemas que têm de ser resolvidos antes que o Gestor de Contacto possa ser utilizado.","ui.boot.run":"Iniciar o Contao Manager","ui.boot.safeMode":"Executar em Modo de Segurança","ui.recovery.headline":"Recuperação de Sistema","ui.recovery.description":"O Contao Manager detectou ficheiros que podem pertencer ao Contao, mas a linha de comandos não funciona como previsto.","ui.recovery.console":"Saída da Consola","ui.recovery.repairOptions":"Por favor escolha uma opção para reparar a sua instalação.","ui.recovery.repairHeadline":"Reparação Automática","ui.recovery.repairDescription":"Efectua uma tentativa de reparar automaticamente a instalação ao reconstruir a cache da aplicação e reinstalando os pacotes Composer.","ui.recovery.repairWarning":"Quaisquer modificações aos ficheiros vendor poderão ser apagadas durante o processo!","ui.recovery.repairFailed":"A reparação automática não teve sucesso. Usar o modo de segurança para reparar manualmente a instalação poderá resolver o problema.","ui.recovery.repairButton":"Executar Reparação de Sistema","ui.recovery.safeModeHeadline":"Modo de Segurança ","ui.recovery.safeModeDescription":"Executar o Contao Manager em Modo de Segurança permite gerir pacotes e correr tarefas de manutenção específicas, mas implementações que dependam de uma instalação Contao funcional não estarão disponíveis.","ui.recovery.safeModeButton":"Executar em Modo de Segurança","ui.server.pending":"Aguarde ...","ui.server.running":"A analisar ...","ui.server.error":"A verificação falhou devido a uma resposta inesperada do servidor.","ui.server.details":"Detalhes","ui.server.prerequisite":"A verificação falhou devido a um pré-requisito em falta.","ui.server.selfUpdate.title":"Actualizações Contao Manager","ui.server.selfUpdate.update":"Uma nova versão do Contao Manager {latest} está disponivel.","ui.server.selfUpdate.manualUpdate":"Está disponível uma nova versão do Contao Manager {latest}. O seu servidor não suporta actualizações automáticas, por favor descarregue a nova versão a partir de {download}.","ui.server.selfUpdate.latest":"Está a usar a versão mais recente {current}.","ui.server.selfUpdate.dev":"Versões de testes não suportam actualizações automáticas.","ui.server.selfUpdate.unsupported":"Uma nova versão está disponível mas não é compatível com a sua versão de PHP.","ui.server.selfUpdate.button":"Executar Auto-Update","ui.server.selfUpdate.continue":"Continuar","ui.server.config.title":"Configuração do Servidor","ui.server.config.setup":"Configurar","ui.server.config.change":"Alterar","ui.server.config.save":"Guardar","ui.server.config.cancel":"Cancelar","ui.server.config.customOption":"Outros ...","ui.server.config.description":"Para executar correctamente as tarefas de fundo, o Gestor de Contacto precisa de saber onde encontrar o binário de linha de comando PHP e como executar comandos separados do processo web.","ui.server.config.formTitle":"Configuração do Servidor","ui.server.config.formText":"Por favor introduza o caminho para o seu binário PHP. Certifique-se que o seu binário usa a mesma versao PHP que o seu processo web.","ui.server.config.cloudTitle":"Composer Resolver Cloud","ui.server.config.cloudText":"A Composer Resolver Cloud permite instalar dependências Composer mesmo se o seu servidor não possuir memória local suficiente. Tenha em consideração que a sua informação de pacotes será transmitida para um servidor em nuvem mantido pela Contao Association.","ui.server.config.cloud":"Usar a Composer Resolver Cloud","ui.server.config.cli":"Binário PHP","ui.server.config.stateErrorCli":"Nenhum binário PHP válido foi encontrado no servidor. ","ui.server.config.stateErrorCloud":"A Composer Resolver Cloud não é suportada.","ui.server.config.stateSuccess":"Binário PHP em {php_cli}.","ui.server.php_web.title":"Processo Web PHP","ui.server.php_web.below7":"Encontrada a versão {version} PHP. Actualize para a versão PHP 7 assim que possivel!","ui.server.php_web.success":"Encontrada a versão PHP {version}, nenhum problema a reportar.","ui.server.php_cli.title":"Interface PHP Linha de Comandos","ui.server.php_cli.success":"Encontrada a versão PHP {version}, nenhum problema a reportar.","ui.server.composer.title":"Ambiente Composer","ui.server.composer.success":"Nenhum problema encontrado.","ui.server.composer.install":"Dependências Composer não instaladas.","ui.server.composer.button":"Instalar","ui.server.contao.title":"Instalação Contao","ui.server.contao.setup":"Configuração","ui.server.contao.check":"Check database","ui.server.contao.empty":"Nenhuma instalação Contao foi encontrada.","ui.server.contao.old":"A versão {version} do Contao não é compatível com o Contao Manager, actualize a sua instalação manualmente.","ui.server.contao.found":"Encontrado Contao com versão {version} (Versão API {api}).","ui.server.contao.connectionError":"Incapaz de se ligar ao servidor da base de dados.","ui.server.contao.connectionProblem":"Database problem found.","ui.server.contao.missingUser":"Conta administrativa não encontrada.","ui.setup.continue":"Continuar","ui.setup.manager":"Iniciar o Contao Manager","ui.setup.cancel":"Cancelar","ui.setup.welcome":"Bem Vindo","ui.setup.welcome1":"Este assistente irá levá-lo através dos passos necessários para configurar a sua instalação do CMS Contao Open Source.","ui.setup.welcome2":"If you have any questions, please find documentation, forums, a Slack channel and more on the {support} page.","ui.setup.support":"Apoio à Comunidade","ui.setup.start":"ComeceComece","ui.setup.complete":"Parabéns!","ui.setup.complete1":"Contao {version} has been installed successfully.","ui.setup.complete2":"To finish the setup process, please open the install tool to configure the database connection and create a back end user.","ui.setup.complete3":"You can now start to create your website in the Contao back end. If you need additional extensions, continue to the Contao Manager.","ui.setup.installTool":"Open the Install Tool","ui.setup.login":"Login to Contao","ui.setup.funding":"Free software is \\"free\\" as in \\"free speech\\", not as in \\"free beer\\". An Open Source project like Contao requires amounts of money that can\'t be raised by a single person or company.\\nIf you have a website or sell websites built with Contao, we would love to see you contribute back financially to the product your business relies upon.","ui.setup.fundingLink":"Learn more","ui.setup.document-root.headline":"Configuração do Webserver","ui.setup.document-root.warning":"Para instalar o Contao através do Contao Manager, é necessário fixar a raiz do documento no servidor web.","ui.setup.document-root.description1":"Contao uses a separate folder for public files, application files are installed in its parent folder. Contao cannot be installed if the folder structure is not correct or the folders are not empty.","ui.setup.document-root.description2":"Se não souber como configurar a raiz do seu documento, leia a documentação do Contacto ou contacte o seu fornecedor de alojamento.","ui.setup.document-root.documentation":"Ler a Documentação","ui.setup.document-root.conflictsTitle":"Directório de instalação não vazio","ui.setup.document-root.conflictsDirectory":"The root directory of your future Contao installation is not empty, we have found {count} file(s) that might be overwritten by the installation process. It is recommended to create an empty directory structure for Contao, but you can also remove the following files and check again if you are sure they are unused.","ui.setup.document-root.ignoreConflicts":"Quero instalar o Contao no directório dos não vazios. Compreendo que isto pode substituir quaisquer ficheiros existentes no meu espaço web.","ui.setup.document-root.check":"Check again","ui.setup.document-root.create":"Criar directórios","ui.setup.document-root.change":"Change directories","ui.setup.document-root.formTitle":"Configuração de directório","ui.setup.document-root.formText1":"O Contao Manager pode criar automaticamente uma nova estrutura de directório no servidor.","ui.setup.document-root.formText2":"Será necessário configurar manualmente a raiz do novo documento (por exemplo, através de um painel de administração de alojamento).","ui.setup.document-root.autoconfig":"Compreendo que tenho de alterar a configuração do meu servidor. Não configurar a raiz do documento irá quebrar o Contao Manager e expor os ficheiros de configuração (incluindo detalhes de conta e palavras-passe)!","ui.setup.document-root.directory":"Novo Directório","ui.setup.document-root.currentRoot":"Raiz do documento actual","ui.setup.document-root.newRoot":"Novo Documento Raiz","ui.setup.document-root.finish":"Directórios de configuração","ui.setup.document-root.publicDir":"Utilizar {dir} para ficheiros públicos (para Contao {version})","ui.setup.document-root.directoryInvalid":"Por favor, introduza um nome de directório válido.","ui.setup.document-root.directoryExists":"O directório alvo já existe. Por favor, introduza um nome diferente.","ui.setup.document-root.confirmation":"O Contao Manager criou com sucesso o directório necessário para a sua instalação de Contao. Tem agora de configurar a raiz do documento no seu servidor web. Não volte a carregar esta página até lá.","ui.setup.document-root.reload":"Recarregar Página","ui.setup.document-root.success":"The directory structure on your web server is set up correctly!","ui.setup.document-root.installingProjectDir":"Application files will be installed to {dir}.","ui.setup.document-root.installingPublicDir":"Public files will be installed to {dir}.","ui.setup.document-root.installedProjectDir":"Application files are installed in {dir}.","ui.setup.document-root.installedPublicDir":"Public files are installed in {dir}.","ui.setup.create-project.headline":"Instalação Contao","ui.setup.create-project.description":"Contao development follows the principle of {semver}, a new minor version is released every six months. The currently supported releases are:","ui.setup.create-project.semver":"Semantic Versioning","ui.setup.create-project.latestTitle":"Mais Recente","ui.setup.create-project.ltsTitle":"Suporte a Longo Prazo (LTS)","ui.setup.create-project.latestQ1":"Our latest version, offers the most features with support until February {year}.","ui.setup.create-project.latestQ3":"Our latest version, offers the most features with support until August {year}.","ui.setup.create-project.ltsText":"Our current LTS version, if you focus on stability. Offers long term support until February {year}.","ui.setup.create-project.pltsText":"The previous LTS version, still has long term support until February {year}.","ui.setup.create-project.requiresPHP":"Requires at least PHP {version}, you have PHP {current}.","ui.setup.create-project.requiresDocroot":"The document root must be \\"{folder}\\".","ui.setup.create-project.releaseplan":"Ver o {contaoReleasePlan} para informações detalhadas.","ui.setup.create-project.releaseplanLink":"Plano de lançamento do Contao","ui.setup.create-project.installed":"Contao {version} is successfully installed on the server. Continue to set up your database or launch the Contao Manager to install a different version.","ui.setup.create-project.formTitle":"Select a distribution","ui.setup.create-project.formText":"Please choose which version should be installed.","ui.setup.create-project.version":"Versão","ui.setup.create-project.demo":"Install the Contao demo website","ui.setup.create-project.demoDescription":"The demo website helps you to get familiar with Contao and all of its core features. More themes can be found in the {store}.","ui.setup.create-project.coreOnly":"Instalação Mínima ( Apenas o \\"core\\")","ui.setup.create-project.noUpdate":"Ignorar Instalação (Utilizador avançado!)","ui.setup.create-project.theme":"Contao Theme","ui.setup.create-project.themeInstall":"To install a Contao theme, use the search input or upload a theme file (.cto/.zip) that supports installation through the Contao Manager.","ui.setup.create-project.themeBuy":"Make sure to visit the official {store}.","ui.setup.create-project.themeStore":"Contao Themes Store","ui.setup.create-project.themeUpload":"Upload theme file (.cto/.zip)","ui.setup.create-project.themeInvalid":"The uploaded file is not a Contao theme or does not support the Contao Manager.","ui.setup.create-project.themeWarning":"The Contao Manager cannot tell whether this theme is compatible with your server. Please check with the theme vendor if you have any questions.","ui.setup.create-project.themeTitle":"Review theme details","ui.setup.create-project.themeDetails":"The following dependencies and files will be installed with this theme.","ui.setup.create-project.themeRequire":"{count} Dependencies | {count} Dependencies","ui.setup.create-project.themeFiles":"{count} File | {count} Files","ui.setup.create-project.theme.or":"or search public themes","ui.setup.create-project.theme.search":"Search themes","ui.setup.create-project.theme.more":"More themes","ui.setup.create-project.theme.empty":"No themes matching {query}","ui.setup.create-project.theme.uploaded":"The theme file was uploaded successfully.","ui.setup.create-project.theme.packageName":"Package name","ui.setup.create-project.theme.version":"Versão","ui.setup.create-project.theme.authors":"Author(s)","ui.setup.create-project.install":"Instalar","ui.setup.create-project.cancel":"Cancelar","ui.setup.database-connection.headline":"Database Connection","ui.setup.database-connection.description":"Contao requires a MySQL database (or a compatible fork like MariaDB) to store pages, content, users and other relational data. Connection parameters are stored in the {env} file in the project root of your Contao installation.","ui.setup.database-connection.formTitle":"Connection Parameters","ui.setup.database-connection.formText":"Enter a database URL or fill in the username, password, server and database fields separately.","ui.setup.database-connection.url":"Database URL","ui.setup.database-connection.validUrl":"Database URL is invalid or connection to server failed.","ui.setup.database-connection.or":"or","ui.setup.database-connection.user":"Utilizador","ui.setup.database-connection.password":"Senha","ui.setup.database-connection.server":"Server (:Port)","ui.setup.database-connection.database":"Database Name","ui.setup.database-connection.connected":"Successfully connected to database {database} on {server}.","ui.setup.database-connection.error":"Error connecting to the database.","ui.setup.database-connection.problem":"Contao has detected a problem with your database server.","ui.setup.database-connection.schemaTitle":"Database Schema","ui.setup.database-connection.migration":"There is one pending migration. | There are {count} pending migrations.","ui.setup.database-connection.schema":"There is one pending schema update. | There are {count} pending schema updates.","ui.setup.database-connection.noChanges":"Your database schema is up to date.","ui.setup.database-connection.check":"Check database","ui.setup.database-connection.skip":"Skip","ui.setup.database-connection.save":"Guardar","ui.setup.database-connection.change":"Change credentials","ui.setup.database-connection.restoreTitle":"Database Import","ui.setup.database-connection.restoreText":"The theme you just installed contains a database backup. Restore the database to import theme data or skip this step to start with a blank Contao installation. | The theme you just installed contains multiple database backups. Select a backup file to import theme data or skip this step to start with a blank Contao installation.","ui.setup.database-connection.backup":"Backup current database before import","ui.setup.database-connection.backupWarning":"All data in database will be overwritten on import! Create a backup first if the database is not empty.","ui.setup.database-connection.restore":"Import theme database","ui.setup.database-connection.restoreOption":"Backup from {date} ({size})","ui.setup.database-connection.restored":"Your theme database was successfully imported. Continue to validate your database schema.","ui.setup.backend-user.success":"An admin account for the Contao back end was found in your database. Use the Contao back end to add more users.","ui.setup.backend-user.error":"Unable to retrieve user list. Check the console output for details.","ui.setup.backend-user.headline":"Backend Account","ui.setup.backend-user.description":"To manage your website, you need to have at least one admin account for the Contao back end. Be aware that this account is not related to the Contao Manager.","ui.setup.backend-user.formTitle":"Criar Conta","ui.setup.backend-user.formText":"Please enter the details for the new back end account.","ui.setup.backend-user.username":"Utilizador","ui.setup.backend-user.name":"Name","ui.setup.backend-user.email":"E-mail address","ui.setup.backend-user.emailInvalid":"Please enter a valid e-mail address","ui.setup.backend-user.password":"Senha","ui.setup.backend-user.passwordPlaceholder":"min. 8 caracteres","ui.setup.backend-user.passwordLength":"Por favor utilize o minimo de 8 caracteres.","ui.setup.backend-user.create":"Add account","ui.task.headline":"Tarefa de fundo","ui.task.loading":"Carregamento de detalhes ...","ui.task.created":"Carregamento de detalhes ...","ui.task.active":"Por favor aguarde enquanto o Gestor de Contacto está a executar operações de tarefas em segundo plano.","ui.task.complete":"Todas as operações são concluídas com sucesso. Verifique a saída da consola para mais detalhes.","ui.task.aborting":"Por favor aguarde enquanto as operações de fundo estão a ser canceladas.","ui.task.stopped":"Algumas operações de fundo foram canceladas. Por favor, verifique a saída da consola.","ui.task.error":"Uma operação de fundo parou inesperadamente. Por favor, verifique a saída da consola.","ui.task.failed":"O Gestor de Contao não conseguiu iniciar uma tarefa de fundo!","ui.task.failedDescription1":"Alguma coisa correu mal ao tentar executar operações em segundo plano.","ui.task.failedDescription2":"Se isto acontecer novamente, o seu servidor poderá não ser suportado.","ui.task.reportProblem":"Reportar um Problema","ui.task.sponsor":"Composer Cloud sponsored by {sponsor}","ui.task.buttonAudit":"Actualizar base de dados ","ui.task.buttonClose":"Fechar","ui.task.buttonConfirm":"Confirmar e Fechar","ui.task.buttonCancel":"Cancelar","ui.task.confirmCancel":"Tem a certeza que pretende interromper esta tarefa? Poderá afectar a instalação do Contao negativamente!","ui.task.autoclose":"Detalhes sobre o sucesso da tarefa","ui.console.toggle":"Mostrar/Esconder saída de Consola","ui.console.showLog":"Mostrar registo completo da consola","ui.console.copyLog":"Copiar log para prancheta","ui.migrate.headline":"Database Updates","ui.migrate.migrationsOnly":"(migrations only)","ui.migrate.schemaOnly":"(schema only)","ui.migrate.loading":"Please wait, we are checking your database …","ui.migrate.empty":"No pending migrations or schema updates found. Your database is up to date.","ui.migrate.emptyMigrations":"No pending migrations found. Make sure to also check for schema updates.","ui.migrate.emptySchema":"No pending schema updates found. Make sure to also check for migrations.","ui.migrate.pending":"Your database is not up to date. Please review the console output below and execute the changes.","ui.migrate.previousChanges":"A previous database migration was not confirmed.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.previousComplete":"A previous database migration was not confirmed, please review the console output below.\\nThere are no more pending changes.","ui.migrate.appliedChanges":"Database updates have been applied.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.appliedComplete":"Database updates have been applied.\\nThere are no more pending migrations or schema updates. Your database is up to date.","ui.migrate.problem":"Contao has detected a problem with your database server.\\nPlease review the console output below to find out what needs to be fixed. | Contao has detected problems with your database server.\\nPlease review the console output below to find out what needs to be fixed.","ui.migrate.warning":"Contao has detected a misconfiguration of your database server.\\nWarnings can be skipped temporarily, but should be fixed for optimal performance and data integrity.","ui.migrate.error":"The changes could not be applied. Your database might have been changed, please check again to retry.","ui.migrate.execute":"Execute","ui.migrate.close":"Fechar","ui.migrate.confirm":"Confirmar e Fechar","ui.migrate.cancel":"Cancelar","ui.migrate.continue":"Continuar","ui.migrate.setup":"Configuração","ui.migrate.skip":"Skip","ui.migrate.retry":"Check again","ui.migrate.retryAll":"Check all","ui.migrate.withDeletes":"Execute all database changes including DROP queries.","ui.migrate.migrationTitle":"Database Migrations","ui.migrate.schemaTitle":"Schema Updates","ui.migrate.problemTitle":"Database Problems","ui.migrate.warningTitle":"Database Warnings","ui.migrate.addTable":"Add table {table}","ui.migrate.dropTable":"Drop table {table}","ui.migrate.addField":"Add field {table}.{field}","ui.migrate.changeField":"Change field {table}.{field}","ui.migrate.dropField":"Drop field {table}.{field}","ui.migrate.createIndex":"Create index \\"{name}\\" on {table}","ui.migrate.dropIndex":"Drop index \\"{name}\\" on {table}","ui.widget.mandatory":"Este campo não pode estar vazio.","ui.widget.blankOption":"Por favor seleccione ...","ui.widget.showPassword":"Show password","ui.widget.hidePassword":"Hide password","ui.error.title":"O pedido HTTP de \\"{method} {url}\\" falhou.","ui.error.server500":"Parece que ocorreu um erro inesperado no seu servidor. Por favor verifique os ficheiros de registo do seu servidor web (Apache/Nginx) e os registos do Contao Manager em \\"Contao-manager/logs\\".","ui.error.response":"O servidor devolveu uma resposta com o código de estado {status}.","ui.error.moreLink":"Mais informação ","ui.error.support":"Contao Support","ui.footer.help":"Ajuda","ui.footer.reportProblem":"Reportar um Problema","ui.navigation.discover":"Descubra","ui.navigation.packages":"Pacotes","ui.navigation.tools":"Ferramentas","ui.navigation.installTool":"Ferramenta de Instalação","ui.navigation.backend":"Backend Contao","ui.navigation.debug":"Depuração Contao","ui.navigation.logViewer":"Log Viewer","ui.navigation.phpinfo":"Informação PHP","ui.navigation.phpinfoLoading":"Carregamento de informação PHP","ui.navigation.maintenance":"Manutenção","ui.navigation.rebuildCache":"Reconstruir Cache","ui.navigation.systemCheck":"Verificar Sistema","ui.navigation.advanced":"Avançado","ui.navigation.logout":"Terminar Sessão","ui.maintenance.database.title":"Database Migrations and Backups","ui.maintenance.database.description":"Database migrations ensure consistent data and table schemas.","ui.maintenance.database.migrations":"One pending database migration | {count} pending database migrations","ui.maintenance.database.schemaUpdates":"One pending schema update | {count} pending schema updates","ui.maintenance.database.error":"Database problem found.","ui.maintenance.database.warning":"Database warnings found.","ui.maintenance.database.button":"Check database","ui.maintenance.database.migrationOnly":"Check migrations only","ui.maintenance.database.schemaOnly":"Check schema only","ui.maintenance.database.installTool":"Open Install Tool","ui.maintenance.database.createBackup":"Create Backup","ui.maintenance.database.backupUnsupported":"Database backups are not supported by your Contao version.","ui.maintenance.database.backupList":"You have one database backup, created on {date}. | You have {count} database backups, the latest one was created on {date}.","ui.maintenance.database.backupEmpty":"You currently have no database backups.","ui.maintenance.rebuildCache.title":"Cache de Aplicação","ui.maintenance.rebuildCache.description":"Reconstruir a cache da aplicação é necessário após modificar quaisquer dos ficheiros de configuração.","ui.maintenance.rebuildCache.rebuildProd":"Reconstruir Cache de Produção","ui.maintenance.rebuildCache.rebuildDev":"Reconstruir Cache Desenvolvimento","ui.maintenance.rebuildCache.clearProd":"Limpar Cache Produção","ui.maintenance.rebuildCache.clearDev":"Limpar Cache Desenvolvimento","ui.maintenance.installTool.title":"Ferramenta de Instalação do Contao","ui.maintenance.installTool.description":"A Ferramenta de Instalação do Contao bloqueia automaticamente caso a senha seja introduzida erradamente três vezes.","ui.maintenance.installTool.unlock":"Desbloquear ferramenta de instalação","ui.maintenance.installTool.lock":"Bloquear ferramenta de instalação","ui.maintenance.dumpAutoload.title":"Carregador de Classes Composer","ui.maintenance.dumpAutoload.description":"O Composer autoloader é responsável por carregar classes PHP. O autoloader tem que ser \\"dumped\\" após adicionar namespaces personalizados ao root compser.json .","ui.maintenance.dumpAutoload.button":"Dump Autoloader","ui.maintenance.composerInstall.title":"Dependências Composer ","ui.maintenance.composerInstall.description":"As dependências dos Composer estão localizadas na pasta {vendor} na raiz da sua aplicação. A reinstalação das dependências pode ser necessária após manipulação ou carregamento manual do ficheiro {composerLock}.","ui.maintenance.composerInstall.button":"Executar Instalador","ui.maintenance.composerInstall.update":"Executar Actualizador Composer","ui.maintenance.composerCache.title":"Cache Composer","ui.maintenance.composerCache.description":"O Composer guarda cache de pacotes provenientes de download para melhor performance. Caso tenha problemas com ficheiros não funcionais, apagar a Composer cache para forçar um novo download poderá resolver o problema.","ui.maintenance.composerCache.button":"Apagar Cache","ui.maintenance.maintenanceMode.title":"Modo Manutenção","ui.maintenance.maintenanceMode.description":"Colocar o Contao em modo de manutenção exibirá um modelo \\"503 Serviço Indisponível\\" para o website.","ui.maintenance.maintenanceMode.enable":"Activar","ui.maintenance.maintenanceMode.disable":"Desactivar","ui.maintenance.debugMode.title":"Modo Depuração","ui.maintenance.debugMode.description":"Activar o modo de depuração definindo um utilizador e uma palavra-passe para o ponto de entrada {appDevPhp}.","ui.maintenance.debugMode.descriptionJwt":"Active o modo de depuração ao definir o cookie de depuração para o domínio corrente.","ui.maintenance.debugMode.activate":"Activar","ui.maintenance.debugMode.deactivate":"Desactivar","ui.maintenance.debugMode.credentials":"Credenciais","ui.maintenance.debugMode.user":"Por favor introduza um nome de utilizador para o modo de depuração.","ui.maintenance.debugMode.password":"Por favor introduza uma senha para o modo de depuração.","ui.maintenance.opcodeCache.title":"Cache Opcode","ui.maintenance.opcodeCache.description":"O Opcode efectua cache de ficheiros PHP no processo web para mais rápida execução. Deverá ser apagado dentro de certas circunstâncias caso ficheiros não sejam reconhecidos após edição.","ui.maintenance.opcodeCache.button":"Truncar Cache","ui.maintenance.safeMode":"Nao disponível em Modo Segurança","ui.maintenance.unsupported":"Não suportado pela sua versão Contao","ui.packages.updateButton":"Actualizar Pacotes","ui.packages.searchButton":"Procurar Pacotes","ui.packages.searchPlaceholder":"Procurar Pacotes ...","ui.packages.uploadOverlay":"Arrastar e largar ficheiros para upload","ui.packages.uploadButton":"Carregar Packages","ui.packages.uploadMessage":"Tem um carregamento não confirmado. | Tem {count} uploads não confirmados.","ui.packages.uploadApply":"Confirmar Uploads","ui.packages.uploadReset":"Apagar Uploads","ui.packages.uploadIncomplete":"Este ficheiro não foi uploaded completamente. Por favor remova-o e tente novamente.","ui.packages.uploadDuplicate":"Este ficheiro aparenta ter sido uploaded várias vezes. Por favor remova os duplicados.","ui.packages.uploadInstalled":"Este ficheiro já se encontra instalado. Por favor remova os duplicados.","ui.packages.uploadUnsupported":"Uploads are not supported in your installation. Please make sure that the PHP ZIP extension is installed and update your dependencies.","ui.packages.changesMessage":"Tem uma alteração não confirmada. | Tem {count} alterações não confirmadas.","ui.packages.changesDryrun":"Dry Run","ui.packages.changesApply":"Aplicar Alterações","ui.packages.changesApplyAll":"Actualizar todos os pacotes","ui.packages.changesDryrunAll":"Funcionamento a seco todos os pacotes","ui.packages.changesReset":"Reverter Alterações","ui.packages.changesReview":"Rever Alterações","ui.packagelist.loading":"A Carregar ...","ui.packagelist.uploads":"Uploads","ui.packagelist.added":"Novos pacotes","ui.packagelist.installed":"Pacotes instalados","ui.package.hintRevert":"Reverter Alterações","ui.package.hintNoupdate":"Não actualizar","ui.package.hintConstraint":" Este pacote será instalado com limitações de entrada {constraint} quando aplicar alterações.","ui.package.hintConstraintBest":"Este pacote será instalado com a melhor versão disponível quando aplicar alterações.","ui.package.hintConstraintChange":"As limitações de entrada para este pacote serão alteradas de \\"{from}\\" para \\"{to}\\" quando aplicar alterações.","ui.package.hintConstraintUpdate":"Este pacote será actualizado quando aplicar alterações. ","ui.package.hintAdded":"Este pacote vai ser instalado quando aplicar as alterações.","ui.package.hintRemoved":"Este pacote será removido quando aplicar alterações.","ui.package.requiredTitle":"manualmente adicionado","ui.package.requiredText":"Este pacote é requerido pelo seu composer.json mas não está instalado.","ui.package.removedTitle":"manualmente removido","ui.package.removedText":"Este pacote vai ser removido do seu composer.json.","ui.package.installed":"Actualmente instalado:","ui.package.version":"Versão {version}","ui.package.additionalDownloads":"{count} Descarregar | {count} Descarregar","ui.package.additionalStars":"{count} Estrela | {count} Estrelas","ui.package.editConstraint":"Editar","ui.package.uploadConstraint":"Esta restrição está definida pelo pacote que foi uploaded.","ui.package.updateButton":"Actualizar","ui.package.removeButton":"Remover","ui.package.installButton":"Adicionar Pacote","ui.package.installButtonShort":"Adicionar","ui.package.detailsButton":"Detalhes","ui.package.latestConstraint":"versão mais recente","ui.package.update":"Actualização disponível","ui.package.updateLatest":"versão mais recente","ui.package.updateAvailable":"{version} disponível","ui.package.updateUnknown":"versão desconhecida","ui.package.updateConstraint":"A newer version outside your version constraint is available.","ui.cloudStatus.headline":"Composer Resolver Cloud","ui.cloudStatus.version":"Versão {version}","ui.cloudStatus.waitingTime":"Tempo de Espera","ui.cloudStatus.jobs":"Tarefas Actuais","ui.cloudStatus.workers":"Trabalhadores","ui.cloudStatus.approx":"{minutes} min","ui.cloudStatus.none":"nenhum","ui.cloudStatus.short":"ca. {minutes} min","ui.cloudStatus.long":"ca. {minutes} min {seconds} seg","ui.cloudStatus.error":"Incapaz de obter o estatuto de Composer Resolver Cloud. Pode ser para questões de manutenção ou de experiência.","ui.cloudStatus.button":"Estado das nuvens","ui.cloudStatus.refresh":"Actualizar o estado das nuvens","ui.log-viewer.loading":"A Carregar ...","ui.log-viewer.empty":"There are no log files on your server.","ui.log-viewer.reload":"Reload","ui.log-viewer.file":"Log file","ui.log-viewer.channel":"Channel","ui.log-viewer.channelTitle":"The channel this message was logged to.","ui.log-viewer.level":"Level","ui.log-viewer.levelTitle":"Severity of the log message.","ui.log-viewer.timeHeader":"Time","ui.log-viewer.messageHeader":"Message","ui.log-viewer.showContext":"Show Context","ui.log-viewer.hideContext":"Hide Context","ui.log-viewer.showExtra":"Show Extra","ui.log-viewer.hideExtra":"Hide Extra","ui.log-viewer.more":"Load more …","ui.log-viewer.download":"Download","ui.log-viewer.downloadTitle":"Download file \\"{file}\\"","ui.log-viewer.prodEnvironment":"Production Environment","ui.log-viewer.devEnvironment":"Development Environment (Debug Mode)"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[540],{4540:function(e){e.exports=JSON.parse('{"ui.app.httpsHeadline":"!! Nezabezpečené připojení !!","ui.app.httpsDescription":"Bez HTTPS budou citlivá data přenášena nezabezpečeně.","ui.app.httpsLink":"Více informací","ui.app.httpsHref":"https://https.cio.gov/everything/","ui.app.safeModeHeadline":"!! Je zapnutý bezpečnostní režim !!","ui.app.safeModeDescription":"Některé funkce nejsou k dispozici ve Správci Contaa.","ui.app.safeModeExit":"Ukončit bezpečnostní režim","ui.app.loading":"Nahrávání Správce Contaa","ui.app.apiError":"Nečekaný status API","ui.app.configSecurity1":"BEZPEČNOSTNÍ UPOZORNĚNÍ!!! Byla objevená nechráněná konfigurační složka ","ui.app.configSecurity2":"Správce Contaa zjistil, že jsou veřejně dostupné jeho konfigurační soubory. Všechny operace byly pozastavené, dokud nebude daná složka chráněná, jinak může dojít k útoku na choulostivá data Vaší instalace.\\n\\nPro opravení této potíže zajistěte omezený přístup ke složce \\"contao-manager\\" na Vašem serveru. Jak to lze provést, se dozvíte od Vašeho hostingového poskytovatele nebo v příručce ke správě serveru.","ui.account.welcome":"Vítejte","ui.account.intro1":"Vítejte ve Správci Contaa, univerzálním nástroji k instalaci a správě Contaa Open Source CMS. Pokud jste tu poprvé, přečtěte si prosím návod {readTheManualToGetStarted}.","ui.account.introGetStarted":"Začínáme {readTheManual}.","ui.account.introManual":"přečíst návod","ui.account.intro2":"Pokud si všimnete jakýchkoli nesrovnalostí, podívejte se na seznam nahlášených chyb {ourGithubIssues}  a případně nahlaste novou, kterou ještě nikdo nenahlásil. ","ui.account.introIssues":"Chyby na GitHubu","ui.account.headline":"Uživatelský účet","ui.account.description":"Abyste mohli spravovat instalaci, vytvořte prosím uživatelský účet. Uvědomte si prosím, že tento účet není stejný pro přihlášení do Contaa. ","ui.account.username":"Uživatelské jméno","ui.account.password":"Heslo","ui.account.passwordPlaceholder":"min. 8 znaků","ui.account.passwordLength":"Zadejte prosím nejméně 8 znaků.","ui.account.submit":"Vytvořit účet","ui.account.contribute1":"Contao a Správce Contaa je podporovaný neziskovou Contao Association.","ui.account.contribute2":"Prosím zvažte, zda se chcete stát členem a spolupracovníkem tohoto open source projektu {becomingAMember}.","ui.account.contributeDonate":"Staňte se dárcem","ui.login.headline":"Přihlásit se","ui.login.description":"Přihlaste se, abyste mohli spravovat Vaši instalaci.","ui.login.username":"Uživatelské jméno","ui.login.password":"Heslo","ui.login.forgotPassword":"Zapomněli jste heslo?","ui.login.button":"Přihlásit se","ui.login.locked":"Přístup byl zamítnut, protože je Správce Contaa uzamčen. Pro odemknutí smažte soubor {lockFile} na Vašem serveru v kořenové složce Contaa.","ui.logout.headline":"Vypršení sezení","ui.logout.warning":"Byli jste neaktivní po dobu 25 minut. Z bezpečnostních důvodů bude Vaše sezení zakrátko ukončené. ","ui.logout.expired":"Vaše sezení bylo automaticky ukončeno, protože jste byli neaktivní více než 30 minut.","ui.logout.renew":"Zůstat přihlášený/á","ui.logout.logout":"Odhlásit","ui.logout.login":"Zpátky k přihlášení","ui.oauth.error":"Neplatný pokus o ověření totožnosti. Zkontrolujte požadované parametry.","ui.oauth.https":"Přeposílací URL musí používat zabezpečený protokol (https), aby nedošlo k přenosu autorizačního kódu v čirém textu.","ui.oauth.headline":"Smazat autorizaci","ui.oauth.description":"Následující aplikace nebo servis požadují vzdálený přístup k Vašemu Správci Contaa. ","ui.oauth.domain":"Ještě než povolíte přístup, ujistěte se, že znáte tuto URL a důvěřujete jejímu majiteli!","ui.oauth.allow":"Povolit přístup","ui.oauth.deny":"Odmítnout přístup","ui.boot.headline":"Kontrola systému","ui.boot.description":"Prosím počkejte, analyzujeme Váš server...","ui.boot.issue1":"Byly zjištěny chyby","ui.boot.issue2":"Vaše instalace obsahuje několik chyb a je ji třeba opravit, než se bude moct použít Správce Contaa.","ui.boot.run":"Spustit Správce Contaa","ui.boot.safeMode":"Spustit v Bezpečnostním módu","ui.recovery.headline":"Záchrana systému","ui.recovery.description":"Správce Contaa našel soubory, které vypadají jako soubory Contaa, ale Příkazový řádek nepracuje tak, jak by měl.","ui.recovery.console":"Výstup konzoly","ui.recovery.repairOptions":"Vyberte prosím jednu z možností pro zotavení Vaší instalace.","ui.recovery.repairHeadline":"Automatická oprava","ui.recovery.repairDescription":"Bude provedené automatické opravení instalace tím, že se přestaví meziúložiště a přeinstalují se balíčky závislé na Composeru.","ui.recovery.repairWarning":"Veškeré úpravy provedené na souborech ve složce vendor budou ztraceny!","ui.recovery.repairFailed":"Automatická oprava neproběhla úspěšně. Pro opravení instalace se pokuste se použít Bezpečnostní mód.","ui.recovery.repairButton":"Spustit opravu systému","ui.recovery.safeModeHeadline":"Bezpečnostní mód","ui.recovery.safeModeDescription":"Spuštěním Správce Contaa v Bezpečném módu lze spravovat balíčky a spouštět základní příkazy/úlohy, nejsou ovšem dostupné funkce závislé na instalaci Contaa.","ui.recovery.safeModeButton":"Spustit v Bezpečnostním módu","ui.server.pending":"Čekám…","ui.server.running":"Analyzování…","ui.server.error":"Kontrola selhala kvůli neočekávané odezvě ze strany serveru.","ui.server.details":"Podrobnosti","ui.server.prerequisite":"Kontrola zrušena kvůli chybějící podmínce.","ui.server.selfUpdate.title":"Aktualizace Správce Contaa","ui.server.selfUpdate.update":"Je k dispozici nová verze Správce Contaa {latest}.","ui.server.selfUpdate.manualUpdate":"Je dostupná nová verze Správce Contaa {latest}. Váš server nepodporuje automatické instalace. Stáhněte si proto prosím novou verzi {download}.","ui.server.selfUpdate.latest":"Používáte poslední verzi {current}.","ui.server.selfUpdate.dev":"Vývojové verze nepodporují automatické aktualizace.","ui.server.selfUpdate.unsupported":"Je dostupná novější verze, ale ta nepodporuje Vaši verzi PHP.","ui.server.selfUpdate.button":"Spustit automatické aktualizace","ui.server.selfUpdate.continue":"Pokračovat","ui.server.config.title":"Nastavení serveru","ui.server.config.setup":"Nastavit","ui.server.config.change":"Změnit","ui.server.config.save":"Uložit","ui.server.config.cancel":"Zrušit","ui.server.config.customOption":"Jíné…","ui.server.config.description":"Pro správný běh úloh na pozadí potřebuje Správce Contaa vědět, kde může najít binární příkazový řádek PHP a jak spouštět příkazy odděleně od webové stránky. ","ui.server.config.formTitle":"Nastavení serveru","ui.server.config.formText":"Zadejte prosím cestu k binární PHP. Ujistěte se, že je binární soubor stejný jako Vámi používaná verze PHP. ","ui.server.config.cloudTitle":"Composer Resolver Cloud","ui.server.config.cloudText":"Composer Resolver Cloud umožňuje nainstalovat na Composeru závislých rozšířeních, i když Váš server nemá dostatek paměti. Mějte na paměti, že informaci o Vaši balíčcích budou odeslané na cloudový server vlastněný Asociací Contaa.","ui.server.config.cloud":"Použít Composer Resolver Cloud","ui.server.config.cli":"Binární PHP","ui.server.config.stateErrorCli":"Na Vašem serveru nebyla nalezena žádná platná binární PHP.","ui.server.config.stateErrorCloud":"Composer Resolver Cloud není podporován.","ui.server.config.stateSuccess":"Binární PHP na {php_cli}.","ui.server.php_web.title":"Webový proces PHP","ui.server.php_web.below7":"Byla nalezena následující verze PHP {version}. Prosím přejděte co nejdříve na PHP 7. ","ui.server.php_web.success":"Byla nalezena následující verze PHP {version}, nejsou známé žádné chyby.","ui.server.php_cli.title":"Rozhraní příkazového řádku PHP","ui.server.php_cli.success":"Byla nalezena následující verze PHP {version}, nejsou známé žádné chyby.","ui.server.composer.title":"Prostředí Composeru","ui.server.composer.success":"Nebyly nalezeny žádné chyby.","ui.server.composer.install":"Věci závislé na Composorovi nejsou nainstalované.","ui.server.composer.button":"Nainstalovat","ui.server.contao.title":"Instalace Contaa","ui.server.contao.setup":"Nastavení","ui.server.contao.check":"Zkontrolovat databázi","ui.server.contao.empty":"Nebyla nalezena žádná instalace Contaa.","ui.server.contao.old":"Verze Contaa {version} není kompatabilní se Správcem Contaa. Zaktualizujte prosím Vaši instalaci manuálně.","ui.server.contao.found":"Nalezeno Contao {version} (verze API {api})","ui.server.contao.connectionError":"Nepodařilo se připojit k serveru databáze.","ui.server.contao.connectionProblem":"Nalezen problém s databází.","ui.server.contao.missingUser":"Nebyl nalezen žádný administrační účet.","ui.setup.continue":"Pokračovat","ui.setup.manager":"Spustit Správce Contaa","ui.setup.cancel":"Zrušit","ui.setup.welcome":"Vítejte","ui.setup.welcome1":"Tento průvodce Vás provede důležitými kroky pro nastavení instalace CMS Contao.","ui.setup.welcome2":"Pokud máte jakýkoli dotaz, prohlédněte si prosím dokumentaci, fóra, kanál Slacku nebo se obraťte na stránku {podpory}.  ","ui.setup.support":"podpora komunity","ui.setup.start":"Začít","ui.setup.complete":"Blahopřejeme!","ui.setup.complete1":"Úspěšně byla nainstalována {version} Contaa.","ui.setup.complete2":"Pro ukončení tohoto procesu otevřete prosím instalační nástroj, abyste nastavili připojení k databázi a vytvořili účet administrátora.","ui.setup.complete3":"Nyní můžete začít vytvářet Vaši webovou stránku v backendu Contaa. Potřebujete-li dodatečná rozšíření, vraťte se do Správce Contaa.","ui.setup.installTool":"Otevřít instalační nástroj","ui.setup.login":"Přihlásit se do Contaa","ui.setup.funding":"Volný software je \\"volný\\" jako \\"volný projev\\", ale ne jako \\"pivo zdarma\\". Open source projekt jako Contaa vyžaduje řadu peněz a nedá se ho uskutečnit bez jediné osoby nebo firmy. \\nPokud máte vytvořenou webovou stránku pomocí Contaa nebo je prodáváte, potěšilo by nás, kdybyste nás finančně podpořili poměrně k Vašemu obchodu.","ui.setup.fundingLink":"Dozvědět se víc","ui.setup.document-root.headline":"Nastavení webového serveru","ui.setup.document-root.warning":"Abyste mohli nainstalovat Contao prostřednictvím Správce Contaa, musíte opravit kořenový dokument na webovém serveru.","ui.setup.document-root.description1":"Contao používá zvláštní složku pro veřejné soubory. Aplikační soubory jsou nainstalované v rodičovské složce. Contao právě nelze nainstalovat, protože Vaše složková struktura není správná nebo složky nejsou prázdné.","ui.setup.document-root.description2":"Pokud nevíte, jak nastavit kořenový dokument, přečtěte si prosím dokumentaci Contaa nebo se obraťte na svého poskytovatele webových služeb.","ui.setup.document-root.documentation":"Přečíst si dokumentaci","ui.setup.document-root.conflictsTitle":"Instalační složka není prázdná","ui.setup.document-root.conflictsDirectory":"Kořenová složka Vaší budoucí instalace Contaa není prázdná. Našli jsem následující počet souborů {count}, které se musí přepsat během instalace. Doporučujeme vytvořit prázdnou složku pro instalaci Contaa, nebo můžete také smazat veškeré věci, které se právě nachází ve vybrané složce, pokud jste si jistí, že je již nepotřebujete. ","ui.setup.document-root.ignoreConflicts":"Chci nainstalova Contao do nevyprázděné složky. Souhlasím s tím, že budou dané soubory přepsány. ","ui.setup.document-root.check":"Znovu zkontrolovat","ui.setup.document-root.create":"Vytvořit složky","ui.setup.document-root.change":"Změnit složky","ui.setup.document-root.formTitle":"Nastavení adresáře","ui.setup.document-root.formText1":"Správce Contaa může automaticky vytvořit novou adresářovou strukturu na tomto webovém serveru.","ui.setup.document-root.formText2":"Budete muset nastavit nový kořenový dokument (např. pomocí správního panelu ve Vašem účtu webových služeb).","ui.setup.document-root.autoconfig":"Rozumím tomu, že musím změnit nastavení serveru. Pokud se tak nestane, může dojít k poruše Správce Contaa a poničení souborů nastavení (včetně podrobností k účtu a heslu)!","ui.setup.document-root.directory":"Nový adresář","ui.setup.document-root.currentRoot":"Současný kořenový dokument","ui.setup.document-root.newRoot":"Nový kořenový dokument","ui.setup.document-root.finish":"Nastavení adresářů","ui.setup.document-root.publicDir":"Použijte {dir} pro veřejné soubory (pro Contao {version})","ui.setup.document-root.directoryInvalid":"Zadejte prosím platný název adresáře","ui.setup.document-root.directoryExists":"Cílový adresář již existuje. Zadejte prosím jiný název.","ui.setup.document-root.confirmation":"Správce Contaa úspěšně vytvořil potřebný adresář pro instalaci Contaa. Nyní musíte nastavit kořenový dokument na svém webovém serveru. Nenačítejte tuto stránku do té doby.","ui.setup.document-root.reload":"Načíst stránku znovu","ui.setup.document-root.success":"Struktura složek je nastavená na Vašem serveru správně!","ui.setup.document-root.installingProjectDir":"Aplikační soubory se nainstalují do {dir}.","ui.setup.document-root.installingPublicDir":"Veřejné soubory se nainstalují do {dir}.","ui.setup.document-root.installedProjectDir":"Aplikační soubory jsou nainstalované v {dir}.","ui.setup.document-root.installedPublicDir":"Veřejné soubory jsou nainstalované v {dir}.","ui.setup.create-project.headline":"Instalace Contaa","ui.setup.create-project.description":"Vývoj Contaa se drží principů {semver}, nová menší verze vychází každých šest měsíců. Současná verze, která se udržuje, je:","ui.setup.create-project.semver":"Sémantické číslování verzí","ui.setup.create-project.latestTitle":"Poslední","ui.setup.create-project.ltsTitle":"Několikaletá podpora - Long Term Support","ui.setup.create-project.latestQ1":"Naše poslední verze nabízí nejnovější funkce s podporou do února {year}.","ui.setup.create-project.latestQ3":"Naše poslední verze nabízí nejnovější funkce s podporou do srpna {year}.","ui.setup.create-project.ltsText":"Naše současná verze LTS, chcete-li se zaměřit na stabilitu. Nabízí dlouhodobou podporu do února {year}.","ui.setup.create-project.pltsText":"Naše předešlá verze LTS má podporu do února {year}.","ui.setup.create-project.requiresPHP":"Vyžaduje přinejmenším PHP {version}, používáte PHP {current}.","ui.setup.create-project.requiresDocroot":"Kořenový dokument musí být \\"{folder}\\".","ui.setup.create-project.releaseplan":"Více informací: {contaoReleasePlan} ","ui.setup.create-project.releaseplanLink":"Plán vydávání nových verzí Contaa","ui.setup.create-project.installed":"Contao {version} je úspěšně nainstalována na vašem serveru. Pokračujte, abyste nastavili vaši databázi nebo spustili Správce Contaa pro nainstalování jiné verze.","ui.setup.create-project.formTitle":"Vyberte distribuci","ui.setup.create-project.formText":"Vyberte prosím verzi, kterou chcete nainstalovat.","ui.setup.create-project.version":"Verze","ui.setup.create-project.demo":"Nainstalovat demoverzi Contaa","ui.setup.create-project.demoDescription":"Demoverze vám pomůže se seznámit s Contaa a pochopit jeho základní fukce. Více předloh můžete najít v {store}.","ui.setup.create-project.coreOnly":"Minimální instalace (pouze jádro)","ui.setup.create-project.noUpdate":"Přeskočit instalaci (pouze pro pokročilé!)","ui.setup.create-project.theme":"Předloha Contaa","ui.setup.create-project.themeInstall":"Abyste nainstalovali předlohu Contaa, použijte vyhledávací políčko nebo načtete soubor (.cto/.zip), který podporuje instalace pomocí Správce Contaa.","ui.setup.create-project.themeBuy":"Podívejte se na další předlohy v obchodě {store}.","ui.setup.create-project.themeStore":"Obchod předloh Contaa","ui.setup.create-project.themeUpload":"Načíst soubor předlohy (.cto/.zip)","ui.setup.create-project.themeInvalid":"Načtený soubor není předloha Contaa nebo ho Správce Contaa nepodporuje.","ui.setup.create-project.themeWarning":"Správce Contaa nemůže říct, jestli je tato předloha kompatibilní s vaším serverem. Podívejte se prosím na informace předlohy pro získání dalších informací.","ui.setup.create-project.themeTitle":"Podívat se na hodnocení předlohy","ui.setup.create-project.themeDetails":"Následující závislosti a soubory se nainstalují spolu s vybranou předlohou.","ui.setup.create-project.themeRequire":"{count} závilostí | {count} závislostí","ui.setup.create-project.themeFiles":"{count} soubor | {count} souborů","ui.setup.create-project.theme.or":"nebo prohledejte veřejně dostupné předlohy","ui.setup.create-project.theme.search":"Prohledat předlohy","ui.setup.create-project.theme.more":"Více předloh","ui.setup.create-project.theme.empty":"Nenalezeny žádné předlohy pro {query}","ui.setup.create-project.theme.uploaded":"Soubory předlohy byly úspěšně nahrány.","ui.setup.create-project.theme.packageName":"Název balíčku","ui.setup.create-project.theme.version":"Verze","ui.setup.create-project.theme.authors":"Autor/Autoři","ui.setup.create-project.install":"Instalace","ui.setup.create-project.cancel":"Zrušit","ui.setup.database-connection.headline":"Připojení k databázi","ui.setup.database-connection.description":"Contao vyžaduje přinejmenším jednu databázi MySQL (nebo kompatibilní druh jako MariaDB) pro ukládání stránek, obsahu, uživatelů a příslušných dat. Přihlašovací údaje jsou uložené v souboru {env} v kořenové složce instalace Contaa.","ui.setup.database-connection.formTitle":"Připojovací údaje","ui.setup.database-connection.formText":"Zadejte prosím cestu k databázi nebo vyplňte zvlášť uživatelské jméno, heslo, server a pole databáze.","ui.setup.database-connection.url":"Cesta k databázi","ui.setup.database-connection.validUrl":"Cesta k databázi je neplatná nebo selhalo spojit se se serverem.","ui.setup.database-connection.or":"nebo","ui.setup.database-connection.user":"Uživatelské jméno","ui.setup.database-connection.password":"Heslo","ui.setup.database-connection.server":"Server (:Port)","ui.setup.database-connection.database":"Jméno databáze","ui.setup.database-connection.connected":"Došlo k úspěšnému připojení k {database} na {server}.","ui.setup.database-connection.error":"Při připojení k databázi došlo k chybě.","ui.setup.database-connection.problem":"Contao zjistil problém s Vaší serverovou databází.","ui.setup.database-connection.schemaTitle":"Databázové schéma","ui.setup.database-connection.migration":"Existuje jedna nedokončená migrace. | Existují následující nedokončené migrace: {count}.","ui.setup.database-connection.schema":"Existuje jedna nedokončená schematická aktualizace. | Existují následující nedokončené schematické aktualizace: {count}.","ui.setup.database-connection.noChanges":"Vaše databáze je aktuální. ","ui.setup.database-connection.check":"Zkontrolovat databázi","ui.setup.database-connection.skip":"Přeskočit","ui.setup.database-connection.save":"Uložit","ui.setup.database-connection.change":"Změnit pověření","ui.setup.database-connection.restoreTitle":"Import databáze","ui.setup.database-connection.restoreText":"Předlohu, kterou jste právě nainstalovali, obsahuje již připravenou databázi s definovaným obsahem. Pokud ji chcete nahrát a začít tak používat Contao s již definovaným obsahem, nahrajte danou databázi, nebo přeskočte tento krok. | Předlohu, kterou jste právě nainstalovali, obsahuje několik připravených databází s již definovaným obsahem. Pokud je chcete nahrát a začít tak používat Contao s již definovaným obsahem, nahrajte dané databázi, nebo přeskočte tento krok","ui.setup.database-connection.backup":"Uložit současnou databázi před importem.","ui.setup.database-connection.backupWarning":"Veškerá data budou v databázi přepsána! Ponejprv si uložte současnou databázi, pokud již není prázdná.","ui.setup.database-connection.restore":"Importovat předlohu databáze","ui.setup.database-connection.restoreOption":"Uložená databáze {date} ({size})","ui.setup.database-connection.restored":"Vaše databáze byla úspěšně importovaná. Pokračujte prosím, abyste provedli aktualizaci schématu databáze.","ui.setup.backend-user.success":"Ve vaší databázi byl nalezen jeden administrátorský účet pro backend Contaa. Pokud si přejete přidat další účty, přihlašte se do backendu Contaa a vytvořte je v sekci Uživatelé.","ui.setup.backend-user.error":"Nebylo možné najít jakýkoli seznam uživatelů. Zkontrolujete prosím výstup příkazového řádku.","ui.setup.backend-user.headline":"Účet backendu","ui.setup.backend-user.description":"Abyste mohli spravovat Vaši webovou stránku, musíte vytvořit alespoň jeden administrativní účet. Uvědomte si prosím, že tento účet není stejný pro přihlášení do Správce Contaa. ","ui.setup.backend-user.formTitle":"Vytvořit účet","ui.setup.backend-user.formText":"Zadejte prosím údaje pro nový účet backendu.","ui.setup.backend-user.username":"Uživatelské jméno","ui.setup.backend-user.name":"Jméno","ui.setup.backend-user.email":"Mailová adresa","ui.setup.backend-user.emailInvalid":"Zadejte prosím platnou mailovou adresu","ui.setup.backend-user.password":"Heslo","ui.setup.backend-user.passwordPlaceholder":"min. 8 znaků","ui.setup.backend-user.passwordLength":"Zadejte prosím nejméně 8 znaků.","ui.setup.backend-user.create":"Přidat účet","ui.task.headline":"Úloha na pozadí","ui.task.loading":"Načítání podrobností...","ui.task.created":"Načítání podrobností...","ui.task.active":"Počkejte prosím, zatímco Spráce Contaa na pozadí vyřizuje operační úlohy.","ui.task.complete":"Všechny operace byly úspěšně provedené. Další podrobnosti získáte v protokolu konzoly.","ui.task.aborting":"Počkejte prosím, zatímco se na pozadí zastavují operace.","ui.task.stopped":"Některé z operačních úloh byly zrušené. Zkontrolujte prosím protokol konzoly.","ui.task.error":"Příkaz na pozadí byl znenadání zastaven. Zkontrolujte prosím protokol konzoly.","ui.task.failed":"Správci Contaa se nepodařilo spustit úlohu!","ui.task.failedDescription1":"Něco se pokazilo, zatímco probíhaly úlohy na pozadí.","ui.task.failedDescription2":"Pokud se to bude opakovat, nejspíš není Váš server podporovaný.","ui.task.reportProblem":"Oznámit problém","ui.task.sponsor":"Cloud Composeru spozorován: {sponsor}","ui.task.buttonAudit":"Zaktualizovat dabázi","ui.task.buttonClose":"Zavřít","ui.task.buttonConfirm":"Potvrdit a zavřít","ui.task.buttonCancel":"Zrušit","ui.task.confirmCancel":"Jste si jistí, že chcete zrušit tuto úlohu? To může zanechat instalaci Contaa v špatném stavu!","ui.task.autoclose":"Zavřít podrobnosti o úloze po úspěšném dokončení","ui.console.toggle":"Zobrazit/skrýt výstup příkazového řádku","ui.console.showLog":"Zobrazit celý protokol konzoly","ui.console.copyLog":"Zkopírovat protokol do schránky.","ui.migrate.headline":"Aktualizace databáze","ui.migrate.migrationsOnly":"(pouze migrace)","ui.migrate.schemaOnly":"(pouze schémata)","ui.migrate.loading":"Prosím počkejte, analyzujeme Vaši databázi...","ui.migrate.empty":"Nebyl nalezené žádné aktualizace pro migraci nebo schémata. Vaši databáze je aktuální.","ui.migrate.emptyMigrations":"Nebyla nalezené žádné nedokončené migrace. Ujistěte se prosím, zda není potřeba také zaktualizovat schémata.","ui.migrate.emptySchema":"Nebyla nalezené žádné nedokončené schémata. Ujistěte se prosím, zda není potřeba také zaktualizovat migrace.","ui.migrate.pending":"Vaše databáze není aktuální. Prohlídněte si prosím níže konzolu a zaktualizujte Vaši databázi.","ui.migrate.previousChanges":"Předešlá migrace nebyla potvrzená.\\nProhlédněte si prosím níže výsledky v konzole a pak proveďte potřebné změny.","ui.migrate.previousComplete":"Předešlá migrace nebyla potvrzená. Prohlédněte si prosím níže výsledky v konzole.\\nExistuje několik nedokončených změn.","ui.migrate.appliedChanges":"Databáze byla zaktualizovaná. \\nProhlédněte si prosím níže výsledky v konzole, pak pokračujte dalšími kroky, abyste provedli další změny. ","ui.migrate.appliedComplete":"Databáze byla zaktualizovaná. \\nNeexistují žádné další nedokončené migrace nebo schémata. Vaše databáze je aktuální. ","ui.migrate.problem":"Contao zjistil problém s Vaším databázovým serverem. \\nProhlédněte si prosím níže výsledky v konzole, abyste se dozvěděli více o tom, co je potřeba opravit. | Contao zjistil problém s Vaším databázovým serverem. \\nProhlédněte si prosím níže výsledky v konzole, abyste se dozvěděli více o tom, co je potřeba opravit. ","ui.migrate.warning":"Contao zjistil nesprávné nastavení Vašeho databázového serveru. \\nVarování lze dočasně přeskočit, měly by se ale opravit, aby docházelo k dobrému výkonu a zacházení s daty. ","ui.migrate.error":"Změny nelze provést. Vaše databáze se možná změnila. Zkontrolujte to prosím ještě jednou a proveďte daný krok ještě jednou.","ui.migrate.execute":"Provést","ui.migrate.close":"Zavřít","ui.migrate.confirm":"Potvrdit a zavřít","ui.migrate.cancel":"Zrušit","ui.migrate.continue":"Pokračovat","ui.migrate.setup":"Nastavení","ui.migrate.skip":"Přeskočit","ui.migrate.retry":"Znovu zkontrolovat","ui.migrate.retryAll":"Vybrat vše","ui.migrate.withDeletes":"Provést všechny změny databáze včetně příkazu ke smazání. ","ui.migrate.migrationTitle":"Migrace databáze","ui.migrate.schemaTitle":"Schématická aktualizace","ui.migrate.problemTitle":"Problémy databáze","ui.migrate.warningTitle":"Varování databáze","ui.migrate.addTable":"Přidat tabulku {table}","ui.migrate.dropTable":"Odstranit tabulku {table}","ui.migrate.addField":"Přidat pole {table}. {field}","ui.migrate.changeField":"Změnit pole {table}.{field}","ui.migrate.dropField":"Odstranit pole {table}.{field}","ui.migrate.createIndex":"Vytvořit index \\"{name}\\" v {table}","ui.migrate.dropIndex":"Odstranit index \\"{name}\\" v {table}","ui.widget.mandatory":"Toto pole nesmí být prázdné.","ui.widget.blankOption":"Vyberte prosím…","ui.widget.showPassword":"Zobrazit heslo","ui.widget.hidePassword":"Skrýt heslo","ui.error.title":"Požadavek HTTP pro \\"{method} {url}\\" selhal.","ui.error.server500":"Vypadá to, že došlo k nečekané chybě na Vašem webovém serveru. Zkontrolujte prosím protokolové soubory na serveru (Apache/Nginx) a protokol Správce Contaa v \\"contao-manager/logs\\".","ui.error.response":"Server odpověděl statusovým kódem {status}.","ui.error.moreLink":"Více informací","ui.error.support":"Podpora Contaa","ui.footer.help":"Nápověda","ui.footer.reportProblem":"Oznámit problém","ui.navigation.discover":"Prohlédnout","ui.navigation.packages":"Balíčky","ui.navigation.tools":"Nástroje","ui.navigation.installTool":"Instalační nástroj","ui.navigation.backend":"Backend Contaa","ui.navigation.debug":"Debugový mód Contaa","ui.navigation.logViewer":"Prohlížeč protokolu","ui.navigation.phpinfo":"Informace o PHP","ui.navigation.phpinfoLoading":"Načítání informací o PHP…","ui.navigation.maintenance":"Údržba","ui.navigation.rebuildCache":"Přetvoření meziúložiště","ui.navigation.systemCheck":"Kontrola systému","ui.navigation.advanced":"Rozšířené","ui.navigation.logout":"Odhlásit","ui.maintenance.database.title":"Migrace databáze","ui.maintenance.database.description":"Migrace databáze zajišťuje konzistentní data a tabulková schémata.","ui.maintenance.database.migrations":"Jedna nedokončená migrace databáze | Počet nedokončených migrací databáze {count} ","ui.maintenance.database.schemaUpdates":"Jedna nedokončená schématická aktualizace | Počet nedokončených schematických aktualizací {count} ","ui.maintenance.database.error":"Nalezen problém s databází.","ui.maintenance.database.warning":"Byly nalezená varování databáze.","ui.maintenance.database.button":"Zkontrolovat databázi","ui.maintenance.database.migrationOnly":"Vybrat pouze migrace","ui.maintenance.database.schemaOnly":"Vybrat pouze schémata","ui.maintenance.database.installTool":"Otevřít instalační nástroj","ui.maintenance.database.createBackup":"Vytvořit uložení databáze","ui.maintenance.database.backupUnsupported":"Ukládání databází není vaší verzí Contaa podporováno.","ui.maintenance.database.backupList":"Máte pouze jednou uloženou vaší databázi, a sice {date}. | Počet uložených databází {count}. Naposledy byla uložená {date}.","ui.maintenance.database.backupEmpty":"Momentálně nemáte žádné uložené databáze.","ui.maintenance.rebuildCache.title":"Meziúložiště aplikace","ui.maintenance.rebuildCache.description":"Přetvoření meziúložiště aplikace je nutné provést po každé změně konfiguračních souborů. ","ui.maintenance.rebuildCache.rebuildProd":"Přetvořit produktivní meziúložiště ","ui.maintenance.rebuildCache.rebuildDev":"Přetvořit vývojářské meziúložiště ","ui.maintenance.rebuildCache.clearProd":"Vyprázdnit produktivní meziúložiště ","ui.maintenance.rebuildCache.clearDev":"Vyprázdnit vývojářské meziúložiště ","ui.maintenance.installTool.title":"Instalační nástroj Contaa","ui.maintenance.installTool.description":"Instalační nástroj Contaa se automaticky uzamkne, pokud nesprávně zadáte heslo třikrát po sobě. ","ui.maintenance.installTool.unlock":"Odemknout Instalační nástroj","ui.maintenance.installTool.lock":"Uzamknout Instalační nástroj","ui.maintenance.dumpAutoload.title":"Composer Class Loader","ui.maintenance.dumpAutoload.description":"Composer autoloader je zodpovědný za načítání tříd PHP. Autoloader musí být spuštěný po přidání vlastních namespace do kořenové composer.json.","ui.maintenance.dumpAutoload.button":"Spustit Autoloadera","ui.maintenance.composerInstall.title":"Na Compseru závislá rozšíření","ui.maintenance.composerInstall.description":"Na Composeru závislá rozšíření se nachází ve složce {vendor} Vaší instalace. Přeinstalování těchto závislostí může být důležité po manipulaci nebo manuálnímu přenosu souboru {composerLock}.","ui.maintenance.composerInstall.button":"Spustit instalátor","ui.maintenance.composerInstall.update":"Spustit aktualizaci Composeru","ui.maintenance.composerCache.title":"Meziúložiště Composeru","ui.maintenance.composerCache.description":"Meziúložiště Composeru stáhla balíčky, aby zlepšily výkonost. Pokud máte potíže jako poničené soubory, pokuste se smazat meziúložiště Composeru, abyste vyvolali nové stáhnutí. ","ui.maintenance.composerCache.button":"Smazat meziúložiště","ui.maintenance.maintenanceMode.title":"Mód údržby","ui.maintenance.maintenanceMode.description":"Když se spustí mód údržby, zobrazí se předloha pro \\"503 service Unavailable\\".","ui.maintenance.maintenanceMode.enable":"Aktivovat","ui.maintenance.maintenanceMode.disable":"Deaktivovat","ui.maintenance.debugMode.title":"Vývojářský mód","ui.maintenance.debugMode.description":"Aktivujte vývojářský mód zadáním uživatele a hesla pro {appDevPhp}.","ui.maintenance.debugMode.descriptionJwt":"Aktivujte vývojářský mód nastavením vývojářské cookie pro danou doménu.","ui.maintenance.debugMode.activate":"Aktivovat","ui.maintenance.debugMode.deactivate":"Deaktivovat","ui.maintenance.debugMode.credentials":"Uživatelské informace","ui.maintenance.debugMode.user":"Zadejte prosím uživatelské jméno pro vývojářský mód.","ui.maintenance.debugMode.password":"Zadejte prosím heslo pro uživatele vývojářského módu.","ui.maintenance.opcodeCache.title":"Operační kód mezinúložiště","ui.maintenance.opcodeCache.description":"Operační kód meziúložiště souborů PHP dokáže rychleji zpracovat data. Musí být smazán za určitých podmínek, pokud se nepodařilo rozpoznat soubory po jejich změně.","ui.maintenance.opcodeCache.button":"Zkrátit meziúložiště","ui.maintenance.safeMode":"Není to dostupné v Bezpečnostním módu","ui.maintenance.unsupported":"Nepodporované Vaší verzí Contaa. ","ui.packages.updateButton":"Zaktualizovat balíčky","ui.packages.searchButton":"Vyhledat balíčky","ui.packages.searchPlaceholder":"Hledají se balíčky…","ui.packages.uploadOverlay":"Přetáhněte a pusťte soubory, které chcete nahrát","ui.packages.uploadButton":"Nahrát balíčky","ui.packages.uploadMessage":"Máte jeden nepotvrzený přenos. | Máte celkem {count} nepotvrzených přenosů.","ui.packages.uploadApply":"Potvrdit nahrání","ui.packages.uploadReset":"Smazat nahráné soubory","ui.packages.uploadIncomplete":"Tyto soubory nebyly zcela nahrány. Smažte je prosím a zkuste to znovu.","ui.packages.uploadDuplicate":"Zdá se, že byly tyto soubory nahrány několikrát. Smažte prosím dané duplikáty.","ui.packages.uploadInstalled":"Tento soubor je již nainstalovaný. Smažte prosím daný duplikát.","ui.packages.uploadUnsupported":"Nahrávání souborů není ve Vaší instalaci podporováno. Ujistěte se prosím, zda je funkce PHP ZIP nainstalovaná a zaktualizujte veškeré závislosti.","ui.packages.changesMessage":"Máte jednu nepotvrzenou změnu. | Máte celkem {count} nepotvrzených změn.","ui.packages.changesDryrun":"Zkouška nanečisto","ui.packages.changesApply":"Provést změny","ui.packages.changesApplyAll":"Zaktualizovat všechny balíčky","ui.packages.changesDryrunAll":"Vyzkoušet všechny balíčky nanečisto","ui.packages.changesReset":"Vrátit změny","ui.packages.changesReview":"Ukázat změny","ui.packagelist.loading":"Spouštění…","ui.packagelist.uploads":"Nahrání/přenos souborů","ui.packagelist.added":"Nové balíčky","ui.packagelist.installed":"Nainstalované balíčky","ui.package.hintRevert":"Vrátit změny","ui.package.hintNoupdate":"Neaktualizovat","ui.package.hintConstraint":"Tento balíček bude nainstalovaný s omezením {constraint}, pokud provedete změny.","ui.package.hintConstraintBest":"Tento balíček bude nainstalovaný v nejnověji dostupné verzi, pokud provedete změny.","ui.package.hintConstraintChange":"Toto omezení pro tento balíček bude změněno z \\"{from}\\" na \\"{to}\\", pokud provedete změny.","ui.package.hintConstraintUpdate":"Tento balíček bude zaktualizovaný, pokud aplikujete tyto změny.","ui.package.hintAdded":"Tento balíček bude nainstalovaný, když aplikujete tyto změny.","ui.package.hintRemoved":"Tento balíček bude smazaný, pokud provedete změny.","ui.package.requiredTitle":"přidáno manuálně","ui.package.requiredText":"Tento balíček je už sice uvedený ve Vašem souboru composer.json, ale ještě není nainstalovaný.","ui.package.removedTitle":"odstaněno manuálně","ui.package.removedText":"Tento balíček byl odstraněn z Vašeho souboru composer.json.","ui.package.installed":"Momentálně nainstalováno:","ui.package.version":"Verze {version}","ui.package.additionalDownloads":"{count} Stažení | {count} Stažení","ui.package.additionalStars":"{count} Hvězda | {count} hvězd","ui.package.editConstraint":"Upravit","ui.package.uploadConstraint":"Toto omezení je nadefinováno nahraným balíčkem.","ui.package.updateButton":"Zaktualizovat","ui.package.removeButton":"Smazat","ui.package.installButton":"Přidat balíček","ui.package.installButtonShort":"Přidat","ui.package.detailsButton":"Podrobnosti","ui.package.latestConstraint":"poslední verze","ui.package.update":"Existuje nová aktualizace","ui.package.updateLatest":"poslední verze","ui.package.updateAvailable":"{version}","ui.package.updateUnknown":"neznámá verze","ui.package.updateConstraint":"Je k dispozici nová verze než ta, kterou právě používáte.","ui.cloudStatus.headline":"Composer Resolver Cloud","ui.cloudStatus.version":"Verze {version}","ui.cloudStatus.waitingTime":"Doba čekání","ui.cloudStatus.jobs":"Současné úlohy","ui.cloudStatus.workers":"Účastníků","ui.cloudStatus.approx":"{minutes} min.","ui.cloudStatus.none":"ne","ui.cloudStatus.short":"ca. {minutes} min.","ui.cloudStatus.long":"ca. {minutes} min. {seconds} sek.","ui.cloudStatus.error":"Nebylo možné získat status Composer Resolver Cloud. Může být zrovna neaktivní nebo má nějaké problémy.","ui.cloudStatus.button":"Status Cloudu","ui.cloudStatus.refresh":"Znovu načíst status Cloudu","ui.log-viewer.loading":"Spouštění…","ui.log-viewer.empty":"Na Vašem serveru se nenacházejí žádné protokolové soubory.","ui.log-viewer.reload":"Načíst znovu","ui.log-viewer.file":"Protokolový soubor","ui.log-viewer.channel":"Kanál","ui.log-viewer.channelTitle":"Kanál zpráv byl přihlášen.","ui.log-viewer.level":"Úroveň","ui.log-viewer.levelTitle":"Vážnost protokolované zprávy.","ui.log-viewer.timeHeader":"Čas","ui.log-viewer.messageHeader":"Zpráva","ui.log-viewer.showContext":"Zobrazit kontext","ui.log-viewer.hideContext":"Skrýt kontext","ui.log-viewer.showExtra":"Zobrazit extra","ui.log-viewer.hideExtra":"Smazat extra","ui.log-viewer.more":"Dozvědět se víc...","ui.log-viewer.download":"Stáhnout","ui.log-viewer.downloadTitle":"Stáhnout soubor \\"{file}\\"","ui.log-viewer.prodEnvironment":"Produktivní prostředí","ui.log-viewer.devEnvironment":"Vývojářské prostředí (vychytávací mód)"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[557],{1557:function(e){e.exports=JSON.parse('{"ui.app.title":"Расширения Contao","ui.app.loading":"Загрузка списка расширений...","ui.discover.advertisement":"Реклама в списке расширений","ui.discover.loading":"Загрузка...","ui.discover.offline":"Не удалось получить результаты.","ui.discover.offlineExplain":"Проверьте подключение к сети Интернет и отключите блокировку JavaScript в своем браузере.","ui.discover.offlineButton":"Попробовать еще раз","ui.discover.searchPlaceholder":"Поиск в {count} расширениях...","ui.discover.empty":"Нет результатов для {query}","ui.discover.more":"Другие результаты","ui.discover.sortBy":"Сортировать по","ui.discover.sortReleased":"Выпуск","ui.discover.sortReleasedTitle":"Сортировать результаты по дате выпуска","ui.discover.sortLatest":"Обновлено","ui.discover.sortLatestTitle":"Сортировать результаты по последнему обновлению","ui.discover.sortDownloads":"Загрузки","ui.discover.sortDownloadsTitle":"Сортировать результаты по количеству загрузок","ui.discover.sortFavers":"Рейтинг","ui.discover.sortFaversTitle":"Сортировать результаты по рейтингу","ui.discover.detailsButton":"Сведения","ui.discover.latestPackages":"Последние и обновленные расширения","ui.discover.faversPackages":"Расширения с высоким рейтингом","ui.discover.downloadsPackages":"Самые загружаемые расширения","ui.package.homepage":"Веб-сайт проекта","ui.package.private":"Частный пакет","ui.package.privateTitle":"Частные пакеты доступны только у поставщика (наприм., в виде загрузки ZIP-файла). Посетите веб-сайт для получения дополнительной информации.","ui.package.abandoned":"Заброшенный","ui.package.abandonedText":"Этот пакет отмечен как заброшенный и больше не поддерживается.","ui.package.abandonedReplace":"Этот пакет имеет статус заброшенного и больше не поддерживается. Автор предлагает вместо него использовать пакет {replacement}.","ui.package-details.previous":"Детали предыдущего расширения","ui.package-details.close":"Закрыть детали расширения","ui.package-details.loading":"Загрузка...","ui.package-details.tabDescription":"Описание","ui.package-details.tabRequire":"Требования","ui.package-details.tabFeatures":"Особенности","ui.package-details.tabSuggest":"Предложения","ui.package-details.tabConflict":"Конфликты","ui.package-details.tabDependents":"Зависимые","ui.package-details.linkRequires":"требует","ui.package-details.linkReplaces":"заменяет","ui.package-details.linkProvides":"обеспечивает","ui.package-details.linkConflicts":"конфликты","ui.package-details.funding":"Финансировать дальнейшее развитие!","ui.package-details.latest":"Последняя версия","ui.package-details.released":"выпуск от","ui.package-details.license":"Лицензия(и)","ui.package-details.authors":"из","ui.package-details.more":"Еще","ui.package-details.packagist":"Сведения о пакете","ui.package-details.metadata":"Редактировать метаданные","ui.package-details.support_docs":"Документация","ui.package-details.support_wiki":"Вики","ui.package-details.support_forum":"Форум поддержки","ui.package-details.support_issues":"Проблемы / Отчет об ошибках","ui.package-details.support_source":"Исходный код","ui.package-details.support_irc":"IRC / Чат","ui.package-details.support_email":"E-Mail поддержки","ui.package-details.support_rss":"RSS-канал"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[523],{523:function(e){e.exports=JSON.parse('{"ui.app.title":"Contao Erweiterungen","ui.app.loading":"Lade Erweiterungsliste …","ui.discover.advertisement":"Anzeigen in der Erweiterungsliste","ui.discover.loading":"Laden …","ui.discover.offline":"Konnte keine Ergebnisse laden.","ui.discover.offlineExplain":"Prüfe deine Internet-Verbindung und deaktiviere alle JavaScript-Blocker.","ui.discover.offlineButton":"Erneut versuchen","ui.discover.searchPlaceholder":"{count} Erweiterungen durchsuchen …","ui.discover.empty":"Keine Ergebnisse für {query}","ui.discover.more":"Mehr Resultate","ui.discover.sortBy":"Sortieren nach","ui.discover.sortReleased":"Veröffentlicht","ui.discover.sortReleasedTitle":"Ergebnisse nach der Veröffentlichung sortieren","ui.discover.sortLatest":"Aktualisiert","ui.discover.sortLatestTitle":"Ergebnisse nach der letzten Aktualisierung sortieren","ui.discover.sortDownloads":"Downloads","ui.discover.sortDownloadsTitle":"Ergebnisse nach Anzahl Downloads sortieren","ui.discover.sortFavers":"Bewertung","ui.discover.sortFaversTitle":"Ergebnisse nach Bewertung sortieren","ui.discover.detailsButton":"Details","ui.discover.latestPackages":"Neuste und aktualisierte Erweiterungen","ui.discover.faversPackages":"Bestbewertete Erweiterungen","ui.discover.downloadsPackages":"Meistgeladene Erweiterungen","ui.package.homepage":"Projektwebseite","ui.package.private":"Privates Paket","ui.package.privateTitle":"Private Pakete sind nur vom jeweiligen Hersteller verfügbar (z.B. als ZIP-Download). Besuche die Webseite für weitere Informationen.","ui.package.abandoned":"verwaist","ui.package.abandonedText":"Diese Erweiterung ist verwaist und wird nicht mehr gepflegt.","ui.package.abandonedReplace":"Diese Erweiterung ist verwaist und wird nicht mehr gepflegt. Der Autor empfiehlt stattdessen das Paket {replacement} zu verwenden.","ui.package-details.previous":"Details der vorherigen Erweiterung","ui.package-details.close":"Details der Erweiterung schließen","ui.package-details.loading":"Laden …","ui.package-details.tabDescription":"Beschreibung","ui.package-details.tabRequire":"Abhängigkeiten","ui.package-details.tabFeatures":"Funktionen","ui.package-details.tabSuggest":"Empfehlungen","ui.package-details.tabConflict":"Konflikte","ui.package-details.tabDependents":"Abhängige","ui.package-details.linkRequires":"benötigt","ui.package-details.linkReplaces":"ersetzt","ui.package-details.linkProvides":"liefert","ui.package-details.linkConflicts":"inkompatibel mit","ui.package-details.funding":"Weiterentwicklung finanzieren!","ui.package-details.latest":"Neuste Version","ui.package-details.released":"veröffentlicht am","ui.package-details.license":"Lizenz(en)","ui.package-details.authors":"von","ui.package-details.more":"Mehr","ui.package-details.packagist":"Paketdetails","ui.package-details.metadata":"Metadaten bearbeiten","ui.package-details.support_docs":"Dokumentation","ui.package-details.support_wiki":"Wiki","ui.package-details.support_forum":"Support-Forum","ui.package-details.support_issues":"Fehler melden","ui.package-details.support_source":"Quellcode","ui.package-details.support_irc":"IRC / Chat","ui.package-details.support_email":"Support E-Mail","ui.package-details.support_rss":"RSS-Feed"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[953],{3953:function(e){e.exports=JSON.parse('{"ui.app.title":"Contao Extensions","ui.app.loading":"Laddar tilläggslista …","ui.discover.advertisement":"Annons i tilläggslistan","ui.discover.loading":"Laddning ...","ui.discover.offline":"Det gick inte att hämta några resultat.","ui.discover.offlineExplain":"Kontrollera din internetanslutning och inaktivera JavaScript-blockerare i din webbläsare.","ui.discover.offlineButton":"Försök igen","ui.discover.searchPlaceholder":"Sök i {count} tillägg …","ui.discover.empty":"Inga resultat för {query}","ui.discover.more":"Fler resultat","ui.discover.sortBy":"Sortera efter","ui.discover.sortReleased":"Släppte","ui.discover.sortReleasedTitle":"Sortera resultat efter releasedatum","ui.discover.sortLatest":"Uppdaterad","ui.discover.sortLatestTitle":"Sortera resultat efter senast uppdaterade","ui.discover.sortDownloads":"Nedladdningar","ui.discover.sortDownloadsTitle":"Sortera resultat efter antal nedladdningar","ui.discover.sortFavers":"Betyg","ui.discover.sortFaversTitle":"Sortera resultat efter betyg","ui.discover.detailsButton":"Detaljer","ui.discover.latestPackages":"Senaste och uppdaterade tillägg","ui.discover.faversPackages":"Högst rankade tillägg","ui.discover.downloadsPackages":"Mest nedladdade tillägg","ui.package.homepage":"Projektets hemsida","ui.package.private":"Privat paket","ui.package.privateTitle":"Privata paket är endast tillgängliga från leverantören (t.ex. som en ZIP-nedladdning). Besök gärna webbplatsen för mer information.","ui.package.abandoned":"övergiven","ui.package.abandonedText":"Detta paket är övergivet och underhålls inte längre.","ui.package.abandonedReplace":"Detta paket är övergivet och underhålls inte längre. Författaren föreslår att du istället använder paketet {replacement}.","ui.package-details.previous":"Tidigare tilläggsinformation","ui.package-details.close":"Stäng tilläggsinformation","ui.package-details.loading":"Laddning ...","ui.package-details.tabDescription":"Beskrivning","ui.package-details.tabRequire":"Krav","ui.package-details.tabFeatures":"Funktioner","ui.package-details.tabSuggest":"Förslag","ui.package-details.tabConflict":"Konflikter","ui.package-details.tabDependents":"Anhöriga","ui.package-details.linkRequires":"kräver","ui.package-details.linkReplaces":"ersätter","ui.package-details.linkProvides":"tillhandahåller","ui.package-details.linkConflicts":"konflikter","ui.package-details.funding":"Underhåll av fondpaket!","ui.package-details.latest":"Senaste versionen","ui.package-details.released":"släppt på","ui.package-details.license":"Licens(er)","ui.package-details.authors":"från","ui.package-details.more":"Mer","ui.package-details.packagist":"Paketdetaljer","ui.package-details.metadata":"Redigera metadata","ui.package-details.support_docs":"Dokumentation","ui.package-details.support_wiki":"Wiki","ui.package-details.support_forum":"Supportforum","ui.package-details.support_issues":"Problem/felrapport","ui.package-details.support_source":"Källkod","ui.package-details.support_irc":"IRC / Chat","ui.package-details.support_email":"Support E-post","ui.package-details.support_rss":"RSS Feed"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[721],{9721:function(e){e.exports=JSON.parse('{"ui.app.title":"Contao機能拡張","ui.app.loading":"機能拡張の一覧を読み込み中...","ui.discover.advertisement":"機能拡張のリストに広告","ui.discover.loading":"読み込み中...","ui.discover.offline":"何も結果を取得できませんでした。","ui.discover.offlineExplain":"Internet接続を確認し、ブラウザーのJavaScriptのブロッカーを無効にしてください。","ui.discover.offlineButton":"再実行","ui.discover.searchPlaceholder":"{count}の機能拡張から検索中...","ui.discover.empty":"{query}の結果はありません。","ui.discover.more":"さらに結果を表示","ui.discover.sortBy":"並べ替え","ui.discover.sortReleased":"リリース","ui.discover.sortReleasedTitle":"リリース日で並べ替え","ui.discover.sortLatest":"更新日時","ui.discover.sortLatestTitle":"更新日時で並べ替え","ui.discover.sortDownloads":"ダウンロード","ui.discover.sortDownloadsTitle":"ダウンロード数で並べ替え","ui.discover.sortFavers":"評価","ui.discover.sortFaversTitle":"評価の結果で並べ替え","ui.discover.detailsButton":"詳細","ui.discover.latestPackages":"最新と更新された機能拡張","ui.discover.faversPackages":"最上位のレートの機能拡張","ui.discover.downloadsPackages":"もっともダウンロードされた機能拡張","ui.package.homepage":"プロジェクトのウェブサイト","ui.package.private":"私的なパッケージ","ui.package.privateTitle":"私的なパッケージはベンダーからだけ(例えば、ZIPのダウンロードで)入手できます。詳細はウェブサイトを参照してください。","ui.package.abandoned":"放棄","ui.package.abandonedText":"このパッケージは放棄され、もう保守されていません。","ui.package.abandonedReplace":"このパッケージは放棄され、もう保守されていません。代わりに{replacement}パッケージの使用を作者は推奨しています。","ui.package-details.previous":"以前の機能拡張の詳細","ui.package-details.close":"機能拡張の詳細を閉じる","ui.package-details.loading":"読み込み中...","ui.package-details.tabDescription":"説明","ui.package-details.tabRequire":"必須要件","ui.package-details.tabFeatures":"機能","ui.package-details.tabSuggest":"提案","ui.package-details.tabConflict":"競合","ui.package-details.tabDependents":"依存関係","ui.package-details.linkRequires":"必要","ui.package-details.linkReplaces":"置き換え","ui.package-details.linkProvides":"提供","ui.package-details.linkConflicts":"競合","ui.package-details.funding":"パッケージの保守に資金を提供!","ui.package-details.latest":"最新版","ui.package-details.released":"リリース日","ui.package-details.license":"ライセンス","ui.package-details.authors":"次から","ui.package-details.more":"詳細","ui.package-details.packagist":"パッケージの詳細","ui.package-details.metadata":"メタデータを編集","ui.package-details.support_docs":"ドキュメント","ui.package-details.support_wiki":"Wiki","ui.package-details.support_forum":"サポートフォーラム","ui.package-details.support_issues":"問題 / バグ報告","ui.package-details.support_source":"ソースコード","ui.package-details.support_irc":"IRC / チャット","ui.package-details.support_email":"サポートの電子メール","ui.package-details.support_rss":"RSSフィード"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[367],{8367:function(e){e.exports=JSON.parse('{"ui.app.httpsHeadline":"!! Веза није безбедна !!","ui.app.httpsDescription":"Ако не користите HTTPS ваши поверљиви подаци ће се преносити без енкрипције.","ui.app.httpsLink":"Детаљније","ui.app.httpsHref":"https://https.cio.gov/everything/","ui.app.safeModeHeadline":"!! Safe Mode enabled !!","ui.app.safeModeDescription":"Some features of the Contao Manager are not available.","ui.app.safeModeExit":"Exit Safe Mode","ui.app.loading":"Contao Manager се учитава...","ui.app.apiError":"Неочекиван статус API","ui.app.configSecurity1":"УПОЗОРЕЊЕ !!! Пронађен је незаштићен config фолдер","ui.app.configSecurity2":"Contao Manager је открио да су конфигурациони фајлови јавно доступни. Све операције су обустављене док се config фолдер не заштити. У супротном би потенцијални нападач могао да приступи осетљивим подацима о Вашој инсталацији.\\n\\nДа поправите ово, осигурајте да приступ фолдеру \\"contao-manager\\" на Вашем серверу буде спречен. Ако је потребно да прво научите како то да урадите, прочитајте упутство за ваш сервер или контактирајте корисничку подршку Вашег хостинг провајдера.","ui.account.welcome":"Добро дошли","ui.account.intro1":"Добродошли у Contao Manager, универзални алат за инсталацију и управљање са Вашом инсталацијом Contao Open Source CMS. Ако се први пут срећете са њим, погледајте {readTheManualToGetStarted}.","ui.account.introGetStarted":"{readTheManual} да почнете","ui.account.introManual":"прочитајте приручник","ui.account.intro2":"Ако наиђете на неки проблем, проверите {ourGithubIssues}  и слободно креирајте нову тему за било шта што до сада није пријављено.","ui.account.introIssues":"наши GitHub проблеми","ui.account.headline":"Кориснички налог","ui.account.description":"Да управљате Вашом инсталацијом, потребно је да креирате налог за Contao Manager. Имајте на уму да овај налог није исто што и налог за Contao Фронт- и БекЕнд.","ui.account.username":"Корисничко име","ui.account.password":"Лозинка","ui.account.passwordPlaceholder":"мин. 8 знакова","ui.account.passwordLength":"Унесите барем 8 знакова.","ui.account.submit":"Креирај налог","ui.account.contribute1":"Contao и Contao Manager су под покровитељством непрофитне организације Contao Association.","ui.account.contribute2":"Молимо Вас да размислите о томе да помогнете отвореном пројекту својом {donate}.","ui.account.contributeDonate":"донирање","ui.login.headline":"Пријава","ui.login.description":"Пријави се ради управљања инсталацијом.","ui.login.username":"Корисничко име","ui.login.password":"Лозинка","ui.login.forgotPassword":"Заборавили сте лозинку?","ui.login.button":"Пријава","ui.login.locked":"Приступ је одбијен јер је Contao Manager закључан. Да бисте га откључали, обришите {lockFile} у почетном директоријуму ваше Contao инсталације.","ui.logout.headline":"Сесија је истекла","ui.logout.warning":"Нисте били активни дуже од 25 минута. Из безбедносних разлога ваша сесија ће бити ускоро прекинута.","ui.logout.expired":"Ваша сесија је аутоматски прекинута јер сте били неактивни дуже од 30 минута.","ui.logout.renew":"Остани пријављен","ui.logout.logout":"Одјава","ui.logout.login":"Назад на пријаву","ui.oauth.error":"Неисправан OAuth покушај. Проверите параметре.","ui.oauth.https":"URI за редирекцију МОРА да користи сигурни протокол (https:) чиме се спречава да се аутентификациони токен преноси као обични текст.","ui.oauth.headline":"Даљинска аутентификација","ui.oauth.description":"Следеће апликације или сервиси захтевају удаљени приступ Вашој инстанци Contao Manager-а.","ui.oauth.domain":"Пре него што дозволите приступ, будите сигурни да познајете овај URL и имате поверење у власника!","ui.oauth.allow":"Дозволи приступ","ui.oauth.deny":"Одбиј приступ","ui.boot.headline":"Провера система","ui.boot.description":"Молимо сачекајте, анализирам ваш сервер ...","ui.boot.issue1":"Пронађен је проблем приликом инсталације","ui.boot.issue2":"Ваша инсталација има проблеме који морају бити решени како бисте могли да користите Contao Manager.","ui.boot.run":"Покрени Contao Manager","ui.boot.safeMode":"Покрени у Safe Mode","ui.recovery.headline":"Опоравак система","ui.recovery.description":"Contao Manager је открио фајлове који наликују на Contaо, али интерфејс командне линије - Command Line Interface - не ради како би требао.","ui.recovery.console":"Испис конзоле","ui.recovery.repairOptions":"Одаберите опцију за опоравак инсталације.","ui.recovery.repairHeadline":"Аутоматска поправка","ui.recovery.repairDescription":"Аутоматска поправка инсталације поновним креирањем апликативног cache-а и реинсталацијом пакета Композера.","ui.recovery.repairWarning":"Било која модификација vendor фајлова можда ће бити обрисана приликом процедуре!","ui.recovery.repairFailed":"Аутоматска поправка није успела. Пробајте ручно да поправите инсталацију у Safe Mode-у.","ui.recovery.repairButton":"Покрени системску поправку","ui.recovery.safeModeHeadline":"Safe Mode","ui.recovery.safeModeDescription":"Покретањем Contao Manager-а у Safe Mode-у омогућиће управљање пакетима и одређеним задацима одржавања, али могућности које се ослањају на постојећу инсталацију Contao неће бити доступне.","ui.recovery.safeModeButton":"Покрени у Safe Mode","ui.server.pending":"Причекајте...","ui.server.running":"Анализа је у току...","ui.server.error":"Провера није успела због неочекиваног одговора са сервера.","ui.server.details":"Детаљи","ui.server.prerequisite":"Провера је заустављена због тога што потребни предуслови нису задовољени.","ui.server.selfUpdate.title":"Ажурирања Contao Manager-а","ui.server.selfUpdate.update":"Нова верзија Contao Manager {latest} је доступна.","ui.server.selfUpdate.manualUpdate":"Нова верзија Contao Manager {latest} је доступна. Ваш сервер не подржава аутоматско ажурирање, панову верзију преузмите са {download}.","ui.server.selfUpdate.latest":"Користите последњу верзију {current}.","ui.server.selfUpdate.dev":"Развојне верзије не подржавају аутоматско ажурирање.","ui.server.selfUpdate.unsupported":"Нова верзија је доступна али не подржава вашу верзију PHP-а.","ui.server.selfUpdate.button":"Покрени Само-ажурирање","ui.server.selfUpdate.continue":"Настави","ui.server.config.title":"Конфигурација сервера","ui.server.config.setup":"Конфигуриши","ui.server.config.change":"Промени","ui.server.config.save":"Сачувај","ui.server.config.cancel":"Откажи","ui.server.config.customOption":"Остало...","ui.server.config.description":"Да би исправно извршавао позадинске задатке, Contao Manager треба да зна где се налази PHP command line binary и како да извршава команде одвојено од мрежних процеса.","ui.server.config.formTitle":"Конфигурација сервера","ui.server.config.formText":"Унесите путању до вашег PHP binary. Проверите да ли је binary у истој верзији PHP као ваш веб процес.","ui.server.config.cloudTitle":"Composer Resolver Cloud","ui.server.config.cloudText":"Composer Resolver Cloud омогућава инсталацију Composer зависности и у случају када ваш локални сервер не поседује довољно радне меморије. Имајте на уму да ће информације о вашим пакетима бити пренете на сервер којим управља Contao Association.","ui.server.config.cloud":"Користи Composer Resolver Cloud","ui.server.config.cli":"PHP binary","ui.server.config.stateErrorCli":"На серверу није пронађен валидан PHP binary.","ui.server.config.stateErrorCloud":"Употреба Composer Resolver Cloud није подржана.","ui.server.config.stateSuccess":"PHP binary на {php_cli}.","ui.server.php_web.title":"PHP Web Process","ui.server.php_web.below7":"Пронађена је верзија PHP {version}. Молимо Вас да што пре пређете на PHP 7!","ui.server.php_web.success":"Пронађена верзија PHP {version}, нема познатих проблема.","ui.server.php_cli.title":"PHP Command Line Interface","ui.server.php_cli.success":"Пронађена верзија PHP {version}, нема познатих проблема.","ui.server.composer.title":"Composer Environment","ui.server.composer.success":"Нема познатих проблема.","ui.server.composer.install":"Зависности Композера нису инсталиране.","ui.server.composer.button":"Install","ui.server.contao.title":"Инсталација Contao","ui.server.contao.setup":"Покретање инсталације","ui.server.contao.check":"Check database","ui.server.contao.empty":"Није пронађена инсталација Contao.","ui.server.contao.old":"Верзија Contao {version} није усклађена са Contao Менаџером, па ћете морати ручно да ажурирате вашу инсталацију.","ui.server.contao.found":"Пронађен Contao {version} (API version {api}).","ui.server.contao.connectionError":"Unable to connect to the database server.","ui.server.contao.connectionProblem":"Database problem found.","ui.server.contao.missingUser":"Admin account not found.","ui.setup.continue":"Настави","ui.setup.manager":"Покрени Contao Manager","ui.setup.cancel":"Откажи","ui.setup.welcome":"Добро дошли","ui.setup.welcome1":"This wizard will take you through the necessary steps to set up your Contao Open Source CMS installation.","ui.setup.welcome2":"If you have any questions, please find documentation, forums, a Slack channel and more on the {support} page.","ui.setup.support":"community support","ui.setup.start":"Get started","ui.setup.complete":"Congratulations!","ui.setup.complete1":"Contao {version} has been installed successfully.","ui.setup.complete2":"To finish the setup process, please open the install tool to configure the database connection and create a back end user.","ui.setup.complete3":"You can now start to create your website in the Contao back end. If you need additional extensions, continue to the Contao Manager.","ui.setup.installTool":"Open the Install Tool","ui.setup.login":"Login to Contao","ui.setup.funding":"Free software is \\"free\\" as in \\"free speech\\", not as in \\"free beer\\". An Open Source project like Contao requires amounts of money that can\'t be raised by a single person or company.\\nIf you have a website or sell websites built with Contao, we would love to see you contribute back financially to the product your business relies upon.","ui.setup.fundingLink":"Learn more","ui.setup.document-root.headline":"Подешавања мрежног сервера","ui.setup.document-root.warning":"Да бисте инсталирали Contao уз помоћ Contao Manager-а, морате да исправно подесите локацију root-а на Вашем мрежном серверу.","ui.setup.document-root.description1":"Contao uses a separate folder for public files, application files are installed in its parent folder. Contao cannot be installed if the folder structure is not correct or the folders are not empty.","ui.setup.document-root.description2":"Ако нисте сигурни како да подесите root, прочитајте документацију или контактирајте администратора Вашег хостинг провајдера.","ui.setup.document-root.documentation":"Прочитајте документацију","ui.setup.document-root.conflictsTitle":"Installation directory not empty","ui.setup.document-root.conflictsDirectory":"The root directory of your future Contao installation is not empty, we have found {count} file(s) that might be overwritten by the installation process. It is recommended to create an empty directory structure for Contao, but you can also remove the following files and check again if you are sure they are unused.","ui.setup.document-root.ignoreConflicts":"I want to install Contao into the non-empty directory. I understand that this might overwrite any existing files on my webspace.","ui.setup.document-root.check":"Check again","ui.setup.document-root.create":"Create directories","ui.setup.document-root.change":"Change directories","ui.setup.document-root.formTitle":"Подешавање директоријума","ui.setup.document-root.formText1":"Contao Manager може аутоматски да креира нову структуру директоријума на серверу.","ui.setup.document-root.formText2":"Мораћете рућно да подесите нови root за документе (нпр. преко администраторског контрол панела на хостингу).","ui.setup.document-root.autoconfig":"Разумем да морам да променим моју конфигурацију сервера. Ако то не урадим, конфигурациони фајлови ће бити изложени (укључујући детаље налога и лозинке)!","ui.setup.document-root.directory":"Нови директоријум","ui.setup.document-root.currentRoot":"Тренутни Document Root","ui.setup.document-root.newRoot":"Нови Document Root","ui.setup.document-root.finish":"Постављање директоријума","ui.setup.document-root.publicDir":"Use {dir} for public files (for Contao {version})","ui.setup.document-root.directoryInvalid":"Унесите исправан назив за директоријум.","ui.setup.document-root.directoryExists":"Одредишни директоријум већ постоји. Унесите други назив.","ui.setup.document-root.confirmation":"Contao Manager је успешно завршио креирање потребног директоријума за Вашу Contao инсталацију. Сада треба да подесите document root на Вашем мрежном серверу. Немојте да поново учитавате ову страницу пре тога.","ui.setup.document-root.reload":"Поново учитај страницу","ui.setup.document-root.success":"The directory structure on your web server is set up correctly!","ui.setup.document-root.installingProjectDir":"Application files will be installed to {dir}.","ui.setup.document-root.installingPublicDir":"Public files will be installed to {dir}.","ui.setup.document-root.installedProjectDir":"Application files are installed in {dir}.","ui.setup.document-root.installedPublicDir":"Public files are installed in {dir}.","ui.setup.create-project.headline":"Инсталација Contao","ui.setup.create-project.description":"Contao development follows the principle of {semver}, a new minor version is released every six months. The currently supported releases are:","ui.setup.create-project.semver":"Semantic Versioning","ui.setup.create-project.latestTitle":"Последња верзија","ui.setup.create-project.ltsTitle":"Long Term Support","ui.setup.create-project.latestQ1":"Our latest version, offers the most features with support until February {year}.","ui.setup.create-project.latestQ3":"Our latest version, offers the most features with support until August {year}.","ui.setup.create-project.ltsText":"Our current LTS version, if you focus on stability. Offers long term support until February {year}.","ui.setup.create-project.pltsText":"The previous LTS version, still has long term support until February {year}.","ui.setup.create-project.requiresPHP":"Requires at least PHP {version}, you have PHP {current}.","ui.setup.create-project.requiresDocroot":"The document root must be \\"{folder}\\".","ui.setup.create-project.releaseplan":"За више информација, проверите {contaoReleasePlan}.","ui.setup.create-project.releaseplanLink":"План издања за Contao","ui.setup.create-project.installed":"Contao {version} is successfully installed on the server. Continue to set up your database or launch the Contao Manager to install a different version.","ui.setup.create-project.formTitle":"Select a distribution","ui.setup.create-project.formText":"Please choose which version should be installed.","ui.setup.create-project.version":"Верзија","ui.setup.create-project.demo":"Install the Contao demo website","ui.setup.create-project.demoDescription":"The demo website helps you to get familiar with Contao and all of its core features. More themes can be found in the {store}.","ui.setup.create-project.coreOnly":"Минимална инсталација (само основни модули)","ui.setup.create-project.noUpdate":"Прескочи инсталацију (само за експерте!)","ui.setup.create-project.theme":"Contao Theme","ui.setup.create-project.themeInstall":"To install a Contao theme, use the search input or upload a theme file (.cto/.zip) that supports installation through the Contao Manager.","ui.setup.create-project.themeBuy":"Make sure to visit the official {store}.","ui.setup.create-project.themeStore":"Contao Themes Store","ui.setup.create-project.themeUpload":"Upload theme file (.cto/.zip)","ui.setup.create-project.themeInvalid":"The uploaded file is not a Contao theme or does not support the Contao Manager.","ui.setup.create-project.themeWarning":"The Contao Manager cannot tell whether this theme is compatible with your server. Please check with the theme vendor if you have any questions.","ui.setup.create-project.themeTitle":"Review theme details","ui.setup.create-project.themeDetails":"The following dependencies and files will be installed with this theme.","ui.setup.create-project.themeRequire":"{count} Dependencies | {count} Dependencies","ui.setup.create-project.themeFiles":"{count} File | {count} Files","ui.setup.create-project.theme.or":"or search public themes","ui.setup.create-project.theme.search":"Search themes","ui.setup.create-project.theme.more":"More themes","ui.setup.create-project.theme.empty":"No themes matching {query}","ui.setup.create-project.theme.uploaded":"The theme file was uploaded successfully.","ui.setup.create-project.theme.packageName":"Package name","ui.setup.create-project.theme.version":"Верзија","ui.setup.create-project.theme.authors":"Author(s)","ui.setup.create-project.install":"Install","ui.setup.create-project.cancel":"Откажи","ui.setup.database-connection.headline":"Database Connection","ui.setup.database-connection.description":"Contao requires a MySQL database (or a compatible fork like MariaDB) to store pages, content, users and other relational data. Connection parameters are stored in the {env} file in the project root of your Contao installation.","ui.setup.database-connection.formTitle":"Connection Parameters","ui.setup.database-connection.formText":"Enter a database URL or fill in the username, password, server and database fields separately.","ui.setup.database-connection.url":"Database URL","ui.setup.database-connection.validUrl":"Database URL is invalid or connection to server failed.","ui.setup.database-connection.or":"or","ui.setup.database-connection.user":"Корисничко име","ui.setup.database-connection.password":"Лозинка","ui.setup.database-connection.server":"Server (:Port)","ui.setup.database-connection.database":"Database Name","ui.setup.database-connection.connected":"Successfully connected to database {database} on {server}.","ui.setup.database-connection.error":"Error connecting to the database.","ui.setup.database-connection.problem":"Contao has detected a problem with your database server.","ui.setup.database-connection.schemaTitle":"Database Schema","ui.setup.database-connection.migration":"There is one pending migration. | There are {count} pending migrations.","ui.setup.database-connection.schema":"There is one pending schema update. | There are {count} pending schema updates.","ui.setup.database-connection.noChanges":"Your database schema is up to date.","ui.setup.database-connection.check":"Check database","ui.setup.database-connection.skip":"Skip","ui.setup.database-connection.save":"Сачувај","ui.setup.database-connection.change":"Change credentials","ui.setup.database-connection.restoreTitle":"Database Import","ui.setup.database-connection.restoreText":"The theme you just installed contains a database backup. Restore the database to import theme data or skip this step to start with a blank Contao installation. | The theme you just installed contains multiple database backups. Select a backup file to import theme data or skip this step to start with a blank Contao installation.","ui.setup.database-connection.backup":"Backup current database before import","ui.setup.database-connection.backupWarning":"All data in database will be overwritten on import! Create a backup first if the database is not empty.","ui.setup.database-connection.restore":"Import theme database","ui.setup.database-connection.restoreOption":"Backup from {date} ({size})","ui.setup.database-connection.restored":"Your theme database was successfully imported. Continue to validate your database schema.","ui.setup.backend-user.success":"An admin account for the Contao back end was found in your database. Use the Contao back end to add more users.","ui.setup.backend-user.error":"Unable to retrieve user list. Check the console output for details.","ui.setup.backend-user.headline":"Backend Account","ui.setup.backend-user.description":"To manage your website, you need to have at least one admin account for the Contao back end. Be aware that this account is not related to the Contao Manager.","ui.setup.backend-user.formTitle":"Креирај налог","ui.setup.backend-user.formText":"Please enter the details for the new back end account.","ui.setup.backend-user.username":"Корисничко име","ui.setup.backend-user.name":"Name","ui.setup.backend-user.email":"E-mail address","ui.setup.backend-user.emailInvalid":"Please enter a valid e-mail address","ui.setup.backend-user.password":"Лозинка","ui.setup.backend-user.passwordPlaceholder":"мин. 8 знакова","ui.setup.backend-user.passwordLength":"Унесите барем 8 знакова.","ui.setup.backend-user.create":"Add account","ui.task.headline":"Позадински задаци","ui.task.loading":"Учитавам детаље...","ui.task.created":"Учитавам детаље...","ui.task.active":"Сачекајте док Contao Manager заврши са позадинским задацима и операцијама.","ui.task.complete":"Сви задаци су успешно завршени. За више детаља, проверите излаз конзоле.","ui.task.aborting":"Сачекајте док се позадински задаци не откажу.","ui.task.stopped":"Неки позадински задаци су отказани. Проверите излаз конзоле.","ui.task.error":"Позадински задатак је неочекивано заустављен. Проверите излаз конзоле.","ui.task.failed":"Contao Manager није успео да покрене позадински задатак!","ui.task.failedDescription1":"Нешто је кренуло погрешно приликом извршавања операција у позадини.","ui.task.failedDescription2":"Ако се ово деси поново, Ваш сервер можда не подржава инсталацију.","ui.task.reportProblem":"Пријави проблем","ui.task.sponsor":"Composer Cloud sponsored by {sponsor}","ui.task.buttonAudit":"Ажурирај базу података","ui.task.buttonClose":"Затвори","ui.task.buttonConfirm":"Потврди и затвори","ui.task.buttonCancel":"Откажи","ui.task.confirmCancel":"Да ли сте сигурни да желите да откажете задатак? Ово ће можда довести до тога да Contao инсталација не буде комплетна.","ui.task.autoclose":"Затвори детаље задатка након успешног завршетка.","ui.console.toggle":"Прикажи/Сакриј конзолу","ui.console.showLog":"Прикажи сав лог конзоле","ui.console.copyLog":"Копирај лог у привремену меморију.","ui.migrate.headline":"Database Updates","ui.migrate.migrationsOnly":"(migrations only)","ui.migrate.schemaOnly":"(schema only)","ui.migrate.loading":"Please wait, we are checking your database …","ui.migrate.empty":"No pending migrations or schema updates found. Your database is up to date.","ui.migrate.emptyMigrations":"No pending migrations found. Make sure to also check for schema updates.","ui.migrate.emptySchema":"No pending schema updates found. Make sure to also check for migrations.","ui.migrate.pending":"Your database is not up to date. Please review the console output below and execute the changes.","ui.migrate.previousChanges":"A previous database migration was not confirmed.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.previousComplete":"A previous database migration was not confirmed, please review the console output below.\\nThere are no more pending changes.","ui.migrate.appliedChanges":"Database updates have been applied.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.appliedComplete":"Database updates have been applied.\\nThere are no more pending migrations or schema updates. Your database is up to date.","ui.migrate.problem":"Contao has detected a problem with your database server.\\nPlease review the console output below to find out what needs to be fixed. | Contao has detected problems with your database server.\\nPlease review the console output below to find out what needs to be fixed.","ui.migrate.warning":"Contao has detected a misconfiguration of your database server.\\nWarnings can be skipped temporarily, but should be fixed for optimal performance and data integrity.","ui.migrate.error":"The changes could not be applied. Your database might have been changed, please check again to retry.","ui.migrate.execute":"Execute","ui.migrate.close":"Затвори","ui.migrate.confirm":"Потврди и затвори","ui.migrate.cancel":"Откажи","ui.migrate.continue":"Настави","ui.migrate.setup":"Покретање инсталације","ui.migrate.skip":"Skip","ui.migrate.retry":"Check again","ui.migrate.retryAll":"Check all","ui.migrate.withDeletes":"Execute all database changes including DROP queries.","ui.migrate.migrationTitle":"Database Migrations","ui.migrate.schemaTitle":"Schema Updates","ui.migrate.problemTitle":"Database Problems","ui.migrate.warningTitle":"Database Warnings","ui.migrate.addTable":"Add table {table}","ui.migrate.dropTable":"Drop table {table}","ui.migrate.addField":"Add field {table}.{field}","ui.migrate.changeField":"Change field {table}.{field}","ui.migrate.dropField":"Drop field {table}.{field}","ui.migrate.createIndex":"Create index \\"{name}\\" on {table}","ui.migrate.dropIndex":"Drop index \\"{name}\\" on {table}","ui.widget.mandatory":"Ово поље не сме бити празно.","ui.widget.blankOption":"Одаберите...","ui.widget.showPassword":"Show password","ui.widget.hidePassword":"Hide password","ui.error.title":"HTTP захтев за \\"{method} {url}\\" није успео.","ui.error.server500":" Изгледа да се десила нека неочекивана грешка на серверу. Проверите логове на Вашем серверу (Apache/Nginx) и логове Contao Manager на локацији \\"contao-manager/logs\\".","ui.error.response":"Сервер је договорио са кодом {status}.","ui.error.moreLink":"Више информација","ui.error.support":"Подршка за Contao","ui.footer.help":"Помоћ","ui.footer.reportProblem":"Пријави проблем","ui.navigation.discover":"Истражи","ui.navigation.packages":"Пакети","ui.navigation.tools":"Алати","ui.navigation.installTool":"Алат за инсталацију","ui.navigation.backend":"Contao БекЕнд","ui.navigation.debug":"Contao мод за дебаговање","ui.navigation.logViewer":"Log Viewer","ui.navigation.phpinfo":"Информације о PHP","ui.navigation.phpinfoLoading":"Loading PHP Information…","ui.navigation.maintenance":"Одржавање","ui.navigation.rebuildCache":"Поновно креирај кеш","ui.navigation.systemCheck":"Провера система","ui.navigation.advanced":"Напредно","ui.navigation.logout":"Одјава","ui.maintenance.database.title":"Database Migrations and Backups","ui.maintenance.database.description":"Database migrations ensure consistent data and table schemas.","ui.maintenance.database.migrations":"One pending database migration | {count} pending database migrations","ui.maintenance.database.schemaUpdates":"One pending schema update | {count} pending schema updates","ui.maintenance.database.error":"Database problem found.","ui.maintenance.database.warning":"Database warnings found.","ui.maintenance.database.button":"Check database","ui.maintenance.database.migrationOnly":"Check migrations only","ui.maintenance.database.schemaOnly":"Check schema only","ui.maintenance.database.installTool":"Open Install Tool","ui.maintenance.database.createBackup":"Create Backup","ui.maintenance.database.backupUnsupported":"Database backups are not supported by your Contao version.","ui.maintenance.database.backupList":"You have one database backup, created on {date}. | You have {count} database backups, the latest one was created on {date}.","ui.maintenance.database.backupEmpty":"You currently have no database backups.","ui.maintenance.rebuildCache.title":"Апликативни cache","ui.maintenance.rebuildCache.description":"Поновно креирање апликативног cache-a је потребно сваки пут када мењате било који конфигурациони фајл.","ui.maintenance.rebuildCache.rebuildProd":"Обнови продукциони Cache","ui.maintenance.rebuildCache.rebuildDev":"Обнови развојни Cache","ui.maintenance.rebuildCache.clearProd":"Обриши продукциони Cache","ui.maintenance.rebuildCache.clearDev":"Обриши развојни Cache","ui.maintenance.installTool.title":"Инсталациони алат за Contao","ui.maintenance.installTool.description":"Инсталациони алат за Contao је аутоматски закључан ако три пута заредом погрешите лозинку.","ui.maintenance.installTool.unlock":"Откључај Инсталациони алат","ui.maintenance.installTool.lock":"Закључај Инсталациони алат","ui.maintenance.dumpAutoload.title":"Composer Class Loader","ui.maintenance.dumpAutoload.description":"The Composer autoloader је одговоран за учитавање PHP класа. Аutoloader мора бити испражњен након додавања властитих namespaces у root composer.json.","ui.maintenance.dumpAutoload.button":"Испразни Autoloader","ui.maintenance.composerInstall.title":"Композер зависности - dependencies","ui.maintenance.composerInstall.description":"Композер зависности су смештене у фолдер {vendor} у root фолдеру ваше апликације. Реинсталирање зависности ће можда бити неопходно након измене или ручног копирања на сервер фајла {composerLock}.","ui.maintenance.composerInstall.button":"Покрени Инсталер","ui.maintenance.composerInstall.update":"Покрени ажурирање Композера","ui.maintenance.composerCache.title":"Композер Cache","ui.maintenance.composerCache.description":"Композер кешира преузете пакете како би побољшао перформансе. Ако имате проблеме са непотпуним фајловима, покушајте да обришете cache Композера како бисте га присилили на поновно преузимање читавог пакета.","ui.maintenance.composerCache.button":"Обриши Cache","ui.maintenance.maintenanceMode.title":"Maintenance Mode","ui.maintenance.maintenanceMode.description":"Putting Contao in maintenance mode will display a \\"503 Service Unavailable\\" template for the website.","ui.maintenance.maintenanceMode.enable":"Enable","ui.maintenance.maintenanceMode.disable":"Disable","ui.maintenance.debugMode.title":"Мод за дебаговање","ui.maintenance.debugMode.description":"Активирајте мод за дебаговање тако што ћете поставити корисничко име и лозинку за улазну тачку {appDevPhp}.","ui.maintenance.debugMode.descriptionJwt":"Активирајте debug мод тако што ћете поставити debug колачић за тренутни домен.","ui.maintenance.debugMode.activate":"Активирај","ui.maintenance.debugMode.deactivate":"Деактивирај","ui.maintenance.debugMode.credentials":"Креденцијали","ui.maintenance.debugMode.user":"Унесите корисничко име за Мод за дебаговање.","ui.maintenance.debugMode.password":"Унесите лозинку за Мод за дебаговање.","ui.maintenance.opcodeCache.title":"Opcode Cache","ui.maintenance.opcodeCache.description":"Opcode кешира PHP фајлове ради бржег извршавања. У неким околностима мора бити обрисан ако фајлови нису препознати након измене.","ui.maintenance.opcodeCache.button":"Испразни Cache","ui.maintenance.safeMode":"Није доступно у Safe Mode","ui.maintenance.unsupported":"Није подржан у вашој верзији Contao","ui.packages.updateButton":"Ажурирај Пакете","ui.packages.searchButton":"Претрага пакета","ui.packages.searchPlaceholder":"Претрага Пакета...","ui.packages.uploadOverlay":"Превуците & пустите фајлове да бисте их пренели на сервер","ui.packages.uploadButton":"Постави пакете","ui.packages.uploadMessage":"Имате непотврђено пребацивање на сервер. | Имате {count} непотврђених пребацивања на сервер.","ui.packages.uploadApply":"Потврди преносе","ui.packages.uploadReset":"Обриши преносе","ui.packages.uploadIncomplete":"Фајл није пренесен у потпуности. Уклоните га и пробајте поново.","ui.packages.uploadDuplicate":"Изгледа да је овај фајл пренесен неколико пута. Уклоните дупликате.","ui.packages.uploadInstalled":"Овај фајл је већ инсталиран. Уклоните дупликате.","ui.packages.uploadUnsupported":"Uploads are not supported in your installation. Please make sure that the PHP ZIP extension is installed and update your dependencies.","ui.packages.changesMessage":"Имате једну непотврђену промену. | Имате {count} непотврђених промена.","ui.packages.changesDryrun":"Dry Run","ui.packages.changesApply":"Примени промене","ui.packages.changesApplyAll":"Ажурирај све пакете","ui.packages.changesDryrunAll":"Обави \\"dry-run\\" симулацију свих пакета.","ui.packages.changesReset":"Врати на почетно стање","ui.packages.changesReview":"Прегледај промене","ui.packagelist.loading":"Учитавам ...","ui.packagelist.uploads":"Преноси","ui.packagelist.added":"Нови пакети","ui.packagelist.installed":"Инсталирани пакети","ui.package.hintRevert":"Опозови измене","ui.package.hintNoupdate":"Не ажурирај","ui.package.hintConstraint":"Када сачувате измене, овај пакет ће бити инсталиран са ограничењима {constraint}.","ui.package.hintConstraintBest":"Када сачувате измене, овај пакет ће бити инсталиран са најбољом доступном верзијом.","ui.package.hintConstraintChange":"Када сачувате измене, ограничење овог пакета биће промењено из \\"{from}\\" у \\"{to}\\" .","ui.package.hintConstraintUpdate":"Ови пакети ће бити ажурирани када потврдите измене.","ui.package.hintAdded":"Ови пакети ће бити инсталирани када потврдите измене.","ui.package.hintRemoved":"Када сачувате измене, овај пакет ће бити уклоњен.","ui.package.requiredTitle":"ручно додати","ui.package.requiredText":"Овај пакет је захтеван у Вашем composer.json али није инсталиран.","ui.package.removedTitle":"ручно уклоњено","ui.package.removedText":"Овај пакет је уклоњен из вашег composer.json.","ui.package.installed":"Тренутно инсталирано:","ui.package.version":"Верзија {version}","ui.package.additionalDownloads":"{count} Преузимање | {count} Преузимања","ui.package.additionalStars":"{count} Звезда | {count} Звезде","ui.package.editConstraint":"Уреди","ui.package.uploadConstraint":"Ова зависности је дефинисана од пренесеног пакета.","ui.package.updateButton":"Ажурирај","ui.package.removeButton":"Уклони","ui.package.installButton":"Додај пакет","ui.package.installButtonShort":"Додавање","ui.package.detailsButton":"Детаљи","ui.package.latestConstraint":"последња верзија","ui.package.update":"Доступно је ажурирање","ui.package.updateLatest":"последња верзија","ui.package.updateAvailable":"{version} доступна","ui.package.updateUnknown":"непозната верзија","ui.package.updateConstraint":"A newer version outside your version constraint is available.","ui.cloudStatus.headline":"Composer Resolver Cloud","ui.cloudStatus.version":"Верзија {version}","ui.cloudStatus.waitingTime":"Време чекања","ui.cloudStatus.jobs":"Тренутни задаци","ui.cloudStatus.workers":"Радници","ui.cloudStatus.approx":"{minutes} min","ui.cloudStatus.none":"нема","ui.cloudStatus.short":"ca. {minutes} min","ui.cloudStatus.long":"ca. {minutes} min {seconds} sec","ui.cloudStatus.error":"Није могуће проверити стање сервиса Composer Resolver Cloud. Можда је у току одржавање или постоје проблеми.","ui.cloudStatus.button":"Статус Cloud-а","ui.cloudStatus.refresh":"Освежи статус Cloud-а","ui.log-viewer.loading":"Учитавам ...","ui.log-viewer.empty":"There are no log files on your server.","ui.log-viewer.reload":"Reload","ui.log-viewer.file":"Log file","ui.log-viewer.channel":"Channel","ui.log-viewer.channelTitle":"The channel this message was logged to.","ui.log-viewer.level":"Level","ui.log-viewer.levelTitle":"Severity of the log message.","ui.log-viewer.timeHeader":"Time","ui.log-viewer.messageHeader":"Message","ui.log-viewer.showContext":"Show Context","ui.log-viewer.hideContext":"Hide Context","ui.log-viewer.showExtra":"Show Extra","ui.log-viewer.hideExtra":"Hide Extra","ui.log-viewer.more":"Load more …","ui.log-viewer.download":"Download","ui.log-viewer.downloadTitle":"Download file \\"{file}\\"","ui.log-viewer.prodEnvironment":"Production Environment","ui.log-viewer.devEnvironment":"Development Environment (Debug Mode)"}')}}]);"use strict";(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[560],{1560:function(e){e.exports=JSON.parse('{"ui.app.httpsHeadline":"!! Nedrošs savienojums !!","ui.app.httpsDescription":"Bez HTTPS jūsu konfidenciālie dati tiks pārsūtīti nešifrēti.","ui.app.httpsLink":"Vairāk informācijas","ui.app.httpsHref":"https://https.cio.gov/everything/","ui.app.safeModeHeadline":"!! Ieslēgts drošais režīms !!","ui.app.safeModeDescription":"Dažas Contao Manager funkcijas nav pieejamas.","ui.app.safeModeExit":"Iziet no drošā režīma","ui.app.loading":"Ielādē Contao pārvaldnieku ...","ui.app.apiError":"Negaidīts API statuss","ui.app.configSecurity1":"DROŠĪBAS BRĪDINĀJUMS !!! Konstatēts neaizsargāts konfigurācijas direktorijs","ui.app.configSecurity2":"The Contao Manager has detected that its config files are publicly accessible. All operations are disabled until the directory is secured, otherwise an attacker could access sensitive data of your installation.\\n\\nTo fix this issue, make sure to prevent access to the \\"contao-manager\\" directory on your server. To learn how to do this, please refer to the manual of your webserver or contact your hosting provider.","ui.account.welcome":"Laipni lūdzam","ui.account.intro1":"Laipni lūgti Contao pārvaldniekā, kas ir universāls rīks Contao atvērtā koda CMS instalēšanai un pārvaldībai. Ja esat jauns lietotājs, lūdzu, {readTheManualToGetStarted}.","ui.account.introGetStarted":"{readTheManual}, lai sāktu","ui.account.introManual":"izlasiet rokasgrāmatu","ui.account.intro2":"Ja jums rodas kādas problēmas, pārbaudiet {ourGithubIssues} un nekautrējieties izveidot jaunu problēmu, ja par to vēl nav ziņots.","ui.account.introIssues":"mūsu GitHub problēmas","ui.account.headline":"Lietotāja konts","ui.account.description":"Lai pārvaldītu savu instalāciju, lūdzu, izveidojiet Contao pārvaldnieka kontu. Ņemiet vērā, ka šis konts nav saistīts ar Contao aizmuguri vai priekšu.","ui.account.username":"Lietotājvārds","ui.account.password":"Parole","ui.account.passwordPlaceholder":"min. 8 rakstzīmes","ui.account.passwordLength":"Lūdzu, ievadiet vismaz 8 rakstzīmes.","ui.account.submit":"Izveidot kontu","ui.account.contribute1":"Contao un Contao Manager sponsorē bezpeļņas organizācija Contao Association.","ui.account.contribute2":"Lūdzu, apsveriet iespēju sniegt ieguldījumu atvērtajā pirmkodā {donate}.","ui.account.contributeDonate":"ziedojuma veikšana","ui.login.headline":"Pierakstīties","ui.login.description":"Pierakstīties, lai pārvaldītu savu instalāciju.","ui.login.username":"Lietotājvārds","ui.login.password":"Parole","ui.login.forgotPassword":"Aizmirsāt paroli?","ui.login.button":"Pierakstīties","ui.login.locked":"Piekļuve ir liegta, jo Contao Manager ir bloķēts. Lai atbloķētu, izdzēsiet {lockFile} datni Contao galvenajā direktorijā.","ui.logout.headline":"Sesijas laika limits","ui.logout.warning":"Jūs esat bijis neaktīvs ilgāk par 25 minūtēm. Drošības apsvērumu dēļ jūsu sesija drīz tiks pārtraukta.","ui.logout.expired":"Jūsu sesija tika automātiski pārtraukta, jo esat bijis neaktīvs ilgāk par 30 minūtēm.","ui.logout.renew":"Palikt pieteikušamies","ui.logout.logout":"Izrakstīties","ui.logout.login":"Atpakaļ uz pieteikšanos","ui.oauth.error":"Nederīgs OAuth mēģinājums. Pārbaudiet pieprasījuma parametrus.","ui.oauth.https":"The redirect URI MUST use a secure protocol (https:) to prevent the authentication token from being transmitted in clear-text.","ui.oauth.headline":"Attālā autentifikācija","ui.oauth.description":"Šāda lietojumprogramma vai pakalpojums pieprasa attālo piekļuvi jūsu Contao Manager instancei.","ui.oauth.domain":"Pirms atļaujat piekļuvi, pārliecinieties, ka zināt šo URL un uzticaties tā īpašniekam!","ui.oauth.allow":"Atļaut piekļuvi","ui.oauth.deny":"Liegt piekļuvi","ui.boot.headline":"Sistēmas pārbaude","ui.boot.description":"Lūdzu, uzgaidiet, mēs analizējam jūsu serveri ...","ui.boot.issue1":"Konstatētās instalēšanas problēmas","ui.boot.issue2":"Jūsu instalācijā ir problēmas, kas jānovērš, pirms var izmantot Contao Manager.","ui.boot.run":"Palaist Contao pārvaldnieku","ui.boot.safeMode":"Palaist drošajā režīmā","ui.recovery.headline":"Sistēmas atkopšana","ui.recovery.description":"Contao Manager atklāja datnes, kas izskatās kā Contao, bet komandrindas saskarne nedarbojas, kā paredzēts.","ui.recovery.console":"Konsoles izvade","ui.recovery.repairOptions":"Lūdzu, izvēlieties iespēju, lai salabotu instalāciju.","ui.recovery.repairHeadline":"Automātiskā izlabošana","ui.recovery.repairDescription":"Mēģina automātiski labot instalāciju, atjaunojot lietojumprogrammu kešatmiņu un atkārtoti instalējot Composer paketes.","ui.recovery.repairWarning":"Procesa laikā var tikt dzēstas visas piegādātāja datņu izmaiņas!","ui.recovery.repairFailed":"Automātiskā labošana nebija veiksmīga. Izmēģiniet instalāciju labot manuāli drošajā režīmā.","ui.recovery.repairButton":"Palaist sistēmas izlabošanu","ui.recovery.safeModeHeadline":"Drošais režīms","ui.recovery.safeModeDescription":"Programmas Contao Manager palaišana drošajā režīmā ļauj pārvaldīt paketes un veikt dažus uzturēšanas uzdevumus, taču funkcijas, kas ir atkarīgas no darbojošās Contao instalācijas, nebūs pieejamas.","ui.recovery.safeModeButton":"Palaist drošajā režīmā","ui.server.pending":"Gaida ...","ui.server.running":"Analizē ...","ui.server.error":"Pārbaude neizdevās, jo no servera saņemta neparedzēta atbilde.","ui.server.details":"Sīkāka informācija","ui.server.prerequisite":"Pārbaude atcelta trūkstošā priekšnosacījuma dēļ.","ui.server.selfUpdate.title":"Contao pārvaldnieka atjauninājumi","ui.server.selfUpdate.update":"Ir pieejama jauna Contao pārvaldnieka versija {latest}.","ui.server.selfUpdate.manualUpdate":"Ir pieejama jauna Contao Manager versija {latest}. Jūsu serveris neatbalsta automātiskus atjauninājumus, lūdzu, lejupielādējiet jauno versiju no {download}.","ui.server.selfUpdate.latest":"Jūs izmantojat jaunāko versiju {current}.","ui.server.selfUpdate.dev":"Izstrādes būves neatbalsta automātiskus atjauninājumus.","ui.server.selfUpdate.unsupported":"Ir pieejama jauna versija, taču tā neatbalsta jūsu PHP versiju.","ui.server.selfUpdate.button":"Palaist pašatjauninājumu","ui.server.selfUpdate.continue":"Turpināt","ui.server.config.title":"Servera konfigurācija","ui.server.config.setup":"Konfigurēt","ui.server.config.change":"Mainīt","ui.server.config.save":"Saglabāt","ui.server.config.cancel":"Atcelt","ui.server.config.customOption":"Cits ...","ui.server.config.description":"To correctly run background tasks, the Contao Manager needs to know where to find the PHP command line binary and how to run commands separated from the web process.","ui.server.config.formTitle":"Servera konfigurācija","ui.server.config.formText":"Please enter the path to your PHP binary. Make sure the binary is the same PHP version as your web process.","ui.server.config.cloudTitle":"Composer Resolver Cloud","ui.server.config.cloudText":"The Composer Resolver Cloud allows to install Composer dependencies even if your server does not provide enough local memory. Please be aware that your package information will be transmitted to a cloud server operated by the Contao Association.","ui.server.config.cloud":"Izmantot Composer Resolver Cloud","ui.server.config.cli":"PHP binārs","ui.server.config.stateErrorCli":"Serverī netika atrasts derīgs PHP binārs.","ui.server.config.stateErrorCloud":"Composer Resolver Cloud netiek atbalstīts.","ui.server.config.stateSuccess":"PHP binārais kodols {php_cli}.","ui.server.php_web.title":"PHP Web process","ui.server.php_web.below7":"Atrasta PHP versija {versija}. Lūdzu, pēc iespējas ātrāk pārejiet uz PHP 7!","ui.server.php_web.success":"Atrasta PHP versija {versija}, neviena zināma problēma nav atrasta.","ui.server.php_cli.title":"PHP komandrindas saskarne","ui.server.php_cli.success":"Atrasta PHP versija {versija}, neviena zināma problēma nav atrasta.","ui.server.composer.title":"Composer vide","ui.server.composer.success":"Netika atrastas zināmas problēmas.","ui.server.composer.install":"Composer dependencies are not installed.","ui.server.composer.button":"Uzstādīt","ui.server.contao.title":"Contao instalācija","ui.server.contao.setup":"Uzstādīt","ui.server.contao.check":"Pārbaudīt datubāzi","ui.server.contao.empty":"Neviena Contao instalācija nav atrasta.","ui.server.contao.old":"Contao versija {versija} nav savietojama ar Contao pārvaldnieku, lūdzu, manuāli atjauniniet instalāciju.","ui.server.contao.found":"Atrasts Contao {versija} (API versija {api}).","ui.server.contao.connectionError":"Nevar izveidot savienojumu ar datubāzes serveri.","ui.server.contao.connectionProblem":"Atrasta datubāzes problēma.","ui.server.contao.missingUser":"Administratora konts nav atrasts.","ui.setup.continue":"Turpināt","ui.setup.manager":"Palaist Contao pārvaldnieku","ui.setup.cancel":"Atcelt","ui.setup.welcome":"Laipni lūdzam","ui.setup.welcome1":"This wizard will take you through the necessary steps to set up your Contao Open Source CMS installation.","ui.setup.welcome2":"If you have any questions, please find documentation, forums, a Slack channel and more on the {support} page.","ui.setup.support":"kopienas atbalsts","ui.setup.start":"Sākt","ui.setup.complete":"Apsveicam!","ui.setup.complete1":"Contao {versija} ir veiksmīgi instalēta.","ui.setup.complete2":"To finish the setup process, please open the install tool to configure the database connection and create a back end user.","ui.setup.complete3":"You can now start to create your website in the Contao back end. If you need additional extensions, continue to the Contao Manager.","ui.setup.installTool":"Atveriet instalēšanas rīku","ui.setup.login":"Pieslēgties Contao","ui.setup.funding":"Free software is \\"free\\" as in \\"free speech\\", not as in \\"free beer\\". An Open Source project like Contao requires amounts of money that can\'t be raised by a single person or company.\\nIf you have a website or sell websites built with Contao, we would love to see you contribute back financially to the product your business relies upon.","ui.setup.fundingLink":"Uzzināt vairāk","ui.setup.document-root.headline":"Tīmekļa servera iestatīšana","ui.setup.document-root.warning":"Lai instalētu Contao, izmantojot Contao Manager, tīmekļa serverī ir jānosaka dokumenta sakne.","ui.setup.document-root.description1":"Contao uses a separate folder for public files, application files are installed in its parent folder. Contao cannot be installed if the folder structure is not correct or the folders are not empty.","ui.setup.document-root.description2":"Ja nezināt, kā konfigurēt dokumentu sakni, izlasiet Contao dokumentāciju vai sazinieties ar savu hostinga pakalpojumu sniedzēju.","ui.setup.document-root.documentation":"Izlasiet dokumentāciju","ui.setup.document-root.conflictsTitle":"Instalācijas direktorijs nav tukšs","ui.setup.document-root.conflictsDirectory":"The root directory of your future Contao installation is not empty, we have found {count} file(s) that might be overwritten by the installation process. It is recommended to create an empty directory structure for Contao, but you can also remove the following files and check again if you are sure they are unused.","ui.setup.document-root.ignoreConflicts":"I want to install Contao into the non-empty directory. I understand that this might overwrite any existing files on my webspace.","ui.setup.document-root.check":"Pārbaudiet vēlreiz","ui.setup.document-root.create":"Izveidot direktorijus","ui.setup.document-root.change":"Change directories","ui.setup.document-root.formTitle":"Direktoriju iestatīšana","ui.setup.document-root.formText1":"Contao Manager var automātiski izveidot jaunu direktoriju struktūru serverī.","ui.setup.document-root.formText2":"Jums būs manuāli jākonfigurē jaunā dokumenta sakne (piemēram, izmantojot hostinga administratora paneli).","ui.setup.document-root.autoconfig":"Es saprotu, ka man ir jāmaina servera konfigurācija. Dokumenta saknes nekonfigurēšana sabojās Contao Manager un atklās konfigurācijas failus (tostarp konta informāciju un paroles)!","ui.setup.document-root.directory":"Jauns direktorijs","ui.setup.document-root.currentRoot":"Pašreizējā dokumenta sakne","ui.setup.document-root.newRoot":"Jauna dokumenta sakne","ui.setup.document-root.finish":"Direktoriju iestatīšana","ui.setup.document-root.publicDir":"Izmantojiet {dir} publiskajām datnēm (Contao {version})","ui.setup.document-root.directoryInvalid":"Lūdzu, ievadiet derīgu direktorija nosaukumu.","ui.setup.document-root.directoryExists":"Mērķa direktorijs jau pastāv. Lūdzu, ievadiet citu nosaukumu.","ui.setup.document-root.confirmation":"Contao pārvaldnieks ir veiksmīgi izveidojis nepieciešamo direktoriju jūsu Contao instalācijai. Tagad jums ir jākonfigurē dokumenta sakne tīmekļa serverī. Līdz tam nepārlādējiet šo lapu no jauna.","ui.setup.document-root.reload":"Pārlādēt lapu","ui.setup.document-root.success":"The directory structure on your web server is set up correctly!","ui.setup.document-root.installingProjectDir":"Lietojumprogrammas datnes tiks instalētas {dir}.","ui.setup.document-root.installingPublicDir":"Publiskās datnes tiks instalētas {dir}.","ui.setup.document-root.installedProjectDir":"Lietojumprogrammas datnes ir instalētas {dir}.","ui.setup.document-root.installedPublicDir":"Publiskās datnes ir instalētas {dir}.","ui.setup.create-project.headline":"Contao instalācija","ui.setup.create-project.description":"Contao development follows the principle of {semver}, a new minor version is released every six months. The currently supported releases are:","ui.setup.create-project.semver":"Semantic Versioning","ui.setup.create-project.latestTitle":"Jaunākais","ui.setup.create-project.ltsTitle":"Ilgtermiņa atbalsts","ui.setup.create-project.latestQ1":"Our latest version, offers the most features with support until February {year}.","ui.setup.create-project.latestQ3":"Our latest version, offers the most features with support until August {year}.","ui.setup.create-project.ltsText":"Our current LTS version, if you focus on stability. Offers long term support until February {year}.","ui.setup.create-project.pltsText":"The previous LTS version, still has long term support until February {year}.","ui.setup.create-project.requiresPHP":"Requires at least PHP {version}, you have PHP {current}.","ui.setup.create-project.requiresDocroot":"Dokumenta saknei jābūt \\"{folder}\\".","ui.setup.create-project.releaseplan":"Sīkāku informāciju skatiet {contaoReleasePlan}.","ui.setup.create-project.releaseplanLink":"Contao izlaišanas plāns","ui.setup.create-project.installed":"Contao {version} is successfully installed on the server. Continue to set up your database or launch the Contao Manager to install a different version.","ui.setup.create-project.formTitle":"Izvēlēties distribūciju","ui.setup.create-project.formText":"Lūdzu, izvēlieties, kura versija jāuzstāda.","ui.setup.create-project.version":"Versija","ui.setup.create-project.demo":"Install the Contao demo website","ui.setup.create-project.demoDescription":"The demo website helps you to get familiar with Contao and all of its core features. More themes can be found in the {store}.","ui.setup.create-project.coreOnly":"Minimāla instalācija (tikai Core)","ui.setup.create-project.noUpdate":"Izlaist instalāciju (tikai eksperts!)","ui.setup.create-project.theme":"Contao Theme","ui.setup.create-project.themeInstall":"To install a Contao theme, use the search input or upload a theme file (.cto/.zip) that supports installation through the Contao Manager.","ui.setup.create-project.themeBuy":"Make sure to visit the official {store}.","ui.setup.create-project.themeStore":"Contao Themes Store","ui.setup.create-project.themeUpload":"Upload theme file (.cto/.zip)","ui.setup.create-project.themeInvalid":"The uploaded file is not a Contao theme or does not support the Contao Manager.","ui.setup.create-project.themeWarning":"The Contao Manager cannot tell whether this theme is compatible with your server. Please check with the theme vendor if you have any questions.","ui.setup.create-project.themeTitle":"Review theme details","ui.setup.create-project.themeDetails":"The following dependencies and files will be installed with this theme.","ui.setup.create-project.themeRequire":"{count} Dependencies | {count} Dependencies","ui.setup.create-project.themeFiles":"{count} File | {count} Files","ui.setup.create-project.theme.or":"or search public themes","ui.setup.create-project.theme.search":"Search themes","ui.setup.create-project.theme.more":"More themes","ui.setup.create-project.theme.empty":"No themes matching {query}","ui.setup.create-project.theme.uploaded":"The theme file was uploaded successfully.","ui.setup.create-project.theme.packageName":"Package name","ui.setup.create-project.theme.version":"Versija","ui.setup.create-project.theme.authors":"Author(s)","ui.setup.create-project.install":"Uzstādīt","ui.setup.create-project.cancel":"Atcelt","ui.setup.database-connection.headline":"Datubāzes savienojums","ui.setup.database-connection.description":"Contao requires a MySQL database (or a compatible fork like MariaDB) to store pages, content, users and other relational data. Connection parameters are stored in the {env} file in the project root of your Contao installation.","ui.setup.database-connection.formTitle":"Savienojuma parametri","ui.setup.database-connection.formText":"Ievadiet datubāzes URL vai atsevišķi aizpildiet lietotājvārdu, paroli, serveri un datubāzes laukus.","ui.setup.database-connection.url":"Datubāzes URL","ui.setup.database-connection.validUrl":"Datubāzes URL ir nederīgs vai neizdevās izveidot savienojumu ar serveri.","ui.setup.database-connection.or":"vai","ui.setup.database-connection.user":"Lietotājvārds","ui.setup.database-connection.password":"Parole","ui.setup.database-connection.server":"Serveris (:ports)","ui.setup.database-connection.database":"Datubāzes nosaukums","ui.setup.database-connection.connected":"Successfully connected to database {database} on {server}.","ui.setup.database-connection.error":"Kļūda, savienojoties ar datubāzi.","ui.setup.database-connection.problem":"Contao ir atklājis problēmu ar jūsu datubāzes serveri.","ui.setup.database-connection.schemaTitle":"Database Schema","ui.setup.database-connection.migration":"There is one pending migration. | There are {count} pending migrations.","ui.setup.database-connection.schema":"There is one pending schema update. | There are {count} pending schema updates.","ui.setup.database-connection.noChanges":"Jūsu datubāzes shēma ir aktuāla.","ui.setup.database-connection.check":"Pārbaudīt datubāzi","ui.setup.database-connection.skip":"Izlaist","ui.setup.database-connection.save":"Saglabāt","ui.setup.database-connection.change":"Mainīt akreditācijas datus","ui.setup.database-connection.restoreTitle":"Database Import","ui.setup.database-connection.restoreText":"The theme you just installed contains a database backup. Restore the database to import theme data or skip this step to start with a blank Contao installation. | The theme you just installed contains multiple database backups. Select a backup file to import theme data or skip this step to start with a blank Contao installation.","ui.setup.database-connection.backup":"Backup current database before import","ui.setup.database-connection.backupWarning":"All data in database will be overwritten on import! Create a backup first if the database is not empty.","ui.setup.database-connection.restore":"Import theme database","ui.setup.database-connection.restoreOption":"Backup from {date} ({size})","ui.setup.database-connection.restored":"Your theme database was successfully imported. Continue to validate your database schema.","ui.setup.backend-user.success":"An admin account for the Contao back end was found in your database. Use the Contao back end to add more users.","ui.setup.backend-user.error":"Unable to retrieve user list. Check the console output for details.","ui.setup.backend-user.headline":"Aizmugures konts","ui.setup.backend-user.description":"To manage your website, you need to have at least one admin account for the Contao back end. Be aware that this account is not related to the Contao Manager.","ui.setup.backend-user.formTitle":"Izveidot kontu","ui.setup.backend-user.formText":"Please enter the details for the new back end account.","ui.setup.backend-user.username":"Lietotājvārds","ui.setup.backend-user.name":"Vārds","ui.setup.backend-user.email":"E-pasta adrese","ui.setup.backend-user.emailInvalid":"Lūdzu, ievadiet derīgu e-pasta adresi","ui.setup.backend-user.password":"Parole","ui.setup.backend-user.passwordPlaceholder":"min. 8 rakstzīmes","ui.setup.backend-user.passwordLength":"Lūdzu, ievadiet vismaz 8 rakstzīmes.","ui.setup.backend-user.create":"Pievienot kontu","ui.task.headline":"Fona uzdevums","ui.task.loading":"Ielādē informāciju ...","ui.task.created":"Ielādē informāciju ...","ui.task.active":"Lūdzu, pagaidiet, kamēr Contao Manager fona režīmā tiek veiktas uzdevumu operācijas.","ui.task.complete":"Visas operācijas ir veiksmīgi pabeigtas. Sīkāku informāciju skatiet konsoles izvades failā.","ui.task.aborting":"Lūdzu, uzgaidiet, kamēr tiek atceltas fona operācijas.","ui.task.stopped":"Dažas fona darbības tika atceltas. Lūdzu, pārbaudiet konsoles izvadi.","ui.task.error":"Fona operācija negaidīti apstājās. Lūdzu, pārbaudiet konsoles izvadi.","ui.task.failed":"Contao pārvaldniekam neizdevās palaist fona uzdevumu!","ui.task.failedDescription1":"Mēģinot izpildīt operācijas fonā, kaut kas notika nepareizi.","ui.task.failedDescription2":"Ja tas atkārtojas, iespējams, ka jūsu serveris netiek atbalstīts.","ui.task.reportProblem":"Ziņot par problēmu","ui.task.sponsor":"Composer Cloud sponsored by {sponsor}","ui.task.buttonAudit":"Atjaunināt datubāzi","ui.task.buttonClose":"Aizvērt","ui.task.buttonConfirm":"Apstiprināt & aizvērt","ui.task.buttonCancel":"Atcelt","ui.task.confirmCancel":"Vai noteikti vēlaties atcelt šo uzdevumu? Tas var atstāt jūsu Contao instalāciju bojātā stāvoklī!","ui.task.autoclose":"Aizvērt uzdevuma informāciju pēc izdošanās","ui.console.toggle":"Rādīt/slēpt konsoles izvadi","ui.console.showLog":"Rādīt pilnu konsoles žurnālu","ui.console.copyLog":"Kopēt žurnālu uz starpliktuvi","ui.migrate.headline":"Datubāzes atjauninājumi","ui.migrate.migrationsOnly":"(migrations only)","ui.migrate.schemaOnly":"(schema only)","ui.migrate.loading":"Lūdzu, uzgaidiet, mēs pārbaudām jūsu datubāzi...","ui.migrate.empty":"No pending migrations or schema updates found. Your database is up to date.","ui.migrate.emptyMigrations":"No pending migrations found. Make sure to also check for schema updates.","ui.migrate.emptySchema":"No pending schema updates found. Make sure to also check for migrations.","ui.migrate.pending":"Your database is not up to date. Please review the console output below and execute the changes.","ui.migrate.previousChanges":"A previous database migration was not confirmed.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.previousComplete":"A previous database migration was not confirmed, please review the console output below.\\nThere are no more pending changes.","ui.migrate.appliedChanges":"Database updates have been applied.\\nPlease review the console output below, then continue to see the next changes.","ui.migrate.appliedComplete":"Database updates have been applied.\\nThere are no more pending migrations or schema updates. Your database is up to date.","ui.migrate.problem":"Contao has detected a problem with your database server.\\nPlease review the console output below to find out what needs to be fixed. | Contao has detected problems with your database server.\\nPlease review the console output below to find out what needs to be fixed.","ui.migrate.warning":"Contao has detected a misconfiguration of your database server.\\nWarnings can be skipped temporarily, but should be fixed for optimal performance and data integrity.","ui.migrate.error":"The changes could not be applied. Your database might have been changed, please check again to retry.","ui.migrate.execute":"Izpildīt","ui.migrate.close":"Aizvērt","ui.migrate.confirm":"Apstiprināt & aizvērt","ui.migrate.cancel":"Atcelt","ui.migrate.continue":"Turpināt","ui.migrate.setup":"Uzstādīt","ui.migrate.skip":"Izlaist","ui.migrate.retry":"Pārbaudiet vēlreiz","ui.migrate.retryAll":"Atzīmēt visus","ui.migrate.withDeletes":"Izpildīt visas datubāzes izmaiņas, tostarp DROP vaicājumus.","ui.migrate.migrationTitle":"Datubāzu migrācija","ui.migrate.schemaTitle":"Shēmas atjauninājumi","ui.migrate.problemTitle":"Datubāzes problēmas","ui.migrate.warningTitle":"Datubāzes brīdinājumi","ui.migrate.addTable":"Pievienot tabulu {table}","ui.migrate.dropTable":"Nomest tabulu {table}","ui.migrate.addField":"Pievienot lauku {table}.{field}","ui.migrate.changeField":"Mainīt lauku {table}.{field}","ui.migrate.dropField":"Nomest lauku {table}.{field}","ui.migrate.createIndex":"Izveidot indeksu \\"{name}\\" uz {table}","ui.migrate.dropIndex":"Nomest indeksu \\"{name}\\" uz {table}","ui.widget.mandatory":"Šis lauks nedrīkst būt tukšs.","ui.widget.blankOption":"Lūdzu, izvēlieties ...","ui.widget.showPassword":"Rādīt paroli","ui.widget.hidePassword":"Slēpt paroli","ui.error.title":"HTTP pieprasījums \\"{metod} {url}\\" neizdevās.","ui.error.server500":"Looks like an unexpected error happened on your server. Please check the log files of your web server (Apache/Nginx) and the Contao Manager logs at \\"contao-manager/logs\\".","ui.error.response":"Serveris atbildēja ar statusa kodu {status}.","ui.error.moreLink":"Vairāk informācijas","ui.error.support":"Contao atbalsts","ui.footer.help":"Palīdzība","ui.footer.reportProblem":"Ziņot par problēmu","ui.navigation.discover":"Atklāt","ui.navigation.packages":"Pakotnes","ui.navigation.tools":"Rīki","ui.navigation.installTool":"Instalēšanas rīks","ui.navigation.backend":"Contao aizmugure","ui.navigation.debug":"Contao atkļūdošanas režīms","ui.navigation.logViewer":"Log Viewer","ui.navigation.phpinfo":"PHP informācija","ui.navigation.phpinfoLoading":"PHP informācijas ielāde...","ui.navigation.maintenance":"Uzturēšana","ui.navigation.rebuildCache":"Pārbūvēt kešatmiņu","ui.navigation.systemCheck":"Sistēmas pārbaude","ui.navigation.advanced":"Papildu","ui.navigation.logout":"Izrakstīties","ui.maintenance.database.title":"Database Migrations and Backups","ui.maintenance.database.description":"Database migrations ensure consistent data and table schemas.","ui.maintenance.database.migrations":"One pending database migration | {count} pending database migrations","ui.maintenance.database.schemaUpdates":"One pending schema update | {count} pending schema updates","ui.maintenance.database.error":"Atrasta datubāzes problēma.","ui.maintenance.database.warning":"Atrasti datubāzes brīdinājumi.","ui.maintenance.database.button":"Pārbaudīt datubāzi","ui.maintenance.database.migrationOnly":"Pārbaudīt tikai migrācijas","ui.maintenance.database.schemaOnly":"Pārbaudīt tikai shēmu","ui.maintenance.database.installTool":"Atvērt instalēšanas rīku","ui.maintenance.database.createBackup":"Create Backup","ui.maintenance.database.backupUnsupported":"Database backups are not supported by your Contao version.","ui.maintenance.database.backupList":"You have one database backup, created on {date}. | You have {count} database backups, the latest one was created on {date}.","ui.maintenance.database.backupEmpty":"You currently have no database backups.","ui.maintenance.rebuildCache.title":"Lietojumprogrammas kešatmiņa","ui.maintenance.rebuildCache.description":"Rebuilding the application cache is required after modifying any of the configuration files.","ui.maintenance.rebuildCache.rebuildProd":"Ražošanas kešatmiņas pārbūve","ui.maintenance.rebuildCache.rebuildDev":"Izstrādes kešatmiņas pārbūve","ui.maintenance.rebuildCache.clearProd":"Notīrīt ražošanas kešatmiņu","ui.maintenance.rebuildCache.clearDev":"Notīrīt izstrādes kešatmiņu","ui.maintenance.installTool.title":"Contao instalēšanas rīks","ui.maintenance.installTool.description":"Contao instalēšanas rīks tiek automātiski bloķēts, ja trīs reizes pēc kārtas ievadāt nepareizu paroli.","ui.maintenance.installTool.unlock":"Atbloķēt instalēšanas rīku","ui.maintenance.installTool.lock":"Bloķēt instalēšanas rīku","ui.maintenance.dumpAutoload.title":"Composer Class Loader","ui.maintenance.dumpAutoload.description":"The Composer autoloader is responsible for PHP class loading. The autoloader must be dumped after adding custom namespaces to the root composer.json.","ui.maintenance.dumpAutoload.button":"Dump Autoloader","ui.maintenance.composerInstall.title":"Composer Dependencies","ui.maintenance.composerInstall.description":"Composer dependencies are located in the {vendor} folder in your application root. Reinstalling the dependencies can be necessary after manipulation or manually uploading the {composerLock} file.","ui.maintenance.composerInstall.button":"Palaist instalētāju","ui.maintenance.composerInstall.update":"Palaist Composer jauninājumu","ui.maintenance.composerCache.title":"Composer kešatmiņa","ui.maintenance.composerCache.description":"Composer caches downloaded packages for improved performance. If you have issues like broken files, try to delete the Composer cache to force a new download.","ui.maintenance.composerCache.button":"Iztīrīt kešatmiņu","ui.maintenance.maintenanceMode.title":"Uzturēšanas režīms","ui.maintenance.maintenanceMode.description":"Putting Contao in maintenance mode will display a \\"503 Service Unavailable\\" template for the website.","ui.maintenance.maintenanceMode.enable":"Iespējot","ui.maintenance.maintenanceMode.disable":"Atspējot","ui.maintenance.debugMode.title":"Atkļūdošanas režīms","ui.maintenance.debugMode.description":"Activate the debug mode by setting a user and password for the {appDevPhp} entry point.","ui.maintenance.debugMode.descriptionJwt":"Activate the debug mode by setting the debug cookie for the current domain.","ui.maintenance.debugMode.activate":"Aktivizēt","ui.maintenance.debugMode.deactivate":"Deaktivizēt","ui.maintenance.debugMode.credentials":"Akreditācijas dati","ui.maintenance.debugMode.user":"Lūdzu, ievadiet atkļūdošanas režīma lietotājvārdu.","ui.maintenance.debugMode.password":"Lūdzu, ievadiet paroli atkļūdošanas režīmam.","ui.maintenance.opcodeCache.title":"Opcode Cache","ui.maintenance.opcodeCache.description":"Opcode caches PHP files on the web process for faster execution. It must be deleted under certain circumstances if files are not recognized after changing.","ui.maintenance.opcodeCache.button":"Saīsināt kešatmiņu","ui.maintenance.safeMode":"Nav pieejams drošajā režīmā","ui.maintenance.unsupported":"Neatbalsta jūsu Contao versija","ui.packages.updateButton":"Atjaunināt pakotnes","ui.packages.searchButton":"Meklēt pakotnes","ui.packages.searchPlaceholder":"Meklēt pakotnes ...","ui.packages.uploadOverlay":"Lai augšupielādētu, velciet un nometiet failus","ui.packages.uploadButton":"Augšupielādēt pakotnes","ui.packages.uploadMessage":"Jums ir viena neapstiprināta augšupielāde. | Jums ir {count} neapstiprināta/u augšupielāde/žu.","ui.packages.uploadApply":"Apstiprināt augšupielādi","ui.packages.uploadReset":"Dzēst augšupielādes","ui.packages.uploadIncomplete":"Šis datne netika augšupielādēts pilnībā. Lūdzu, noņemiet to un mēģiniet vēlreiz.","ui.packages.uploadDuplicate":"Šķiet, ka šī datne ir augšupielādēts vairākas reizes. Lūdzu, izdzēsiet dublikātus.","ui.packages.uploadInstalled":"Šis datne jau ir instalēta. Lūdzu, izdzēsiet dublikātus.","ui.packages.uploadUnsupported":"Uploads are not supported in your installation. Please make sure that the PHP ZIP extension is installed and update your dependencies.","ui.packages.changesMessage":"You have one unconfirmed change. | You have {count} unconfirmed changes.","ui.packages.changesDryrun":"Dry Run","ui.packages.changesApply":"Pielietot izmaiņas","ui.packages.changesApplyAll":"Atjaunināt visas pakotnes","ui.packages.changesDryrunAll":"Dry run all packages","ui.packages.changesReset":"Atiestatīt izmaiņas","ui.packages.changesReview":"Pārskatīt izmaiņas","ui.packagelist.loading":"Ielādē ...","ui.packagelist.uploads":"Augšupielādes","ui.packagelist.added":"Jaunas pakotnes","ui.packagelist.installed":"Instalētās pakotnes","ui.package.hintRevert":"Atgriezt izmaiņas","ui.package.hintNoupdate":"Neatjaunināt","ui.package.hintConstraint":"This package will be installed with constraint {constraint} when you apply the changes.","ui.package.hintConstraintBest":"This package will be installed in the best available version when you apply the changes.","ui.package.hintConstraintChange":"The constraint for this package will be changed from \\"{from}\\" to \\"{to}\\" when you apply the changes.","ui.package.hintConstraintUpdate":"Šī pakotne tiks atjaunināta, kad piemērosiet izmaiņas.","ui.package.hintAdded":"Šī pakotne tiks instalēta, kad piemērosiet izmaiņas.","ui.package.hintRemoved":"Šī pakotne tiks noņemta, kad piemērosiet izmaiņas.","ui.package.requiredTitle":"manuāli pievienots","ui.package.requiredText":"Šī pakotne ir nepieciešama jūsu composer.json, bet nav instalēta.","ui.package.removedTitle":"manuāli noņemts","ui.package.removedText":"Šī pakotne tika noņemta no jūsu composer.json.","ui.package.installed":"Pašlaik instalēts:","ui.package.version":"Versija {version}","ui.package.additionalDownloads":"{count} Lejupielāde | {count} Lejupielādes","ui.package.additionalStars":"{count} Star | {count} Stars","ui.package.editConstraint":"Rediģēt","ui.package.uploadConstraint":"This constraint is defined by the uploaded package.","ui.package.updateButton":"Jaunināt","ui.package.removeButton":"Noņemt","ui.package.installButton":"Pievienot pakotni","ui.package.installButtonShort":"Pievienot","ui.package.detailsButton":"Sīkāka informācija","ui.package.latestConstraint":"jaunākā versija","ui.package.update":"Pieejams atjauninājums","ui.package.updateLatest":"jaunākā versija","ui.package.updateAvailable":"Pieejama {versija}","ui.package.updateUnknown":"nezināma versija","ui.package.updateConstraint":"A newer version outside your version constraint is available.","ui.cloudStatus.headline":"Composer Resolver Cloud","ui.cloudStatus.version":"Versija {version}","ui.cloudStatus.waitingTime":"Gaidīšanas laiks","ui.cloudStatus.jobs":"Pašreizējie darbi","ui.cloudStatus.workers":"Strādnieki","ui.cloudStatus.approx":"{minutes} min","ui.cloudStatus.none":"nav","ui.cloudStatus.short":"apt. {minutes} min","ui.cloudStatus.long":"apt. {minutes} min {seconds} sek","ui.cloudStatus.error":"Unable to fetch the status of the Composer Resolver Cloud. It might be down for maintenance or experience issues.","ui.cloudStatus.button":"Mākoņa statuss","ui.cloudStatus.refresh":"Atjaunināt mākoņa statusu","ui.log-viewer.loading":"Ielādē ...","ui.log-viewer.empty":"There are no log files on your server.","ui.log-viewer.reload":"Reload","ui.log-viewer.file":"Log file","ui.log-viewer.channel":"Channel","ui.log-viewer.channelTitle":"The channel this message was logged to.","ui.log-viewer.level":"Level","ui.log-viewer.levelTitle":"Severity of the log message.","ui.log-viewer.timeHeader":"Time","ui.log-viewer.messageHeader":"Message","ui.log-viewer.showContext":"Show Context","ui.log-viewer.hideContext":"Hide Context","ui.log-viewer.showExtra":"Show Extra","ui.log-viewer.hideExtra":"Hide Extra","ui.log-viewer.more":"Load more …","ui.log-viewer.download":"Download","ui.log-viewer.downloadTitle":"Download file \\"{file}\\"","ui.log-viewer.prodEnvironment":"Production Environment","ui.log-viewer.devEnvironment":"Development Environment (Debug Mode)"}')}}]);(self["webpackChunkcontao_manager"]=self["webpackChunkcontao_manager"]||[]).push([[710],{1710:function(e,t,r){ /*! JSZip v3.10.1 - A JavaScript class for generating and reading zip files (c) 2009-2016 Stuart Knightley Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. JSZip uses the library pako released under the MIT license : https://github.com/nodeca/pako/blob/main/LICENSE */ !function(t){e.exports=t()}((function(){return function e(t,r,n){function i(a,o){if(!r[a]){if(!t[a]){var h=void 0;if(!o&&h)return require(a,!0);if(s)return s(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=r[a]={exports:{}};t[a][0].call(l.exports,(function(e){var r=t[a][1][e];return i(r||e)}),l,l.exports,e,t,r,n)}return r[a].exports}for(var s=void 0,a=0;a>2,o=(3&t)<<4|r>>4,h=1>6:64,u=2>4,r=(15&a)<<4|(o=s.indexOf(e.charAt(u++)))>>2,n=(3&o)<<6|(h=s.indexOf(e.charAt(u++))),c[l++]=t,64!==o&&(c[l++]=r),64!==h&&(c[l++]=n);return c}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",(function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")})),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils"),i=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var s=i,a=n+r;e^=-1;for(var o=n;o>>8^s[255&(e^t[o])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var s=i,a=n+r;e^=-1;for(var o=n;o>>8^s[255&(e^t.charCodeAt(o))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=function(t){e.push({data:t,meta:e.meta})}},r.compressWorker=function(e){return new h("Deflate",e)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function n(e,t){var r,n="";for(r=0;r>>=8;return n}function i(e,t,r,i,a,l){var f,c,d=e.file,p=e.compression,m=l!==o.utf8encode,_=s.transformTo("string",l(d.name)),g=s.transformTo("string",o.utf8encode(d.name)),b=d.comment,v=s.transformTo("string",l(b)),y=s.transformTo("string",o.utf8encode(b)),w=g.length!==d.name.length,k=y.length!==b.length,x="",S="",z="",C=d.dir,E=d.date,A={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(A.crc32=e.crc32,A.compressedSize=e.compressedSize,A.uncompressedSize=e.uncompressedSize);var I=0;t&&(I|=8),m||!w&&!k||(I|=2048);var O=0,B=0;C&&(O|=16),"UNIX"===a?(B=798,O|=function(e,t){var r=e;return e||(r=t?16893:33204),(65535&r)<<16}(d.unixPermissions,C)):(B=20,O|=function(e){return 63&(e||0)}(d.dosPermissions)),f=E.getUTCHours(),f<<=6,f|=E.getUTCMinutes(),f<<=5,f|=E.getUTCSeconds()/2,c=E.getUTCFullYear()-1980,c<<=4,c|=E.getUTCMonth()+1,c<<=5,c|=E.getUTCDate(),w&&(S=n(1,1)+n(h(_),4)+g,x+="up"+n(S.length,2)+S),k&&(z=n(1,1)+n(h(v),4)+y,x+="uc"+n(z.length,2)+z);var R="";return R+="\n\0",R+=n(I,2),R+=p.magic,R+=n(f,2),R+=n(c,2),R+=n(A.crc32,4),R+=n(A.compressedSize,4),R+=n(A.uncompressedSize,4),R+=n(_.length,2),R+=n(x.length,2),{fileRecord:u.LOCAL_FILE_HEADER+R+_+x,dirRecord:u.CENTRAL_FILE_HEADER+n(B,2)+R+n(v.length,2)+"\0\0\0\0"+n(O,4)+n(i,4)+_+x+v}}var s=e("../utils"),a=e("../stream/GenericWorker"),o=e("../utf8"),h=e("../crc32"),u=e("../signature");function l(e,t,r,n){a.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}s.inherits(l,a),l.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,a.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},l.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=i(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},l.prototype.closedSource=function(e){this.accumulate=!1;var t=this.streamFiles&&!e.file.dir,r=i(e,t,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),t)this.push({data:function(e){return u.DATA_DESCRIPTOR+n(e.crc32,4)+n(e.compressedSize,4)+n(e.uncompressedSize,4)}(e),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},l.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),h=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new h(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then((function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()}),(function(e){t.error(e)}))}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),a=e("../base64"),o=e("../support"),h=e("../external"),u=null;if(o.nodestream)try{u=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function l(e,t){return new h.Promise((function(r,i){var s=[],o=e._internalType,h=e._outputType,u=e._mimeType;e.on("data",(function(e,r){s.push(e),t&&t(r)})).on("error",(function(e){s=[],i(e)})).on("end",(function(){try{var e=function(e,t,r){switch(e){case"blob":return n.newBlob(n.transformTo("arraybuffer",t),r);case"base64":return a.encode(t);default:return n.transformTo(e,t)}}(h,function(e,t){var r,n=0,i=null,s=0;for(r=0;r>>6:(r<65536?t[a++]=224|r>>>12:(t[a++]=240|r>>>18,t[a++]=128|r>>>12&63),t[a++]=128|r>>>6&63),t[a++]=128|63&r);return t}(e)},r.utf8decode=function(e){return i.nodebuffer?n.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,i,s,a=e.length,h=new Array(2*a);for(t=r=0;t>10&1023,h[r++]=56320|1023&i)}return h.length!==r&&(h.subarray?h=h.subarray(0,r):h.length=r),n.applyFromCharCode(h)}(e=n.transformTo(i.uint8array?"uint8array":"array",e))},n.inherits(u,a),u.prototype.processChunk=function(e){var t=n.transformTo(i.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(i.uint8array){var s=t;(t=new Uint8Array(s.length+this.leftOver.length)).set(this.leftOver,0),t.set(s,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var a=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0||0===r?t:r+o[e[r]]>t?r:t}(t),h=t;a!==t.length&&(i.uint8array?(h=t.subarray(0,a),this.leftOver=t.subarray(a,t.length)):(h=t.slice(0,a),this.leftOver=t.slice(a,t.length))),this.push({data:r.utf8decode(h),meta:e.meta})},u.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:r.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},r.Utf8DecodeWorker=u,n.inherits(l,a),l.prototype.processChunk=function(e){this.push({data:r.utf8encode(e.data),meta:e.meta})},r.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,r){"use strict";var n=e("./support"),i=e("./base64"),s=e("./nodejsUtils"),a=e("./external");function o(e){return e}function h(e,t){for(var r=0;r>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var e=n(this.extraFields[1].value);this.uncompressedSize===i.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===i.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===i.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===i.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4>>6:(r<65536?t[a++]=224|r>>>12:(t[a++]=240|r>>>18,t[a++]=128|r>>>12&63),t[a++]=128|r>>>6&63),t[a++]=128|63&r);return t},r.buf2binstring=function(e){return h(e,e.length)},r.binstring2buf=function(e){for(var t=new n.Buf8(e.length),r=0,i=t.length;r>10&1023,u[n++]=56320|1023&i)}return h(u,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0||0===r?t:r+a[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,i){var s=n,a=i+r;e^=-1;for(var o=i;o>>8^s[255&(e^t[o])];return-1^e}},{}],46:[function(e,t,r){"use strict";var n,i=e("../utils/common"),s=e("./trees"),a=e("./adler32"),o=e("./crc32"),h=e("./messages"),u=0,l=4,f=0,c=-2,d=-1,p=4,m=2,_=8,g=9,b=286,v=30,y=19,w=2*b+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(e,t){return e.msg=h[t],t}function T(e){return(e<<1)-(4e.avail_out&&(r=e.avail_out),0!==r&&(i.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function N(e,t){s._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,F(e.strm)}function U(e,t){e.pending_buf[e.pending++]=t}function P(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function L(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,h=e.strstart>e.w_size-z?e.strstart-(e.w_size-z):0,u=e.window,l=e.w_mask,f=e.prev,c=e.strstart+S,d=u[s+a-1],p=u[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(u[(r=t)+a]===p&&u[r+a-1]===d&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&sh&&0!=--i);return a<=e.lookahead?a:e.lookahead}function j(e){var t,r,n,s,h,u,l,f,c,d,p=e.w_size;do{if(s=e.window_size-e.lookahead-e.strstart,e.strstart>=p+(p-z)){for(i.arraySet(e.window,e.window,p,p,0),e.match_start-=p,e.strstart-=p,e.block_start-=p,t=r=e.hash_size;n=e.head[--t],e.head[t]=p<=n?n-p:0,--r;);for(t=r=p;n=e.prev[--t],e.prev[t]=p<=n?n-p:0,--r;);s+=p}if(0===e.strm.avail_in)break;if(u=e.strm,l=e.window,f=e.strstart+e.lookahead,c=s,d=void 0,d=u.avail_in,c=x)for(h=e.strstart-e.insert,e.ins_h=e.window[h],e.ins_h=(e.ins_h<=x&&(e.ins_h=(e.ins_h<=x)if(n=s._tr_tally(e,e.strstart-e.match_start,e.match_length-x),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=x){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<=x&&(e.ins_h=(e.ins_h<=x&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-x,n=s._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-x),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(j(e),0===e.lookahead&&t===u)return A;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,N(e,!1),0===e.strm.avail_out))return A;if(e.strstart-e.block_start>=e.w_size-z&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===l?(N(e,!0),0===e.strm.avail_out?O:B):(e.strstart>e.block_start&&(N(e,!1),e.strm.avail_out),A)})),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(e,t){return Y(e,t,_,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?c:(e.state.gzhead=t,f):c},r.deflate=function(e,t){var r,i,a,h;if(!e||!e.state||5>8&255),U(i,i.gzhead.time>>16&255),U(i,i.gzhead.time>>24&255),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&(U(i,255&i.gzhead.extra.length),U(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(e.adler=o(e.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=69):(U(i,0),U(i,0),U(i,0),U(i,0),U(i,0),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,3),i.status=E);else{var d=_+(i.w_bits-8<<4)<<8;d|=(2<=i.strategy||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(d|=32),d+=31-d%31,i.status=E,P(i,d),0!==i.strstart&&(P(i,e.adler>>>16),P(i,65535&e.adler)),e.adler=1}if(69===i.status)if(i.gzhead.extra){for(a=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>a&&(e.adler=o(e.adler,i.pending_buf,i.pending-a,a)),F(e),a=i.pending,i.pending!==i.pending_buf_size));)U(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>a&&(e.adler=o(e.adler,i.pending_buf,i.pending-a,a)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=73)}else i.status=73;if(73===i.status)if(i.gzhead.name){a=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>a&&(e.adler=o(e.adler,i.pending_buf,i.pending-a,a)),F(e),a=i.pending,i.pending===i.pending_buf_size)){h=1;break}h=i.gzindexa&&(e.adler=o(e.adler,i.pending_buf,i.pending-a,a)),0===h&&(i.gzindex=0,i.status=91)}else i.status=91;if(91===i.status)if(i.gzhead.comment){a=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>a&&(e.adler=o(e.adler,i.pending_buf,i.pending-a,a)),F(e),a=i.pending,i.pending===i.pending_buf_size)){h=1;break}h=i.gzindexa&&(e.adler=o(e.adler,i.pending_buf,i.pending-a,a)),0===h&&(i.status=103)}else i.status=103;if(103===i.status&&(i.gzhead.hcrc?(i.pending+2>i.pending_buf_size&&F(e),i.pending+2<=i.pending_buf_size&&(U(i,255&e.adler),U(i,e.adler>>8&255),e.adler=0,i.status=E)):i.status=E),0!==i.pending){if(F(e),0===e.avail_out)return i.last_flush=-1,f}else if(0===e.avail_in&&T(t)<=T(r)&&t!==l)return R(e,-5);if(666===i.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==i.lookahead||t!==u&&666!==i.status){var p=2===i.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(j(e),0===e.lookahead)){if(t===u)return A;break}if(e.match_length=0,r=s._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===l?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(i,t):3===i.strategy?function(e,t){for(var r,n,i,a,o=e.window;;){if(e.lookahead<=S){if(j(e),e.lookahead<=S&&t===u)return A;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=x&&0e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=x?(r=s._tr_tally(e,1,e.match_length-x),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=s._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===l?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(i,t):n[i.level].func(i,t);if(p!==O&&p!==B||(i.status=666),p===A||p===O)return 0===e.avail_out&&(i.last_flush=-1),f;if(p===I&&(1===t?s._tr_align(i):5!==t&&(s._tr_stored_block(i,0,0,!1),3===t&&(D(i.head),0===i.lookahead&&(i.strstart=0,i.block_start=0,i.insert=0))),F(e),0===e.avail_out))return i.last_flush=-1,f}return t!==l?f:i.wrap<=0?1:(2===i.wrap?(U(i,255&e.adler),U(i,e.adler>>8&255),U(i,e.adler>>16&255),U(i,e.adler>>24&255),U(i,255&e.total_in),U(i,e.total_in>>8&255),U(i,e.total_in>>16&255),U(i,e.total_in>>24&255)):(P(i,e.adler>>>16),P(i,65535&e.adler)),F(e),0=r.w_size&&(0===o&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),d=new i.Buf8(r.w_size),i.arraySet(d,t,p-r.w_size,r.w_size,0),t=d,p=r.w_size),h=e.avail_in,u=e.next_in,l=e.input,e.avail_in=p,e.next_in=0,e.input=t,j(r);r.lookahead>=x;){for(n=r.strstart,s=r.lookahead-(x-1);r.ins_h=(r.ins_h<>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(d&(1<>>=y,p-=y),p<15&&(d+=z[n++]<>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<>>=y,p-=y,(y=s-a)>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function _(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new n.Buf16(320),this.work=new n.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function g(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=c,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new n.Buf32(d),t.distcode=t.distdyn=new n.Buf32(p),t.sane=1,t.back=-1,l):f}function b(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,g(e)):f}function v(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15=a.wsize?(n.arraySet(a.window,t,r-a.wsize,a.wsize,0),a.wnext=0,a.whave=a.wsize):(i<(s=a.wsize-a.wnext)&&(s=i),n.arraySet(a.window,t,r-i,s,a.wnext),(i-=s)?(n.arraySet(a.window,t,r-i,i,0),a.wnext=i,a.whave=a.wsize):(a.wnext+=s,a.wnext===a.wsize&&(a.wnext=0),a.whave>>8&255,r.check=s(r.check,j,2,0),w=y=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&y)<<8)+(y>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&y)){e.msg="unknown compression method",r.mode=30;break}if(w-=4,F=8+(15&(y>>>=4)),0===r.wbits)r.wbits=F;else if(F>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<>8&1),512&r.flags&&(j[0]=255&y,j[1]=y>>>8&255,r.check=s(r.check,j,2,0)),w=y=0,r.mode=3;case 3:for(;w<32;){if(0===b)break e;b--,y+=d[_++]<>>8&255,j[2]=y>>>16&255,j[3]=y>>>24&255,r.check=s(r.check,j,4,0)),w=y=0,r.mode=4;case 4:for(;w<16;){if(0===b)break e;b--,y+=d[_++]<>8),512&r.flags&&(j[0]=255&y,j[1]=y>>>8&255,r.check=s(r.check,j,2,0)),w=y=0,r.mode=5;case 5:if(1024&r.flags){for(;w<16;){if(0===b)break e;b--,y+=d[_++]<>>8&255,r.check=s(r.check,j,2,0)),w=y=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(b<(C=r.length)&&(C=b),C&&(r.head&&(F=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),n.arraySet(r.head.extra,d,_,C,F)),512&r.flags&&(r.check=s(r.check,d,C,_)),b-=C,_+=C,r.length-=C),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===b)break e;for(C=0;F=d[_+C++],r.head&&F&&r.length<65536&&(r.head.name+=String.fromCharCode(F)),F&&C>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;w<32;){if(0===b)break e;b--,y+=d[_++]<>>=7&w,w-=7&w,r.mode=27;break}for(;w<3;){if(0===b)break e;b--,y+=d[_++]<>>=1)){case 0:r.mode=14;break;case 1:if(S(r),r.mode=20,6!==t)break;y>>>=2,w-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}y>>>=2,w-=2;break;case 14:for(y>>>=7&w,w-=7&w;w<32;){if(0===b)break e;b--,y+=d[_++]<>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&y,w=y=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(C=r.length){if(b>>=5,w-=5,r.ndist=1+(31&y),y>>>=5,w-=5,r.ncode=4+(15&y),y>>>=4,w-=4,286>>=3,w-=3}for(;r.have<19;)r.lens[Z[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,U={bits:r.lenbits},N=o(0,r.lens,0,19,r.lencode,0,r.work,U),r.lenbits=U.bits,N){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,B=65535&L,!((I=L>>>24)<=w);){if(0===b)break e;b--,y+=d[_++]<>>=I,w-=I,r.lens[r.have++]=B;else{if(16===B){for(P=I+2;w>>=I,w-=I,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}F=r.lens[r.have-1],C=3+(3&y),y>>>=2,w-=2}else if(17===B){for(P=I+3;w>>=I)),y>>>=3,w-=3}else{for(P=I+7;w>>=I)),y>>>=7,w-=7}if(r.have+C>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;C--;)r.lens[r.have++]=F}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,U={bits:r.lenbits},N=o(h,r.lens,0,r.nlen,r.lencode,0,r.work,U),r.lenbits=U.bits,N){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,U={bits:r.distbits},N=o(u,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,U),r.distbits=U.bits,N){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=b&&258<=v){e.next_out=g,e.avail_out=v,e.next_in=_,e.avail_in=b,r.hold=y,r.bits=w,a(e,x),g=e.next_out,p=e.output,v=e.avail_out,_=e.next_in,d=e.input,b=e.avail_in,y=r.hold,w=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;O=(L=r.lencode[y&(1<>>16&255,B=65535&L,!((I=L>>>24)<=w);){if(0===b)break e;b--,y+=d[_++]<>R)])>>>16&255,B=65535&L,!(R+(I=L>>>24)<=w);){if(0===b)break e;b--,y+=d[_++]<>>=R,w-=R,r.back+=R}if(y>>>=I,w-=I,r.back+=I,r.length=B,0===O){r.mode=26;break}if(32&O){r.back=-1,r.mode=12;break}if(64&O){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&O,r.mode=22;case 22:if(r.extra){for(P=r.extra;w>>=r.extra,w-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;O=(L=r.distcode[y&(1<>>16&255,B=65535&L,!((I=L>>>24)<=w);){if(0===b)break e;b--,y+=d[_++]<>R)])>>>16&255,B=65535&L,!(R+(I=L>>>24)<=w);){if(0===b)break e;b--,y+=d[_++]<>>=R,w-=R,r.back+=R}if(y>>>=I,w-=I,r.back+=I,64&O){e.msg="invalid distance code",r.mode=30;break}r.offset=B,r.extra=15&O,r.mode=24;case 24:if(r.extra){for(P=r.extra;w>>=r.extra,w-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===v)break e;if(C=x-v,r.offset>C){if((C=r.offset-C)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}E=C>r.wnext?(C-=r.wnext,r.wsize-C):r.wnext-C,C>r.length&&(C=r.length),A=r.window}else A=p,E=g-r.offset,C=r.length;for(vb?(y=U[P+f[S]],T[D+f[S]]):(y=96,0),d=1<>I)+(p-=d)]=v<<24|y<<16|w|0,0!==p;);for(d=1<>=1;if(0!==d?(R&=d-1,R+=d):R=0,S++,0==--F[x]){if(x===C)break;x=t[r+f[S]]}if(E>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>m-r?(e.bi_buf|=t<>m-e.bi_valid,e.bi_valid+=r-m):(e.bi_buf|=t<>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(p+1),a=0;for(n=1;n<=p;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,m=t.stat_desc.extra_base,_=t.stat_desc.max_length,g=0;for(s=0;s<=p;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r>=7;n>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return i;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return s;for(t=32;t>>3,(o=e.static_len+3+7>>>3)<=a&&(a=o)):a=o=r+5,r+4<=a&&-1!==t?J(e,t,r,n):4===e.strategy||o===a?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,g,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,n){(function(e){!function(e,t){"use strict";if(!e.setImmediate){var r,n,i,s,a=1,o={},h=!1,u=e.document,l=Object.getPrototypeOf&&Object.getPrototypeOf(e);l=l&&l.setTimeout?l:e,r="[object process]"==={}.toString.call(e.process)?function(e){process.nextTick((function(){c(e)}))}:function(){if(e.postMessage&&!e.importScripts){var t=!0,r=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=r,t}}()?(s="setImmediate$"+Math.random()+"$",e.addEventListener?e.addEventListener("message",d,!1):e.attachEvent("onmessage",d),function(t){e.postMessage(s+t,"*")}):e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){i.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(n=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,n.removeChild(t),t=null},n.appendChild(t)}):function(e){setTimeout(c,0,e)},l.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1?e("div",{staticClass:"container"},[e("vueper-slides",{staticClass:"no-shadow",attrs:{autoplay:"",infinite:"",duration:1e4,"slide-ratio":1/4,"visible-slides":2,breakpoints:{630:{slideRatio:.5,visibleSlides:1}},bullets:!1,touchable:!1,"arrows-outside":!1}},t._l(t.ads,(function(n){return e("vueper-slide",{key:n.objectID,scopedSlots:t._u([{key:"content",fn:function(){return[n.package?e("a",{attrs:{href:"#",title:n.title},on:{click:function(e){return e.stopPropagation(),t.setCurrent(n.package)}}},[e("img",{attrs:{src:n.image,alt:""}})]):e("a",{attrs:{href:n.url,title:n.title,target:"_blank",rel:"noreferrer noopener"}},[e("img",{attrs:{src:n.image,alt:""}})])]},proxy:!0}],null,!0)})})),1)],1):t._e(),"de"===t.$i18n.locale?e("div",{staticClass:"link"},[e("a",{attrs:{href:"https://contao.org/de/anzeigen-erweiterungsliste.html",target:"_blank"}},[t._v(t._s(t.$t("ui.discover.advertisement")))])]):e("div",{staticClass:"link"},[e("a",{attrs:{href:"https://contao.org/en/extension-ads.html",target:"_blank"}},[t._v(t._s(t.$t("ui.discover.advertisement")))])])])},l=[],f=n(429),p={components:{VueperSlides:f.VueperSlides,VueperSlide:f.VueperSlide},computed:{...(0,o.aH)("algolia",["ads"])},methods:{...(0,o.PY)("packages/details",["setCurrent"])}},d=p,h=n(1656),v=(0,h.A)(d,u,l,!1,null,"5f0fe3e2",null),m=v.exports,g=function(){var t=this,e=t._self._c;return e("section",{staticClass:"package-sorting",on:{click:function(e){t.open=!t.open}}},[e("label",{staticClass:"package-sorting__label"},[t._v(t._s(t.$t("ui.discover.sortBy")))]),e("ul",{staticClass:"package-sorting__group",class:{"package-sorting__group--open":t.open}},[t._l(t.sortOptions,(function(n,r){return[e("li",{key:n,staticClass:"package-sorting__item",class:{"package-sorting__item--active":t.sorting===n,"package-sorting__item--open":t.open},attrs:{title:t.$t(`ui.discover.sort${r}Title`)},on:{click:function(e){return t.sortBy(n)}}},[t._v(" "+t._s(t.$t(`ui.discover.sort${r}`))+" ")])]}))],2)])},y=[],b={mixins:[a.A],data:()=>({open:!1,sortOptions:{Released:"released",Latest:"latest",Favers:"favers",Downloads:"downloads"}})},_=b,w=(0,h.A)(_,g,y,!1,null,null,null),x=w.exports,k=n(5678),S=n(9036),O={mixins:[a.A],components:{LoadingButton:S.A,SearchInput:k.A,SearchSorting:x,AdBanner:m,LoadingSpinner:c.A,DiscoverPackage:s.A},props:{wrapper:{required:!0},hideThemes:{type:Boolean,default:!1}},data:()=>({offline:!1,searching:!1,extensionCount:0,results:null,hasMore:!1}),computed:{...(0,o.aH)("algolia",["discover"])},methods:{...(0,o.PY)("packages/details",["setCurrent"]),async searchPackages(){this.searching=!0,this.offline=!1;try{const t={hitsPerPage:10*this.pages};this.hideThemes&&(t.facetFilters=["type:-contao-theme"]),this.query?t.query=this.query:this.sorting&&(t.sorting=this.sorting);const e=await this.$store.dispatch("algolia/findPackages",t);if(this.hasMore=e.nbPages>1,0===e.nbHits)return void(this.results={});const n={};e.hits.forEach((t=>{n[t.name]=t})),this.results=n}catch(t){this.offline=!0}this.searching=!1},async getOnline(){this.searching=!0,this.offline=!1,await this.$store.dispatch("algolia/discover"),this.searching=!1},async openSearch(t){this.results=null,await this.sortBy(t)}},watch:{sorting(){this.searchPackages()},query(){this.results=null,this.searchPackages()},pages(){this.searchPackages()}},created(){this.$watch(this.$i18n.locale,(()=>{this.isSearching&&this.searchPackages()}))},async mounted(){const t={hitsPerPage:0,attributesToRetrieve:null,attributesToHighlight:null,analytics:!1};this.hideThemes&&(t.facetFilters=["type:-contao-theme"]),this.$store.dispatch("algolia/findPackages",t).then((t=>{this.extensionCount=t.nbHits}),(()=>{})),this.isSearching&&this.searchPackages()}},E=O,C=(0,h.A)(E,r,i,!1,null,null,null),T=C.exports},9185:function(t,e,n){"use strict";n.d(e,{A:function(){return f}});var r=function(){var t=this,e=t._self._c;return e("article",{staticClass:"discover-package"},[t.data.abandoned?e("div",{staticClass:"discover-package__abandoned",attrs:{title:t.abandonedText}},[t._v(t._s(t.$t("ui.package.abandoned")))]):t._e(),e("package-logo",{staticClass:"discover-package__icon",class:{"discover-package__icon--fallback":!t.data.logo},attrs:{src:t.data.logo}}),e("div",{staticClass:"discover-package__details"},[e("h1",{staticClass:"discover-package__headline",class:{"discover-package__headline--fallback":!t.data.logo},attrs:{title:t.data.name!==t.data.title?t.data.name:""}},[t._l(t.title.split("%%"),(function(n,r){return[r%2?e("em",{key:r},[t._v(t._s(n))]):[t._v(t._s(n))]]}))],2),e("p",{staticClass:"discover-package__description",class:{"discover-package__description--fallback":!t.data.logo}},[t._l(t.description.split("%%"),(function(n,r){return[r%2?e("em",{key:r},[t._v(t._s(n))]):[t._v(t._s(n))]]}))],2),e("div",{staticClass:"discover-package__more"},[e("p",{staticClass:"discover-package__counts"},[t.data.private?e("span",{staticClass:"discover-package__count discover-package__count--private",attrs:{title:t.$t("ui.package.privateTitle")}},[t._v(t._s(t.$t("ui.package.private")))]):t._e(),t.data.updated?e("span",{staticClass:"discover-package__count discover-package__count--updated"},[t._v(t._s(t._f("datimFormat")(t.data.updated,!1,"short")))]):t._e(),t.data.downloads?e("span",{staticClass:"discover-package__count discover-package__count--downloads"},[t._v(t._s(t._f("numberFormat")(t.data.downloads)))]):t._e(),t.data.favers?e("span",{staticClass:"discover-package__count discover-package__count--favers"},[t._v(t._s(t._f("numberFormat")(t.data.favers)))]):t._e()]),e("div",{staticClass:"discover-package__actions"},[e("details-button",{attrs:{small:"",name:t.data.name}}),t._t("default")],2)])])],1)},i=[],o=n(8732),a=n(1159),s={components:{PackageLogo:o.A,DetailsButton:a.A},props:{data:Object},computed:{title:t=>t.data._highlightResult?t.data._highlightResult.title.value:t.data.title,description:t=>t.data._highlightResult?t.data._highlightResult.description.value:t.data.description,abandonedText:t=>!0===t.data.abandoned?t.$t("ui.package.abandonedText"):t.$t("ui.package.abandonedReplace",{replacement:t.data.abandoned})}},c=s,u=n(1656),l=(0,u.A)(c,r,i,!1,null,null,null),f=l.exports},6354:function(t,e,n){"use strict";n.d(e,{A:function(){return u}});var r=function(){var t=this,e=t._self._c;return e("ul",{class:t.cssClass},t._l(t.items,(function(n,r){return e("li",{key:r,staticClass:"link-menu__item"},[e("a",{staticClass:"link-menu__action",attrs:{href:n.href,target:n.target},on:{click:e=>t.click(e,n)}},[t._v(t._s(n.label))])])})),0)},i=[],o={props:{items:{type:Array,required:!0},color:String,align:String,valign:String},computed:{cssClass:t=>({"link-menu":!0,[`link-menu--${t.color}`]:!!t.color,[`link-menu--align-${t.align}`]:!!t.align,[`link-menu--valign-${t.valign}`]:!!t.valign})},methods:{click(t,e){e.action&&(t.preventDefault(),e.action(e))}}},a=o,s=n(1656),c=(0,s.A)(a,r,i,!1,null,null,null),u=c.exports},9036:function(t,e,n){"use strict";n.d(e,{A:function(){return l}});var r=function(){var t=this,e=t._self._c;return e(t.link?"a":"button",{tag:"component",class:t.buttonClass,attrs:{type:t.link?null:t.submit?"submit":"button",href:t.link,disabled:t.disabled||t.loading},on:{click:t.click,mouseover:e=>t.$emit("mouseover",e),mouseout:e=>t.$emit("mouseout",e)}},[e("span",{class:t.slotClass},[t._t("default")],2),e("loading-spinner",{directives:[{name:"show",rawName:"v-show",value:t.loading,expression:"loading"}]})],1)},i=[],o=n(7648),a={components:{LoadingSpinner:o.A},props:{href:String,to:[String,Object],color:String,icon:String,inline:Boolean,loading:Boolean,disabled:Boolean,submit:Boolean},computed:{buttonClass:t=>({"loading-button":!0,"widget-button":!0,"widget-button--inline":t.inline,[`widget-button--${t.color}`]:t.color,disabled:t.link&&(t.loading||t.disabled)}),slotClass:t=>({loading:t.loading,[`widget-button--${t.icon}`]:t.icon}),link:t=>t.href||t.to&&t.$router.resolve(t.to).href||null},methods:{click(t){this.submit||this.link||(t.preventDefault(),this.$emit("click",t))}}},s=a,c=n(1656),u=(0,c.A)(s,r,i,!1,null,null,null),l=u.exports},7648:function(t,e,n){"use strict";n.d(e,{A:function(){return u}});var r=function(){var t=this,e=t._self._c;return t.horizontal?e("div",{staticClass:"loader",on:{transitionend:function(t){t.stopPropagation()}}},[e("div",{staticClass:"loader__item loader__item--20"}),e("div",{staticClass:"loader__item loader__item--40"}),e("div",{staticClass:"loader__item loader__item--60"}),e("div",{staticClass:"loader__item loader__item--80"}),e("div",{staticClass:"loader__item loader__item--100"}),t._t("default")],2):e("div",{staticClass:"loader",on:{transitionend:function(t){t.stopPropagation()}}},[t._m(0),t._t("default")],2)},i=[function(){var t=this,e=t._self._c;return e("div",{staticClass:"sk-circle"},[e("div",{staticClass:"sk-circle1 sk-child"}),e("div",{staticClass:"sk-circle2 sk-child"}),e("div",{staticClass:"sk-circle3 sk-child"}),e("div",{staticClass:"sk-circle4 sk-child"}),e("div",{staticClass:"sk-circle5 sk-child"}),e("div",{staticClass:"sk-circle6 sk-child"}),e("div",{staticClass:"sk-circle7 sk-child"}),e("div",{staticClass:"sk-circle8 sk-child"}),e("div",{staticClass:"sk-circle9 sk-child"}),e("div",{staticClass:"sk-circle10 sk-child"}),e("div",{staticClass:"sk-circle11 sk-child"}),e("div",{staticClass:"sk-circle12 sk-child"})])}],o={props:{horizontal:Boolean}},a=o,s=n(1656),c=(0,s.A)(a,r,i,!1,null,null,null),u=c.exports},4253:function(t,e,n){"use strict";n.d(e,{A:function(){return K}});var r=function(){var t=this,e=t._self._c;return e("popup-overlay",{attrs:{"popup-class":t.popupClass},on:{clear:function(e){return t.clearCurrent()}}},[e("div",{staticClass:"package-popup__headline"},[t.hasPrevious?e("button",{staticClass:"package-popup__button package-popup__button--previous",attrs:{title:t.$t("ui.package-details.previous")},on:{click:function(e){return t.$router.go(-1)}}},[e("svg",{attrs:{xmlns:"http://www.w3.org/2000/svg",fill:"#fff",width:"24",height:"24",viewBox:"0 0 24 24"}},[e("path",{attrs:{d:"M0 0h24v24H0z",fill:"none"}}),e("path",{attrs:{d:"M21 11H6.83l3.58-3.59L9 6l-6 6 6 6 1.41-1.41L6.83 13H21z"}})])]):t._e(),t._v(" "+t._s(t.data.name)+" "),e("button",{staticClass:"package-popup__button package-popup__button--close",attrs:{title:t.$t("ui.package-details.close")},on:{click:function(e){return t.clearCurrent()}}},[e("svg",{attrs:{height:"24",viewBox:"0 0 24 24",width:"24",fill:"#fff",xmlns:"http://www.w3.org/2000/svg"}},[e("path",{attrs:{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"}}),e("path",{attrs:{d:"M0 0h24v24H0z",fill:"none"}})])])]),t.metadata&&t.metadata.hasOwnProperty("name")?[e("div",{staticClass:"package-popup__summary"},[e("package-logo",{attrs:{"component-class":"package-popup__icon",src:t.metadata.logo}}),e("div",{staticClass:"package-popup__text"},[e("h1",{staticClass:"package-popup__title"},[t._v(t._s(t.metadata.title||t.data.name))]),t.authors?e("p",{staticClass:"package-popup__authors"},[t._v(" "+t._s(t.$t("ui.package-details.authors"))+" "),t._l(t.authors,(function(n,r){return[n.homepage?e("a",{key:r,staticClass:"package-popup__author",attrs:{href:n.homepage,target:"_blank",rel:"noreferrer noopener"}},[t._v(t._s(n.name))]):e("span",{key:r,staticClass:"package-popup__author"},[t._v(t._s(n.name))])]}))],2):t._e(),e("p",{staticClass:"package-popup__statistics"},[t.metadata.private?e("span",{staticClass:"package-popup__stats package-popup__stats--private",attrs:{title:t.$t("ui.package.privateTitle")}},[t._v(t._s(t.$t("ui.package.private")))]):t._e(),t.metadata.updated?e("span",{staticClass:"package-popup__stats package-popup__stats--updated"},[t._v(t._s(t._f("datimFormat")(t.metadata.updated,!1)))]):t._e(),t.metadata.downloads>0?e("span",{staticClass:"package-popup__stats package-popup__stats--downloads"},[t._v(t._s(t._f("numberFormat")(t.metadata.downloads)))]):t._e(),t.metadata.favers>0?e("span",{staticClass:"package-popup__stats package-popup__stats--favers"},[t._v(t._s(t._f("numberFormat")(t.metadata.favers)))]):t._e(),e("more-links",{attrs:{name:t.metadata.name,homepage:t.metadata.homepage,support:Object.assign({},t.metadata.support),metadata:t.metadata.metadata,"hide-packagist":t.metadata.private}})],1)]),e("div",{staticClass:"package-popup__actions"},[t._t("package-actions",(function(){return[t.metadata&&t.metadata.homepage?e("a",{staticClass:"widget-button widget-button--primary widget-button--link",attrs:{target:"_blank",href:t.metadata.homepage}},[t._v(t._s(t.$t("ui.package.homepage")))]):t.metadata.private?t._e():e("a",{staticClass:"widget-button widget-button--primary widget-button--link",attrs:{target:"_blank",href:`https://packagist.org/packages/${t.data.name}`}},[t._v(t._s(t.$t("ui.package-details.packagist")))])]}),null,{data:t.metadata})],2)],1),e("ul",{staticClass:"package-popup__tabs"},[e("details-tab",{attrs:{"show-empty":"",links:!1,active:""===t.tab},on:{click:function(e){return t.setTab("")}}},[t._v(t._s(t.$t("ui.package-details.tabDescription")))]),t.metadata.features?e("details-tab",{attrs:{highlight:"",links:t.metadata.features,active:"features"===t.tab},on:{click:function(e){return t.setTab("features")}}},[t._v(t._s(t.$t("ui.package-details.tabFeatures")))]):t._e(),e("details-tab",{attrs:{highlight:"",active:"suggest"===t.tab,links:t.metadata.suggest},on:{click:function(e){return t.setTab("suggest")}}},[t._v(t._s(t.$t("ui.package-details.tabSuggest")))]),e("details-tab",{attrs:{"show-empty":"",active:"require"===t.tab,links:t.metadata.require},on:{click:function(e){return t.setTab("require")}}},[t._v(t._s(t.$t("ui.package-details.tabRequire")))]),e("details-tab",{attrs:{"show-empty":"",active:"conflict"===t.tab,links:t.metadata.conflict},on:{click:function(e){return t.setTab("conflict")}}},[t._v(t._s(t.$t("ui.package-details.tabConflict")))]),t.dependents?e("details-tab",{attrs:{active:"dependents"===t.tab,links:t.dependents},on:{click:function(e){return t.setTab("dependents")}}},[t._v(t._s(t.$t("ui.package-details.tabDependents")))]):t._e()],1),e("details-content",{directives:[{name:"show",rawName:"v-show",value:""===t.tab,expression:"tab === ''"}]},[t.metadata.abandoned?e("div",{staticClass:"package-popup__abandoned"},[!0===t.metadata.abandoned?[t._v(t._s(t.$t("ui.package.abandonedText")))]:e("i18n",{attrs:{tag:!1,path:"ui.package.abandonedReplace"},scopedSlots:t._u([{key:"replacement",fn:function(){return[e("router-link",{attrs:{to:{query:{p:t.metadata.abandoned}}}},[t._v(t._s(t.metadata.abandoned))])]},proxy:!0}],null,!1,1454401874)})],2):t._e(),t.metadata.funding?e("package-funding",{staticClass:"package-popup__funding",attrs:{items:t.metadata.funding}}):t._e(),t._t("package-update"),t.metadata.latest?e("p",[e("strong",[t._v(t._s(t.$t("ui.package-details.latest"))+":")]),t._v(" "+t._s(t.metadata.latest.version)+" ("+t._s(t.$t("ui.package-details.released"))+" "+t._s(t._f("datimFormat")(t.metadata.latest.time,"short","long"))+")")]):t._e(),t.metadata.license?e("p",[e("strong",[t._v(t._s(t.$t("ui.package-details.license"))+":")]),t._v(" "+t._s(t.license))]):t._e(),e("p",{staticClass:"package-popup__description"},[t._v(t._s(t.metadata.description))])],2),t.metadata.features?e("details-content",{directives:[{name:"show",rawName:"v-show",value:"features"===t.tab,expression:"tab === 'features'"}],attrs:{links:t.metadata.features},scopedSlots:t._u([{key:"actions",fn:function({name:e}){return t._t("features-actions",null,null,{name:e})}}],null,!0)}):t._e(),e("details-content",{directives:[{name:"show",rawName:"v-show",value:"suggest"===t.tab,expression:"tab === 'suggest'"}],attrs:{links:t.metadata.suggest},scopedSlots:t._u([{key:"actions",fn:function({name:e}){return t._t("suggest-actions",null,null,{name:e})}}],null,!0)}),e("details-content",{directives:[{name:"show",rawName:"v-show",value:"require"===t.tab,expression:"tab === 'require'"}],attrs:{links:t.metadata.require},scopedSlots:t._u([{key:"actions",fn:function({name:e}){return t._t("require-actions",null,null,{name:e})}}],null,!0)}),e("details-content",{directives:[{name:"show",rawName:"v-show",value:"conflict"===t.tab,expression:"tab === 'conflict'"}],attrs:{links:t.metadata.conflict},scopedSlots:t._u([{key:"actions",fn:function({name:e}){return t._t("conflict-actions",null,null,{name:e})}}],null,!0)}),t.dependents?e("details-content",{directives:[{name:"show",rawName:"v-show",value:"dependents"===t.tab,expression:"tab === 'dependents'"}],attrs:{links:t.dependents},scopedSlots:t._u([{key:"actions",fn:function({name:e}){return t._t("dependents-actions",null,null,{name:e})}}],null,!0)}):t._e()]:e("div",{staticClass:"package-popup__loader"},[e("loading-spinner",{attrs:{horizontal:""}}),e("p",[t._v(t._s(t.$t("ui.package-details.loading")))])],1)],2)},i=[],o=n(5353),a=n(9376),s=n(416),c=n(8732),u=n(7648),l=function(){var t=this,e=t._self._c;return t.linkItems.length?e("div",{staticClass:"link-more"},[e("button",{on:{click:t.toggle}},[t._v(t._s(t.$t("ui.package-details.more")))]),t.visible?e("div",{ref:"menu",staticClass:"link-more__menu",attrs:{tabindex:"-1"},on:{blur:t.close,click:t.close}},[e("link-menu",{attrs:{items:t.linkItems,color:"contao"}})],1):t._e()]):t._e()},f=[],p=n(6354),d={components:{LinkMenu:p.A},props:{name:String,homepage:String,support:Object,metadata:String,hidePackagist:Boolean},data:()=>({visible:!1}),computed:{linkItems(){const t=[];return this.homepage&&t.push({label:this.$t("ui.package.homepage"),href:this.homepage,target:"_blank"}),this.name&&!this.hidePackagist&&t.push({label:this.$t("ui.package-details.packagist"),href:`https://packagist.org/packages/${this.name}`,target:"_blank"}),this.support&&Object.keys(this.support).forEach((e=>{const n=this.$te(`ui.package-details.support_${e}`)?this.$t(`ui.package-details.support_${e}`):e;"email"===e?t.push({label:n,href:`mailto:${this.support[e]}`}):t.push({label:n,href:this.support[e],target:"_blank"})})),this.metadata&&t.push({label:this.$t("ui.package-details.metadata"),href:this.metadata,target:"_blank"}),t}},methods:{open(){this.visible=!0,this.$nextTick((()=>this.$refs.menu.focus()))},close(){this.$refs.menu.blur(),setTimeout((()=>{this.visible=!1}),300)},toggle(){this.visible?this.close():this.open()}}},h=d,v=n(1656),m=(0,v.A)(h,l,f,!1,null,null,null),g=m.exports,y=function(){var t=this,e=t._self._c;return t.showEmpty||t.count>0?e("li",{staticClass:"package-popup__tab",class:{"package-popup__tab--active":t.active}},[e("button",{attrs:{disabled:0===t.count&&!1!==t.links},on:{click:function(e){return t.$emit("click")}}},[t._t("default"),!1!==t.links?e("span",{class:{"package-popup__pill":!0,"package-popup__pill--highlight":t.highlight&&t.count>0}},[t._v(t._s(t.count))]):t._e()],2)]):t._e()},b=[],_={props:{active:Boolean,showEmpty:Boolean,highlight:Boolean,links:[Object,Array,Boolean]},computed:{count(){return this.links?this.links instanceof Array?this.links.length:Object.values(this.links).length:0}}},w=_,x=(0,v.A)(w,y,b,!1,null,null,null),k=x.exports,S=function(){var t=this,e=t._self._c;return e("div",{staticClass:"package-popup__tabcontent"},[t._t("default",(function(){return[t.links?e("div",{staticClass:"package-popup__packagelist"},[t._l(t.iterableLinks,(function(n,r){return[t._t("links",(function(){return[e("package-link",{key:r,attrs:{name:r,text:n}},[t._t("actions",null,null,{name:r})],2)]}),null,{name:r,text:n})]}))],2):t._e()]}))],2)},O=[],E=function(){var t=this,e=t._self._c;return e("article",{staticClass:"package-link",class:{"package-link--limit":!t.text}},[e("div",{staticClass:"package-link__details"},[e("p",{staticClass:"package-link__name",attrs:{title:t.name}},[t._v(t._s(t.metadata&&t.metadata.title||t.name))]),e("p",{staticClass:"package-link__text"},[t._v(t._s(t.text||t.metadata&&t.metadata.description))])]),e("div",{staticClass:"package-link__actions"},[t._t("default"),e("details-button",{attrs:{small:"",name:t.name}})],2)])},C=[],T=n(1159),A={mixins:[s.A],components:{DetailsButton:T.A},props:{name:String,text:String},computed:{data:t=>({name:t.name})}},j=A,I=(0,v.A)(j,E,C,!1,null,null,null),R=I.exports,P={components:{PackageLink:R},props:{links:[Object,Array]},computed:{iterableLinks(){if(this.links instanceof Array){const t={};return this.links.forEach((e=>{t[e]=null})),t}return this.links}}},$=P,L=(0,v.A)($,S,O,!1,null,null,null),N=L.exports,D=n(3777),M=function(){var t=this,e=t._self._c;return t.items&&t.items.length?e("div",{staticClass:"package-funding"},[e("span",[t._v(t._s(t.$t("ui.package-details.funding")))]),t._l(t.items,(function(n,r){return["github"===n.type?e("a",{key:r,attrs:{href:t.githubUrl(n),target:"_blank",rel:"noreferrer noopener"}},[t._v("GitHub")]):"tidelift"===n.type?e("a",{key:r,attrs:{href:n.url,target:"_blank",rel:"noreferrer noopener"}},[t._v("Tidelift")]):"patreon"===n.type?e("a",{key:r,attrs:{href:n.url,target:"_blank",rel:"noreferrer noopener"}},[t._v("Patreon")]):"opencollective"===n.type?e("a",{key:r,attrs:{href:n.url,target:"_blank",rel:"noreferrer noopener"}},[t._v("OpenCollective")]):e("a",{key:r,attrs:{href:n.url,target:"_blank",rel:"noreferrer noopener"}},[t._v(t._s(n.url.replace(/^https?:\/\/(www.)?([^/]+).*$/,"$2")))])]}))],2):t._e()},U=[],F={name:"PackageFunding",props:{items:{type:Array,required:!0}},computed:{githubUrl:()=>t=>t.url.replace(/^https:\/\/github.com\/([^/]+)$/,"https://github.com/sponsors/$1")}},q=F,B=(0,v.A)(q,M,U,!1,null,"7ac04620",null),H=B.exports,V={mixins:[s.A],components:{PopupOverlay:D.A,MoreLinks:g,LoadingSpinner:u.A,PackageLogo:c.A,PackageFunding:H,DetailsTab:k,DetailsContent:N},props:{local:{type:Object},dependents:{type:Object}},data:()=>({appTitle:"",links:[]}),computed:{...(0,o.aH)("packages/details",["current"]),...(0,o.L8)("packages/details",["hasPrevious"]),tab:t=>String(t.$route.hash).substr(1),popupClass(){return{"package-popup":!0}},requireCount:t=>t.metadata.require?Object.values(t.metadata.require).length:0,featuresCount:t=>t.metadata.features?t.metadata.features.length:0,suggestCount:t=>t.metadata.suggest?Object.values(t.metadata.suggest).length:0,conflictCount:t=>t.metadata.conflict?Object.values(t.metadata.conflict).length:0,exists:t=>t.metadata,data:t=>t.local||{name:t.current},authors:t=>t.metadata.authors&&t.metadata.authors.length?t.metadata.authors.filter((t=>!!t.name)):null,license:t=>t.metadata.license?t.metadata.license instanceof Array?t.metadata.license.join(", "):t.metadata.license:"–"},methods:{...(0,o.PY)("packages/details",["clearCurrent","popCurrent"]),setTab(t){this.$router.replace({query:this.$route.query,hash:t,append:!0})},updatePage(){let t=`${this.current} - ${this.appTitle}`,e="";this.metadata&&(this.metadata.title&&(t=`${this.metadata.title} (${this.current}) - ${this.appTitle}`),e=this.metadata.description||""),document.title=t,document.head.querySelector('meta[name="description"]').setAttribute("content",e)},addLink(t,e,n=null){const r=new URL(location.pathname,location);r.search=t;const i=document.createElement("link");i.setAttribute("rel",e),i.setAttribute("href",r.toString()),n&&i.setAttribute("hrefLang",n),document.head.appendChild(i),this.links.push(i)}},watch:{current(){this.updatePage()},exists(t){t||this.clearCurrent()},metadata(){this.updatePage()}},created(){this.appTitle=document.title},mounted(){document.head.querySelector('meta[name="robots"]').setAttribute("content","index,follow"),this.updatePage(),this.addLink(`?p=${this.current}&_locale=${this.$i18n.locale}`,"canonical"),Object.keys(a.A).forEach((t=>{this.addLink(`?p=${this.current}&_locale=${t}`,"alternate",t)}))},beforeDestroy(){document.title=this.appTitle,document.head.querySelector('meta[name="description"]').setAttribute("content",""),this.links.forEach((t=>{document.head.removeChild(t)}))}},z=V,G=(0,v.A)(z,r,i,!1,null,null,null),K=G.exports},8732:function(t,e,n){"use strict";n.d(e,{A:function(){return u}});var r=function(){var t=this,e=t._self._c;return e("div",[e("figure",{class:t.computedClass},[t.src?e("img",{attrs:{src:t.src,loading:"lazy",alt:""}}):[e("svg",{attrs:{xmlns:"http://www.w3.org/2000/svg",height:"24",viewBox:"0 0 24 24",width:"24"}},[e("path",{attrs:{d:"M0 0h24v24H0z",fill:"none"}}),e("path",{attrs:{d:"M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5c0-.83-.67-1.5-1.5-1.5S14 7.67 14 8.5s.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2v7zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4v-7z"}})])]],2)])},i=[],o={props:{src:String,componentClass:String},computed:{computedClass:t=>({"package-logo--fallback":!t.src,[t.componentClass]:!0,[`${t.componentClass}--fallback`]:!t.src})}},a=o,s=n(1656),c=(0,s.A)(a,r,i,!1,null,"622648b4",null),u=c.exports},3777:function(t,e,n){"use strict";n.d(e,{A:function(){return u}});var r=function(){var t=this,e=t._self._c;return e("div",{staticClass:"popup-overlay",on:{click:function(e){return t.clearCurrent()}}},[e("div",{ref:"popup",class:t.popupClass,on:{click:function(t){t.stopPropagation()}}},[t._t("default")],2)])},i=[],o={props:{popupClass:[String,Object]},methods:{clearCurrent(){this.$emit("clear")}}},a=o,s=n(1656),c=(0,s.A)(a,r,i,!1,null,null,null),u=c.exports},5678:function(t,e,n){"use strict";n.d(e,{A:function(){return l}});var r=function(){var t=this,e=t._self._c;return e("section",{staticClass:"search-bar"},[e("input",{ref:"search",staticClass:"search-bar__input",attrs:{id:"search",type:"text",placeholder:t.placeholder,disabled:t.disabled,autocomplete:"off"},domProps:{value:t.query},on:{input:t.searchInput,keypress:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"esc",27,e.key,["Esc","Escape"])?null:(e.preventDefault(),t.stopSearch.apply(null,arguments))}}}),t.query?e("button",{staticClass:"search-bar__button search-bar__button--stop",on:{click:t.stopSearch}},[e("svg",{attrs:{height:"24",viewBox:"0 0 24 24",width:"24",fill:"#737373",xmlns:"http://www.w3.org/2000/svg"}},[e("path",{attrs:{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"}}),e("path",{attrs:{d:"M0 0h24v24H0z",fill:"none"}})])]):e("button",{staticClass:"search-bar__button search-bar__button--start",on:{click:function(e){return t.$refs.search.focus()}}},[e("svg",{attrs:{fill:"#737373",height:"24",viewBox:"0 0 24 24",width:"24",xmlns:"http://www.w3.org/2000/svg"}},[e("path",{attrs:{d:"M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}}),e("path",{attrs:{d:"M0 0h24v24H0z",fill:"none"}})])])])},i=[],o=n(1881),a={mixins:[o.A],props:{placeholder:String,disabled:Boolean},methods:{searchInput(t){this.startSearch(t.target.value)}}},s=a,c=n(1656),u=(0,c.A)(s,r,i,!1,null,null,null),l=u.exports},1656:function(t,e,n){"use strict";function r(t,e,n,r,i,o,a,s){var c,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),o&&(u._scopeId="data-v-"+o),a?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),i&&i.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},u._ssrRegister=c):i&&(c=s?function(){i.call(this,(u.functional?this.parent:this).$root.$options.shadowRoot)}:i),c)if(u.functional){u._injectStyles=c;var l=u.render;u.render=function(t,e){return c.call(e),l(t,e)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,c):[c]}return{exports:t,options:u}}n.d(e,{A:function(){return r}})},7294:function(t,e,n){function r(){return!("undefined"===typeof window||!window.process||"renderer"!==window.process.type)||("undefined"!==typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!==typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!==typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!==typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var i=0,o=0;t[0].replace(/%[a-zA-Z%]/g,(function(t){"%%"!==t&&(i++,"%c"===t&&(o=i))})),t.splice(o,0,r)}}function o(){return"object"===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function s(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!==typeof process&&"env"in process&&(t={NODE_ENV:"production",BASE_URL:""}.DEBUG),t}function c(){try{return window.localStorage}catch(t){}}e=t.exports=n(8761),e.log=o,e.formatArgs=i,e.save=a,e.load=s,e.useColors=r,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:c(),e.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(s())},8761:function(t,e,n){var r;function i(t){var n,r=0;for(n in t)r=(r<<5)-r+t.charCodeAt(n),r|=0;return e.colors[Math.abs(r)%e.colors.length]}function o(t){function n(){if(n.enabled){var t=n,i=+new Date,o=i-(r||i);t.diff=o,t.prev=r,t.curr=i,r=i;for(var a=new Array(arguments.length),s=0;s100)){var a=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(a){var s=parseFloat(a[1]),c=(a[2]||"ms").toLowerCase();switch(c){case"years":case"year":case"yrs":case"yr":case"y":return s*o;case"days":case"day":case"d":return s*i;case"hours":case"hour":case"hrs":case"hr":case"h":return s*r;case"minutes":case"minute":case"mins":case"min":case"m":return s*n;case"seconds":case"second":case"secs":case"sec":case"s":return s*e;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return s;default:return}}}}function s(t){return t>=i?Math.round(t/i)+"d":t>=r?Math.round(t/r)+"h":t>=n?Math.round(t/n)+"m":t>=e?Math.round(t/e)+"s":t+"ms"}function c(t){return u(t,i,"day")||u(t,r,"hour")||u(t,n,"minute")||u(t,e,"second")||t+" ms"}function u(t,e,n){if(!(t0)return a(t);if("number"===n&&!1===isNaN(t))return e.long?c(t):s(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},4818:function(t,e,n){t.exports=u;var r=n(2369),i=n(9038),o=n(3901),a=n(3141),s=n(6698),c=n(2806);function u(){a.apply(this,arguments)}function l(){var t="Not implemented in this environment.\nIf you feel this is a mistake, write to support@algolia.com";throw new c.AlgoliaSearchError(t)}s(u,a),u.prototype.deleteIndex=function(t,e){return this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(t),hostType:"write",callback:e})},u.prototype.moveIndex=function(t,e,n){var r={operation:"move",destination:e};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(t)+"/operation",body:r,hostType:"write",callback:n})},u.prototype.copyIndex=function(t,e,n,r){var i={operation:"copy",destination:e},o=r;if("function"===typeof n)o=n;else if(Array.isArray(n)&&n.length>0)i.scope=n;else if("undefined"!==typeof n)throw new Error("the scope given to `copyIndex` was not an array with settings, synonyms or rules");return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(t)+"/operation",body:i,hostType:"write",callback:o})},u.prototype.getLogs=function(t,e,r){var i=n(6428),o={};return"object"===typeof t?(o=i(t),r=e):0===arguments.length||"function"===typeof t?r=t:1===arguments.length||"function"===typeof e?(r=e,o.offset=t):(o.offset=t,o.length=e),void 0===o.offset&&(o.offset=0),void 0===o.length&&(o.length=10),this._jsonRequest({method:"GET",url:"/1/logs?"+this._getSearchParams(o,""),hostType:"read",callback:r})},u.prototype.listIndexes=function(t,e){var n="";return void 0===t||"function"===typeof t?e=t:n="?page="+t,this._jsonRequest({method:"GET",url:"/1/indexes"+n,hostType:"read",callback:e})},u.prototype.initIndex=function(t){return new r(this,t)},u.prototype.initAnalytics=function(t){var e=n(9734);return e(this.applicationID,this.apiKey,t)},u.prototype.listUserKeys=i((function(t){return this.listApiKeys(t)}),o("client.listUserKeys()","client.listApiKeys()")),u.prototype.listApiKeys=function(t){return this._jsonRequest({method:"GET",url:"/1/keys",hostType:"read",callback:t})},u.prototype.getUserKeyACL=i((function(t,e){return this.getApiKey(t,e)}),o("client.getUserKeyACL()","client.getApiKey()")),u.prototype.getApiKey=function(t,e){return this._jsonRequest({method:"GET",url:"/1/keys/"+t,hostType:"read",callback:e})},u.prototype.deleteUserKey=i((function(t,e){return this.deleteApiKey(t,e)}),o("client.deleteUserKey()","client.deleteApiKey()")),u.prototype.deleteApiKey=function(t,e){return this._jsonRequest({method:"DELETE",url:"/1/keys/"+t,hostType:"write",callback:e})},u.prototype.restoreApiKey=function(t,e){return this._jsonRequest({method:"POST",url:"/1/keys/"+t+"/restore",hostType:"write",callback:e})},u.prototype.addUserKey=i((function(t,e,n){return this.addApiKey(t,e,n)}),o("client.addUserKey()","client.addApiKey()")),u.prototype.addApiKey=function(t,e,r){var i=n(2253),o="Usage: client.addApiKey(arrayOfAcls[, params, callback])";if(!i(t))throw new Error(o);1!==arguments.length&&"function"!==typeof e||(r=e,e=null);var a={acl:t};return e&&(a.validity=e.validity,a.maxQueriesPerIPPerHour=e.maxQueriesPerIPPerHour,a.maxHitsPerQuery=e.maxHitsPerQuery,a.indexes=e.indexes,a.description=e.description,e.queryParameters&&(a.queryParameters=this._getSearchParams(e.queryParameters,"")),a.referers=e.referers),this._jsonRequest({method:"POST",url:"/1/keys",body:a,hostType:"write",callback:r})},u.prototype.addUserKeyWithValidity=i((function(t,e,n){return this.addApiKey(t,e,n)}),o("client.addUserKeyWithValidity()","client.addApiKey()")),u.prototype.updateUserKey=i((function(t,e,n,r){return this.updateApiKey(t,e,n,r)}),o("client.updateUserKey()","client.updateApiKey()")),u.prototype.updateApiKey=function(t,e,r,i){var o=n(2253),a="Usage: client.updateApiKey(key, arrayOfAcls[, params, callback])";if(!o(e))throw new Error(a);2!==arguments.length&&"function"!==typeof r||(i=r,r=null);var s={acl:e};return r&&(s.validity=r.validity,s.maxQueriesPerIPPerHour=r.maxQueriesPerIPPerHour,s.maxHitsPerQuery=r.maxHitsPerQuery,s.indexes=r.indexes,s.description=r.description,r.queryParameters&&(s.queryParameters=this._getSearchParams(r.queryParameters,"")),s.referers=r.referers),this._jsonRequest({method:"PUT",url:"/1/keys/"+t,body:s,hostType:"write",callback:i})},u.prototype.startQueriesBatch=i((function(){this._batch=[]}),o("client.startQueriesBatch()","client.search()")),u.prototype.addQueryInBatch=i((function(t,e,n){this._batch.push({indexName:t,query:e,params:n})}),o("client.addQueryInBatch()","client.search()")),u.prototype.sendQueriesBatch=i((function(t){return this.search(this._batch,t)}),o("client.sendQueriesBatch()","client.search()")),u.prototype.batch=function(t,e){var r=n(2253),i="Usage: client.batch(operations[, callback])";if(!r(t))throw new Error(i);return this._jsonRequest({method:"POST",url:"/1/indexes/*/batch",body:{requests:t},hostType:"write",callback:e})},u.prototype.assignUserID=function(t,e){if(!t.userID||!t.cluster)throw new c.AlgoliaSearchError("You have to provide both a userID and cluster",t);return this._jsonRequest({method:"POST",url:"/1/clusters/mapping",hostType:"write",body:{cluster:t.cluster},callback:e,headers:{"x-algolia-user-id":t.userID}})},u.prototype.assignUserIDs=function(t,e){if(!t.userIDs||!t.cluster)throw new c.AlgoliaSearchError("You have to provide both an array of userIDs and cluster",t);return this._jsonRequest({method:"POST",url:"/1/clusters/mapping/batch",hostType:"write",body:{cluster:t.cluster,users:t.userIDs},callback:e})},u.prototype.getTopUserID=function(t){return this._jsonRequest({method:"GET",url:"/1/clusters/mapping/top",hostType:"read",callback:t})},u.prototype.getUserID=function(t,e){if(!t.userID)throw new c.AlgoliaSearchError("You have to provide a userID",{debugData:t});return this._jsonRequest({method:"GET",url:"/1/clusters/mapping/"+t.userID,hostType:"read",callback:e})},u.prototype.listClusters=function(t){return this._jsonRequest({method:"GET",url:"/1/clusters",hostType:"read",callback:t})},u.prototype.listUserIDs=function(t,e){return this._jsonRequest({method:"GET",url:"/1/clusters/mapping",body:t,hostType:"read",callback:e})},u.prototype.removeUserID=function(t,e){if(!t.userID)throw new c.AlgoliaSearchError("You have to provide a userID",{debugData:t});return this._jsonRequest({method:"DELETE",url:"/1/clusters/mapping",hostType:"write",callback:e,headers:{"x-algolia-user-id":t.userID}})},u.prototype.searchUserIDs=function(t,e){return this._jsonRequest({method:"POST",url:"/1/clusters/mapping/search",body:t,hostType:"read",callback:e})},u.prototype.setPersonalizationStrategy=function(t,e){return this._jsonRequest({method:"POST",url:"/1/recommendation/personalization/strategy",body:t,hostType:"write",callback:e})},u.prototype.getPersonalizationStrategy=function(t){return this._jsonRequest({method:"GET",url:"/1/recommendation/personalization/strategy",hostType:"read",callback:t})},u.prototype.destroy=l,u.prototype.enableRateLimitForward=l,u.prototype.disableRateLimitForward=l,u.prototype.useSecuredAPIKey=l,u.prototype.disableSecuredAPIKey=l,u.prototype.generateSecuredApiKey=l,u.prototype.getSecuredApiKeyRemainingValidity=l},3141:function(t,e,n){t.exports=u;var r=n(2806),i=n(6944),o=n(546),a=n(1259),s=500,c={NODE_ENV:"production",BASE_URL:""}.RESET_APP_DATA_TIMER&&parseInt({NODE_ENV:"production",BASE_URL:""}.RESET_APP_DATA_TIMER,10)||12e4;function u(t,e,i){var o=n(7294)("algoliasearch"),a=n(6428),s=n(2253),c=n(9987),u="Usage: algoliasearch(applicationID, apiKey, opts)";if(!0!==i._allowEmptyCredentials&&!t)throw new r.AlgoliaSearchError("Please provide an application ID. "+u);if(!0!==i._allowEmptyCredentials&&!e)throw new r.AlgoliaSearchError("Please provide an API key. "+u);this.applicationID=t,this.apiKey=e,this.hosts={read:[],write:[]},i=i||{},this._timeouts=i.timeouts||{connect:1e3,read:2e3,write:3e4},i.timeout&&(this._timeouts.connect=this._timeouts.read=this._timeouts.write=i.timeout);var f=i.protocol||"https:";if(/:$/.test(f)||(f+=":"),"http:"!==f&&"https:"!==f)throw new r.AlgoliaSearchError("protocol must be `http:` or `https:` (was `"+i.protocol+"`)");if(this._checkAppIdData(),i.hosts)s(i.hosts)?(this.hosts.read=a(i.hosts),this.hosts.write=a(i.hosts)):(this.hosts.read=a(i.hosts.read),this.hosts.write=a(i.hosts.write));else{var p=c(this._shuffleResult,(function(e){return t+"-"+e+".algolianet.com"})),d=(!1===i.dsn?"":"-dsn")+".algolia.net";this.hosts.read=[this.applicationID+d].concat(p),this.hosts.write=[this.applicationID+".algolia.net"].concat(p)}this.hosts.read=c(this.hosts.read,l(f)),this.hosts.write=c(this.hosts.write,l(f)),this.extraHeaders={},this.cache=i._cache||{},this._ua=i._ua,this._useCache=!(void 0!==i._useCache&&!i._cache)||i._useCache,this._useRequestCache=this._useCache&&i._useRequestCache,this._useFallback=void 0===i.useFallback||i.useFallback,this._setTimeout=i._setTimeout,o("init done, %j",this)}function l(t){return function(e){return t+"//"+e.toLowerCase()}}function f(t){if(void 0===Array.prototype.toJSON)return JSON.stringify(t);var e=Array.prototype.toJSON;delete Array.prototype.toJSON;var n=JSON.stringify(t);return Array.prototype.toJSON=e,n}function p(t){var e,n,r=t.length;while(0!==r)n=Math.floor(Math.random()*r),r-=1,e=t[r],t[r]=t[n],t[n]=e;return t}function d(t){var e={};for(var n in t){var r;if(Object.prototype.hasOwnProperty.call(t,n))r="x-algolia-api-key"===n||"x-algolia-application-id"===n?"**hidden for security purposes**":t[n],e[n]=r}return e}u.prototype.initIndex=function(t){return new o(this,t)},u.prototype.setExtraHeader=function(t,e){this.extraHeaders[t.toLowerCase()]=e},u.prototype.getExtraHeader=function(t){return this.extraHeaders[t.toLowerCase()]},u.prototype.unsetExtraHeader=function(t){delete this.extraHeaders[t.toLowerCase()]},u.prototype.addAlgoliaAgent=function(t){var e="; "+t;-1===this._ua.indexOf(e)&&(this._ua+=e)},u.prototype._jsonRequest=function(t){this._checkAppIdData();var e,o,a,c=n(7294)("algoliasearch:"+t.url),u=t.additionalUA||"",l=t.cache,p=this,h=0,v=!1,m=p._useFallback&&p._request.fallback&&t.fallback;this.apiKey.length>s&&void 0!==t.body&&(void 0!==t.body.params||void 0!==t.body.requests)?(t.body.apiKey=this.apiKey,a=this._computeRequestHeaders({additionalUA:u,withApiKey:!1,headers:t.headers})):a=this._computeRequestHeaders({additionalUA:u,headers:t.headers}),void 0!==t.body&&(e=f(t.body)),c("request start");var g=[];function y(n,i){p._checkAppIdData();var s=new Date;if(p._useCache&&!p._useRequestCache&&(o=t.url),p._useCache&&!p._useRequestCache&&e&&(o+="_body_"+i.body),b(!p._useRequestCache,l,o)){c("serving response from cache");var _=l[o];return p._promise.resolve({body:JSON.parse(_),responseText:_})}if(h>=p.hosts[t.hostType].length)return!m||v?(c("could not get any response"),p._promise.reject(new r.AlgoliaSearchError("Cannot connect to the AlgoliaSearch API. Send an email to support@algolia.com to report and resolve the issue. Application id was: "+p.applicationID,{debugData:g}))):(c("switching to fallback"),h=0,i.method=t.fallback.method,i.url=t.fallback.url,i.jsonBody=t.fallback.body,i.jsonBody&&(i.body=f(i.jsonBody)),a=p._computeRequestHeaders({additionalUA:u,headers:t.headers}),i.timeouts=p._getTimeoutsForRequest(t.hostType),p._setHostIndexByType(0,t.hostType),v=!0,y(p._request.fallback,i));var w=p._getHostByType(t.hostType),x=w+i.url,k={body:i.body,jsonBody:i.jsonBody,method:i.method,headers:a,timeouts:i.timeouts,debug:c,forceAuthHeaders:i.forceAuthHeaders};return c("method: %s, url: %s, headers: %j, timeouts: %d",k.method,x,k.headers,k.timeouts),n===p._request.fallback&&c("using fallback"),n.call(p,x,k).then(S,O);function S(t){var n=t&&t.body&&t.body.message&&t.body.status||t.statusCode||t&&t.body&&200;c("received response: statusCode: %s, computed statusCode: %d, headers: %j",t.statusCode,n,t.headers);var u=2===Math.floor(n/100),f=new Date;if(g.push({currentHost:w,headers:d(a),content:e||null,contentLength:void 0!==e?e.length:null,method:i.method,timeouts:i.timeouts,url:i.url,startTime:s,endTime:f,duration:f-s,statusCode:n}),u)return p._useCache&&!p._useRequestCache&&l&&(l[o]=t.responseText),{responseText:t.responseText,body:t.body};var v=4!==Math.floor(n/100);if(v)return h+=1,E();c("unrecoverable error");var m=new r.AlgoliaSearchError(t.body&&t.body.message,{debugData:g,statusCode:n});return p._promise.reject(m)}function O(n){c("error: %s, stack: %s",n.message,n.stack);var o=new Date;return g.push({currentHost:w,headers:d(a),content:e||null,contentLength:void 0!==e?e.length:null,method:i.method,timeouts:i.timeouts,url:i.url,startTime:s,endTime:o,duration:o-s}),n instanceof r.AlgoliaSearchError||(n=new r.Unknown(n&&n.message,n)),h+=1,n instanceof r.Unknown||n instanceof r.UnparsableJSON||h>=p.hosts[t.hostType].length&&(v||!m)?(n.debugData=g,p._promise.reject(n)):n instanceof r.RequestTimeout?C():E()}function E(){return c("retrying request"),p._incrementHostIndex(t.hostType),y(n,i)}function C(){return c("retrying request with higher timeout"),p._incrementHostIndex(t.hostType),p._incrementTimeoutMultipler(),i.timeouts=p._getTimeoutsForRequest(t.hostType),y(n,i)}}function b(t,e,n){return p._useCache&&t&&e&&void 0!==e[n]}function _(e,n){if(b(p._useRequestCache,l,o)&&e.catch((function(){delete l[o]})),"function"!==typeof t.callback)return e.then(n);e.then((function(e){i((function(){t.callback(null,n(e))}),p._setTimeout||setTimeout)}),(function(e){i((function(){t.callback(e)}),p._setTimeout||setTimeout)}))}if(p._useCache&&p._useRequestCache&&(o=t.url),p._useCache&&p._useRequestCache&&e&&(o+="_body_"+e),b(p._useRequestCache,l,o)){c("serving request from cache");var w=l[o],x="function"!==typeof w.then?p._promise.resolve({responseText:w}):w;return _(x,(function(t){return JSON.parse(t.responseText)}))}var k=y(p._request,{url:t.url,method:t.method,body:e,jsonBody:t.body,timeouts:p._getTimeoutsForRequest(t.hostType),forceAuthHeaders:t.forceAuthHeaders});return p._useCache&&p._useRequestCache&&l&&(l[o]=k),_(k,(function(t){return t.body}))},u.prototype._getSearchParams=function(t,e){if(void 0===t||null===t)return e;for(var n in t)null!==n&&void 0!==t[n]&&t.hasOwnProperty(n)&&(e+=""===e?"":"&",e+=n+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(t[n])?f(t[n]):t[n]));return e},u.prototype._computeRequestHeaders=function(t){var e=n(7593),r=t.additionalUA?this._ua+"; "+t.additionalUA:this._ua,i={"x-algolia-agent":r,"x-algolia-application-id":this.applicationID};return!1!==t.withApiKey&&(i["x-algolia-api-key"]=this.apiKey),this.userToken&&(i["x-algolia-usertoken"]=this.userToken),this.securityTags&&(i["x-algolia-tagfilters"]=this.securityTags),e(this.extraHeaders,(function(t,e){i[e]=t})),t.headers&&e(t.headers,(function(t,e){i[e]=t})),i},u.prototype.search=function(t,e,r){var i=n(2253),o=n(9987),a="Usage: client.search(arrayOfQueries[, callback])";if(!i(t))throw new Error(a);"function"===typeof e?(r=e,e={}):void 0===e&&(e={});var s=this,c={requests:o(t,(function(t){var e="";return void 0!==t.query&&(e+="query="+encodeURIComponent(t.query)),{indexName:t.indexName,params:s._getSearchParams(t.params,e)}}))},u=o(c.requests,(function(t,e){return e+"="+encodeURIComponent("/1/indexes/"+encodeURIComponent(t.indexName)+"?"+t.params)})).join("&"),l="/1/indexes/*/queries";return void 0!==e.strategy&&(c.strategy=e.strategy),this._jsonRequest({cache:this.cache,method:"POST",url:l,body:c,hostType:"read",fallback:{method:"GET",url:"/1/indexes/*",body:{params:u}},callback:r})},u.prototype.searchForFacetValues=function(t){var e=n(2253),r=n(9987),i="Usage: client.searchForFacetValues([{indexName, params: {facetName, facetQuery, ...params}}, ...queries])";if(!e(t))throw new Error(i);var o=this;return o._promise.all(r(t,(function(t){if(!t||void 0===t.indexName||void 0===t.params.facetName||void 0===t.params.facetQuery)throw new Error(i);var e=n(6428),r=n(4224),a=t.indexName,s=t.params,c=s.facetName,u=r(e(s),(function(t){return"facetName"===t})),l=o._getSearchParams(u,"");return o._jsonRequest({cache:o.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/facets/"+encodeURIComponent(c)+"/query",hostType:"read",body:{params:l}})})))},u.prototype.setSecurityTags=function(t){if("[object Array]"===Object.prototype.toString.call(t)){for(var e=[],n=0;nc?this._resetInitialAppIdData(t):t},u.prototype._resetInitialAppIdData=function(t){var e=t||{};return e.hostIndexes={read:0,write:0},e.timeoutMultiplier=1,e.shuffleResult=e.shuffleResult||p([1,2,3]),this._setAppIdData(e)},u.prototype._cacheAppIdData=function(t){this._hostIndexes=t.hostIndexes,this._timeoutMultiplier=t.timeoutMultiplier,this._shuffleResult=t.shuffleResult},u.prototype._partialAppIdDataUpdate=function(t){var e=n(7593),r=this._getAppIdData();return e(t,(function(t,e){r[e]=t})),this._setAppIdData(r)},u.prototype._getHostByType=function(t){return this.hosts[t][this._getHostIndexByType(t)]},u.prototype._getTimeoutMultiplier=function(){return this._timeoutMultiplier},u.prototype._getHostIndexByType=function(t){return this._hostIndexes[t]},u.prototype._setHostIndexByType=function(t,e){var r=n(6428),i=r(this._hostIndexes);return i[e]=t,this._partialAppIdDataUpdate({hostIndexes:i}),t},u.prototype._incrementHostIndex=function(t){return this._setHostIndexByType((this._getHostIndexByType(t)+1)%this.hosts[t].length,t)},u.prototype._incrementTimeoutMultipler=function(){var t=Math.max(this._timeoutMultiplier+1,4);return this._partialAppIdDataUpdate({timeoutMultiplier:t})},u.prototype._getTimeoutsForRequest=function(t){return{connect:this._timeouts.connect*this._timeoutMultiplier,complete:this._timeouts[t]*this._timeoutMultiplier}}},2369:function(t,e,n){var r=n(6698),i=n(546),o=n(9038),a=n(3901),s=n(6944),c=n(2806),u=o((function(){}),a("forwardToSlaves","forwardToReplicas"));function l(){i.apply(this,arguments)}function f(t,e,n){function r(n,i){var o={page:n||0,hitsPerPage:e||100},a=i||[];return t(o).then((function(t){var e=t.hits,n=t.nbHits,i=e.map((function(t){return delete t._highlightResult,t})),s=a.concat(i);return s.lengthr&&(e=r),"published"!==t.status?a._promise.delay(e).then(u):t}))}if(!e)return c;function l(t){s((function(){e(null,t)}),a._setTimeout||setTimeout)}function f(t){s((function(){e(t)}),a._setTimeout||setTimeout)}c.then(l,f)},l.prototype.clearIndex=function(t){var e=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(e.indexName)+"/clear",hostType:"write",callback:t})},l.prototype.getSettings=function(t,e){1===arguments.length&&"function"===typeof t&&(e=t,t={}),t=t||{};var n=encodeURIComponent(this.indexName);return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+n+"/settings?getVersion=2"+(t.advanced?"&advanced="+t.advanced:""),hostType:"read",callback:e})},l.prototype.searchSynonyms=function(t,e){return"function"===typeof t?(e=t,t={}):void 0===t&&(t={}),this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/search",body:t,hostType:"read",callback:e})},l.prototype.exportSynonyms=function(t,e){return f(this.searchSynonyms.bind(this),t,e)},l.prototype.saveSynonym=function(t,e,n){"function"===typeof e?(n=e,e={}):void 0===e&&(e={}),void 0!==e.forwardToSlaves&&u();var r=e.forwardToSlaves||e.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/"+encodeURIComponent(t.objectID)+"?forwardToReplicas="+r,body:t,hostType:"write",callback:n})},l.prototype.getSynonym=function(t,e){return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/"+encodeURIComponent(t),hostType:"read",callback:e})},l.prototype.deleteSynonym=function(t,e,n){"function"===typeof e?(n=e,e={}):void 0===e&&(e={}),void 0!==e.forwardToSlaves&&u();var r=e.forwardToSlaves||e.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/"+encodeURIComponent(t)+"?forwardToReplicas="+r,hostType:"write",callback:n})},l.prototype.clearSynonyms=function(t,e){"function"===typeof t?(e=t,t={}):void 0===t&&(t={}),void 0!==t.forwardToSlaves&&u();var n=t.forwardToSlaves||t.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/clear?forwardToReplicas="+n,hostType:"write",callback:e})},l.prototype.batchSynonyms=function(t,e,n){"function"===typeof e?(n=e,e={}):void 0===e&&(e={}),void 0!==e.forwardToSlaves&&u();var r=e.forwardToSlaves||e.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/synonyms/batch?forwardToReplicas="+r+"&replaceExistingSynonyms="+(e.replaceExistingSynonyms?"true":"false"),hostType:"write",body:t,callback:n})},l.prototype.searchRules=function(t,e){return"function"===typeof t?(e=t,t={}):void 0===t&&(t={}),this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/rules/search",body:t,hostType:"read",callback:e})},l.prototype.exportRules=function(t,e){return f(this.searchRules.bind(this),t,e)},l.prototype.saveRule=function(t,e,n){if("function"===typeof e?(n=e,e={}):void 0===e&&(e={}),!t.objectID)throw new c.AlgoliaSearchError("Missing or empty objectID field for rule");var r=!0===e.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/rules/"+encodeURIComponent(t.objectID)+"?forwardToReplicas="+r,body:t,hostType:"write",callback:n})},l.prototype.getRule=function(t,e){return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/rules/"+encodeURIComponent(t),hostType:"read",callback:e})},l.prototype.deleteRule=function(t,e,n){"function"===typeof e?(n=e,e={}):void 0===e&&(e={});var r=!0===e.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/rules/"+encodeURIComponent(t)+"?forwardToReplicas="+r,hostType:"write",callback:n})},l.prototype.clearRules=function(t,e){"function"===typeof t?(e=t,t={}):void 0===t&&(t={});var n=!0===t.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/rules/clear?forwardToReplicas="+n,hostType:"write",callback:e})},l.prototype.batchRules=function(t,e,n){"function"===typeof e?(n=e,e={}):void 0===e&&(e={});var r=!0===e.forwardToReplicas?"true":"false";return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/rules/batch?forwardToReplicas="+r+"&clearExistingRules="+(!0===e.clearExistingRules?"true":"false"),hostType:"write",body:t,callback:n})},l.prototype.exists=function(t){var e=this.getSettings().then((function(){return!0})).catch((function(t){if(t instanceof c.AlgoliaSearchError&&404===t.statusCode)return!1;throw t}));if("function"!==typeof t)return e;e.then((function(e){t(null,e)})).catch((function(e){t(e)}))},l.prototype.findObject=function(t,e,n){e=void 0===e?{}:e;var r=void 0===e.paginate||e.paginate,i=void 0!==e.query?e.query:"",o=this,a=0,s=function(){return e.page=a,o.search(i,e).then((function(e){for(var n=e.hits,i=0;i=e.nbPages)throw new c.ObjectNotFound("Object not found");return s()}))},u=s(a);if(void 0===n)return u;u.then((function(t){n(null,t)})).catch((function(t){n(t)}))},l.prototype.getObjectPosition=function(t,e){for(var n=t.hits,r=0;r1&&v()}}))},p.prototype._request.fallback=function(t,e){return t=s(t,e.headers),new i((function(n,r){c(t,e,(function(t,e){t?r(t):n(e)}))}))},p.prototype._promise={reject:function(t){return i.reject(t)},resolve:function(t){return i.resolve(t)},delay:function(t){return new i((function(e){setTimeout(e,t)}))},all:function(t){return i.all(t)}},l}},7274:function(t,e,n){"use strict";t.exports=i;var r=n(1590);function i(t,e){return/\?/.test(t)?t+="&":t+="?",t+r(e)}},3852:function(t,e,n){"use strict";t.exports=o;var r=n(2806),i=0;function o(t,e,n){if("GET"===e.method){e.debug("JSONP: start");var o=!1,a=!1;i+=1;var s=document.getElementsByTagName("head")[0],c=document.createElement("script"),u="algoliaJSONP_"+i,l=!1;window[u]=function(t){v(),a?e.debug("JSONP: Late answer, ignoring"):(o=!0,h(),n(null,{body:t,responseText:JSON.stringify(t)}))},t+="&callback="+u,e.jsonBody&&e.jsonBody.params&&(t+="&"+e.jsonBody.params);var f=setTimeout(m,e.timeouts.complete);c.onreadystatechange=d,c.onload=p,c.onerror=g,c.async=!0,c.defer=!0,c.src=t,s.appendChild(c)}else n(new Error("Method "+e.method+" "+t+" is not supported by JSONP."));function p(){e.debug("JSONP: success"),l||a||(l=!0,o||(e.debug("JSONP: Fail. Script loaded but did not call the callback"),h(),n(new r.JSONPScriptFail)))}function d(){"loaded"!==this.readyState&&"complete"!==this.readyState||p()}function h(){clearTimeout(f),c.onload=null,c.onreadystatechange=null,c.onerror=null,s.removeChild(c)}function v(){try{delete window[u],delete window[u+"_loaded"]}catch(t){window[u]=window[u+"_loaded"]=void 0}}function m(){e.debug("JSONP: Script timeout"),a=!0,h(),n(new r.RequestTimeout)}function g(){e.debug("JSONP: Script error"),l||a||(h(),n(new r.JSONPScriptError))}}},9516:function(t,e,n){t.exports=i;var r=n(2806);function i(t,e){return function(n,i,o){if("function"===typeof n&&"object"===typeof i||"object"===typeof o)throw new r.AlgoliaSearchError("index.search usage is index.search(query, params, cb)");0===arguments.length||"function"===typeof n?(o=n,n=""):1!==arguments.length&&"function"!==typeof i||(o=i,i=void 0),"object"===typeof n&&null!==n?(i=n,n=void 0):void 0!==n&&null!==n||(n="");var a,s="";return void 0!==n&&(s+=t+"="+encodeURIComponent(n)),void 0!==i&&(i.additionalUA&&(a=i.additionalUA,delete i.additionalUA),s=this.as._getSearchParams(i,s)),this._search(s,e,o,a)}}},6428:function(t){t.exports=function(t){return JSON.parse(JSON.stringify(t))}},9734:function(t,e,n){t.exports=i;var r=n(1417);function i(t,e,n){var i={};return n=n||{},n.hosts=n.hosts||["analytics.algolia.com","analytics.algolia.com","analytics.algolia.com","analytics.algolia.com"],n.protocol=n.protocol||"https:",i.as=r(t,e,n),i.getABTests=function(t,e){var n=n||{},r=n.offset||0,i=n.limit||10;return this.as._jsonRequest({method:"GET",url:"/2/abtests?offset="+encodeURIComponent(r)+"&limit="+encodeURIComponent(i),hostType:"read",forceAuthHeaders:!0,callback:e})},i.getABTest=function(t,e){return this.as._jsonRequest({method:"GET",url:"/2/abtests/"+encodeURIComponent(t),hostType:"read",forceAuthHeaders:!0,callback:e})},i.addABTest=function(t,e){return this.as._jsonRequest({method:"POST",url:"/2/abtests",body:t,hostType:"read",forceAuthHeaders:!0,callback:e})},i.stopABTest=function(t,e){return this.as._jsonRequest({method:"POST",url:"/2/abtests/"+encodeURIComponent(t)+"/stop",hostType:"read",forceAuthHeaders:!0,callback:e})},i.deleteABTest=function(t,e){return this.as._jsonRequest({method:"DELETE",url:"/2/abtests/"+encodeURIComponent(t),hostType:"write",forceAuthHeaders:!0,callback:e})},i.waitTask=function(t,e,n){return this.as.initIndex(t).waitTask(e,n)},i}},9038:function(t){t.exports=function(t,e){var n=!1;function r(){return n||(console.warn(e),n=!0),t.apply(this,arguments)}return r}},3901:function(t){t.exports=function(t,e){var n=t.toLowerCase().replace(/[\.\(\)]/g,"");return"algoliasearch: `"+t+"` was replaced by `"+e+"`. Please see https://github.com/algolia/algoliasearch-client-javascript/wiki/Deprecated#"+n}},2806:function(t,e,n){"use strict";var r=n(6698);function i(t,e){var r=n(7593),i=this;"function"===typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):i.stack=(new Error).stack||"Cannot get a stacktrace, browser is too old",this.name="AlgoliaSearchError",this.message=t||"Unknown error",e&&r(e,(function(t,e){i[e]=t}))}function o(t,e){function n(){var n=Array.prototype.slice.call(arguments,0);"string"!==typeof n[0]&&n.unshift(e),i.apply(this,n),this.name="AlgoliaSearch"+t+"Error"}return r(n,i),n}r(i,Error),t.exports={AlgoliaSearchError:i,UnparsableJSON:o("UnparsableJSON","Could not parse the incoming response as JSON, see err.more for details"),RequestTimeout:o("RequestTimeout","Request timed out before getting a response"),Network:o("Network","Network issue, see err.more for details"),JSONPScriptFail:o("JSONPScriptFail","'; } die("Contao Manager was downgraded to the latest version supported by your PHP version.\n{$reload}"); } } /** * @see Composer\Util\StreamContextFactory */ class StreamContextFactory { /** * Creates a context supporting HTTP proxies * * @param string $url URL the context is to be used for * @param array $defaultOptions Options to merge with the default * @param array $defaultParams Parameters to specify on the context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled * @return resource Default context */ public static function getContext($url, array $defaultOptions = array(), array $defaultParams = array()) { $options = array('http' => array( // specify defaults again to try and work better with curlwrappers enabled 'follow_location' => 1, 'max_redirects' => 20, )); // Handle HTTP_PROXY/http_proxy on CLI only for security reasons if ((\PHP_SAPI === 'cli' || \PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { $proxy = \parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); } // Prefer CGI_HTTP_PROXY if available if (!empty($_SERVER['CGI_HTTP_PROXY'])) { $proxy = \parse_url($_SERVER['CGI_HTTP_PROXY']); } // Override with HTTPS proxy if present and URL is https if (\preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) { $proxy = \parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']); } // Remove proxy if URL matches no_proxy directive if ((!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy'])) && \parse_url($url, \PHP_URL_HOST)) { $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']); if ($pattern->test($url)) { unset($proxy); } } if (!empty($proxy)) { $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : ''; $proxyURL .= isset($proxy['host']) ? $proxy['host'] : ''; if (isset($proxy['port'])) { $proxyURL .= ":" . $proxy['port']; } elseif ('http://' === \substr($proxyURL, 0, 7)) { $proxyURL .= ":80"; } elseif ('https://' === \substr($proxyURL, 0, 8)) { $proxyURL .= ":443"; } // http(s):// is not supported in proxy $proxyURL = \str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL); if (0 === \strpos($proxyURL, 'ssl:') && !\extension_loaded('openssl')) { throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); } $options['http']['proxy'] = $proxyURL; // enabled request_fulluri unless it is explicitly disabled switch (\parse_url($url, \PHP_URL_SCHEME)) { case 'http': // default request_fulluri to true $reqFullUriEnv = \getenv('HTTP_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === \false || $reqFullUriEnv === '' || \strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv) { $options['http']['request_fulluri'] = \true; } break; case 'https': // default request_fulluri to true $reqFullUriEnv = \getenv('HTTPS_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === \false || $reqFullUriEnv === '' || \strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv) { $options['http']['request_fulluri'] = \true; } break; } // add SNI opts for https URLs if ('https' === \parse_url($url, \PHP_URL_SCHEME)) { $options['ssl']['SNI_enabled'] = \true; if (\PHP_VERSION_ID < 50600) { $options['ssl']['SNI_server_name'] = \parse_url($url, \PHP_URL_HOST); } } // handle proxy auth if present if (isset($proxy['user'])) { $auth = \rawurldecode($proxy['user']); if (isset($proxy['pass'])) { $auth .= ':' . \rawurldecode($proxy['pass']); } $auth = \base64_encode($auth); // Preserve headers if already set in default options if (isset($defaultOptions['http']['header'])) { if (\is_string($defaultOptions['http']['header'])) { $defaultOptions['http']['header'] = array($defaultOptions['http']['header']); } $defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}"; } else { $options['http']['header'] = array("Proxy-Authorization: Basic {$auth}"); } } } $options = \array_replace_recursive($options, $defaultOptions); if (isset($options['http']['header'])) { $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); } if (\defined('_ContaoManager\\HHVM_VERSION')) { $phpVersion = 'HHVM ' . \_ContaoManager\HHVM_VERSION; } else { $phpVersion = 'PHP ' . \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION . '.' . \PHP_RELEASE_VERSION; } if (!isset($options['http']['header']) || \false === \stripos(\implode('', $options['http']['header']), 'user-agent')) { $options['http']['header'][] = \sprintf('User-Agent: Contao Manager/@package_version@ (%s; %s; %s%s)', \function_exists('php_uname') ? \php_uname('s') : 'Unknown', \function_exists('php_uname') ? \php_uname('r') : 'Unknown', $phpVersion, \getenv('CI') ? '; CI' : ''); } return \stream_context_create($options, $defaultParams); } /** * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and * NOT at the end of the array * * This method fixes the array by moving the content-type header to the end * * @link https://bugs.php.net/bug.php?id=61548 * @param string|array $header * @return array */ private static function fixHttpHeaderField($header) { if (!\is_array($header)) { $header = \explode("\r\n", $header); } \uasort($header, function ($el) { return \stripos($el, 'content-type') === 0 ? 1 : -1; }); return $header; } } /** * @see Composer\Util\NoProxyPattern */ class NoProxyPattern { /** * @var string[] */ protected $rules = array(); /** * @param string $pattern no_proxy pattern */ public function __construct($pattern) { $this->rules = \preg_split("/[\\s,]+/", $pattern); } /** * Test a URL against the stored pattern. * * @param string $url * * @return bool true if the URL matches one of the rules. */ public function test($url) { $host = \parse_url($url, \PHP_URL_HOST); $port = \parse_url($url, \PHP_URL_PORT); if (empty($port)) { switch (\parse_url($url, \PHP_URL_SCHEME)) { case 'http': $port = 80; break; case 'https': $port = 443; break; } } foreach ($this->rules as $rule) { if ($rule === '*') { return \true; } list($ruleHost) = \explode(':', $rule); list($base) = \explode('/', $ruleHost); if (\filter_var($base, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { // ip or cidr match if (!isset($ip)) { $ip = \gethostbyname($host); } if (\strpos($ruleHost, '/') === \false) { $match = $ip === $ruleHost; } else { // gethostbyname() failed to resolve $host to an ip, so we assume // it must be proxied to let the proxy's DNS resolve it if ($ip === $host) { $match = \false; } else { // match resolved IP against the rule $match = self::inCIDRBlock($ruleHost, $ip); } } } else { // match end of domain $haystack = '.' . \trim($host, '.') . '.'; $needle = '.' . \trim($ruleHost, '.') . '.'; $match = \stripos(\strrev($haystack), \strrev($needle)) === 0; } // final port check if ($match && \strpos($rule, ':') !== \false) { list(, $rulePort) = \explode(':', $rule); if (!empty($rulePort) && $port != $rulePort) { $match = \false; } } if ($match) { return \true; } } return \false; } /** * Check an IP address against a CIDR * * http://framework.zend.com/svn/framework/extras/incubator/library/ZendX/Whois/Adapter/Cidr.php * * @param string $cidr IPv4 block in CIDR notation * @param string $ip IPv4 address * * @return bool */ private static function inCIDRBlock($cidr, $ip) { // Get the base and the bits from the CIDR list($base, $bits) = \explode('/', $cidr); // Now split it up into it's classes list($a, $b, $c, $d) = \explode('.', $base); // Now do some bit shifting/switching to convert to ints $i = ($a << 24) + ($b << 16) + ($c << 8) + $d; $mask = $bits == 0 ? 0 : ~0 << 32 - $bits; // Here's our lowest int $low = $i & $mask; // Here's our highest int $high = $i | ~$mask & 0xffffffff; // Now split the ip we're checking against up into classes list($a, $b, $c, $d) = \explode('.', $ip); // Now convert the ip we're checking against to an int $check = ($a << 24) + ($b << 16) + ($c << 8) + $d; // If the ip is within the range, including highest/lowest values, // then it's within the CIDR range return $check >= $low && $check <= $high; } } if (\PHP_VERSION_ID < 70205) { ContaoManagerDowngrade::run(); } { "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], "hash": "e5afe72073d9266712c8e1ddc1648513", "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=5.3" }, "platform-dev": [] } Copyright (c) 2015 Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PHAR Utils ========== PHAR file format utilities, for when PHP phars you up. Installation ------------ `composer require seld/phar-utils` API --- ### `Seld\PharUtils\Timestamps` - `__construct($pharFile)` > Load a phar file in memory. - `updateTimestamps($timestamp = null)` > Updates each file's unix timestamps in the PHAR so the PHAR signature > can be produced in a reproducible manner. - `save($path, $signatureAlgo = '')` > Saves the updated phar file with an updated signature. > Algo must be one of `Phar::MD5`, `Phar::SHA1`, `Phar::SHA256` > or `Phar::SHA512` ### `Seld\PharUtils\Linter` - `Linter::lint($pharFile)` > Lints all php files inside a given phar with the current PHP version. Requirements ------------ PHP 5.3 and above License ------- PHAR Utils is licensed under the MIT License - see the LICENSE file for details { "name": "seld\/phar-utils", "description": "PHAR file format utilities, for when PHP phars you up", "type": "library", "keywords": [ "phar" ], "license": "MIT", "require": { "php": ">=5.3" }, "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be" } ], "autoload": { "psr-4": { "_ContaoManager\\Seld\\PharUtils\\": "src\/" } }, "extra": { "branch-alias": { "dev-master": "1.x-dev" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\PharUtils; class Linter { /** * Lints all php files inside a given phar with the current PHP version * * @param string $path Phar file path * @param list $excludedPaths Paths which should be skipped by the linter */ public static function lint($path, array $excludedPaths = array()) { $php = \defined('PHP_BINARY') ? \PHP_BINARY : 'php'; if ($isWindows = \defined('PHP_WINDOWS_VERSION_BUILD')) { $tmpFile = @\tempnam(\sys_get_temp_dir(), ''); if (!$tmpFile || !\is_writable($tmpFile)) { throw new \RuntimeException('Unable to create temp file'); } $php = self::escapeWindowsPath($php); $tmpFile = self::escapeWindowsPath($tmpFile); // PHP 8 encloses the command in double-quotes if (\PHP_VERSION_ID >= 80000) { $format = '%s -l %s'; } else { $format = '"%s -l %s"'; } $command = \sprintf($format, $php, $tmpFile); } else { $command = "'" . $php . "' -l"; } $descriptorspec = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')); // path to phar + phar:// + trailing slash $baseLen = \strlen(\realpath($path)) + 7 + 1; foreach (new \RecursiveIteratorIterator(new \Phar($path)) as $file) { if ($file->isDir()) { continue; } if (\substr($file, -4) === '.php') { $filename = (string) $file; if (\in_array(\substr($filename, $baseLen), $excludedPaths, \true)) { continue; } if ($isWindows) { \file_put_contents($tmpFile, \file_get_contents($filename)); } $process = \proc_open($command, $descriptorspec, $pipes); if (\is_resource($process)) { if (!$isWindows) { \fwrite($pipes[0], \file_get_contents($filename)); } \fclose($pipes[0]); $stdout = \stream_get_contents($pipes[1]); \fclose($pipes[1]); $stderr = \stream_get_contents($pipes[2]); \fclose($pipes[2]); $exitCode = \proc_close($process); if ($exitCode !== 0) { if ($isWindows) { $stderr = \str_replace($tmpFile, $filename, $stderr); } throw new \UnexpectedValueException('Failed linting ' . $file . ': ' . $stderr); } } else { throw new \RuntimeException('Could not start linter process'); } } } if ($isWindows) { @\unlink($tmpFile); } } /** * Escapes a Windows file path * * @param string $path * @return string The escaped path */ private static function escapeWindowsPath($path) { // Quote if path contains spaces or brackets if (\strpbrk($path, " ()") !== \false) { $path = '"' . $path . '"'; } return $path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\PharUtils; class Timestamps { private $contents; /** * @param string $file path to the phar file to use */ public function __construct($file) { $this->contents = \file_get_contents($file); } /** * Updates each file's unix timestamps in the PHAR * * The PHAR signature can then be produced in a reproducible manner. * * @param int|\DateTimeInterface|string $timestamp Date string or DateTime or unix timestamp to use */ public function updateTimestamps($timestamp = null) { if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) { $timestamp = $timestamp->getTimestamp(); } elseif (\is_string($timestamp)) { $timestamp = \strtotime($timestamp); } elseif (!\is_int($timestamp)) { $timestamp = \strtotime('1984-12-24T00:00:00Z'); } // detect manifest offset / end of stub if (!\preg_match('{__HALT_COMPILER\\(\\);(?: +\\?>)?\\r?\\n}', $this->contents, $match, \PREG_OFFSET_CAPTURE)) { throw new \RuntimeException('Could not detect the stub\'s end in the phar'); } // set starting position and skip past manifest length $pos = $match[0][1] + \strlen($match[0][0]); $stubEnd = $pos + $this->readUint($pos, 4); $pos += 4; $numFiles = $this->readUint($pos, 4); $pos += 4; // skip API version (YOLO) $pos += 2; // skip PHAR flags $pos += 4; $aliasLength = $this->readUint($pos, 4); $pos += 4 + $aliasLength; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; while ($pos < $stubEnd) { $filenameLength = $this->readUint($pos, 4); $pos += 4 + $filenameLength; // skip filesize $pos += 4; // update timestamp to a fixed value $timeStampBytes = \pack('L', $timestamp); $this->contents[$pos + 0] = $timeStampBytes[0]; $this->contents[$pos + 1] = $timeStampBytes[1]; $this->contents[$pos + 2] = $timeStampBytes[2]; $this->contents[$pos + 3] = $timeStampBytes[3]; // skip timestamp, compressed file size, crc32 checksum and file flags $pos += 4 * 4; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $numFiles--; } if ($numFiles !== 0) { throw new \LogicException('All files were not processed, something must have gone wrong'); } } /** * Saves the updated phar file, optionally with an updated signature. * * @param string $path * @param int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512 * @return bool */ public function save($path, $signatureAlgo) { $pos = $this->determineSignatureBegin(); $algos = array(\Phar::MD5 => 'md5', \Phar::SHA1 => 'sha1', \Phar::SHA256 => 'sha256', \Phar::SHA512 => 'sha512'); if (!isset($algos[$signatureAlgo])) { throw new \UnexpectedValueException('Invalid hash algorithm given: ' . $signatureAlgo . ' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512'); } $algo = $algos[$signatureAlgo]; // re-sign phar // signature $signature = \hash($algo, \substr($this->contents, 0, $pos), \true) . \pack('L', $signatureAlgo) . 'GBMB'; $this->contents = \substr($this->contents, 0, $pos) . $signature; return \file_put_contents($path, $this->contents); } private function readUint($pos, $bytes) { $res = \unpack('V', \substr($this->contents, $pos, $bytes)); return $res[1]; } /** * Determine the beginning of the signature. * * @return int */ private function determineSignatureBegin() { // detect signature position if (!\preg_match('{__HALT_COMPILER\\(\\);(?: +\\?>)?\\r?\\n}', $this->contents, $match, \PREG_OFFSET_CAPTURE)) { throw new \RuntimeException('Could not detect the stub\'s end in the phar'); } // set starting position and skip past manifest length $pos = $match[0][1] + \strlen($match[0][0]); $manifestEnd = $pos + 4 + $this->readUint($pos, 4); $pos += 4; $numFiles = $this->readUint($pos, 4); $pos += 4; // skip API version (YOLO) $pos += 2; // skip PHAR flags $pos += 4; $aliasLength = $this->readUint($pos, 4); $pos += 4 + $aliasLength; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $compressedSizes = 0; while ($numFiles > 0 && $pos < $manifestEnd - 24) { $filenameLength = $this->readUint($pos, 4); $pos += 4 + $filenameLength; // skip filesize and timestamp $pos += 2 * 4; $compressedSizes += $this->readUint($pos, 4); // skip compressed file size, crc32 checksum and file flags $pos += 3 * 4; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $numFiles--; } if ($numFiles !== 0) { throw new \LogicException('All files were not processed, something must have gone wrong'); } return $manifestEnd + $compressedSizes; } } Copyright (c) 2011 Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #!/usr/bin/env php * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ function includeIfExists($file) { if (\file_exists($file)) { return include $file; } } if (!includeIfExists(__DIR__ . '/../vendor/autoload.php') && !includeIfExists(__DIR__ . '/../../../autoload.php')) { $msg = 'You must set up the project dependencies, run the following commands:' . \PHP_EOL . 'curl -sS https://getcomposer.org/installer | php' . \PHP_EOL . 'php composer.phar install' . \PHP_EOL; \fwrite(\STDERR, $msg); exit(1); } use _ContaoManager\Seld\JsonLint\JsonParser; $files = array(); $quiet = \false; if (isset($_SERVER['argc']) && $_SERVER['argc'] > 1) { for ($i = 1; $i < $_SERVER['argc']; $i++) { $arg = $_SERVER['argv'][$i]; if ($arg == '-q' || $arg == '--quiet') { $quiet = \true; } else { if ($arg == '-h' || $arg == '--help') { showUsage($_SERVER['argv'][0]); } else { $files[] = $arg; } } } } if (!empty($files)) { // file linting $exitCode = 0; foreach ($files as $file) { $result = lintFile($file, $quiet); if ($result === \false) { $exitCode = 1; } } exit($exitCode); } else { //stdin linting if ($contents = \file_get_contents('php://stdin')) { lint($contents, $quiet); } else { \fwrite(\STDERR, 'No file name or json input given' . \PHP_EOL); exit(1); } } // stdin lint function function lint($content, $quiet = \false) { $parser = new JsonParser(); if ($err = $parser->lint($content)) { \fwrite(\STDERR, $err->getMessage() . ' (stdin)' . \PHP_EOL); exit(1); } if (!$quiet) { echo 'Valid JSON (stdin)' . \PHP_EOL; exit(0); } } // file lint function function lintFile($file, $quiet = \false) { if (!\preg_match('{^https?://}i', $file)) { if (!\file_exists($file)) { \fwrite(\STDERR, 'File not found: ' . $file . \PHP_EOL); return \false; } if (!\is_readable($file)) { \fwrite(\STDERR, 'File not readable: ' . $file . \PHP_EOL); return \false; } } $content = \file_get_contents($file); $parser = new JsonParser(); if ($err = $parser->lint($content)) { \fwrite(\STDERR, $file . ': ' . $err->getMessage() . \PHP_EOL); return \false; } if (!$quiet) { echo 'Valid JSON (' . $file . ')' . \PHP_EOL; } return \true; } // usage text function function showUsage($programPath) { echo 'Usage: ' . $programPath . ' file [options]' . \PHP_EOL; echo \PHP_EOL; echo 'Options:' . \PHP_EOL; echo ' -q, --quiet Cause jsonlint to be quiet when no errors are found' . \PHP_EOL; echo ' -h, --help Show this message' . \PHP_EOL; exit(0); } You can find newer changelog entries in [GitHub releases](https://github.com/Seldaek/jsonlint/releases) ### 1.10.0 (2023-05-11) * Added ALLOW_COMMENTS flag to parse while allowing (and ignoring) inline `//` and multiline `/* */` comments in the JSON document (#81) ### 1.9.0 (2022-04-01) * Internal cleanups and type fixes ### 1.8.1 (2020-08-13) * Added type annotations ### 1.8.0 (2020-04-30) * Improved lexer performance * Added (tentative) support for PHP 8 * Fixed wording of error reporting for invalid strings when the error happened after the 20th character ### 1.7.2 (2019-10-24) * Fixed issue decoding some unicode escaped characters (for " and ') ### 1.7.1 (2018-01-24) * Fixed PHP 5.3 compatibility in bin/jsonlint ### 1.7.0 (2018-01-03) * Added ability to lint multiple files at once using the jsonlint binary ### 1.6.2 (2017-11-30) * No meaningful public changes ### 1.6.1 (2017-06-18) * Fixed parsing of `0` as invalid ### 1.6.0 (2017-03-06) * Added $flags arg to JsonParser::lint() to take the same flag as parse() did * Fixed backtracking performance issues on long strings with a lot of escaped characters ### 1.5.0 (2016-11-14) * Added support for PHP 7.1 (which converts `{"":""}` to an object property called `""` and not `"_empty_"` like 7.0 and below). ### 1.4.0 (2015-11-21) * Added a DuplicateKeyException allowing for more specific error detection and handling ### 1.3.1 (2015-01-04) * Fixed segfault when parsing large JSON strings ### 1.3.0 (2014-09-05) * Added parsing to an associative array via JsonParser::PARSE_TO_ASSOC * Fixed a warning when rendering parse errors on empty lines ### 1.2.0 (2014-07-20) * Added support for linting multiple files at once in bin/jsonlint * Added a -q/--quiet flag to suppress the output * Fixed error output being on STDOUT instead of STDERR * Fixed parameter parsing ### 1.1.2 (2013-11-04) * Fixed handling of Unicode BOMs to give a better failure hint ### 1.1.1 (2013-02-12) * Fixed handling of empty keys in objects in certain cases ### 1.1.0 (2012-12-13) * Added optional parsing of duplicate keys into key.2, key.3, etc via JsonParser::ALLOW_DUPLICATE_KEYS * Improved error reporting for common mistakes ### 1.0.1 (2012-04-03) * Added optional detection and error reporting for duplicate keys via JsonParser::DETECT_KEY_CONFLICTS * Added ability to pipe content through stdin into bin/jsonlint ### 1.0.0 (2012-03-12) * Initial release JSON Lint ========= [![Build Status](https://github.com/Seldaek/jsonlint/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/Seldaek/jsonlint/actions/workflows/continuous-integration.yml) Usage ----- ```php use Seld\JsonLint\JsonParser; $parser = new JsonParser(); // returns null if it's valid json, or a ParsingException object. $parser->lint($json); // Call getMessage() on the exception object to get // a well formatted error message error like this // Parse error on line 2: // ... "key": "value" "numbers": [1, 2, 3] // ----------------------^ // Expected one of: 'EOF', '}', ':', ',', ']' // Call getDetails() on the exception to get more info. // returns parsed json, like json_decode() does, but slower, throws // exceptions on failure. $parser->parse($json); ``` You can also pass additional flags to `JsonParser::lint/parse` that tweak the functionality: - `JsonParser::DETECT_KEY_CONFLICTS` throws an exception on duplicate keys. - `JsonParser::ALLOW_DUPLICATE_KEYS` collects duplicate keys. e.g. if you have two `foo` keys they will end up as `foo` and `foo.2`. - `JsonParser::PARSE_TO_ASSOC` parses to associative arrays instead of stdClass objects. - `JsonParser::ALLOW_COMMENTS` parses while allowing (and ignoring) inline `//` and multiline `/* */` comments in the JSON document. Example: ```php $parser = new JsonParser; try { $parser->parse(file_get_contents($jsonFile), JsonParser::DETECT_KEY_CONFLICTS); } catch (DuplicateKeyException $e) { $details = $e->getDetails(); echo 'Key '.$details['key'].' is a duplicate in '.$jsonFile.' at line '.$details['line']; } ``` > **Note:** This library is meant to parse JSON while providing good error messages on failure. There is no way it can be as fast as php native `json_decode()`. > > It is recommended to parse with `json_decode`, and when it fails parse again with seld/jsonlint to get a proper error message back to the user. See for example [how Composer uses this library](https://github.com/composer/composer/blob/56edd53046fd697d32b2fd2fbaf45af5d7951671/src/Composer/Json/JsonFile.php#L283-L318): Installation ------------ For a quick install with Composer use: ```bash composer require seld/jsonlint ``` JSON Lint can easily be used within another app if you have a [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) autoloader, or it can be installed through [Composer](https://getcomposer.org/) for use as a CLI util. Once installed via Composer you can run the following command to lint a json file or URL: $ bin/jsonlint file.json Requirements ------------ - PHP 5.3+ - [optional] PHPUnit 3.5+ to execute the test suite (phpunit --version) Submitting bugs and feature requests ------------------------------------ Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/jsonlint/issues) Author ------ Jordi Boggiano - - License ------- JSON Lint is licensed under the MIT License - see the LICENSE file for details Acknowledgements ---------------- This library is a port of the JavaScript [jsonlint](https://github.com/zaach/jsonlint) library. { "name": "seld\/jsonlint", "description": "JSON Linter", "keywords": [ "json", "parser", "linter", "validator" ], "type": "library", "license": "MIT", "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https:\/\/seld.be" } ], "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpunit\/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13", "phpstan\/phpstan": "^1.5" }, "autoload": { "psr-4": { "_ContaoManager\\Seld\\JsonLint\\": "src\/Seld\/JsonLint\/" } }, "bin": [ "bin\/jsonlint" ], "scripts": { "test": "vendor\/bin\/phpunit", "phpstan": "vendor\/bin\/phpstan analyse" } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\JsonLint; class DuplicateKeyException extends ParsingException { /** * @var array{key: string, line: int} */ protected $details; /** * @param string $message * @param string $key * @phpstan-param array{line: int} $details */ public function __construct($message, $key, array $details) { $details['key'] = $key; parent::__construct($message, $details); } /** * @return string */ public function getKey() { return $this->details['key']; } /** * @phpstan-return array{key: string, line: int} */ public function getDetails() { return $this->details; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\JsonLint; class ParsingException extends \Exception { /** * @var array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} */ protected $details; /** * @param string $message * @phpstan-param array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} $details */ public function __construct($message, $details = array()) { $this->details = $details; parent::__construct($message); } /** * @phpstan-return array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} */ public function getDetails() { return $this->details; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\JsonLint; /** * Lexer class * * Ported from https://github.com/zaach/jsonlint */ class Lexer { /** @internal */ const EOF = 1; /** @internal */ const T_INVALID = -1; const T_SKIP_WHITESPACE = 0; const T_ERROR = 2; /** @internal */ const T_BREAK_LINE = 3; /** @internal */ const T_COMMENT = 30; /** @internal */ const T_OPEN_COMMENT = 31; /** @internal */ const T_CLOSE_COMMENT = 32; /** * @phpstan-var array, string> * @const */ private $rules = array(0 => '/\\G\\s*\\n\\r?/', 1 => '/\\G\\s+/', 2 => '/\\G-?([0-9]|[1-9][0-9]+)(\\.[0-9]+)?([eE][+-]?[0-9]+)?\\b/', 3 => '{\\G"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\\0-\\x1f\\\\"]++)*+"}', 4 => '/\\G\\{/', 5 => '/\\G\\}/', 6 => '/\\G\\[/', 7 => '/\\G\\]/', 8 => '/\\G,/', 9 => '/\\G:/', 10 => '/\\Gtrue\\b/', 11 => '/\\Gfalse\\b/', 12 => '/\\Gnull\\b/', 13 => '/\\G$/', 14 => '/\\G\\/\\//', 15 => '/\\G\\/\\*/', 16 => '/\\G\\*\\//', 17 => '/\\G./'); /** @var string */ private $input; /** @var bool */ private $more; /** @var bool */ private $done; /** @var 0|positive-int */ private $offset; /** @var int */ private $flags; /** @var string */ public $match; /** @var 0|positive-int */ public $yylineno; /** @var 0|positive-int */ public $yyleng; /** @var string */ public $yytext; /** @var array{first_line: 0|positive-int, first_column: 0|positive-int, last_line: 0|positive-int, last_column: 0|positive-int} */ public $yylloc; /** * @param int $flags */ public function __construct($flags = 0) { $this->flags = $flags; } /** * @return 0|1|4|6|8|10|11|14|17|18|21|22|23|24|30|-1 */ public function lex() { while (\true) { $symbol = $this->next(); switch ($symbol) { case self::T_SKIP_WHITESPACE: case self::T_BREAK_LINE: break; case self::T_COMMENT: case self::T_OPEN_COMMENT: if (!($this->flags & JsonParser::ALLOW_COMMENTS)) { $this->parseError('Lexical error on line ' . ($this->yylineno + 1) . ". Comments are not allowed.\n" . $this->showPosition()); } $this->skipUntil($symbol === self::T_COMMENT ? self::T_BREAK_LINE : self::T_CLOSE_COMMENT); if ($this->done) { // last symbol '/\G$/' before EOF return 14; } break; case self::T_CLOSE_COMMENT: $this->parseError('Lexical error on line ' . ($this->yylineno + 1) . ". Unexpected token.\n" . $this->showPosition()); default: return $symbol; } } } /** * @param string $input * @return $this */ public function setInput($input) { $this->input = $input; $this->more = \false; $this->done = \false; $this->offset = 0; $this->yylineno = $this->yyleng = 0; $this->yytext = $this->match = ''; $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0); return $this; } /** * @return string */ public function showPosition() { if ($this->yylineno === 0 && $this->offset === 1 && $this->match !== '{') { return $this->match . '...' . "\n^"; } $pre = \str_replace("\n", '', $this->getPastInput()); $c = \str_repeat('-', \max(0, \strlen($pre) - 1)); // new Array(pre.length + 1).join("-"); return $pre . \str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^"; } /** * @return string */ public function getPastInput() { $pastLength = $this->offset - \strlen($this->match); return ($pastLength > 20 ? '...' : '') . \substr($this->input, \max(0, $pastLength - 20), \min(20, $pastLength)); } /** * @return string */ public function getUpcomingInput() { $next = $this->match; if (\strlen($next) < 20) { $next .= \substr($this->input, $this->offset, 20 - \strlen($next)); } return \substr($next, 0, 20) . (\strlen($next) > 20 ? '...' : ''); } /** * @return string */ public function getFullUpcomingInput() { $next = $this->match; if (\substr($next, 0, 1) === '"' && \substr_count($next, '"') === 1) { $len = \strlen($this->input); if ($len === $this->offset) { $strEnd = $len; } else { $strEnd = \min(\strpos($this->input, '"', $this->offset + 1) ?: $len, \strpos($this->input, "\n", $this->offset + 1) ?: $len); } $next .= \substr($this->input, $this->offset, $strEnd - $this->offset); } elseif (\strlen($next) < 20) { $next .= \substr($this->input, $this->offset, 20 - \strlen($next)); } return $next; } /** * @param string $str * @return never */ protected function parseError($str) { throw new ParsingException($str); } /** * @param int $token * @return void */ private function skipUntil($token) { $symbol = $this->next(); while ($symbol !== $token && \false === $this->done) { $symbol = $this->next(); } } /** * @return 0|1|3|4|6|8|10|11|14|17|18|21|22|23|24|30|31|32|-1 */ private function next() { if ($this->done) { return self::EOF; } if ($this->offset === \strlen($this->input)) { $this->done = \true; } $token = null; $match = null; $col = null; $lines = null; if (!$this->more) { $this->yytext = ''; $this->match = ''; } $rulesLen = \count($this->rules); for ($i = 0; $i < $rulesLen; $i++) { if (\preg_match($this->rules[$i], $this->input, $match, 0, $this->offset)) { $lines = \explode("\n", $match[0]); \array_shift($lines); $lineCount = \count($lines); $this->yylineno += $lineCount; $this->yylloc = array('first_line' => $this->yylloc['last_line'], 'last_line' => $this->yylineno + 1, 'first_column' => $this->yylloc['last_column'], 'last_column' => $lineCount > 0 ? \strlen($lines[$lineCount - 1]) : $this->yylloc['last_column'] + \strlen($match[0])); $this->yytext .= $match[0]; $this->match .= $match[0]; $this->yyleng = \strlen($this->yytext); $this->more = \false; $this->offset += \strlen($match[0]); return $this->performAction($i); } } if ($this->offset === \strlen($this->input)) { return self::EOF; } $this->parseError('Lexical error on line ' . ($this->yylineno + 1) . ". Unrecognized text.\n" . $this->showPosition()); } /** * @param int $rule * @return 0|3|4|6|8|10|11|14|17|18|21|22|23|24|30|31|32|-1 */ private function performAction($rule) { switch ($rule) { case 0: /* skip break line */ return self::T_BREAK_LINE; case 1: /* skip whitespace */ return self::T_SKIP_WHITESPACE; case 2: return 6; case 3: $this->yytext = \substr($this->yytext, 1, $this->yyleng - 2); return 4; case 4: return 17; case 5: return 18; case 6: return 23; case 7: return 24; case 8: return 22; case 9: return 21; case 10: return 10; case 11: return 11; case 12: return 8; case 13: return 14; case 14: return self::T_COMMENT; case 15: return self::T_OPEN_COMMENT; case 16: return self::T_CLOSE_COMMENT; case 17: return self::T_INVALID; default: throw new \LogicException('Unsupported rule ' . $rule); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\JsonLint; class Undefined { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\JsonLint; use stdClass; /** * Parser class * * Example: * * $parser = new JsonParser(); * // returns null if it's valid json, or an error object * $parser->lint($json); * // returns parsed json, like json_decode does, but slower, throws exceptions on failure. * $parser->parse($json); * * Ported from https://github.com/zaach/jsonlint */ class JsonParser { const DETECT_KEY_CONFLICTS = 1; const ALLOW_DUPLICATE_KEYS = 2; const PARSE_TO_ASSOC = 4; const ALLOW_COMMENTS = 8; /** @var Lexer */ private $lexer; /** * @var int * @phpstan-var int-mask-of */ private $flags; /** @var list */ private $stack; /** @var list|int|bool|float|string|null> */ private $vstack; // semantic value stack /** @var list */ private $lstack; // location stack /** * @phpstan-var array */ private $symbols = array('error' => 2, 'JSONString' => 3, 'STRING' => 4, 'JSONNumber' => 5, 'NUMBER' => 6, 'JSONNullLiteral' => 7, 'NULL' => 8, 'JSONBooleanLiteral' => 9, 'TRUE' => 10, 'FALSE' => 11, 'JSONText' => 12, 'JSONValue' => 13, 'EOF' => 14, 'JSONObject' => 15, 'JSONArray' => 16, '{' => 17, '}' => 18, 'JSONMemberList' => 19, 'JSONMember' => 20, ':' => 21, ',' => 22, '[' => 23, ']' => 24, 'JSONElementList' => 25, '$accept' => 0, '$end' => 1); /** * @phpstan-var array * @const */ private $terminals_ = array(2 => "error", 4 => "STRING", 6 => "NUMBER", 8 => "NULL", 10 => "TRUE", 11 => "FALSE", 14 => "EOF", 17 => "{", 18 => "}", 21 => ":", 22 => ",", 23 => "[", 24 => "]"); /** * @phpstan-var array, array{int, int}> * @const */ private $productions_ = array(1 => array(3, 1), 2 => array(5, 1), 3 => array(7, 1), 4 => array(9, 1), 5 => array(9, 1), 6 => array(12, 2), 7 => array(13, 1), 8 => array(13, 1), 9 => array(13, 1), 10 => array(13, 1), 11 => array(13, 1), 12 => array(13, 1), 13 => array(15, 2), 14 => array(15, 3), 15 => array(20, 3), 16 => array(19, 1), 17 => array(19, 3), 18 => array(16, 2), 19 => array(16, 3), 20 => array(25, 1), 21 => array(25, 3)); /** * @var array, array|int>> List of stateID=>symbolID=>actionIDs|actionID * @const */ private $table = array(0 => array(3 => 5, 4 => array(1, 12), 5 => 6, 6 => array(1, 13), 7 => 3, 8 => array(1, 9), 9 => 4, 10 => array(1, 10), 11 => array(1, 11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1, 14), 23 => array(1, 15)), 1 => array(1 => array(3)), 2 => array(14 => array(1, 16)), 3 => array(14 => array(2, 7), 18 => array(2, 7), 22 => array(2, 7), 24 => array(2, 7)), 4 => array(14 => array(2, 8), 18 => array(2, 8), 22 => array(2, 8), 24 => array(2, 8)), 5 => array(14 => array(2, 9), 18 => array(2, 9), 22 => array(2, 9), 24 => array(2, 9)), 6 => array(14 => array(2, 10), 18 => array(2, 10), 22 => array(2, 10), 24 => array(2, 10)), 7 => array(14 => array(2, 11), 18 => array(2, 11), 22 => array(2, 11), 24 => array(2, 11)), 8 => array(14 => array(2, 12), 18 => array(2, 12), 22 => array(2, 12), 24 => array(2, 12)), 9 => array(14 => array(2, 3), 18 => array(2, 3), 22 => array(2, 3), 24 => array(2, 3)), 10 => array(14 => array(2, 4), 18 => array(2, 4), 22 => array(2, 4), 24 => array(2, 4)), 11 => array(14 => array(2, 5), 18 => array(2, 5), 22 => array(2, 5), 24 => array(2, 5)), 12 => array(14 => array(2, 1), 18 => array(2, 1), 21 => array(2, 1), 22 => array(2, 1), 24 => array(2, 1)), 13 => array(14 => array(2, 2), 18 => array(2, 2), 22 => array(2, 2), 24 => array(2, 2)), 14 => array(3 => 20, 4 => array(1, 12), 18 => array(1, 17), 19 => 18, 20 => 19), 15 => array(3 => 5, 4 => array(1, 12), 5 => 6, 6 => array(1, 13), 7 => 3, 8 => array(1, 9), 9 => 4, 10 => array(1, 10), 11 => array(1, 11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1, 14), 23 => array(1, 15), 24 => array(1, 21), 25 => 22), 16 => array(1 => array(2, 6)), 17 => array(14 => array(2, 13), 18 => array(2, 13), 22 => array(2, 13), 24 => array(2, 13)), 18 => array(18 => array(1, 24), 22 => array(1, 25)), 19 => array(18 => array(2, 16), 22 => array(2, 16)), 20 => array(21 => array(1, 26)), 21 => array(14 => array(2, 18), 18 => array(2, 18), 22 => array(2, 18), 24 => array(2, 18)), 22 => array(22 => array(1, 28), 24 => array(1, 27)), 23 => array(22 => array(2, 20), 24 => array(2, 20)), 24 => array(14 => array(2, 14), 18 => array(2, 14), 22 => array(2, 14), 24 => array(2, 14)), 25 => array(3 => 20, 4 => array(1, 12), 20 => 29), 26 => array(3 => 5, 4 => array(1, 12), 5 => 6, 6 => array(1, 13), 7 => 3, 8 => array(1, 9), 9 => 4, 10 => array(1, 10), 11 => array(1, 11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1, 14), 23 => array(1, 15)), 27 => array(14 => array(2, 19), 18 => array(2, 19), 22 => array(2, 19), 24 => array(2, 19)), 28 => array(3 => 5, 4 => array(1, 12), 5 => 6, 6 => array(1, 13), 7 => 3, 8 => array(1, 9), 9 => 4, 10 => array(1, 10), 11 => array(1, 11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1, 14), 23 => array(1, 15)), 29 => array(18 => array(2, 17), 22 => array(2, 17)), 30 => array(18 => array(2, 15), 22 => array(2, 15)), 31 => array(22 => array(2, 21), 24 => array(2, 21))); /** * @var array{16: array{2, 6}} * @const */ private $defaultActions = array(16 => array(2, 6)); /** * @param string $input JSON string * @param int $flags Bitmask of parse/lint options (see constants of this class) * @return null|ParsingException null if no error is found, a ParsingException containing all details otherwise * * @phpstan-param int-mask-of $flags */ public function lint($input, $flags = 0) { try { $this->parse($input, $flags); } catch (ParsingException $e) { return $e; } return null; } /** * @param string $input JSON string * @param int $flags Bitmask of parse/lint options (see constants of this class) * @return mixed * @throws ParsingException * * @phpstan-param int-mask-of $flags */ public function parse($input, $flags = 0) { $this->failOnBOM($input); $this->flags = $flags; $this->stack = array(0); $this->vstack = array(null); $this->lstack = array(); $yytext = ''; $yylineno = 0; $yyleng = 0; /** @var int<0,3> */ $recovering = 0; $this->lexer = new Lexer($flags); $this->lexer->setInput($input); $yyloc = $this->lexer->yylloc; $this->lstack[] = $yyloc; $symbol = null; $preErrorSymbol = null; $action = null; $a = null; $r = null; $p = null; $len = null; $newState = null; $expected = null; /** @var string|null */ $errStr = null; while (\true) { // retrieve state number from top of stack $state = $this->stack[\count($this->stack) - 1]; // use default actions if available if (isset($this->defaultActions[$state])) { $action = $this->defaultActions[$state]; } else { if ($symbol === null) { $symbol = $this->lexer->lex(); } // read action for current state and first input /** @var array|false */ $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : \false; } // handle parse error if (!$action || !$action[0]) { \assert(isset($symbol)); if (!$recovering) { // Report error $expected = array(); foreach ($this->table[$state] as $p => $ignore) { if (isset($this->terminals_[$p]) && $p > 2) { $expected[] = "'" . $this->terminals_[$p] . "'"; } } $message = null; if (\in_array("'STRING'", $expected) && \in_array(\substr($this->lexer->match, 0, 1), array('"', "'"))) { $message = "Invalid string"; if ("'" === \substr($this->lexer->match, 0, 1)) { $message .= ", it appears you used single quotes instead of double quotes"; } elseif (\preg_match('{".+?(\\\\[^"bfnrt/\\\\u](...)?)}', $this->lexer->getFullUpcomingInput(), $match)) { $message .= ", it appears you have an unescaped backslash at: " . $match[1]; } elseif (\preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getFullUpcomingInput())) { $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid"; } } $errStr = 'Parse error on line ' . ($yylineno + 1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; if ($message) { $errStr .= $message; } else { $errStr .= \count($expected) > 1 ? "Expected one of: " : "Expected: "; $errStr .= \implode(', ', $expected); } if (',' === \substr(\trim($this->lexer->getPastInput()), -1)) { $errStr .= " - It appears you have an extra trailing comma"; } $this->parseError($errStr, array('text' => $this->lexer->match, 'token' => isset($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol, 'line' => $this->lexer->yylineno, 'loc' => $yyloc, 'expected' => $expected)); } // just recovered from another error if ($recovering == 3) { if ($symbol === Lexer::EOF) { throw new ParsingException($errStr ?: 'Parsing halted.'); } // discard current lookahead and grab another $yyleng = $this->lexer->yyleng; $yytext = $this->lexer->yytext; $yylineno = $this->lexer->yylineno; $yyloc = $this->lexer->yylloc; $symbol = $this->lexer->lex(); } // try to recover from error while (\true) { // check for error recovery rule in this state if (\array_key_exists(Lexer::T_ERROR, $this->table[$state])) { break; } if ($state == 0) { throw new ParsingException($errStr ?: 'Parsing halted.'); } $this->popStack(1); $state = $this->stack[\count($this->stack) - 1]; } $preErrorSymbol = $symbol; // save the lookahead token $symbol = Lexer::T_ERROR; // insert generic error symbol as new lookahead $state = $this->stack[\count($this->stack) - 1]; /** @var array|false */ $action = isset($this->table[$state][Lexer::T_ERROR]) ? $this->table[$state][Lexer::T_ERROR] : \false; if ($action === \false) { throw new \LogicException('No table value found for ' . $state . ' => ' . Lexer::T_ERROR); } $recovering = 3; // allow 3 real symbols to be shifted before reporting a new error } // this shouldn't happen, unless resolve defaults are off if (\is_array($action[0]) && \count($action) > 1) { // @phpstan-ignore-line throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol); } switch ($action[0]) { case 1: // shift \assert(isset($symbol)); $this->stack[] = $symbol; $this->vstack[] = $this->lexer->yytext; $this->lstack[] = $this->lexer->yylloc; $this->stack[] = $action[1]; // push state $symbol = null; if (!$preErrorSymbol) { // normal execution/no error $yyleng = $this->lexer->yyleng; $yytext = $this->lexer->yytext; $yylineno = $this->lexer->yylineno; $yyloc = $this->lexer->yylloc; if ($recovering > 0) { $recovering--; } } else { // error just occurred, resume old lookahead from before error $symbol = $preErrorSymbol; $preErrorSymbol = null; } break; case 2: // reduce $len = $this->productions_[$action[1]][1]; // perform semantic action $currentToken = $this->vstack[\count($this->vstack) - $len]; // default to $$ = $1 // default location, uses first token for firsts, last for lasts $position = array( // _$ = store 'first_line' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_line'], 'last_line' => $this->lstack[\count($this->lstack) - 1]['last_line'], 'first_column' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_column'], 'last_column' => $this->lstack[\count($this->lstack) - 1]['last_column'], ); list($newToken, $actionResult) = $this->performAction($currentToken, $yytext, $yyleng, $yylineno, $action[1]); if (!$actionResult instanceof Undefined) { return $actionResult; } if ($len) { $this->popStack($len); } $this->stack[] = $this->productions_[$action[1]][0]; // push nonterminal (reduce) $this->vstack[] = $newToken; $this->lstack[] = $position; /** @var int */ $newState = $this->table[$this->stack[\count($this->stack) - 2]][$this->stack[\count($this->stack) - 1]]; $this->stack[] = $newState; break; case 3: // accept return \true; } } } /** * @param string $str * @param array{text: string, token: string|int, line: int, loc: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected: string[]}|null $hash * @return never */ protected function parseError($str, $hash = null) { throw new ParsingException($str, $hash ?: array()); } /** * @param stdClass|array|int|bool|float|string|null $currentToken * @param string $yytext * @param int $yyleng * @param int $yylineno * @param int $yystate * @return array{stdClass|array|int|bool|float|string|null, stdClass|array|int|bool|float|string|null|Undefined} */ private function performAction($currentToken, $yytext, $yyleng, $yylineno, $yystate) { $token = $currentToken; $len = \count($this->vstack) - 1; switch ($yystate) { case 1: $yytext = \preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext); $token = $yytext; break; case 2: if (\strpos($yytext, 'e') !== \false || \strpos($yytext, 'E') !== \false) { $token = \floatval($yytext); } else { $token = \strpos($yytext, '.') === \false ? \intval($yytext) : \floatval($yytext); } break; case 3: $token = null; break; case 4: $token = \true; break; case 5: $token = \false; break; case 6: $token = $this->vstack[$len - 1]; return array($token, $token); case 13: if ($this->flags & self::PARSE_TO_ASSOC) { $token = array(); } else { $token = new stdClass(); } break; case 14: $token = $this->vstack[$len - 1]; break; case 15: $token = array($this->vstack[$len - 2], $this->vstack[$len]); break; case 16: \assert(\is_array($this->vstack[$len])); if (\PHP_VERSION_ID < 70100) { $property = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0]; } else { $property = $this->vstack[$len][0]; } if ($this->flags & self::PARSE_TO_ASSOC) { $token = array(); $token[$property] = $this->vstack[$len][1]; } else { $token = new stdClass(); $token->{$property} = $this->vstack[$len][1]; } break; case 17: \assert(\is_array($this->vstack[$len])); if ($this->flags & self::PARSE_TO_ASSOC) { \assert(\is_array($this->vstack[$len - 2])); $token =& $this->vstack[$len - 2]; $key = $this->vstack[$len][0]; if ($this->flags & self::DETECT_KEY_CONFLICTS && isset($this->vstack[$len - 2][$key])) { $errStr = 'Parse error on line ' . ($yylineno + 1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; $errStr .= "Duplicate key: " . $this->vstack[$len][0]; throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno + 1)); } elseif ($this->flags & self::ALLOW_DUPLICATE_KEYS && isset($this->vstack[$len - 2][$key])) { $duplicateCount = 1; do { $duplicateKey = $key . '.' . $duplicateCount++; } while (isset($this->vstack[$len - 2][$duplicateKey])); $key = $duplicateKey; } $this->vstack[$len - 2][$key] = $this->vstack[$len][1]; } else { \assert($this->vstack[$len - 2] instanceof stdClass); $token = $this->vstack[$len - 2]; if (\PHP_VERSION_ID < 70100) { $key = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0]; } else { $key = $this->vstack[$len][0]; } if ($this->flags & self::DETECT_KEY_CONFLICTS && isset($this->vstack[$len - 2]->{$key})) { $errStr = 'Parse error on line ' . ($yylineno + 1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; $errStr .= "Duplicate key: " . $this->vstack[$len][0]; throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno + 1)); } elseif ($this->flags & self::ALLOW_DUPLICATE_KEYS && isset($this->vstack[$len - 2]->{$key})) { $duplicateCount = 1; do { $duplicateKey = $key . '.' . $duplicateCount++; } while (isset($this->vstack[$len - 2]->{$duplicateKey})); $key = $duplicateKey; } $this->vstack[$len - 2]->{$key} = $this->vstack[$len][1]; } break; case 18: $token = array(); break; case 19: $token = $this->vstack[$len - 1]; break; case 20: $token = array($this->vstack[$len]); break; case 21: \assert(\is_array($this->vstack[$len - 2])); $this->vstack[$len - 2][] = $this->vstack[$len]; $token = $this->vstack[$len - 2]; break; } return array($token, new Undefined()); } /** * @param string $match * @return string */ private function stringInterpolation($match) { switch ($match[0]) { case '\\\\': return '\\'; case '\\"': return '"'; case '\\b': return \chr(8); case '\\f': return \chr(12); case '\\n': return "\n"; case '\\r': return "\r"; case '\\t': return "\t"; case '\\/': return "/"; default: return \html_entity_decode('&#x' . \ltrim(\substr($match[0], 2), '0') . ';', \ENT_QUOTES, 'UTF-8'); } } /** * @param int $n * @return void */ private function popStack($n) { $this->stack = \array_slice($this->stack, 0, -(2 * $n)); $this->vstack = \array_slice($this->vstack, 0, -$n); $this->lstack = \array_slice($this->lstack, 0, -$n); } /** * @param string $input * @return void */ private function failOnBOM($input) { // UTF-8 ByteOrderMark sequence $bom = ""; if (\substr($input, 0, 3) === $bom) { $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark"); } } } Copyright (c) 2015 Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. { "name": "seld\/signal-handler", "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", "keywords": [ "unix", "posix", "signal", "sigint", "sigterm" ], "type": "library", "license": "MIT", "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http:\/\/seld.be" } ], "require": { "php": ">=7.2.0" }, "require-dev": { "phpunit\/phpunit": "^7.5.20 || ^8.5.23", "psr\/log": "^1 || ^2 || ^3", "phpstan\/phpstan": "^1", "phpstan\/phpstan-phpunit": "^1", "phpstan\/phpstan-strict-rules": "^1.3", "phpstan\/phpstan-deprecation-rules": "^1.0" }, "autoload": { "psr-4": { "_ContaoManager\\Seld\\Signal\\": "src\/" } }, "autoload-dev": { "psr-4": { "_ContaoManager\\Seld\\Signal\\": "tests\/" } }, "scripts": { "phpstan": "@php phpstan analyse", "test": "@php phpunit" }, "extra": { "branch-alias": { "dev-main": "2.x-dev" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Seld\Signal; use _ContaoManager\Psr\Log\LoggerInterface; use Closure; use WeakReference; /** * SignalHandler and factory */ final class SignalHandler { /** * The SIGHUP signal is sent to a process when its controlling terminal is closed. It was originally designed to * notify the process of a serial line drop (a hangup). In modern systems, this signal usually means that the * controlling pseudo or virtual terminal has been closed. Many daemons will reload their configuration files and * reopen their logfiles instead of exiting when receiving this signal. nohup is a command to make a command ignore * the signal. */ public const SIGHUP = 'SIGHUP'; /** * The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process. * This is typically initiated by pressing Ctrl-C, but on some systems, the "delete" character or "break" key can be * used. * * On Windows this is used to denote a PHP_WINDOWS_EVENT_CTRL_C */ public const SIGINT = 'SIGINT'; /** * The SIGQUIT signal is sent to a process by its controlling terminal when the user requests that the process quit * and perform a core dump. */ public const SIGQUIT = 'SIGQUIT'; /** * The SIGILL signal is sent to a process when it attempts to execute an illegal, malformed, unknown, or privileged * instruction. */ public const SIGILL = 'SIGILL'; /** * The SIGTRAP signal is sent to a process when an exception (or trap) occurs: a condition that a debugger has * requested to be informed of — for example, when a particular function is executed, or when a particular variable * changes value. */ public const SIGTRAP = 'SIGTRAP'; /** * The SIGABRT signal is sent to a process to tell it to abort, i.e. to terminate. The signal is usually initiated * by the process itself when it calls abort function of the C Standard Library, but it can be sent to the process * from outside like any other signal. */ public const SIGABRT = 'SIGABRT'; public const SIGIOT = 'SIGIOT'; /** * The SIGBUS signal is sent to a process when it causes a bus error. The conditions that lead to the signal being * sent are, for example, incorrect memory access alignment or non-existent physical address. */ public const SIGBUS = 'SIGBUS'; public const SIGFPE = 'SIGFPE'; /** * The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and * SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon * receiving this signal. */ public const SIGKILL = 'SIGKILL'; /** * The SIGUSR1 signal is sent to a process to indicate user-defined conditions. */ public const SIGUSR1 = 'SIGUSR1'; /** * The SIGUSR1 signa2 is sent to a process to indicate user-defined conditions. */ public const SIGUSR2 = 'SIGUSR2'; /** * The SIGSEGV signal is sent to a process when it makes an invalid virtual memory reference, or segmentation fault, * i.e. when it performs a segmentation violation. */ public const SIGSEGV = 'SIGSEGV'; /** * The SIGPIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the * other end. */ public const SIGPIPE = 'SIGPIPE'; /** * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a * preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses. * SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process * and by the system on behalf of the process elapses. */ public const SIGALRM = 'SIGALRM'; /** * The SIGTERM signal is sent to a process to request its termination. Unlike the SIGKILL signal, it can be caught * and interpreted or ignored by the process. This allows the process to perform nice termination releasing * resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM. */ public const SIGTERM = 'SIGTERM'; public const SIGSTKFLT = 'SIGSTKFLT'; public const SIGCLD = 'SIGCLD'; /** * The SIGCHLD signal is sent to a process when a child process terminates, is interrupted, or resumes after being * interrupted. One common usage of the signal is to instruct the operating system to clean up the resources used by * a child process after its termination without an explicit call to the wait system call. */ public const SIGCHLD = 'SIGCHLD'; /** * The SIGCONT signal instructs the operating system to continue (restart) a process previously paused by the * SIGSTOP or SIGTSTP signal. One important use of this signal is in job control in the Unix shell. */ public const SIGCONT = 'SIGCONT'; /** * The SIGSTOP signal instructs the operating system to stop a process for later resumption. */ public const SIGSTOP = 'SIGSTOP'; /** * The SIGTSTP signal is sent to a process by its controlling terminal to request it to stop (terminal stop). It is * commonly initiated by the user pressing Ctrl+Z. Unlike SIGSTOP, the process can register a signal handler for or * ignore the signal. */ public const SIGTSTP = 'SIGTSTP'; /** * The SIGTTIN signal is sent to a process when it attempts to read in from the tty while in the background. * Typically, this signal is received only by processes under job control; daemons do not have controlling */ public const SIGTTIN = 'SIGTTIN'; /** * The SIGTTOU signal is sent to a process when it attempts to write out from the tty while in the background. * Typically, this signal is received only by processes under job control; daemons do not have controlling */ public const SIGTTOU = 'SIGTTOU'; /** * The SIGURG signal is sent to a process when a socket has urgent or out-of-band data available to read. */ public const SIGURG = 'SIGURG'; /** * The SIGXCPU signal is sent to a process when it has used up the CPU for a duration that exceeds a certain * predetermined user-settable value. The arrival of a SIGXCPU signal provides the receiving process a chance to * quickly save any intermediate results and to exit gracefully, before it is terminated by the operating system * using the SIGKILL signal. */ public const SIGXCPU = 'SIGXCPU'; /** * The SIGXFSZ signal is sent to a process when it grows a file larger than the maximum allowed size */ public const SIGXFSZ = 'SIGXFSZ'; /** * The SIGVTALRM signal is sent to a process when the time limit specified in a call to a preceding alarm setting * function (such as setitimer) elapses. SIGVTALRM is sent when CPU time used by the process elapses. */ public const SIGVTALRM = 'SIGVTALRM'; /** * The SIGPROF signal is sent to a process when the time limit specified in a call to a preceding alarm setting * function (such as setitimer) elapses. SIGPROF is sent when CPU time used by the process and by the system on * behalf of the process elapses. */ public const SIGPROF = 'SIGPROF'; /** * The SIGWINCH signal is sent to a process when its controlling terminal changes its size (a window change). */ public const SIGWINCH = 'SIGWINCH'; /** * The SIGPOLL signal is sent when an event occurred on an explicitly watched file descriptor.Using it effectively * leads to making asynchronous I/O requests since the kernel will poll the descriptor in place of the caller. It * provides an alternative to active polling. */ public const SIGPOLL = 'SIGPOLL'; public const SIGIO = 'SIGIO'; /** * The SIGPWR signal is sent to a process when the system experiences a power failure. */ public const SIGPWR = 'SIGPWR'; /** * The SIGSYS signal is sent to a process when it passes a bad argument to a system call. In practice, this kind of * signal is rarely encountered since applications rely on libraries (e.g. libc) to make the call for them. */ public const SIGSYS = 'SIGSYS'; public const SIGBABY = 'SIGBABY'; /** * CTRL+Break support, available on Windows only for PHP_WINDOWS_EVENT_CTRL_BREAK */ public const SIGBREAK = 'SIGBREAK'; private const ALL_SIGNALS = [self::SIGHUP, self::SIGINT, self::SIGQUIT, self::SIGILL, self::SIGTRAP, self::SIGABRT, self::SIGIOT, self::SIGBUS, self::SIGFPE, self::SIGKILL, self::SIGUSR1, self::SIGUSR2, self::SIGSEGV, self::SIGPIPE, self::SIGALRM, self::SIGTERM, self::SIGSTKFLT, self::SIGCLD, self::SIGCHLD, self::SIGCONT, self::SIGSTOP, self::SIGTSTP, self::SIGTTIN, self::SIGTTOU, self::SIGURG, self::SIGXCPU, self::SIGXFSZ, self::SIGVTALRM, self::SIGPROF, self::SIGWINCH, self::SIGPOLL, self::SIGIO, self::SIGPWR, self::SIGSYS, self::SIGBABY, self::SIGBREAK]; /** * @var self::SIG*|null */ private $triggered = null; /** * @var list * @readonly */ private $signals; /** * @var LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null * @readonly */ private $loggerOrCallback; /** * @var array> */ private static $handlers = []; /** @var Closure|null */ private static $windowsHandler = null; /** * @param array $signals * @param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null $loggerOrCallback */ private function __construct(array $signals, $loggerOrCallback) { if (!\is_callable($loggerOrCallback) && !$loggerOrCallback instanceof LoggerInterface && $loggerOrCallback !== null) { throw new \InvalidArgumentException('$loggerOrCallback must be a ' . LoggerInterface::class . ' instance, a callable, or null, ' . (\is_object($loggerOrCallback) ? \get_class($loggerOrCallback) : \gettype($loggerOrCallback)) . ' received.'); } $this->signals = $signals; $this->loggerOrCallback = $loggerOrCallback; } /** * @param self::SIG* $signalName */ private function trigger(string $signalName) : void { $this->triggered = $signalName; if ($this->loggerOrCallback instanceof LoggerInterface) { $this->loggerOrCallback->info('Received ' . $signalName); } elseif ($this->loggerOrCallback !== null) { ($this->loggerOrCallback)($signalName, $this); } } /** * Fetches the triggered state of the handler * * @phpstan-impure */ public function isTriggered() : bool { return $this->triggered !== null; } /** * Exits the process while communicating that the handled signal was what killed the process * * This is different from doing exit(SIGINT), and is also different to a successful exit(0). * * This should only be used when you received a signal and then handled it to gracefully shutdown and are now ready to shutdown. * * ``` * $signal = SignalHandler::create([SignalHandler::SIGINT], function (string $signal, SignalHandler $handler) { * // do cleanup here.. * * $handler->exitWithLastSignal(); * }); * * // or... * * $signal = SignalHandler::create([SignalHandler::SIGINT]); * * while ($doingThings) { * if ($signal->isTriggered()) { * $signal->exitWithLastSignal(); * } * * // do more things * } * ``` * * @see https://www.cons.org/cracauer/sigint.html * @return never */ public function exitWithLastSignal() : void { $signal = $this->triggered ?? 'SIGINT'; $signal = \defined($signal) ? \constant($signal) : 2; if (\function_exists('posix_kill') && \function_exists('posix_getpid')) { \pcntl_signal($signal, \SIG_DFL); \posix_kill(\posix_getpid(), $signal); } // just in case posix_kill above could not run // not strictly correct but it's the best we can do here exit(128 + $signal); } /** * Resets the state to let a handler accept a signal again */ public function reset() : void { $this->triggered = null; } public function __destruct() { $this->unregister(); } /** * @param (string|int)[] $signals array of signal names (more portable, see SignalHandler::SIG*) or constants - defaults to [SIGINT, SIGTERM] * @param LoggerInterface|callable $loggerOrCallback A PSR-3 Logger or a callback($signal, $signalName) * @return self A handler on which you can call isTriggered to know if the signal was received, and reset() to forget * * @phpstan-param list $signals * @phpstan-param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void) $loggerOrCallback */ public static function create(?array $signals = null, $loggerOrCallback = null) : self { if ($signals === null) { $signals = [self::SIGINT, self::SIGTERM]; } $signals = \array_map(function ($signal) { if (\is_int($signal)) { return self::getSignalName($signal); } elseif (!\in_array($signal, self::ALL_SIGNALS, \true)) { throw new \InvalidArgumentException('$signals must be an array of SIG* constants or self::SIG* constants, got ' . \var_export($signal, \true)); } return $signal; }, (array) $signals); $handler = new self($signals, $loggerOrCallback); if (\PHP_VERSION_ID >= 80000) { \array_unshift(self::$handlers, WeakReference::create($handler)); } else { \array_unshift(self::$handlers, $handler); } if (\function_exists('sapi_windows_set_ctrl_handler') && \PHP_SAPI === 'cli' && (\in_array(self::SIGINT, $signals, \true) || \in_array(self::SIGBREAK, $signals, \true))) { if (null === self::$windowsHandler) { self::$windowsHandler = Closure::fromCallable([self::class, 'handleWindowsSignal']); \sapi_windows_set_ctrl_handler(self::$windowsHandler); } } if (\function_exists('pcntl_signal') && \function_exists('pcntl_async_signals')) { \pcntl_async_signals(\true); self::registerPcntlHandler($signals); } return $handler; } /** * Clears the signal handler * * On PHP 8+ this is not necessary and it will happen automatically on __destruct, but PHP 7 does not * support weak references and thus there you need to manually do this. * * If another handler was registered previously to this one, it becomes active again */ public function unregister() : void { $signals = $this->signals; $index = \false; foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference && $handler->get() === $this || $handler === $this) { $index = $key; break; } } if ($index === \false) { // guard against double-unregistration when __destruct happens return; } unset(self::$handlers[$index]); if (self::$windowsHandler !== null && (\in_array(self::SIGINT, $signals, \true) || \in_array(self::SIGBREAK, $signals, \true))) { if (self::getHandlerFor(self::SIGINT) === null && self::getHandlerFor(self::SIGBREAK) === null) { \sapi_windows_set_ctrl_handler(self::$windowsHandler, \false); self::$windowsHandler = null; } } if (\function_exists('pcntl_signal')) { foreach ($signals as $signal) { // skip missing signals, for example OSX does not have all signals if (!\defined($signal)) { continue; } // keep listening to signals where we have a handler registered if (self::getHandlerFor($signal) !== null) { continue; } \pcntl_signal(\constant($signal), \SIG_DFL); } } } /** * Clears all signal handlers * * On PHP 8+ this should not be necessary as it will happen automatically on __destruct, but PHP 7 does not * support weak references and thus there you need to manually do this. * * This can be done to reset the global state, but ideally you should always call ->unregister() in a try/finally block to ensure it happens. */ public static function unregisterAll() : void { if (self::$windowsHandler !== null) { \sapi_windows_set_ctrl_handler(self::$windowsHandler, \false); self::$windowsHandler = null; } foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference) { $handler = $handler->get(); if ($handler === null) { unset(self::$handlers[$key]); continue; } } $handler->unregister(); } } /** * @param list $signals */ private static function registerPcntlHandler(array $signals) : void { static $callable; if ($callable === null) { $callable = Closure::fromCallable([self::class, 'handlePcntlSignal']); } foreach ($signals as $signal) { // skip missing signals, for example OSX does not have all signals if (!\defined($signal)) { continue; } \pcntl_signal(\constant($signal), $callable); } } private static function handleWindowsSignal(int $event) : void { if (\PHP_WINDOWS_EVENT_CTRL_C === $event) { self::callHandlerFor(self::SIGINT); } elseif (\PHP_WINDOWS_EVENT_CTRL_BREAK === $event) { self::callHandlerFor(self::SIGBREAK); } } private static function handlePcntlSignal(int $signal) : void { self::callHandlerFor(self::getSignalName($signal)); } /** * Calls the first handler from the top of the stack that can handle a given signal * * @param self::SIG* $signal */ private static function callHandlerFor(string $signal) : void { $handler = self::getHandlerFor($signal); if ($handler !== null) { $handler->trigger($signal); } } /** * Returns the first handler from the top of the stack that can handle a given signal * * @param self::SIG* $signal * @return self|null */ private static function getHandlerFor(string $signal) : ?self { foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference) { $handler = $handler->get(); if ($handler === null) { unset(self::$handlers[$key]); continue; } } if (\in_array($signal, $handler->signals, \true)) { return $handler; } } return null; } /** * @return self::SIG* */ private static function getSignalName(int $signo) : string { static $signals = null; if ($signals === null) { $signals = []; foreach (self::ALL_SIGNALS as $value) { if (\defined($value)) { $signals[\constant($value)] = $value; } } } if (isset($signals[$signo])) { return $signals[$signo]; } throw new \InvalidArgumentException('Unknown signal #' . $signo); } } realpath = \realpath($opened_path) ?: $opened_path; $opened_path = $this->realpath; $this->handle = \fopen($this->realpath, $mode); $this->position = 0; return (bool) $this->handle; } public function stream_read($count) { $data = \fread($this->handle, $count); if ($this->position === 0) { $data = \preg_replace('{^#!.*\\r?\\n}', '', $data); } $this->position += \strlen($data); return $data; } public function stream_cast($castAs) { return $this->handle; } public function stream_close() { \fclose($this->handle); } public function stream_lock($operation) { return $operation ? \flock($this->handle, $operation) : \true; } public function stream_seek($offset, $whence) { if (0 === \fseek($this->handle, $offset, $whence)) { $this->position = \ftell($this->handle); return \true; } return \false; } public function stream_tell() { return $this->position; } public function stream_eof() { return \feof($this->handle); } public function stream_stat() { return array(); } public function stream_set_option($option, $arg1, $arg2) { return \true; } public function url_stat($path, $flags) { $path = \substr($path, 17); if (\file_exists($path)) { return \stat($path); } return \false; } } } if (\function_exists('stream_get_wrappers') && \in_array('phpvfscomposer', \stream_get_wrappers(), \true) || \function_exists('stream_wrapper_register') && \stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) { return include "phpvfscomposer://" . __DIR__ . '/..' . '/symfony/yaml/Resources/bin/yaml-lint'; } } return include __DIR__ . '/..' . '/symfony/yaml/Resources/bin/yaml-lint'; #!/usr/bin/env php realpath = \realpath($opened_path) ?: $opened_path; $opened_path = $this->realpath; $this->handle = \fopen($this->realpath, $mode); $this->position = 0; return (bool) $this->handle; } public function stream_read($count) { $data = \fread($this->handle, $count); if ($this->position === 0) { $data = \preg_replace('{^#!.*\\r?\\n}', '', $data); } $this->position += \strlen($data); return $data; } public function stream_cast($castAs) { return $this->handle; } public function stream_close() { \fclose($this->handle); } public function stream_lock($operation) { return $operation ? \flock($this->handle, $operation) : \true; } public function stream_seek($offset, $whence) { if (0 === \fseek($this->handle, $offset, $whence)) { $this->position = \ftell($this->handle); return \true; } return \false; } public function stream_tell() { return $this->position; } public function stream_eof() { return \feof($this->handle); } public function stream_stat() { return array(); } public function stream_set_option($option, $arg1, $arg2) { return \true; } public function url_stat($path, $flags) { $path = \substr($path, 17); if (\file_exists($path)) { return \stat($path); } return \false; } } } if (\function_exists('stream_get_wrappers') && \in_array('phpvfscomposer', \stream_get_wrappers(), \true) || \function_exists('stream_wrapper_register') && \stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) { return include "phpvfscomposer://" . __DIR__ . '/..' . '/symfony/error-handler/Resources/bin/patch-type-declarations'; } } return include __DIR__ . '/..' . '/symfony/error-handler/Resources/bin/patch-type-declarations'; #!/usr/bin/env php realpath = \realpath($opened_path) ?: $opened_path; $opened_path = $this->realpath; $this->handle = \fopen($this->realpath, $mode); $this->position = 0; return (bool) $this->handle; } public function stream_read($count) { $data = \fread($this->handle, $count); if ($this->position === 0) { $data = \preg_replace('{^#!.*\\r?\\n}', '', $data); } $this->position += \strlen($data); return $data; } public function stream_cast($castAs) { return $this->handle; } public function stream_close() { \fclose($this->handle); } public function stream_lock($operation) { return $operation ? \flock($this->handle, $operation) : \true; } public function stream_seek($offset, $whence) { if (0 === \fseek($this->handle, $offset, $whence)) { $this->position = \ftell($this->handle); return \true; } return \false; } public function stream_tell() { return $this->position; } public function stream_eof() { return \feof($this->handle); } public function stream_stat() { return array(); } public function stream_set_option($option, $arg1, $arg2) { return \true; } public function url_stat($path, $flags) { $path = \substr($path, 17); if (\file_exists($path)) { return \stat($path); } return \false; } } } if (\function_exists('stream_get_wrappers') && \in_array('phpvfscomposer', \stream_get_wrappers(), \true) || \function_exists('stream_wrapper_register') && \stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) { return include "phpvfscomposer://" . __DIR__ . '/..' . '/justinrainbow/json-schema/bin/validate-json'; } } return include __DIR__ . '/..' . '/justinrainbow/json-schema/bin/validate-json'; #!/usr/bin/env php realpath = \realpath($opened_path) ?: $opened_path; $opened_path = $this->realpath; $this->handle = \fopen($this->realpath, $mode); $this->position = 0; return (bool) $this->handle; } public function stream_read($count) { $data = \fread($this->handle, $count); if ($this->position === 0) { $data = \preg_replace('{^#!.*\\r?\\n}', '', $data); } $this->position += \strlen($data); return $data; } public function stream_cast($castAs) { return $this->handle; } public function stream_close() { \fclose($this->handle); } public function stream_lock($operation) { return $operation ? \flock($this->handle, $operation) : \true; } public function stream_seek($offset, $whence) { if (0 === \fseek($this->handle, $offset, $whence)) { $this->position = \ftell($this->handle); return \true; } return \false; } public function stream_tell() { return $this->position; } public function stream_eof() { return \feof($this->handle); } public function stream_stat() { return array(); } public function stream_set_option($option, $arg1, $arg2) { return \true; } public function url_stat($path, $flags) { $path = \substr($path, 17); if (\file_exists($path)) { return \stat($path); } return \false; } } } if (\function_exists('stream_get_wrappers') && \in_array('phpvfscomposer', \stream_get_wrappers(), \true) || \function_exists('stream_wrapper_register') && \stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) { return include "phpvfscomposer://" . __DIR__ . '/..' . '/composer/composer/bin/composer'; } } return include __DIR__ . '/..' . '/composer/composer/bin/composer'; #!/usr/bin/env php realpath = \realpath($opened_path) ?: $opened_path; $opened_path = $this->realpath; $this->handle = \fopen($this->realpath, $mode); $this->position = 0; return (bool) $this->handle; } public function stream_read($count) { $data = \fread($this->handle, $count); if ($this->position === 0) { $data = \preg_replace('{^#!.*\\r?\\n}', '', $data); } $this->position += \strlen($data); return $data; } public function stream_cast($castAs) { return $this->handle; } public function stream_close() { \fclose($this->handle); } public function stream_lock($operation) { return $operation ? \flock($this->handle, $operation) : \true; } public function stream_seek($offset, $whence) { if (0 === \fseek($this->handle, $offset, $whence)) { $this->position = \ftell($this->handle); return \true; } return \false; } public function stream_tell() { return $this->position; } public function stream_eof() { return \feof($this->handle); } public function stream_stat() { return array(); } public function stream_set_option($option, $arg1, $arg2) { return \true; } public function url_stat($path, $flags) { $path = \substr($path, 17); if (\file_exists($path)) { return \stat($path); } return \false; } } } if (\function_exists('stream_get_wrappers') && \in_array('phpvfscomposer', \stream_get_wrappers(), \true) || \function_exists('stream_wrapper_register') && \stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) { return include "phpvfscomposer://" . __DIR__ . '/..' . '/symfony/var-dumper/Resources/bin/var-dump-server'; } } return include __DIR__ . '/..' . '/symfony/var-dumper/Resources/bin/var-dump-server'; #!/usr/bin/env php realpath = \realpath($opened_path) ?: $opened_path; $opened_path = $this->realpath; $this->handle = \fopen($this->realpath, $mode); $this->position = 0; return (bool) $this->handle; } public function stream_read($count) { $data = \fread($this->handle, $count); if ($this->position === 0) { $data = \preg_replace('{^#!.*\\r?\\n}', '', $data); } $this->position += \strlen($data); return $data; } public function stream_cast($castAs) { return $this->handle; } public function stream_close() { \fclose($this->handle); } public function stream_lock($operation) { return $operation ? \flock($this->handle, $operation) : \true; } public function stream_seek($offset, $whence) { if (0 === \fseek($this->handle, $offset, $whence)) { $this->position = \ftell($this->handle); return \true; } return \false; } public function stream_tell() { return $this->position; } public function stream_eof() { return \feof($this->handle); } public function stream_stat() { return array(); } public function stream_set_option($option, $arg1, $arg2) { return \true; } public function url_stat($path, $flags) { $path = \substr($path, 17); if (\file_exists($path)) { return \stat($path); } return \false; } } } if (\function_exists('stream_get_wrappers') && \in_array('phpvfscomposer', \stream_get_wrappers(), \true) || \function_exists('stream_wrapper_register') && \stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) { return include "phpvfscomposer://" . __DIR__ . '/..' . '/seld/jsonlint/bin/jsonlint'; } } return include __DIR__ . '/..' . '/seld/jsonlint/bin/jsonlint'; `Composer\XdebugHandler\XdebugHandler` [Unreleased]: https://github.com/composer/xdebug-handler/compare/3.0.3...HEAD [3.0.2]: https://github.com/composer/xdebug-handler/compare/3.0.2...3.0.3 [3.0.2]: https://github.com/composer/xdebug-handler/compare/3.0.1...3.0.2 [3.0.1]: https://github.com/composer/xdebug-handler/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/composer/xdebug-handler/compare/2.0.3...3.0.0 [2.0.3]: https://github.com/composer/xdebug-handler/compare/2.0.2...2.0.3 [2.0.2]: https://github.com/composer/xdebug-handler/compare/2.0.1...2.0.2 [2.0.1]: https://github.com/composer/xdebug-handler/compare/2.0.0...2.0.1 [2.0.0]: https://github.com/composer/xdebug-handler/compare/1.4.6...2.0.0 [1.4.6]: https://github.com/composer/xdebug-handler/compare/1.4.5...1.4.6 [1.4.5]: https://github.com/composer/xdebug-handler/compare/1.4.4...1.4.5 [1.4.4]: https://github.com/composer/xdebug-handler/compare/1.4.3...1.4.4 [1.4.3]: https://github.com/composer/xdebug-handler/compare/1.4.2...1.4.3 [1.4.2]: https://github.com/composer/xdebug-handler/compare/1.4.1...1.4.2 [1.4.1]: https://github.com/composer/xdebug-handler/compare/1.4.0...1.4.1 [1.4.0]: https://github.com/composer/xdebug-handler/compare/1.3.3...1.4.0 [1.3.3]: https://github.com/composer/xdebug-handler/compare/1.3.2...1.3.3 [1.3.2]: https://github.com/composer/xdebug-handler/compare/1.3.1...1.3.2 [1.3.1]: https://github.com/composer/xdebug-handler/compare/1.3.0...1.3.1 [1.3.0]: https://github.com/composer/xdebug-handler/compare/1.2.1...1.3.0 [1.2.1]: https://github.com/composer/xdebug-handler/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/composer/xdebug-handler/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/composer/xdebug-handler/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/composer/xdebug-handler/compare/d66f0d15cb57...1.0.0 # composer/xdebug-handler [![packagist](https://img.shields.io/packagist/v/composer/xdebug-handler)](https://packagist.org/packages/composer/xdebug-handler) [![Continuous Integration](https://github.com/composer/xdebug-handler/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/xdebug-handler/actions?query=branch:main) ![license](https://img.shields.io/github/license/composer/xdebug-handler.svg) ![php](https://img.shields.io/packagist/php-v/composer/xdebug-handler?colorB=8892BF) Restart a CLI process without loading the Xdebug extension, unless `xdebug.mode=off`. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. ### Version 3 Removed support for legacy PHP versions and added type declarations. Long term support for version 2 (PHP 5.3.2 - 7.2.4) follows [Composer 2.2 LTS](https://blog.packagist.com/composer-2-2/) policy. ## Installation Install the latest version with: ```bash $ composer require composer/xdebug-handler ``` ## Requirements * PHP 7.2.5 minimum, although using the latest PHP version is highly recommended. ## Basic Usage ```php use Composer\XdebugHandler\XdebugHandler; $xdebug = new XdebugHandler('myapp'); $xdebug->check(); unset($xdebug); ``` The constructor takes a single parameter, `$envPrefix`, which is upper-cased and prepended to default base values to create two distinct environment variables. The above example enables the use of: - `MYAPP_ALLOW_XDEBUG=1` to override automatic restart and allow Xdebug - `MYAPP_ORIGINAL_INIS` to obtain ini file locations in a restarted process ## Advanced Usage * [How it works](#how-it-works) * [Limitations](#limitations) * [Helper methods](#helper-methods) * [Setter methods](#setter-methods) * [Process configuration](#process-configuration) * [Troubleshooting](#troubleshooting) * [Extending the library](#extending-the-library) ### How it works A temporary ini file is created from the loaded (and scanned) ini files, with any references to the Xdebug extension commented out. Current ini settings are merged, so that most ini settings made on the command-line or by the application are included (see [Limitations](#limitations)) * `MYAPP_ALLOW_XDEBUG` is set with internal data to flag and use in the restart. * The command-line and environment are [configured](#process-configuration) for the restart. * The application is restarted in a new process. * The restart settings are stored in the environment. * `MYAPP_ALLOW_XDEBUG` is unset. * The application runs and exits. * The main process exits with the exit code from the restarted process. #### Signal handling Asynchronous signal handling is automatically enabled if the pcntl extension is loaded. `SIGINT` is set to `SIG_IGN` in the parent process and restored to `SIG_DFL` in the restarted process (if no other handler has been set). From PHP 7.4 on Windows, `CTRL+C` and `CTRL+BREAK` handling is automatically enabled in the restarted process and ignored in the parent process. ### Limitations There are a few things to be aware of when running inside a restarted process. * Extensions set on the command-line will not be loaded. * Ini file locations will be reported as per the restart - see [getAllIniFiles()](#getallinifiles). * Php sub-processes may be loaded with Xdebug enabled - see [Process configuration](#process-configuration). ### Helper methods These static methods provide information from the current process, regardless of whether it has been restarted or not. #### _getAllIniFiles(): array_ Returns an array of the original ini file locations. Use this instead of calling `php_ini_loaded_file` and `php_ini_scanned_files`, which will report the wrong values in a restarted process. ```php use Composer\XdebugHandler\XdebugHandler; $files = XdebugHandler::getAllIniFiles(); # $files[0] always exists, it could be an empty string $loadedIni = array_shift($files); $scannedInis = $files; ``` These locations are also available in the `MYAPP_ORIGINAL_INIS` environment variable. This is a path-separated string comprising the location returned from `php_ini_loaded_file`, which could be empty, followed by locations parsed from calling `php_ini_scanned_files`. #### _getRestartSettings(): ?array_ Returns an array of settings that can be used with PHP [sub-processes](#sub-processes), or null if the process was not restarted. ```php use Composer\XdebugHandler\XdebugHandler; $settings = XdebugHandler::getRestartSettings(); /** * $settings: array (if the current process was restarted, * or called with the settings from a previous restart), or null * * 'tmpIni' => the temporary ini file used in the restart (string) * 'scannedInis' => if there were any scanned inis (bool) * 'scanDir' => the original PHP_INI_SCAN_DIR value (false|string) * 'phprc' => the original PHPRC value (false|string) * 'inis' => the original inis from getAllIniFiles (array) * 'skipped' => the skipped version from getSkippedVersion (string) */ ``` #### _getSkippedVersion(): string_ Returns the Xdebug version string that was skipped by the restart, or an empty string if there was no restart (or Xdebug is still loaded, perhaps by an extending class restarting for a reason other than removing Xdebug). ```php use Composer\XdebugHandler\XdebugHandler; $version = XdebugHandler::getSkippedVersion(); # $version: '3.1.1' (for example), or an empty string ``` #### _isXdebugActive(): bool_ Returns true if Xdebug is loaded and is running in an active mode (if it supports modes). Returns false if Xdebug is not loaded, or it is running with `xdebug.mode=off`. ### Setter methods These methods implement a fluent interface and must be called before the main `check()` method. #### _setLogger(LoggerInterface $logger): self_ Enables the output of status messages to an external PSR3 logger. All messages are reported with either `DEBUG` or `WARNING` log levels. For example (showing the level and message): ``` // No restart DEBUG Checking MYAPP_ALLOW_XDEBUG DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=off DEBUG No restart (APP_ALLOW_XDEBUG=0) Allowed by xdebug.mode // Restart overridden DEBUG Checking MYAPP_ALLOW_XDEBUG DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=coverage,debug,develop DEBUG No restart (MYAPP_ALLOW_XDEBUG=1) // Failed restart DEBUG Checking MYAPP_ALLOW_XDEBUG DEBUG The Xdebug extension is loaded (3.1.0) WARNING No restart (Unable to create temp ini file at: ...) ``` Status messages can also be output with `XDEBUG_HANDLER_DEBUG`. See [Troubleshooting](#troubleshooting). #### _setMainScript(string $script): self_ Sets the location of the main script to run in the restart. This is only needed in more esoteric use-cases, or if the `argv[0]` location is inaccessible. The script name `--` is supported for standard input. #### _setPersistent(): self_ Configures the restart using [persistent settings](#persistent-settings), so that Xdebug is not loaded in any sub-process. Use this method if your application invokes one or more PHP sub-process and the Xdebug extension is not needed. This avoids the overhead of implementing specific [sub-process](#sub-processes) strategies. Alternatively, this method can be used to set up a default _Xdebug-free_ environment which can be changed if a sub-process requires Xdebug, then restored afterwards: ```php function SubProcessWithXdebug() { $phpConfig = new Composer\XdebugHandler\PhpConfig(); # Set the environment to the original configuration $phpConfig->useOriginal(); # run the process with Xdebug loaded ... # Restore Xdebug-free environment $phpConfig->usePersistent(); } ``` ### Process configuration The library offers two strategies to invoke a new PHP process without loading Xdebug, using either _standard_ or _persistent_ settings. Note that this is only important if the application calls a PHP sub-process. #### Standard settings Uses command-line options to remove Xdebug from the new process only. * The -n option is added to the command-line. This tells PHP not to scan for additional inis. * The temporary ini is added to the command-line with the -c option. >_If the new process calls a PHP sub-process, Xdebug will be loaded in that sub-process (unless it implements xdebug-handler, in which case there will be another restart)._ This is the default strategy used in the restart. #### Persistent settings Uses environment variables to remove Xdebug from the new process and persist these settings to any sub-process. * `PHP_INI_SCAN_DIR` is set to an empty string. This tells PHP not to scan for additional inis. * `PHPRC` is set to the temporary ini. >_If the new process calls a PHP sub-process, Xdebug will not be loaded in that sub-process._ This strategy can be used in the restart by calling [setPersistent()](#setpersistent). #### Sub-processes The `PhpConfig` helper class makes it easy to invoke a PHP sub-process (with or without Xdebug loaded), regardless of whether there has been a restart. Each of its methods returns an array of PHP options (to add to the command-line) and sets up the environment for the required strategy. The [getRestartSettings()](#getrestartsettings) method is used internally. * `useOriginal()` - Xdebug will be loaded in the new process. * `useStandard()` - Xdebug will **not** be loaded in the new process - see [standard settings](#standard-settings). * `userPersistent()` - Xdebug will **not** be loaded in the new process - see [persistent settings](#persistent-settings) If there was no restart, an empty options array is returned and the environment is not changed. ```php use Composer\XdebugHandler\PhpConfig; $config = new PhpConfig; $options = $config->useOriginal(); # $options: empty array # environment: PHPRC and PHP_INI_SCAN_DIR set to original values $options = $config->useStandard(); # $options: [-n, -c, tmpIni] # environment: PHPRC and PHP_INI_SCAN_DIR set to original values $options = $config->usePersistent(); # $options: empty array # environment: PHPRC=tmpIni, PHP_INI_SCAN_DIR='' ``` ### Troubleshooting The following environment settings can be used to troubleshoot unexpected behavior: * `XDEBUG_HANDLER_DEBUG=1` Outputs status messages to `STDERR`, if it is defined, irrespective of any PSR3 logger. Each message is prefixed `xdebug-handler[pid]`, where pid is the process identifier. * `XDEBUG_HANDLER_DEBUG=2` As above, but additionally saves the temporary ini file and reports its location in a status message. ### Extending the library The API is defined by classes and their accessible elements that are not annotated as @internal. The main class has two protected methods that can be overridden to provide additional functionality: #### _requiresRestart(bool $default): bool_ By default the process will restart if Xdebug is loaded and not running with `xdebug.mode=off`. Extending this method allows an application to decide, by returning a boolean (or equivalent) value. It is only called if `MYAPP_ALLOW_XDEBUG` is empty, so it will not be called in the restarted process (where this variable contains internal data), or if the restart has been overridden. Note that the [setMainScript()](#setmainscriptscript) and [setPersistent()](#setpersistent) setters can be used here, if required. #### _restart(array $command): void_ An application can extend this to modify the temporary ini file, its location given in the `tmpIni` property. New settings can be safely appended to the end of the data, which is `PHP_EOL` terminated. The `$command` parameter is an array of unescaped command-line arguments that will be used for the new process. Remember to finish with `parent::restart($command)`. #### Example This example demonstrates two ways to extend basic functionality: * To avoid the overhead of spinning up a new process, the restart is skipped if a simple help command is requested. * The application needs write-access to phar files, so it will force a restart if `phar.readonly` is set (regardless of whether Xdebug is loaded) and change this value in the temporary ini file. ```php use Composer\XdebugHandler\XdebugHandler; use MyApp\Command; class MyRestarter extends XdebugHandler { private $required; protected function requiresRestart(bool $default): bool { if (Command::isHelp()) { # No need to disable Xdebug for this return false; } $this->required = (bool) ini_get('phar.readonly'); return $this->required || $default; } protected function restart(array $command): void { if ($this->required) { # Add required ini setting to tmpIni $content = file_get_contents($this->tmpIni); $content .= 'phar.readonly=0'.PHP_EOL; file_put_contents($this->tmpIni, $content); } parent::restart($command); } } ``` ## License composer/xdebug-handler is licensed under the MIT License, see the LICENSE file for details. { "name": "composer\/xdebug-handler", "description": "Restarts a process without Xdebug.", "type": "library", "license": "MIT", "keywords": [ "xdebug", "performance" ], "authors": [ { "name": "John Stevenson", "email": "john-stevenson@blueyonder.co.uk" } ], "support": { "irc": "irc:\/\/irc.freenode.org\/composer", "issues": "https:\/\/github.com\/composer\/xdebug-handler\/issues" }, "require": { "php": "^7.2.5 || ^8.0", "psr\/log": "^1 || ^2 || ^3", "composer\/pcre": "^1 || ^2 || ^3" }, "require-dev": { "symfony\/phpunit-bridge": "^6.0", "phpstan\/phpstan": "^1.0", "phpstan\/phpstan-strict-rules": "^1.1" }, "autoload": { "psr-4": { "Composer\\XdebugHandler\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\XdebugHandler\\Tests\\": "tests" } }, "scripts": { "test": "@php vendor\/bin\/simple-phpunit", "phpstan": "@php vendor\/bin\/phpstan analyse" } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; /** * @author John Stevenson * * @phpstan-type restartData array{tmpIni: string, scannedInis: bool, scanDir: false|string, phprc: false|string, inis: string[], skipped: string} */ class PhpConfig { /** * Use the original PHP configuration * * @return string[] Empty array of PHP cli options */ public function useOriginal() : array { $this->getDataAndReset(); return []; } /** * Use standard restart settings * * @return string[] PHP cli options */ public function useStandard() : array { $data = $this->getDataAndReset(); if ($data !== null) { return ['-n', '-c', $data['tmpIni']]; } return []; } /** * Use environment variables to persist settings * * @return string[] Empty array of PHP cli options */ public function usePersistent() : array { $data = $this->getDataAndReset(); if ($data !== null) { $this->updateEnv('PHPRC', $data['tmpIni']); $this->updateEnv('PHP_INI_SCAN_DIR', ''); } return []; } /** * Returns restart data if available and resets the environment * * @phpstan-return restartData|null */ private function getDataAndReset() : ?array { $data = \Composer\XdebugHandler\XdebugHandler::getRestartSettings(); if ($data !== null) { $this->updateEnv('PHPRC', $data['phprc']); $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']); } return $data; } /** * Updates a restart settings value in the environment * * @param string $name * @param string|false $value */ private function updateEnv(string $name, $value) : void { \Composer\XdebugHandler\Process::setEnv($name, \false !== $value ? $value : null); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ declare (strict_types=1); namespace Composer\XdebugHandler; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Psr\Log\LogLevel; /** * @author John Stevenson * @internal */ class Status { const ENV_RESTART = 'XDEBUG_HANDLER_RESTART'; const CHECK = 'Check'; const ERROR = 'Error'; const INFO = 'Info'; const NORESTART = 'NoRestart'; const RESTART = 'Restart'; const RESTARTING = 'Restarting'; const RESTARTED = 'Restarted'; /** @var bool */ private $debug; /** @var string */ private $envAllowXdebug; /** @var string|null */ private $loaded; /** @var LoggerInterface|null */ private $logger; /** @var bool */ private $modeOff; /** @var float */ private $time; /** * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name * @param bool $debug Whether debug output is required */ public function __construct(string $envAllowXdebug, bool $debug) { $start = \getenv(self::ENV_RESTART); \Composer\XdebugHandler\Process::setEnv(self::ENV_RESTART); $this->time = \is_numeric($start) ? \round((\microtime(\true) - $start) * 1000) : 0; $this->envAllowXdebug = $envAllowXdebug; $this->debug = $debug && \defined('STDERR'); $this->modeOff = \false; } /** * Activates status message output to a PSR3 logger * * @return void */ public function setLogger(LoggerInterface $logger) : void { $this->logger = $logger; } /** * Calls a handler method to report a message * * @throws \InvalidArgumentException If $op is not known */ public function report(string $op, ?string $data) : void { if ($this->logger !== null || $this->debug) { $callable = [$this, 'report' . $op]; if (!\is_callable($callable)) { throw new \InvalidArgumentException('Unknown op handler: ' . $op); } $params = $data !== null ? [$data] : []; \call_user_func_array($callable, $params); } } /** * Outputs a status message */ private function output(string $text, ?string $level = null) : void { if ($this->logger !== null) { $this->logger->log($level !== null ? $level : LogLevel::DEBUG, $text); } if ($this->debug) { \fwrite(\STDERR, \sprintf('xdebug-handler[%d] %s', \getmypid(), $text . \PHP_EOL)); } } /** * Checking status message */ private function reportCheck(string $loaded) : void { list($version, $mode) = \explode('|', $loaded); if ($version !== '') { $this->loaded = '(' . $version . ')' . ($mode !== '' ? ' xdebug.mode=' . $mode : ''); } $this->modeOff = $mode === 'off'; $this->output('Checking ' . $this->envAllowXdebug); } /** * Error status message */ private function reportError(string $error) : void { $this->output(\sprintf('No restart (%s)', $error), LogLevel::WARNING); } /** * Info status message */ private function reportInfo(string $info) : void { $this->output($info); } /** * No restart status message */ private function reportNoRestart() : void { $this->output($this->getLoadedMessage()); if ($this->loaded !== null) { $text = \sprintf('No restart (%s)', $this->getEnvAllow()); if (!(bool) \getenv($this->envAllowXdebug)) { $text .= ' Allowed by ' . ($this->modeOff ? 'xdebug.mode' : 'application'); } $this->output($text); } } /** * Restart status message */ private function reportRestart() : void { $this->output($this->getLoadedMessage()); \Composer\XdebugHandler\Process::setEnv(self::ENV_RESTART, (string) \microtime(\true)); } /** * Restarted status message */ private function reportRestarted() : void { $loaded = $this->getLoadedMessage(); $text = \sprintf('Restarted (%d ms). %s', $this->time, $loaded); $level = $this->loaded !== null ? LogLevel::WARNING : null; $this->output($text, $level); } /** * Restarting status message */ private function reportRestarting(string $command) : void { $text = \sprintf('Process restarting (%s)', $this->getEnvAllow()); $this->output($text); $text = 'Running ' . $command; $this->output($text); } /** * Returns the _ALLOW_XDEBUG environment variable as name=value */ private function getEnvAllow() : string { return $this->envAllowXdebug . '=' . \getenv($this->envAllowXdebug); } /** * Returns the Xdebug status and version */ private function getLoadedMessage() : string { $loaded = $this->loaded !== null ? \sprintf('loaded %s', $this->loaded) : 'not loaded'; return 'The Xdebug extension is ' . $loaded; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ declare (strict_types=1); namespace Composer\XdebugHandler; use Composer\Pcre\Preg; use _ContaoManager\Psr\Log\LoggerInterface; /** * @author John Stevenson * * @phpstan-import-type restartData from PhpConfig */ class XdebugHandler { const SUFFIX_ALLOW = '_ALLOW_XDEBUG'; const SUFFIX_INIS = '_ORIGINAL_INIS'; const RESTART_ID = 'internal'; const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS'; const DEBUG = 'XDEBUG_HANDLER_DEBUG'; /** @var string|null */ protected $tmpIni; /** @var bool */ private static $inRestart; /** @var string */ private static $name; /** @var string|null */ private static $skipped; /** @var bool */ private static $xdebugActive; /** @var string|null */ private static $xdebugMode; /** @var string|null */ private static $xdebugVersion; /** @var bool */ private $cli; /** @var string|null */ private $debug; /** @var string */ private $envAllowXdebug; /** @var string */ private $envOriginalInis; /** @var bool */ private $persistent; /** @var string|null */ private $script; /** @var Status */ private $statusWriter; /** * Constructor * * The $envPrefix is used to create distinct environment variables. It is * uppercased and prepended to the default base values. For example 'myapp' * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS. * * @param string $envPrefix Value used in environment variables * @throws \RuntimeException If the parameter is invalid */ public function __construct(string $envPrefix) { if ($envPrefix === '') { throw new \RuntimeException('Invalid constructor parameter'); } self::$name = \strtoupper($envPrefix); $this->envAllowXdebug = self::$name . self::SUFFIX_ALLOW; $this->envOriginalInis = self::$name . self::SUFFIX_INIS; self::setXdebugDetails(); self::$inRestart = \false; if ($this->cli = \PHP_SAPI === 'cli') { $this->debug = (string) \getenv(self::DEBUG); } $this->statusWriter = new \Composer\XdebugHandler\Status($this->envAllowXdebug, (bool) $this->debug); } /** * Activates status message output to a PSR3 logger */ public function setLogger(LoggerInterface $logger) : self { $this->statusWriter->setLogger($logger); return $this; } /** * Sets the main script location if it cannot be called from argv */ public function setMainScript(string $script) : self { $this->script = $script; return $this; } /** * Persist the settings to keep Xdebug out of sub-processes */ public function setPersistent() : self { $this->persistent = \true; return $this; } /** * Checks if Xdebug is loaded and the process needs to be restarted * * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG * environment variable to 1. This variable is used internally so that * the restarted process is created only once. */ public function check() : void { $this->notify(\Composer\XdebugHandler\Status::CHECK, self::$xdebugVersion . '|' . self::$xdebugMode); $envArgs = \explode('|', (string) \getenv($this->envAllowXdebug)); if (!(bool) $envArgs[0] && $this->requiresRestart(self::$xdebugActive)) { // Restart required $this->notify(\Composer\XdebugHandler\Status::RESTART); if ($this->prepareRestart()) { $command = $this->getCommand(); $this->restart($command); } return; } if (self::RESTART_ID === $envArgs[0] && \count($envArgs) === 5) { // Restarted, so unset environment variable and use saved values $this->notify(\Composer\XdebugHandler\Status::RESTARTED); \Composer\XdebugHandler\Process::setEnv($this->envAllowXdebug); self::$inRestart = \true; if (self::$xdebugVersion === null) { // Skipped version is only set if Xdebug is not loaded self::$skipped = $envArgs[1]; } $this->tryEnableSignals(); // Put restart settings in the environment $this->setEnvRestartSettings($envArgs); return; } $this->notify(\Composer\XdebugHandler\Status::NORESTART); $settings = self::getRestartSettings(); if ($settings !== null) { // Called with existing settings, so sync our settings $this->syncSettings($settings); } } /** * Returns an array of php.ini locations with at least one entry * * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. * The loaded ini location is the first entry and may be empty. * * @return string[] */ public static function getAllIniFiles() : array { if (self::$name !== null) { $env = \getenv(self::$name . self::SUFFIX_INIS); if (\false !== $env) { return \explode(\PATH_SEPARATOR, $env); } } $paths = [(string) \php_ini_loaded_file()]; $scanned = \php_ini_scanned_files(); if ($scanned !== \false) { $paths = \array_merge($paths, \array_map('trim', \explode(',', $scanned))); } return $paths; } /** * Returns an array of restart settings or null * * Settings will be available if the current process was restarted, or * called with the settings from an existing restart. * * @phpstan-return restartData|null */ public static function getRestartSettings() : ?array { $envArgs = \explode('|', (string) \getenv(self::RESTART_SETTINGS)); if (\count($envArgs) !== 6 || !self::$inRestart && \php_ini_loaded_file() !== $envArgs[0]) { return null; } return ['tmpIni' => $envArgs[0], 'scannedInis' => (bool) $envArgs[1], 'scanDir' => '*' === $envArgs[2] ? \false : $envArgs[2], 'phprc' => '*' === $envArgs[3] ? \false : $envArgs[3], 'inis' => \explode(\PATH_SEPARATOR, $envArgs[4]), 'skipped' => $envArgs[5]]; } /** * Returns the Xdebug version that triggered a successful restart */ public static function getSkippedVersion() : string { return (string) self::$skipped; } /** * Returns whether Xdebug is loaded and active * * true: if Xdebug is loaded and is running in an active mode. * false: if Xdebug is not loaded, or it is running with xdebug.mode=off. */ public static function isXdebugActive() : bool { self::setXdebugDetails(); return self::$xdebugActive; } /** * Allows an extending class to decide if there should be a restart * * The default is to restart if Xdebug is loaded and its mode is not "off". */ protected function requiresRestart(bool $default) : bool { return $default; } /** * Allows an extending class to access the tmpIni * * @param string[] $command * */ protected function restart(array $command) : void { $this->doRestart($command); } /** * Executes the restarted command then deletes the tmp ini * * @param string[] $command * @phpstan-return never */ private function doRestart(array $command) : void { $this->tryEnableSignals(); $this->notify(\Composer\XdebugHandler\Status::RESTARTING, \implode(' ', $command)); if (\PHP_VERSION_ID >= 70400) { $cmd = $command; } else { $cmd = \Composer\XdebugHandler\Process::escapeShellCommand($command); if (\defined('PHP_WINDOWS_VERSION_BUILD')) { // Outer quotes required on cmd string below PHP 8 $cmd = '"' . $cmd . '"'; } } $process = \proc_open($cmd, [], $pipes); if (\is_resource($process)) { $exitCode = \proc_close($process); } if (!isset($exitCode)) { // Unlikely that php or the default shell cannot be invoked $this->notify(\Composer\XdebugHandler\Status::ERROR, 'Unable to restart process'); $exitCode = -1; } else { $this->notify(\Composer\XdebugHandler\Status::INFO, 'Restarted process exited ' . $exitCode); } if ($this->debug === '2') { $this->notify(\Composer\XdebugHandler\Status::INFO, 'Temp ini saved: ' . $this->tmpIni); } else { @\unlink((string) $this->tmpIni); } exit($exitCode); } /** * Returns true if everything was written for the restart * * If any of the following fails (however unlikely) we must return false to * stop potential recursion: * - tmp ini file creation * - environment variable creation */ private function prepareRestart() : bool { $error = null; $iniFiles = self::getAllIniFiles(); $scannedInis = \count($iniFiles) > 1; $tmpDir = \sys_get_temp_dir(); if (!$this->cli) { $error = 'Unsupported SAPI: ' . \PHP_SAPI; } elseif (!$this->checkConfiguration($info)) { $error = $info; } elseif (!$this->checkMainScript()) { $error = 'Unable to access main script: ' . $this->script; } elseif (!$this->writeTmpIni($iniFiles, $tmpDir, $error)) { $error = $error !== null ? $error : 'Unable to create temp ini file at: ' . $tmpDir; } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) { $error = 'Unable to set environment variables'; } if ($error !== null) { $this->notify(\Composer\XdebugHandler\Status::ERROR, $error); } return $error === null; } /** * Returns true if the tmp ini file was written * * @param string[] $iniFiles All ini files used in the current process */ private function writeTmpIni(array $iniFiles, string $tmpDir, ?string &$error) : bool { if (($tmpfile = @\tempnam($tmpDir, '')) === \false) { return \false; } $this->tmpIni = $tmpfile; // $iniFiles has at least one item and it may be empty if ($iniFiles[0] === '') { \array_shift($iniFiles); } $content = ''; $sectionRegex = '/^\\s*\\[(?:PATH|HOST)\\s*=/mi'; $xdebugRegex = '/^\\s*(zend_extension\\s*=.*xdebug.*)$/mi'; foreach ($iniFiles as $file) { // Check for inaccessible ini files if (($data = @\file_get_contents($file)) === \false) { $error = 'Unable to read ini: ' . $file; return \false; } // Check and remove directives after HOST and PATH sections if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches, \PREG_OFFSET_CAPTURE)) { $data = \substr($data, 0, $matches[0][1]); } $content .= Preg::replace($xdebugRegex, ';$1', $data) . \PHP_EOL; } // Merge loaded settings into our ini content, if it is valid $config = \parse_ini_string($content); $loaded = \ini_get_all(null, \false); if (\false === $config || \false === $loaded) { $error = 'Unable to parse ini data'; return \false; } $content .= $this->mergeLoadedConfig($loaded, $config); // Work-around for https://bugs.php.net/bug.php?id=75932 $content .= 'opcache.enable_cli=0' . \PHP_EOL; return (bool) @\file_put_contents($this->tmpIni, $content); } /** * Returns the command line arguments for the restart * * @return string[] */ private function getCommand() : array { $php = [\PHP_BINARY]; $args = \array_slice($_SERVER['argv'], 1); if (!$this->persistent) { // Use command-line options \array_push($php, '-n', '-c', $this->tmpIni); } return \array_merge($php, [$this->script], $args); } /** * Returns true if the restart environment variables were set * * No need to update $_SERVER since this is set in the restarted process. * * @param string[] $iniFiles All ini files used in the current process */ private function setEnvironment(bool $scannedInis, array $iniFiles) : bool { $scanDir = \getenv('PHP_INI_SCAN_DIR'); $phprc = \getenv('PHPRC'); // Make original inis available to restarted process if (!\putenv($this->envOriginalInis . '=' . \implode(\PATH_SEPARATOR, $iniFiles))) { return \false; } if ($this->persistent) { // Use the environment to persist the settings if (!\putenv('PHP_INI_SCAN_DIR=') || !\putenv('PHPRC=' . $this->tmpIni)) { return \false; } } // Flag restarted process and save values for it to use $envArgs = [self::RESTART_ID, self::$xdebugVersion, (int) $scannedInis, \false === $scanDir ? '*' : $scanDir, \false === $phprc ? '*' : $phprc]; return \putenv($this->envAllowXdebug . '=' . \implode('|', $envArgs)); } /** * Logs status messages */ private function notify(string $op, ?string $data = null) : void { $this->statusWriter->report($op, $data); } /** * Returns default, changed and command-line ini settings * * @param mixed[] $loadedConfig All current ini settings * @param mixed[] $iniConfig Settings from user ini files * */ private function mergeLoadedConfig(array $loadedConfig, array $iniConfig) : string { $content = ''; foreach ($loadedConfig as $name => $value) { // Value will either be null, string or array (HHVM only) if (!\is_string($value) || \strpos($name, 'xdebug') === 0 || $name === 'apc.mmap_file_mask') { continue; } if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { // Double-quote escape each value $content .= $name . '="' . \addcslashes($value, '\\"') . '"' . \PHP_EOL; } } return $content; } /** * Returns true if the script name can be used */ private function checkMainScript() : bool { if ($this->script !== null) { // Allow an application to set -- for standard input return \file_exists($this->script) || '--' === $this->script; } if (\file_exists($this->script = $_SERVER['argv'][0])) { return \true; } // Use a backtrace to resolve Phar and chdir issues. $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); $main = \end($trace); if ($main !== \false && isset($main['file'])) { return \file_exists($this->script = $main['file']); } return \false; } /** * Adds restart settings to the environment * * @param string[] $envArgs */ private function setEnvRestartSettings(array $envArgs) : void { $settings = [\php_ini_loaded_file(), $envArgs[2], $envArgs[3], $envArgs[4], \getenv($this->envOriginalInis), self::$skipped]; \Composer\XdebugHandler\Process::setEnv(self::RESTART_SETTINGS, \implode('|', $settings)); } /** * Syncs settings and the environment if called with existing settings * * @phpstan-param restartData $settings */ private function syncSettings(array $settings) : void { if (\false === \getenv($this->envOriginalInis)) { // Called by another app, so make original inis available \Composer\XdebugHandler\Process::setEnv($this->envOriginalInis, \implode(\PATH_SEPARATOR, $settings['inis'])); } self::$skipped = $settings['skipped']; $this->notify(\Composer\XdebugHandler\Status::INFO, 'Process called with existing restart settings'); } /** * Returns true if there are no known configuration issues */ private function checkConfiguration(?string &$info) : bool { if (!\function_exists('proc_open')) { $info = 'proc_open function is disabled'; return \false; } if (\extension_loaded('uopz') && !(bool) \ini_get('uopz.disable')) { // uopz works at opcode level and disables exit calls if (\function_exists('uopz_allow_exit')) { @\uopz_allow_exit(\true); } else { $info = 'uopz extension is not compatible'; return \false; } } // Check UNC paths when using cmd.exe if (\defined('PHP_WINDOWS_VERSION_BUILD') && \PHP_VERSION_ID < 70400) { $workingDir = \getcwd(); if ($workingDir === \false) { $info = 'unable to determine working directory'; return \false; } if (0 === \strpos($workingDir, '\\\\')) { $info = 'cmd.exe does not support UNC paths: ' . $workingDir; return \false; } } return \true; } /** * Enables async signals and control interrupts in the restarted process * * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+. */ private function tryEnableSignals() : void { if (\function_exists('pcntl_async_signals') && \function_exists('pcntl_signal')) { \pcntl_async_signals(\true); $message = 'Async signals enabled'; if (!self::$inRestart) { // Restarting, so ignore SIGINT in parent \pcntl_signal(\SIGINT, \SIG_IGN); } elseif (\is_int(\pcntl_signal_get_handler(\SIGINT))) { // Restarted, no handler set so force default action \pcntl_signal(\SIGINT, \SIG_DFL); } } if (!self::$inRestart && \function_exists('sapi_windows_set_ctrl_handler')) { // Restarting, so set a handler to ignore CTRL events in the parent. // This ensures that CTRL+C events will be available in the child // process without having to enable them there, which is unreliable. \sapi_windows_set_ctrl_handler(function ($evt) { }); } } /** * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode */ private static function setXdebugDetails() : void { if (self::$xdebugActive !== null) { return; } self::$xdebugActive = \false; if (!\extension_loaded('xdebug')) { return; } $version = \phpversion('xdebug'); self::$xdebugVersion = $version !== \false ? $version : 'unknown'; if (\version_compare(self::$xdebugVersion, '3.1', '>=')) { $modes = \xdebug_info('mode'); self::$xdebugMode = \count($modes) === 0 ? 'off' : \implode(',', $modes); self::$xdebugActive = self::$xdebugMode !== 'off'; return; } // See if xdebug.mode is supported in this version $iniMode = \ini_get('xdebug.mode'); if ($iniMode === \false) { self::$xdebugActive = \true; return; } // Environment value wins but cannot be empty $envMode = (string) \getenv('XDEBUG_MODE'); if ($envMode !== '') { self::$xdebugMode = $envMode; } else { self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off'; } // An empty comma-separated list is treated as mode 'off' if (Preg::isMatch('/^,+$/', \str_replace(' ', '', self::$xdebugMode))) { self::$xdebugMode = 'off'; } self::$xdebugActive = self::$xdebugMode !== 'off'; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ declare (strict_types=1); namespace Composer\XdebugHandler; use Composer\Pcre\Preg; /** * Process utility functions * * @author John Stevenson */ class Process { /** * Escapes a string to be used as a shell argument. * * From https://github.com/johnstevenson/winbox-args * MIT Licensed (c) John Stevenson * * @param string $arg The argument to be escaped * @param bool $meta Additionally escape cmd.exe meta characters * @param bool $module The argument is the module to invoke */ public static function escape(string $arg, bool $meta = \true, bool $module = \false) : string { if (!\defined('PHP_WINDOWS_VERSION_BUILD')) { return "'" . \str_replace("'", "'\\''", $arg) . "'"; } $quote = \strpbrk($arg, " \t") !== \false || $arg === ''; $arg = Preg::replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); if ($meta) { $meta = $dquotes || Preg::isMatch('/%[^%]+%/', $arg); if (!$meta) { $quote = $quote || \strpbrk($arg, '^&|<>()') !== \false; } elseif ($module && !$dquotes && $quote) { $meta = \false; } } if ($quote) { $arg = '"' . Preg::replace('/(\\\\*)$/', '$1$1', $arg) . '"'; } if ($meta) { $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg); } return $arg; } /** * Escapes an array of arguments that make up a shell command * * @param string[] $args Argument list, with the module name first */ public static function escapeShellCommand(array $args) : string { $command = ''; $module = \array_shift($args); if ($module !== null) { $command = self::escape($module, \true, \true); foreach ($args as $arg) { $command .= ' ' . self::escape($arg); } } return $command; } /** * Makes putenv environment changes available in $_SERVER and $_ENV * * @param string $name * @param ?string $value A null value unsets the variable */ public static function setEnv(string $name, ?string $value = null) : bool { $unset = null === $value; if (!\putenv($unset ? $name : $name . '=' . $value)) { return \false; } if ($unset) { unset($_SERVER[$name]); } else { $_SERVER[$name] = $value; } // Update $_ENV if it is being used if (\false !== \stripos((string) \ini_get('variables_order'), 'E')) { if ($unset) { unset($_ENV[$name]); } else { $_ENV[$name] = $value; } } return \true; } } Copyright (C) 2022 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer/class-map-generator ============================ Utilities to generate class maps and scan PHP code. [![Continuous Integration](https://github.com/composer/class-map-generator/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/class-map-generator/actions) Installation ------------ Install the latest version with: ```bash $ composer require composer/class-map-generator ``` Requirements ------------ * PHP 7.2 is required. Basic usage ----------- If all you want is to scan a directory and extract a classmap with all classes/interfaces/traits/enums mapped to their paths, you can simply use: ```php use Composer\ClassMapGenerator\ClassMapGenerator; $map = ClassMapGenerator::createMap('path/to/scan'); foreach ($map as $symbol => $path) { // do your thing } ``` For more advanced usage, you can instantiate a generator object and call scanPaths one or more time then call getClassMap to get a ClassMap object containing the resulting map + eventual warnings. ```php use Composer\ClassMapGenerator\ClassMapGenerator; $generator = new ClassMapGenerator; $generator->scanPaths('path/to/scan'); $generator->scanPaths('path/to/scan2'); $classMap = $generator->getClassMap(); $classMap->sort(); // optionally sort classes alphabetically foreach ($classMap->getMap() as $symbol => $path) { // do your thing } foreach ($classMap->getAmbiguousClasses() as $symbol => $paths) { // warn user about ambiguous class resolution } ``` License ------- composer/class-map-generator is licensed under the MIT License, see the LICENSE file for details. { "name": "composer\/class-map-generator", "description": "Utilities to scan PHP code and generate class maps.", "type": "library", "license": "MIT", "keywords": [ "classmap" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https:\/\/seld.be" } ], "require": { "php": "^7.2 || ^8.0", "symfony\/finder": "^4.4 || ^5.3 || ^6 || ^7", "composer\/pcre": "^2.1 || ^3.1" }, "require-dev": { "symfony\/phpunit-bridge": "^5", "phpstan\/phpstan": "^1.6", "phpstan\/phpstan-deprecation-rules": "^1", "phpstan\/phpstan-strict-rules": "^1.1", "phpstan\/phpstan-phpunit": "^1", "symfony\/filesystem": "^5.4 || ^6" }, "autoload": { "psr-4": { "Composer\\ClassMapGenerator\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\ClassMapGenerator\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "scripts": { "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit", "phpstan": "phpstan analyse" } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /* * This file was initially based on a version from the Symfony package. * * (c) Fabien Potencier */ namespace Composer\ClassMapGenerator; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Finder\Finder; use Composer\IO\IOInterface; /** * ClassMapGenerator * * @author Gyula Sallai * @author Jordi Boggiano */ class ClassMapGenerator { /** * @var list */ private $extensions; /** * @var FileList|null */ private $scannedFiles = null; /** * @var ClassMap */ private $classMap; /** * @param list $extensions File extensions to scan for classes in the given paths */ public function __construct(array $extensions = ['php', 'inc']) { $this->extensions = $extensions; $this->classMap = new \Composer\ClassMapGenerator\ClassMap(); } /** * When calling scanPaths repeatedly with paths that may overlap, calling this will ensure that the same class is never scanned twice * * You can provide your own FileList instance or use the default one if you pass no argument * * @return $this */ public function avoidDuplicateScans(\Composer\ClassMapGenerator\FileList $scannedFiles = null) : self { $this->scannedFiles = $scannedFiles ?? new \Composer\ClassMapGenerator\FileList(); return $this; } /** * Iterate over all files in the given directory searching for classes * * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance) * @return array A class map array * * @throws \RuntimeException When the path is neither an existing file nor directory */ public static function createMap($path) : array { $generator = new self(); $generator->scanPaths($path); return $generator->getClassMap()->getMap(); } public function getClassMap() : \Composer\ClassMapGenerator\ClassMap { return $this->classMap; } /** * Iterate over all files in the given directory searching for classes * * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance) * @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap * @param 'classmap'|'psr-0'|'psr-4' $autoloadType Optional autoload standard to use mapping rules with the namespace instead of purely doing a classmap * @param string|null $namespace Optional namespace prefix to filter by, only for psr-0/psr-4 autoloading * * @throws \RuntimeException When the path is neither an existing file nor directory */ public function scanPaths($path, string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null) : void { if (!\in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], \true)) { throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"'); } if ('classmap' !== $autoloadType) { if (!\is_string($path)) { throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type'); } if (!\is_string($namespace)) { throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type'); } $basePath = $path; } if (\is_string($path)) { if (\is_file($path)) { $path = [new \SplFileInfo($path)]; } elseif (\is_dir($path) || \strpos($path, '*') !== \false) { $path = Finder::create()->files()->followLinks()->name('/\\.(?:' . \implode('|', \array_map('preg_quote', $this->extensions)) . ')$/')->in($path); } else { throw new \RuntimeException('Could not scan for classes inside "' . $path . '" which does not appear to be a file nor a folder'); } } $cwd = \realpath(self::getCwd()); foreach ($path as $file) { $filePath = $file->getPathname(); if (!\in_array(\pathinfo($filePath, \PATHINFO_EXTENSION), $this->extensions, \true)) { continue; } if (!self::isAbsolutePath($filePath)) { $filePath = $cwd . '/' . $filePath; $filePath = self::normalizePath($filePath); } else { $filePath = Preg::replace('{[\\\\/]{2,}}', '/', $filePath); } if ('' === $filePath) { throw new \LogicException('Got an empty $filePath for ' . $file->getPathname()); } $realPath = \realpath($filePath); // fallback just in case but this really should not happen if (\false === $realPath) { throw new \RuntimeException('realpath of ' . $filePath . ' failed to resolve, got false'); } // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) { continue; } // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved if (null !== $excluded && Preg::isMatch($excluded, \strtr($realPath, '\\', '/'))) { continue; } // check non-realpath of file for directories symlink in project dir if (null !== $excluded && Preg::isMatch($excluded, \strtr($filePath, '\\', '/'))) { continue; } $classes = \Composer\ClassMapGenerator\PhpFileParser::findClasses($filePath); if ('classmap' !== $autoloadType && isset($namespace)) { $classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath); // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later if (\count($classes) > 0 && $this->scannedFiles !== null) { $this->scannedFiles->add($realPath); } } elseif ($this->scannedFiles !== null) { // classmap autoload rules always collect all classes so for these we definitely do not want to scan again $this->scannedFiles->add($realPath); } foreach ($classes as $class) { if (!$this->classMap->hasClass($class)) { $this->classMap->addClass($class, $filePath); } elseif ($filePath !== $this->classMap->getClassPath($class) && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i', \strtr($this->classMap->getClassPath($class) . ' ' . $filePath, '\\', '/'))) { $this->classMap->addAmbiguousClass($class, $filePath); } } } } /** * Remove classes which could not have been loaded by namespace autoloaders * * @param array $classes found classes in given file * @param string $filePath current file * @param string $baseNamespace prefix of given autoload mapping * @param 'psr-0'|'psr-4' $namespaceType * @param string $basePath root directory of given autoload mapping * @return array valid classes */ private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath) : array { $validClasses = []; $rejectedClasses = []; $realSubPath = \substr($filePath, \strlen($basePath) + 1); $dotPosition = \strrpos($realSubPath, '.'); $realSubPath = \substr($realSubPath, 0, $dotPosition === \false ? \PHP_INT_MAX : $dotPosition); foreach ($classes as $class) { // silently skip if ns doesn't have common root if ('' !== $baseNamespace && 0 !== \strpos($class, $baseNamespace)) { continue; } // transform class name to file path and validate if ('psr-0' === $namespaceType) { $namespaceLength = \strrpos($class, '\\'); if (\false !== $namespaceLength) { $namespace = \substr($class, 0, $namespaceLength + 1); $className = \substr($class, $namespaceLength + 1); $subPath = \str_replace('\\', \DIRECTORY_SEPARATOR, $namespace) . \str_replace('_', \DIRECTORY_SEPARATOR, $className); } else { $subPath = \str_replace('_', \DIRECTORY_SEPARATOR, $class); } } elseif ('psr-4' === $namespaceType) { $subNamespace = '' !== $baseNamespace ? \substr($class, \strlen($baseNamespace)) : $class; $subPath = \str_replace('\\', \DIRECTORY_SEPARATOR, $subNamespace); } else { throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"'); } if ($subPath === $realSubPath) { $validClasses[] = $class; } else { $rejectedClasses[] = $class; } } // warn only if no valid classes, else silently skip invalid if (\count($validClasses) === 0) { foreach ($rejectedClasses as $class) { $this->classMap->addPsrViolation("Class {$class} located in " . Preg::replace('{^' . \preg_quote(self::getCwd()) . '}', '.', $filePath, 1) . " does not comply with {$namespaceType} autoloading standard. Skipping."); } return []; } return $validClasses; } /** * Checks if the given path is absolute * * @see Composer\Util\Filesystem::isAbsolutePath * * @param string $path * @return bool */ private static function isAbsolutePath(string $path) { return \strpos($path, '/') === 0 || \substr($path, 1, 1) === ':' || \strpos($path, '\\\\') === 0; } /** * Normalize a path. This replaces backslashes with slashes, removes ending * slash and collapses redundant separators and up-level references. * * @see Composer\Util\Filesystem::normalizePath * * @param string $path Path to the file or directory * @return string */ private static function normalizePath(string $path) { $parts = []; $path = \strtr($path, '\\', '/'); $prefix = ''; $absolute = ''; // extract windows UNC paths e.g. \\foo\bar if (\strpos($path, '//') === 0 && \strlen($path) > 2) { $absolute = '//'; $path = \substr($path, 2); } // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { $prefix = $match[1]; $path = \substr($path, \strlen($prefix)); } if (\strpos($path, '/') === 0) { $absolute = '/'; $path = \substr($path, 1); } $up = \false; foreach (\explode('/', $path) as $chunk) { if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { \array_pop($parts); $up = !(\count($parts) === 0 || '..' === \end($parts)); } elseif ('.' !== $chunk && '' !== $chunk) { $parts[] = $chunk; $up = '..' !== $chunk; } } // ensure c: is normalized to C: $prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', function (array $m) { return \strtoupper((string) $m[0]); }, $prefix); return $prefix . $absolute . \implode('/', $parts); } /** * @see Composer\Util\Platform::getCwd */ private static function getCwd() : string { $cwd = \getcwd(); if (\false === $cwd) { throw new \RuntimeException('Could not determine the current working directory'); } return $cwd; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\ClassMapGenerator; use Composer\Pcre\Preg; /** * @author Jordi Boggiano * @internal */ class PhpFileCleaner { /** @var array */ private static $typeConfig; /** @var non-empty-string */ private static $restPattern; /** * @readonly * @var string */ private $contents; /** * @readonly * @var int */ private $len; /** * @readonly * @var int */ private $maxMatches; /** @var int */ private $index = 0; /** * @param string[] $types */ public static function setTypeConfig(array $types) : void { foreach ($types as $type) { self::$typeConfig[$type[0]] = array('name' => $type, 'length' => \strlen($type), 'pattern' => '{.\\b(?])' . $type . '\\s++[a-zA-Z_\\x7f-\\xff:][a-zA-Z0-9_\\x7f-\\xff:\\-]*+}Ais'); } self::$restPattern = '{[^?"\'contents = $contents; $this->len = \strlen($this->contents); $this->maxMatches = $maxMatches; } public function clean() : string { $clean = ''; while ($this->index < $this->len) { $this->skipToPhp(); $clean .= 'index < $this->len) { $char = $this->contents[$this->index]; if ($char === '?' && $this->peek('>')) { $clean .= '?>'; $this->index += 2; continue 2; } if ($char === '"') { $this->skipString('"'); $clean .= 'null'; continue; } if ($char === "'") { $this->skipString("'"); $clean .= 'null'; continue; } if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \\t]*+([\'"]?)([a-zA-Z_\\x80-\\xff][a-zA-Z0-9_\\x80-\\xff]*+)\\1(?:\\r\\n|\\n|\\r)}A', $match)) { $this->index += \strlen($match[0]); $this->skipHeredoc($match[2]); $clean .= 'null'; continue; } if ($char === '/') { if ($this->peek('/')) { $this->skipToNewline(); continue; } if ($this->peek('*')) { $this->skipComment(); continue; } } if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) { $type = self::$typeConfig[$char]; if (\substr($this->contents, $this->index, $type['length']) === $type['name'] && Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)) { $clean .= $match[0]; return $clean; } } $this->index += 1; if ($this->match(self::$restPattern, $match)) { $clean .= $char . $match[0]; $this->index += \strlen($match[0]); } else { $clean .= $char; } } } return $clean; } private function skipToPhp() : void { while ($this->index < $this->len) { if ($this->contents[$this->index] === '<' && $this->peek('?')) { $this->index += 2; break; } $this->index += 1; } } private function skipString(string $delimiter) : void { $this->index += 1; while ($this->index < $this->len) { if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) { $this->index += 2; continue; } if ($this->contents[$this->index] === $delimiter) { $this->index += 1; break; } $this->index += 1; } } private function skipComment() : void { $this->index += 2; while ($this->index < $this->len) { if ($this->contents[$this->index] === '*' && $this->peek('/')) { $this->index += 2; break; } $this->index += 1; } } private function skipToNewline() : void { while ($this->index < $this->len) { if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") { return; } $this->index += 1; } } private function skipHeredoc(string $delimiter) : void { $firstDelimiterChar = $delimiter[0]; $delimiterLength = \strlen($delimiter); $delimiterPattern = '{' . \preg_quote($delimiter) . '(?![a-zA-Z0-9_\\x80-\\xff])}A'; while ($this->index < $this->len) { // check if we find the delimiter after some spaces/tabs switch ($this->contents[$this->index]) { case "\t": case " ": $this->index += 1; continue 2; case $firstDelimiterChar: if (\substr($this->contents, $this->index, $delimiterLength) === $delimiter && $this->match($delimiterPattern)) { $this->index += $delimiterLength; return; } break; } // skip the rest of the line while ($this->index < $this->len) { $this->skipToNewline(); // skip newlines while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) { $this->index += 1; } break; } } } private function peek(string $char) : bool { return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char; } /** * @param non-empty-string $regex * @param null|array $match */ private function match(string $regex, array &$match = null) : bool { return Preg::isMatch($regex, $this->contents, $match, 0, $this->index); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\ClassMapGenerator; /** * Contains a list of files which were scanned to generate a classmap * * @author Jordi Boggiano */ class FileList { /** * @var array */ public $files = []; /** * @param non-empty-string $path */ public function add(string $path) : void { $this->files[$path] = \true; } /** * @param non-empty-string $path */ public function contains(string $path) : bool { return isset($this->files[$path]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\ClassMapGenerator; use Composer\Pcre\Preg; /** * @author Jordi Boggiano */ class PhpFileParser { /** * Extract the classes in the given file * * @param string $path The file to check * @throws \RuntimeException * @return array The found classes */ public static function findClasses(string $path) : array { $extraTypes = self::getExtraTypes(); // Use @ here instead of Silencer to actively suppress 'unhelpful' output // @link https://github.com/composer/composer/pull/4886 $contents = @\php_strip_whitespace($path); if ('' === $contents) { if (!\file_exists($path)) { $message = 'File at "%s" does not exist, check your classmap definitions'; } elseif (!self::isReadable($path)) { $message = 'File at "%s" is not readable, check its permissions'; } elseif ('' === \trim((string) \file_get_contents($path))) { // The input file was really empty and thus contains no classes return array(); } else { $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; } $error = \error_get_last(); if (isset($error['message'])) { $message .= \PHP_EOL . 'The following message may be helpful:' . \PHP_EOL . $error['message']; } throw new \RuntimeException(\sprintf($message, $path)); } // return early if there is no chance of matching anything in this file Preg::matchAllStrictGroups('{\\b(?:class|interface|trait' . $extraTypes . ')\\s}i', $contents, $matches); if (0 === \count($matches)) { return array(); } $p = new \Composer\ClassMapGenerator\PhpFileCleaner($contents, \count($matches[0])); $contents = $p->clean(); unset($p); Preg::matchAll('{ (?: \\b(?])(?Pclass|interface|trait' . $extraTypes . ') \\s++ (?P[a-zA-Z_\\x7f-\\xff:][a-zA-Z0-9_\\x7f-\\xff:\\-]*+) | \\b(?])(?Pnamespace) (?P\\s++[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+(?:\\s*+\\\\\\s*+[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+)*+)? \\s*+ [\\{;] ) }ix', $contents, $matches); $classes = array(); $namespace = ''; for ($i = 0, $len = \count($matches['type']); $i < $len; $i++) { if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') { $namespace = \str_replace(array(' ', "\t", "\r", "\n"), '', (string) $matches['nsname'][$i]) . '\\'; } else { $name = $matches['name'][$i]; \assert(\is_string($name)); // skip anon classes extending/implementing if ($name === 'extends' || $name === 'implements') { continue; } if ($name[0] === ':') { // This is an XHP class, https://github.com/facebook/xhp $name = 'xhp' . \substr(\str_replace(array('-', ':'), array('_', '__'), $name), 1); } elseif (\strtolower((string) $matches['type'][$i]) === 'enum') { // something like: // enum Foo: int { HERP = '123'; } // The regex above captures the colon, which isn't part of // the class name. // or: // enum Foo:int { HERP = '123'; } // The regex above captures the colon and type, which isn't part of // the class name. $colonPos = \strrpos($name, ':'); if (\false !== $colonPos) { $name = \substr($name, 0, $colonPos); } } $classes[] = \ltrim($namespace . $name, '\\'); } } return $classes; } /** * @return string */ private static function getExtraTypes() : string { static $extraTypes = null; if (null === $extraTypes) { $extraTypes = ''; if (\PHP_VERSION_ID >= 80100 || \defined('_ContaoManager\\HHVM_VERSION') && \version_compare(HHVM_VERSION, '3.3', '>=')) { $extraTypes .= '|enum'; } \Composer\ClassMapGenerator\PhpFileCleaner::setTypeConfig(\array_merge(['class', 'interface', 'trait'], \array_filter(\explode('|', $extraTypes)))); } return $extraTypes; } /** * Cross-platform safe version of is_readable() * * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 * * @see Composer\Util\Filesystem::isReadable * * @param string $path * @return bool */ private static function isReadable(string $path) { if (\is_readable($path)) { return \true; } if (\is_file($path)) { return \false !== @\file_get_contents($path, \false, null, 0, 1); } // assume false otherwise return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\ClassMapGenerator; /** * @author Jordi Boggiano */ class ClassMap implements \Countable { /** * @var array */ public $map = []; /** * @var array> */ private $ambiguousClasses = []; /** * @var string[] */ private $psrViolations = []; /** * Returns the class map, which is a list of paths indexed by class name * * @return array */ public function getMap() : array { return $this->map; } /** * Returns warning strings containing details about PSR-0/4 violations that were detected * * Violations are for ex a class which is in the wrong file/directory and thus should not be * found using psr-0/psr-4 autoloading but was found by the ClassMapGenerator as it scans all files. * * This is only happening when scanning paths using psr-0/psr-4 autoload type. Classmap type * always accepts every class as it finds it. * * @return string[] */ public function getPsrViolations() : array { return $this->psrViolations; } /** * A map of class names to their list of ambiguous paths * * This occurs when the same class can be found in several files * * To get the path the class is being mapped to, call getClassPath * * @return array> */ public function getAmbiguousClasses() : array { return $this->ambiguousClasses; } /** * Sorts the class map alphabetically by class names */ public function sort() : void { \ksort($this->map); } /** * @param class-string $className * @param non-empty-string $path */ public function addClass(string $className, string $path) : void { $this->map[$className] = $path; } /** * @param class-string $className * @return non-empty-string */ public function getClassPath(string $className) : string { if (!isset($this->map[$className])) { throw new \OutOfBoundsException('Class ' . $className . ' is not present in the map'); } return $this->map[$className]; } /** * @param class-string $className */ public function hasClass(string $className) : bool { return isset($this->map[$className]); } public function addPsrViolation(string $warning) : void { $this->psrViolations[] = $warning; } /** * @param class-string $className * @param non-empty-string $path */ public function addAmbiguousClass(string $className, string $path) : void { $this->ambiguousClasses[$className][] = $path; } public function count() : int { return \count($this->map); } } Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var \Closure(string):void */ private static $includeFile; /** @var string|null */ private $vendorDir; // PSR-4 /** * @var array> */ private $prefixLengthsPsr4 = array(); /** * @var array> */ private $prefixDirsPsr4 = array(); /** * @var list */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * List of PSR-0 prefixes * * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) * * @var array>> */ private $prefixesPsr0 = array(); /** * @var list */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var array */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var array */ private $missingClasses = array(); /** @var string|null */ private $apcuPrefix; /** * @var array */ private static $registeredLoaders = array(); /** * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; self::initializeIncludeClosure(); } /** * @return array> */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return list */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return list */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return array Array of classname => path */ public function getClassMap() { return $this->classMap; } /** * @param array $classMap Class to filename map * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param list|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], $paths ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param list|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { $includeFile = self::$includeFile; $includeFile($file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } /** * Returns the currently registered loaders keyed by their corresponding vendor directories. * * @return array */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } /** * @return void */ private static function initializeIncludeClosure() { if (self::$includeFile !== null) { return; } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void */ self::$includeFile = \Closure::bind(static function($file) { include $file; }, null, null); } } # Composer-specific PHPStan extensions # # These can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon' # in your phpstan config services: - class: Composer\PHPStan\ConfigReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - class: Composer\PHPStan\RuleReasonDataReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension { "$schema": "https://json-schema.org/draft-04/schema#", "title": "Package", "type": "object", "properties": { "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix.", "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$" }, "description": { "type": "string", "description": "Short package description." }, "license": { "type": ["string", "array"], "description": "License name. Or an array of license names." }, "type": { "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", "type": "string", "pattern": "^[a-z0-9-]+$" }, "abandoned": { "type": ["boolean", "string"], "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false." }, "version": { "type": "string", "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.", "pattern": "^v?\\d+(\\.\\d+){0,3}|^dev-" }, "default-branch": { "type": ["boolean"], "description": "Internal use only, do not specify this in composer.json. Indicates whether this version is the default branch of the linked VCS repository. Defaults to false." }, "non-feature-branches": { "type": ["array"], "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.", "items": { "type": "string" } }, "keywords": { "type": "array", "items": { "type": "string", "description": "A tag/keyword that this package relates to." } }, "readme": { "type": "string", "description": "Relative path to the readme document." }, "time": { "type": "string", "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format." }, "authors": { "$ref": "#/definitions/authors" }, "homepage": { "type": "string", "description": "Homepage URL for the project.", "format": "uri" }, "support": { "type": "object", "properties": { "email": { "type": "string", "description": "Email address for support.", "format": "email" }, "issues": { "type": "string", "description": "URL to the issue tracker.", "format": "uri" }, "forum": { "type": "string", "description": "URL to the forum.", "format": "uri" }, "wiki": { "type": "string", "description": "URL to the wiki.", "format": "uri" }, "irc": { "type": "string", "description": "IRC channel for support, as irc://server/channel.", "format": "uri" }, "chat": { "type": "string", "description": "URL to the support chat.", "format": "uri" }, "source": { "type": "string", "description": "URL to browse or download the sources.", "format": "uri" }, "docs": { "type": "string", "description": "URL to the documentation.", "format": "uri" }, "rss": { "type": "string", "description": "URL to the RSS feed.", "format": "uri" }, "security": { "type": "string", "description": "URL to the vulnerability disclosure policy (VDP).", "format": "uri" } } }, "funding": { "type": "array", "description": "A list of options to fund the development and maintenance of the package.", "items": { "type": "object", "properties": { "type": { "type": "string", "description": "Type of funding or platform through which funding is possible." }, "url": { "type": "string", "description": "URL to a website with details on funding and a way to fund the package.", "format": "uri" } } } }, "source": { "$ref": "#/definitions/source" }, "dist": { "$ref": "#/definitions/dist" }, "_comment": { "type": ["array", "string"], "description": "A key to store comments in" }, "require": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that are required to run this package.", "additionalProperties": { "type": "string" } }, "require-dev": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", "additionalProperties": { "type": "string" } }, "replace": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that can be replaced by this package.", "additionalProperties": { "type": "string" } }, "conflict": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that conflict with this package.", "additionalProperties": { "type": "string" } }, "provide": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", "additionalProperties": { "type": "string" } }, "suggest": { "type": "object", "description": "This is an object of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).", "additionalProperties": { "type": "string" } }, "repositories": { "type": ["object", "array"], "description": "A set of additional repositories where packages can be found.", "additionalProperties": { "anyOf": [ { "$ref": "#/definitions/repository" }, { "type": "boolean", "enum": [false] } ] }, "items": { "anyOf": [ { "$ref": "#/definitions/repository" }, { "type": "object", "additionalProperties": { "type": "boolean", "enum": [false] }, "minProperties": 1, "maxProperties": 1 } ] } }, "minimum-stability": { "type": ["string"], "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.", "enum": ["dev", "alpha", "beta", "rc", "RC", "stable"] }, "prefer-stable": { "type": ["boolean"], "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages." }, "autoload": { "$ref": "#/definitions/autoload" }, "autoload-dev": { "type": "object", "description": "Description of additional autoload rules for development purpose (eg. a test suite).", "properties": { "psr-0": { "type": "object", "description": "This is an object of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "psr-4": { "type": "object", "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "classmap": { "type": "array", "description": "This is an array of paths that contain classes to be included in the class-map generation process." }, "files": { "type": "array", "description": "This is an array of files that are always required on every request." } } }, "target-dir": { "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "type": "string" }, "include-path": { "type": ["array"], "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", "items": { "type": "string" } }, "bin": { "type": ["string", "array"], "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } }, "archive": { "type": ["object"], "description": "Options for creating package archives for distribution.", "properties": { "name": { "type": "string", "description": "A base name for archive." }, "exclude": { "type": "array", "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark." } } }, "config": { "type": "object", "description": "Composer options.", "properties": { "platform": { "type": "object", "description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.", "additionalProperties": { "type": ["string", "boolean"] } }, "allow-plugins": { "type": ["object", "boolean"], "description": "This is an object of {\"pattern\": true|false} with packages which are allowed to be loaded as plugins, or true to allow all, false to allow none. Defaults to {} which prompts when an unknown plugin is added.", "additionalProperties": { "type": ["boolean"] } }, "process-timeout": { "type": "integer", "description": "The timeout in seconds for process executions, defaults to 300 (5mins)." }, "use-include-path": { "type": "boolean", "description": "If true, the Composer autoloader will also look for classes in the PHP include path." }, "use-parent-dir": { "type": ["string", "boolean"], "description": "When running Composer in a directory where there is no composer.json, if there is one present in a directory above Composer will by default ask you whether you want to use that directory's composer.json instead. One of: true (always use parent if needed), false (never ask or use it) or \"prompt\" (ask every time), defaults to prompt." }, "preferred-install": { "type": ["string", "object"], "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or an object of {\"pattern\": \"preference\"}.", "additionalProperties": { "type": ["string"] } }, "audit": { "type": "object", "description": "Security audit configuration options", "properties": { "ignore": { "anyOf": [ { "type": "object", "description": "A list of advisory ids, remote ids or CVE ids (keys) and the explanations (values) for why they're being ignored. The listed items are reported but let the audit command pass.", "additionalProperties": { "type": ["string", "string"] } }, { "type": "array", "description": "A set of advisory ids, remote ids or CVE ids that are reported but let the audit command pass.", "items": { "type": "string" } } ] }, "abandoned": { "enum": ["ignore", "report", "fail"], "description": "Whether abandoned packages should be ignored, reported as problems or cause an audit failure." } } }, "notify-on-install": { "type": "boolean", "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true." }, "github-protocols": { "type": "array", "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].", "items": { "type": "string" } }, "github-oauth": { "type": "object", "description": "An object of domain name => github API oauth tokens, typically {\"github.com\":\"\"}.", "additionalProperties": { "type": "string" } }, "gitlab-oauth": { "type": "object", "description": "An object of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":{\"expires-at\":\"\", \"refresh-token\":\"\", \"token\":\"\"}}.", "additionalProperties": { "type": ["string", "object"], "required": [ "token"], "properties": { "expires-at": { "type": "integer", "description": "The expiration date for this GitLab token" }, "refresh-token": { "type": "string", "description": "The refresh token used for GitLab authentication" }, "token": { "type": "string", "description": "The token used for GitLab authentication" } } } }, "gitlab-token": { "type": "object", "description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"\"}, or an object with username and token keys.", "additionalProperties": { "type": ["string", "object"], "required": ["username", "token"], "properties": { "username": { "type": "string", "description": "The username used for GitLab authentication" }, "token": { "type": "string", "description": "The token used for GitLab authentication" } } } }, "gitlab-protocol": { "enum": ["git", "http", "https"], "description": "A protocol to force use of when creating a repository URL for the `source` value of the package metadata. One of `git` or `http`. By default, Composer will generate a git URL for private repositories and http one for public repos." }, "bearer": { "type": "object", "description": "An object of domain name => bearer authentication token, for example {\"example.com\":\"\"}.", "additionalProperties": { "type": "string" } }, "disable-tls": { "type": "boolean", "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini." }, "secure-http": { "type": "boolean", "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative." }, "secure-svn-domains": { "type": "array", "description": "A list of domains which should be trusted/marked as using a secure Subversion/SVN transport. By default svn:// protocol is seen as insecure and will throw. This is a better/safer alternative to disabling `secure-http` altogether.", "items": { "type": "string" } }, "cafile": { "type": "string", "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically." }, "capath": { "type": "string", "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory." }, "http-basic": { "type": "object", "description": "An object of domain name => {\"username\": \"...\", \"password\": \"...\"}.", "additionalProperties": { "type": "object", "required": ["username", "password"], "properties": { "username": { "type": "string", "description": "The username used for HTTP Basic authentication" }, "password": { "type": "string", "description": "The password used for HTTP Basic authentication" } } } }, "store-auths": { "type": ["string", "boolean"], "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt." }, "vendor-dir": { "type": "string", "description": "The location where all packages are installed, defaults to \"vendor\"." }, "bin-dir": { "type": "string", "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." }, "data-dir": { "type": "string", "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes." }, "cache-dir": { "type": "string", "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows." }, "cache-files-dir": { "type": "string", "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"." }, "cache-repo-dir": { "type": "string", "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"." }, "cache-vcs-dir": { "type": "string", "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"." }, "cache-ttl": { "type": "integer", "description": "The default cache time-to-live, defaults to 15552000 (6 months)." }, "cache-files-ttl": { "type": "integer", "description": "The cache time-to-live for files, defaults to the value of cache-ttl." }, "cache-files-maxsize": { "type": ["string", "integer"], "description": "The cache max size for the files cache, defaults to \"300MiB\"." }, "cache-read-only": { "type": ["boolean"], "description": "Whether to use the Composer cache in read-only mode." }, "bin-compat": { "enum": ["auto", "full", "proxy", "symlink"], "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed), can be \"full\" (compatible with both Windows and Unix-based systems) and \"proxy\" (only bash-style proxy)." }, "discard-changes": { "type": ["string", "boolean"], "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"." }, "autoloader-suffix": { "type": "string", "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated." }, "optimize-autoloader": { "type": "boolean", "description": "Always optimize when dumping the autoloader." }, "prepend-autoloader": { "type": "boolean", "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." }, "classmap-authoritative": { "type": "boolean", "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false." }, "apcu-autoloader": { "type": "boolean", "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false." }, "github-domains": { "type": "array", "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", "items": { "type": "string" } }, "github-expose-hostname": { "type": "boolean", "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname." }, "gitlab-domains": { "type": "array", "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].", "items": { "type": "string" } }, "bitbucket-oauth": { "type": "object", "description": "An object of domain name => {\"consumer-key\": \"...\", \"consumer-secret\": \"...\"}.", "additionalProperties": { "type": "object", "required": ["consumer-key", "consumer-secret"], "properties": { "consumer-key": { "type": "string", "description": "The consumer-key used for OAuth authentication" }, "consumer-secret": { "type": "string", "description": "The consumer-secret used for OAuth authentication" }, "access-token": { "type": "string", "description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it." }, "access-token-expiration": { "type": "integer", "description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it." } } } }, "use-github-api": { "type": "boolean", "description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository." }, "archive-format": { "type": "string", "description": "The default archiving format when not provided on cli, defaults to \"tar\"." }, "archive-dir": { "type": "string", "description": "The default archive path when not provided on cli, defaults to \".\"." }, "htaccess-protect": { "type": "boolean", "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." }, "sort-packages": { "type": "boolean", "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency." }, "lock": { "type": "boolean", "description": "Defaults to true. If set to false, Composer will not create a composer.lock file." }, "platform-check": { "type": ["boolean", "string"], "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap." } } }, "extra": { "type": ["object", "array"], "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.", "additionalProperties": true }, "scripts": { "type": ["object"], "description": "Script listeners that will be executed before/after some events.", "properties": { "pre-install-cmd": { "type": ["array", "string"], "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands." }, "post-install-cmd": { "type": ["array", "string"], "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands." }, "pre-update-cmd": { "type": ["array", "string"], "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands." }, "post-update-cmd": { "type": ["array", "string"], "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands." }, "pre-status-cmd": { "type": ["array", "string"], "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands." }, "post-status-cmd": { "type": ["array", "string"], "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands." }, "pre-package-install": { "type": ["array", "string"], "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands." }, "post-package-install": { "type": ["array", "string"], "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands." }, "pre-package-update": { "type": ["array", "string"], "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands." }, "post-package-update": { "type": ["array", "string"], "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands." }, "pre-package-uninstall": { "type": ["array", "string"], "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands." }, "post-package-uninstall": { "type": ["array", "string"], "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands." }, "pre-autoload-dump": { "type": ["array", "string"], "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands." }, "post-autoload-dump": { "type": ["array", "string"], "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands." }, "post-root-package-install": { "type": ["array", "string"], "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands." }, "post-create-project-cmd": { "type": ["array", "string"], "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands." } } }, "scripts-descriptions": { "type": ["object"], "description": "Descriptions for custom commands, shown in console help.", "additionalProperties": { "type": "string" } }, "scripts-aliases": { "type": ["object"], "description": "Aliases for custom commands.", "additionalProperties": { "type": "array" } } }, "definitions": { "authors": { "type": "array", "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", "items": { "type": "object", "additionalProperties": false, "required": [ "name"], "properties": { "name": { "type": "string", "description": "Full name of the author." }, "email": { "type": "string", "description": "Email address of the author.", "format": "email" }, "homepage": { "type": "string", "description": "Homepage URL for the author.", "format": "uri" }, "role": { "type": "string", "description": "Author's role in the project." } } } }, "autoload": { "type": "object", "description": "Description of how the package can be autoloaded.", "properties": { "psr-0": { "type": "object", "description": "This is an object of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "psr-4": { "type": "object", "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "classmap": { "type": "array", "description": "This is an array of paths that contain classes to be included in the class-map generation process." }, "files": { "type": "array", "description": "This is an array of files that are always required on every request." }, "exclude-from-classmap": { "type": "array", "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]" } } }, "repository": { "type": "object", "anyOf": [ { "$ref": "#/definitions/composer-repository" }, { "$ref": "#/definitions/vcs-repository" }, { "$ref": "#/definitions/path-repository" }, { "$ref": "#/definitions/artifact-repository" }, { "$ref": "#/definitions/pear-repository" }, { "$ref": "#/definitions/package-repository" } ] }, "composer-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["composer"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "options": { "type": "object", "additionalProperties": true }, "allow_ssl_downgrade": { "type": "boolean" }, "force-lazy-providers": { "type": "boolean" } } }, "vcs-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "bitbucket", "git-bitbucket", "hg", "fossil", "perforce", "svn"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "no-api": { "type": "boolean" }, "secure-http": { "type": "boolean" }, "svn-cache-credentials": { "type": "boolean" }, "trunk-path": { "type": ["string", "boolean"] }, "branches-path": { "type": ["string", "boolean"] }, "tags-path": { "type": ["string", "boolean"] }, "package-path": { "type": "string" }, "depot": { "type": "string" }, "branch": { "type": "string" }, "unique_perforce_client_name": { "type": "string" }, "p4user": { "type": "string" }, "p4password": { "type": "string" } } }, "path-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["path"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "options": { "type": "object", "properties": { "symlink": { "type": ["boolean", "null"] } }, "additionalProperties": true } } }, "artifact-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["artifact"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } } } }, "pear-repository": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["pear"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "vendor-alias": { "type": "string" } } }, "package-repository": { "type": "object", "required": ["type", "package"], "properties": { "type": { "type": "string", "enum": ["package"] }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "package": { "oneOf": [ { "$ref": "#/definitions/inline-package" }, { "type": "array", "items": { "$ref": "#/definitions/inline-package" } } ] } } }, "inline-package": { "type": "object", "required": ["name", "version"], "properties": { "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix." }, "type": { "type": "string" }, "target-dir": { "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "type": "string" }, "description": { "type": "string" }, "keywords": { "type": "array", "items": { "type": "string" } }, "homepage": { "type": "string", "format": "uri" }, "version": { "type": "string" }, "time": { "type": "string" }, "license": { "type": [ "string", "array" ] }, "authors": { "$ref": "#/definitions/authors" }, "require": { "type": "object", "additionalProperties": { "type": "string" } }, "replace": { "type": "object", "additionalProperties": { "type": "string" } }, "conflict": { "type": "object", "additionalProperties": { "type": "string" } }, "provide": { "type": "object", "additionalProperties": { "type": "string" } }, "require-dev": { "type": "object", "additionalProperties": { "type": "string" } }, "suggest": { "type": "object", "additionalProperties": { "type": "string" } }, "extra": { "type": ["object", "array"], "additionalProperties": true }, "autoload": { "$ref": "#/definitions/autoload" }, "archive": { "type": ["object"], "properties": { "exclude": { "type": "array" } } }, "bin": { "type": ["string", "array"], "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } }, "include-path": { "type": ["array"], "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", "items": { "type": "string" } }, "source": { "$ref": "#/definitions/source" }, "dist": { "$ref": "#/definitions/dist" } }, "additionalProperties": true }, "source": { "type": "object", "required": ["type", "url", "reference"], "properties": { "type": { "type": "string" }, "url": { "type": "string" }, "reference": { "type": "string" }, "mirrors": { "type": "array" } } }, "dist": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string" }, "url": { "type": "string" }, "reference": { "type": "string" }, "shasum": { "type": "string" }, "mirrors": { "type": "array" } } } } } { "$schema": "https://json-schema.org/draft-04/schema#", "description": "A representation of packages metadata.", "type": "object", "oneOf": [ { "required": [ "packages" ] }, { "required": [ "providers" ] }, { "required": [ "provider-includes", "providers-url" ] } ], "properties": { "packages": { "type": ["object", "array"], "description": "A hashmap of package names in the form of /.", "additionalProperties": { "$ref": "#/definitions/versions" } }, "providers-url": { "type": "string", "description": "Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'." }, "provider-includes": { "type": "object", "description": "A hashmap of provider listings.", "additionalProperties": { "$ref": "#/definitions/provider" } }, "providers": { "type": "object", "description": "A hashmap of package names in the form of /.", "additionalProperties": { "$ref": "#/definitions/provider" } }, "notify-batch": { "type": "string", "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'." }, "search": { "type": "string", "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'." }, "warning": { "type": "string", "description": "A message that will be output by Composer as a warning when this source is consulted." } }, "definitions": { "versions": { "type": "object", "description": "A hashmap of versions and their metadata.", "additionalProperties": { "$ref": "#/definitions/version" } }, "version": { "type": "object", "oneOf": [ { "$ref": "#/definitions/package" }, { "$ref": "#/definitions/metapackage" } ] }, "package-base": { "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "version": { "type": "string" }, "version_normalized": { "type": "string", "description": "Normalized version, optional but can save computational time on client side." }, "autoload": { "type": "object" }, "require": { "type": "object" }, "replace": { "type": "object" }, "conflict": { "type": "object" }, "provide": { "type": "object" }, "time": { "type": "string" } }, "additionalProperties": true }, "package": { "allOf": [ { "$ref": "#/definitions/package-base" }, { "properties": { "dist": { "type": "object" }, "source": { "type": "object" } } }, { "oneOf": [ { "required": [ "name", "version", "source" ] }, { "required": [ "name", "version", "dist" ] } ] } ] }, "metapackage": { "allOf": [ { "$ref": "#/definitions/package-base" }, { "properties": { "type": { "type": "string", "enum": [ "metapackage" ] } }, "required": [ "name", "version", "type" ] } ] }, "provider": { "type": "object", "properties": { "sha256": { "type": "string", "description": "Hash value that can be used to validate the resource." } } } } } { "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "bbb9ffc97dcec54f38fdc5b4bf9a287d", "packages": [ { "name": "composer/ca-bundle", "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", "reference": "b66d11b7479109ab547f9405b97205640b17d385" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", "reference": "b66d11b7479109ab547f9405b97205640b17d385", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", "keywords": [ "cabundle", "cacert", "certificate", "ssl", "tls" ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", "source": "https://github.com/composer/ca-bundle/tree/1.4.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2023-12-18T12:05:55+00:00" }, { "name": "composer/class-map-generator", "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9", "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9", "shasum": "" }, "require": { "composer/pcre": "^2.1 || ^3.1", "php": "^7.2 || ^8.0", "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" }, "require-dev": { "phpstan/phpstan": "^1.6", "phpstan/phpstan-deprecation-rules": "^1", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", "symfony/filesystem": "^5.4 || ^6", "symfony/phpunit-bridge": "^5" }, "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { "Composer\\ClassMapGenerator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https://seld.be" } ], "description": "Utilities to scan PHP code and generate class maps.", "keywords": [ "classmap" ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", "source": "https://github.com/composer/class-map-generator/tree/1.1.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2023-06-30T13:58:57+00:00" }, { "name": "composer/metadata-minifier", "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/composer/metadata-minifier.git", "reference": "c549d23829536f0d0e984aaabbf02af91f443207" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", "reference": "c549d23829536f0d0e984aaabbf02af91f443207", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "composer/composer": "^2", "phpstan/phpstan": "^0.12.55", "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { "Composer\\MetadataMinifier\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "Small utility library that handles metadata minification and expansion.", "keywords": [ "composer", "compression" ], "support": { "issues": "https://github.com/composer/metadata-minifier/issues", "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2021-04-07T13:37:33+00:00" }, { "name": "composer/pcre", "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", "reference": "b439557066cd445732fa57cbc8d905394b4db8a0" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/pcre/zipball/b439557066cd445732fa57cbc8d905394b4db8a0", "reference": "b439557066cd445732fa57cbc8d905394b4db8a0", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.3", "phpstan/phpstan-strict-rules": "^1.1", "symfony/phpunit-bridge": "^5" }, "type": "library", "extra": { "branch-alias": { "dev-main": "2.x-dev" } }, "autoload": { "psr-4": { "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ "PCRE", "preg", "regex", "regular expression" ], "support": { "issues": "https://github.com/composer/pcre/issues", "source": "https://github.com/composer/pcre/tree/2.1.1" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2023-10-11T07:10:55+00:00" }, { "name": "composer/semver", "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.4", "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "http://www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" }, { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http://robbast.nl" } ], "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", "semver", "validation", "versioning" ], "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", "source": "https://github.com/composer/semver/tree/3.4.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2023-08-31T09:50:34+00:00" }, { "name": "composer/spdx-licenses", "version": "1.5.8", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^0.12.55", "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { "Composer\\Spdx\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "http://www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" }, { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http://robbast.nl" } ], "description": "SPDX licenses list and validation library.", "keywords": [ "license", "spdx", "validator" ], "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/spdx-licenses/issues", "source": "https://github.com/composer/spdx-licenses/tree/1.5.8" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2023-11-20T07:44:33+00:00" }, { "name": "composer/xdebug-handler", "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", "reference": "ced299686f41dce890debac69273b47ffe98a40c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", "reference": "ced299686f41dce890debac69273b47ffe98a40c", "shasum": "" }, "require": { "composer/pcre": "^1 || ^2 || ^3", "php": "^7.2.5 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", "symfony/phpunit-bridge": "^6.0" }, "type": "library", "autoload": { "psr-4": { "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "John Stevenson", "email": "john-stevenson@blueyonder.co.uk" } ], "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", "performance" ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "time": "2022-02-25T21:32:43+00:00" }, { "name": "justinrainbow/json-schema", "version": "v5.2.13", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", "json-schema/json-schema-test-suite": "1.2.0", "phpunit/phpunit": "^4.8.35" }, "bin": [ "bin/validate-json" ], "type": "library", "extra": { "branch-alias": { "dev-master": "5.0.x-dev" } }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Bruno Prieto Reis", "email": "bruno.p.reis@gmail.com" }, { "name": "Justin Rainbow", "email": "justin.rainbow@gmail.com" }, { "name": "Igor Wiedler", "email": "igor@wiedler.ch" }, { "name": "Robert Schönthal", "email": "seroscho@googlemail.com" } ], "description": "A library to validate a json schema.", "homepage": "https://github.com/justinrainbow/json-schema", "keywords": [ "json", "schema" ], "support": { "issues": "https://github.com/justinrainbow/json-schema/issues", "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13" }, "time": "2023-09-26T02:20:38+00:00" }, { "name": "psr/container", "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { "php": ">=7.2.0" }, "type": "library", "autoload": { "psr-4": { "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", "homepage": "https://github.com/php-fig/container", "keywords": [ "PSR-11", "container", "container-interface", "container-interop", "psr" ], "support": { "issues": "https://github.com/php-fig/container/issues", "source": "https://github.com/php-fig/container/tree/1.1.1" }, "time": "2021-03-05T17:36:06+00:00" }, { "name": "psr/log", "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], "support": { "source": "https://github.com/php-fig/log/tree/1.1.4" }, "time": "2021-05-03T11:20:27+00:00" }, { "name": "react/promise", "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { "phpstan/phpstan": "1.10.39 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { "files": [ "src/functions_include.php" ], "psr-4": { "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jan Sorgalla", "email": "jsorgalla@gmail.com", "homepage": "https://sorgalla.com/" }, { "name": "Christian Lück", "email": "christian@clue.engineering", "homepage": "https://clue.engineering/" }, { "name": "Cees-Jan Kiewiet", "email": "reactphp@ceesjankiewiet.nl", "homepage": "https://wyrihaximus.net/" }, { "name": "Chris Boden", "email": "cboden@gmail.com", "homepage": "https://cboden.dev/" } ], "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ "promise", "promises" ], "support": { "issues": "https://github.com/reactphp/promise/issues", "source": "https://github.com/reactphp/promise/tree/v3.1.0" }, "funding": [ { "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], "time": "2023-11-16T16:21:57+00:00" }, { "name": "seld/jsonlint", "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", "shasum": "" }, "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.5", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, "bin": [ "bin/jsonlint" ], "type": "library", "autoload": { "psr-4": { "Seld\\JsonLint\\": "src/Seld/JsonLint/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https://seld.be" } ], "description": "JSON Linter", "keywords": [ "json", "linter", "parser", "validator" ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" }, "funding": [ { "url": "https://github.com/Seldaek", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", "type": "tidelift" } ], "time": "2024-02-07T12:57:50+00:00" }, { "name": "seld/phar-utils", "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", "shasum": "" }, "require": { "php": ">=5.3" }, "type": "library", "extra": { "branch-alias": { "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { "Seld\\PharUtils\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be" } ], "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ "phar" ], "support": { "issues": "https://github.com/Seldaek/phar-utils/issues", "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" }, "time": "2022-08-31T10:31:18+00:00" }, { "name": "seld/signal-handler", "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/Seldaek/signal-handler.git", "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", "shasum": "" }, "require": { "php": ">=7.2.0" }, "require-dev": { "phpstan/phpstan": "^1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^7.5.20 || ^8.5.23", "psr/log": "^1 || ^2 || ^3" }, "type": "library", "extra": { "branch-alias": { "dev-main": "2.x-dev" } }, "autoload": { "psr-4": { "Seld\\Signal\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", "keywords": [ "posix", "sigint", "signal", "sigterm", "unix" ], "support": { "issues": "https://github.com/Seldaek/signal-handler/issues", "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" }, "time": "2023-09-03T09:24:00+00:00" }, { "name": "symfony/console", "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/console.git", "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/console/zipball/dbdf6adcb88d5f83790e1efb57ef4074309d3931", "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2|^3", "symfony/string": "^5.1|^6.0" }, "conflict": { "psr/log": ">=3", "symfony/dependency-injection": "<4.4", "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", "symfony/lock": "<4.4", "symfony/process": "<4.4" }, "provide": { "psr/log-implementation": "1.0|2.0" }, "require-dev": { "psr/log": "^1|^2", "symfony/config": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/event-dispatcher": "^4.4|^5.0|^6.0", "symfony/lock": "^4.4|^5.0|^6.0", "symfony/process": "^4.4|^5.0|^6.0", "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ "cli", "command-line", "console", "terminal" ], "support": { "source": "https://github.com/symfony/console/tree/v5.4.35" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-23T14:28:09+00:00" }, { "name": "symfony/deprecation-contracts", "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", "url": "https://github.com/symfony/contracts" } }, "autoload": { "files": [ "function.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/filesystem", "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/filesystem/zipball/5a553607d4ffbfa9c0ab62facadea296c9db7086", "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8", "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/filesystem/tree/v5.4.35" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/finder", "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/finder/zipball/abe6d6f77d9465fed3cd2d029b29d03b56b56435", "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/finder/tree/v5.4.35" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/polyfill-ctype", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { "php": ">=7.1" }, "provide": { "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "ctype", "polyfill", "portable" ], "support": { "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "grapheme", "intl", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "intl", "normalizer", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { "php": ">=7.1" }, "provide": { "ext-mbstring": "*" }, "suggest": { "ext-mbstring": "For best performance" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", "mbstring", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php73", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", "shasum": "" }, "require": { "php": ">=7.1" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { "php": ">=7.1" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Ion Bazan", "email": "ion.bazan@gmail.com" }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php81", "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { "php": ">=7.1" }, "type": "library", "extra": { "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/process.git", "reference": "cbc28e34015ad50166fc2f9c8962d28d0fe861eb" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/process/zipball/cbc28e34015ad50166fc2f9c8962d28d0fe861eb", "reference": "cbc28e34015ad50166fc2f9c8962d28d0fe861eb", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/process/tree/v5.4.35" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/service-contracts", "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/container": "^1.1", "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, "suggest": { "symfony/service-implementation": "" }, "type": "library", "extra": { "branch-alias": { "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", "url": "https://github.com/symfony/contracts" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ "abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards" ], "support": { "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2022-05-30T19:17:29+00:00" }, { "name": "symfony/string", "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/string.git", "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/string/zipball/c209c4d0559acce1c9a2067612cfb5d35756edc2", "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "~1.15" }, "conflict": { "symfony/translation-contracts": ">=3.0" }, "require-dev": { "symfony/error-handler": "^4.4|^5.0|^6.0", "symfony/http-client": "^4.4|^5.0|^6.0", "symfony/translation-contracts": "^1.1|^2", "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { "files": [ "Resources/functions.php" ], "psr-4": { "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", "i18n", "string", "unicode", "utf-8", "utf8" ], "support": { "source": "https://github.com/symfony/string/tree/v5.4.35" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-23T13:51:25+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", "version": "1.10.57", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", "reference": "1627b1d03446904aaa77593f370c5201d2ecc34e" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1627b1d03446904aaa77593f370c5201d2ecc34e", "reference": "1627b1d03446904aaa77593f370c5201d2ecc34e", "shasum": "" }, "require": { "php": "^7.2|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" }, "bin": [ "phpstan", "phpstan.phar" ], "type": "library", "autoload": { "files": [ "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", "static analysis" ], "support": { "docs": "https://phpstan.org/user-guide/getting-started", "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", "security": "https://github.com/phpstan/phpstan/security/policy", "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { "url": "https://github.com/ondrejmirtes", "type": "github" }, { "url": "https://github.com/phpstan", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", "type": "tidelift" } ], "time": "2024-01-24T11:51:34+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", "reference": "089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa", "reference": "089d8a8258ed0aeefdc7b68b6c3d25572ebfdbaa", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", "phpstan/phpstan": "^1.10.3" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-php-parser": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, "type": "phpstan-extension", "extra": { "phpstan": { "includes": [ "rules.neon" ] } }, "autoload": { "psr-4": { "PHPStan\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.1.4" }, "time": "2023-08-05T09:02:04+00:00" }, { "name": "phpstan/phpstan-phpunit", "version": "1.3.15", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", "reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/70ecacc64fe8090d8d2a33db5a51fe8e88acd93a", "reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", "phpstan/phpstan": "^1.10" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-strict-rules": "^1.5.1", "phpunit/phpunit": "^9.5" }, "type": "phpstan-extension", "extra": { "phpstan": { "includes": [ "extension.neon", "rules.neon" ] } }, "autoload": { "psr-4": { "PHPStan\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.15" }, "time": "2023-10-09T18:58:39+00:00" }, { "name": "phpstan/phpstan-strict-rules", "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542", "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", "phpstan/phpstan": "^1.10.34" }, "require-dev": { "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-deprecation-rules": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, "type": "phpstan-extension", "extra": { "phpstan": { "includes": [ "rules.neon" ] } }, "autoload": { "psr-4": { "PHPStan\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2" }, "time": "2023-10-30T14:35:06+00:00" }, { "name": "phpstan/phpstan-symfony", "version": "1.3.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/ef7db637be9b85fa00278fc3477ac66abe8eb7d1", "reference": "ef7db637be9b85fa00278fc3477ac66abe8eb7d1", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", "phpstan/phpstan": "^1.10.36" }, "conflict": { "symfony/framework-bundle": "<3.0" }, "require-dev": { "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-phpunit": "^1.3.11", "phpstan/phpstan-strict-rules": "^1.5.1", "phpunit/phpunit": "^8.5.29 || ^9.5", "psr/container": "1.0 || 1.1.1", "symfony/config": "^5.4 || ^6.1", "symfony/console": "^5.4 || ^6.1", "symfony/dependency-injection": "^5.4 || ^6.1", "symfony/form": "^5.4 || ^6.1", "symfony/framework-bundle": "^5.4 || ^6.1", "symfony/http-foundation": "^5.4 || ^6.1", "symfony/messenger": "^5.4", "symfony/polyfill-php80": "^1.24", "symfony/serializer": "^5.4", "symfony/service-contracts": "^2.2.0" }, "type": "phpstan-extension", "extra": { "phpstan": { "includes": [ "extension.neon", "rules.neon" ] } }, "autoload": { "psr-4": { "PHPStan\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Lukáš Unger", "email": "looky.msc@gmail.com", "homepage": "https://lookyman.net" } ], "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.7" }, "time": "2024-01-10T21:54:42+00:00" }, { "name": "symfony/phpunit-bridge", "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", "reference": "0a2eeb0d9e68bf01660e3e903f8113508bb46132" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0a2eeb0d9e68bf01660e3e903f8113508bb46132", "reference": "0a2eeb0d9e68bf01660e3e903f8113508bb46132", "shasum": "" }, "require": { "php": ">=7.2.5" }, "conflict": { "phpunit/phpunit": "<7.5|9.1.2" }, "require-dev": { "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/error-handler": "^5.4|^6.4|^7.0", "symfony/polyfill-php81": "^1.27" }, "bin": [ "bin/simple-phpunit" ], "type": "symfony-bridge", "extra": { "thanks": { "name": "phpunit/phpunit", "url": "https://github.com/sebastianbergmann/phpunit" } }, "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Bridge\\PhpUnit\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.3" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "time": "2024-01-23T15:02:46+00:00" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": "^7.2.5 || ^8.0" }, "platform-dev": [], "platform-overrides": { "php": "7.2.5" }, "plugin-api-version": "2.6.0" } Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #!/usr/bin/env php check(); unset($xdebug); if (\defined('_ContaoManager\\HHVM_VERSION') && \version_compare(\_ContaoManager\HHVM_VERSION, '4.0', '>=')) { echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.' . \PHP_EOL; exit(1); } if (!\extension_loaded('iconv') && !\extension_loaded('mbstring')) { echo 'The iconv OR mbstring extension is required and both are missing.' . \PHP_EOL . 'Install either of them or recompile php without --disable-iconv.' . \PHP_EOL . 'Aborting.' . \PHP_EOL; exit(1); } if (\function_exists('ini_set')) { @\ini_set('display_errors', '1'); // Set user defined memory limit if ($memoryLimit = \getenv('COMPOSER_MEMORY_LIMIT')) { @\ini_set('memory_limit', $memoryLimit); } else { $memoryInBytes = function ($value) { $unit = \strtolower(\substr($value, -1, 1)); $value = (int) $value; switch ($unit) { case 'g': $value *= 1024; // no break (cumulative multiplier) case 'm': $value *= 1024; // no break (cumulative multiplier) case 'k': $value *= 1024; } return $value; }; $memoryLimit = \trim(\ini_get('memory_limit')); // Increase memory_limit if it is lower than 1.5GB if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { @\ini_set('memory_limit', '1536M'); } unset($memoryInBytes); } unset($memoryLimit); } // Workaround PHP bug on Windows where env vars containing Unicode chars are mangled in $_SERVER // see https://github.com/php/php-src/issues/7896 if (\PHP_VERSION_ID >= 70113 && (\PHP_VERSION_ID < 80016 || \PHP_VERSION_ID >= 80100 && \PHP_VERSION_ID < 80103) && Platform::isWindows()) { foreach ($_SERVER as $serverVar => $serverVal) { if (($serverVal = \getenv($serverVar)) !== \false) { $_SERVER[$serverVar] = $serverVal; } } } Platform::putEnv('COMPOSER_BINARY', \realpath($_SERVER['argv'][0])); ErrorHandler::register(); // run the command application $application = new Application(); $application->run(); #!/usr/bin/env php compile(); } catch (\Exception $e) { echo 'Failed to compile phar: [' . \get_class($e) . '] ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . \PHP_EOL; exit(1); } { "name": "composer\/composer", "type": "library", "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", "keywords": [ "package", "dependency", "autoload" ], "homepage": "https:\/\/getcomposer.org\/", "license": "MIT", "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "https:\/\/www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https:\/\/seld.be" } ], "require": { "php": "^7.2.5 || ^8.0", "composer\/ca-bundle": "^1.0", "composer\/class-map-generator": "^1.0", "composer\/metadata-minifier": "^1.0", "composer\/semver": "^3.2.5", "composer\/spdx-licenses": "^1.5.7", "composer\/xdebug-handler": "^2.0.2 || ^3.0.3", "justinrainbow\/json-schema": "^5.2.11", "psr\/log": "^1.0 || ^2.0 || ^3.0", "seld\/jsonlint": "^1.4", "seld\/phar-utils": "^1.2", "symfony\/console": "^5.4.11 || ^6.0.11 || ^7", "symfony\/filesystem": "^5.4 || ^6.0 || ^7", "symfony\/finder": "^5.4 || ^6.0 || ^7", "symfony\/process": "^5.4 || ^6.0 || ^7", "react\/promise": "^2.8 || ^3", "composer\/pcre": "^2.1 || ^3.1", "symfony\/polyfill-php73": "^1.24", "symfony\/polyfill-php80": "^1.24", "symfony\/polyfill-php81": "^1.24", "seld\/signal-handler": "^2.0" }, "require-dev": { "symfony\/phpunit-bridge": "^6.4.1 || ^7.0.1", "phpstan\/phpstan": "^1.9.3", "phpstan\/phpstan-phpunit": "^1.0", "phpstan\/phpstan-deprecation-rules": "^1", "phpstan\/phpstan-strict-rules": "^1", "phpstan\/phpstan-symfony": "^1.2.10" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", "ext-zip": "Enabling the zip extension allows you to unzip archives", "ext-zlib": "Allow gzip compression of HTTP requests" }, "config": { "platform": { "php": "7.2.5" }, "platform-check": false }, "extra": { "branch-alias": { "dev-main": "2.7-dev" }, "phpstan": { "includes": [ "phpstan\/rules.neon" ] } }, "autoload": { "psr-4": { "Composer\\": "src\/Composer\/" } }, "autoload-dev": { "psr-4": { "Composer\\Test\\": "tests\/Composer\/Test\/" } }, "bin": [ "bin\/composer" ], "scripts": { "compile": "@php -dphar.readonly=0 bin\/compile", "test": "@php simple-phpunit", "phpstan": "@php vendor\/bin\/phpstan analyse --configuration=phpstan\/config.neon" }, "scripts-descriptions": { "compile": "Compile composer.phar", "test": "Run all tests", "phpstan": "Runs PHPStan" }, "support": { "issues": "https:\/\/github.com\/composer\/composer\/issues", "irc": "ircs:\/\/irc.libera.chat:6697\/composer", "security": "https:\/\/github.com\/composer\/composer\/security\/policy" } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\PHPStan; use Composer\DependencyResolver\Rule; use Composer\Package\BasePackage; use Composer\Package\Link; use Composer\Semver\Constraint\ConstraintInterface; use _ContaoManager\PhpParser\Node\Expr\MethodCall; use _ContaoManager\PHPStan\Analyser\Scope; use _ContaoManager\PHPStan\Reflection\MethodReflection; use _ContaoManager\PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use _ContaoManager\PHPStan\Type\Constant\ConstantArrayType; use _ContaoManager\PHPStan\Type\Constant\ConstantStringType; use _ContaoManager\PHPStan\Type\Constant\ConstantIntegerType; use _ContaoManager\PHPStan\Type\DynamicMethodReturnTypeExtension; use _ContaoManager\PHPStan\Type\IntegerType; use _ContaoManager\PHPStan\Type\StringType; use _ContaoManager\PHPStan\Type\Type; use _ContaoManager\PHPStan\Type\ObjectType; use _ContaoManager\PHPStan\Type\TypeCombinator; use _ContaoManager\PhpParser\Node\Identifier; final class RuleReasonDataReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function getClass() : string { return Rule::class; } public function isMethodSupported(MethodReflection $methodReflection) : bool { return \strtolower($methodReflection->getName()) === 'getreasondata'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope) : Type { $reasonType = $scope->getType(new MethodCall($methodCall->var, new Identifier('getReason'))); $types = [Rule::RULE_ROOT_REQUIRE => new ConstantArrayType([new ConstantStringType('packageName'), new ConstantStringType('constraint')], [new StringType(), new ObjectType(ConstraintInterface::class)]), Rule::RULE_FIXED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]), Rule::RULE_PACKAGE_CONFLICT => new ObjectType(Link::class), Rule::RULE_PACKAGE_REQUIRES => new ObjectType(Link::class), Rule::RULE_PACKAGE_SAME_NAME => TypeCombinator::intersect(new StringType(), new AccessoryNonEmptyStringType()), Rule::RULE_LEARNED => new IntegerType(), Rule::RULE_PACKAGE_ALIAS => new ObjectType(BasePackage::class), Rule::RULE_PACKAGE_INVERSE_ALIAS => new ObjectType(BasePackage::class)]; foreach ($types as $const => $type) { if ((new ConstantIntegerType($const))->isSuperTypeOf($reasonType)->yes()) { return $type; } } return TypeCombinator::union(...$types); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\PHPStan; use Composer\Config; use Composer\Json\JsonFile; use _ContaoManager\PhpParser\Node\Expr\MethodCall; use _ContaoManager\PHPStan\Analyser\Scope; use _ContaoManager\PHPStan\Reflection\MethodReflection; use _ContaoManager\PHPStan\Reflection\ParametersAcceptorSelector; use _ContaoManager\PHPStan\Type\ArrayType; use _ContaoManager\PHPStan\Type\BooleanType; use _ContaoManager\PHPStan\Type\Constant\ConstantArrayType; use _ContaoManager\PHPStan\Type\Constant\ConstantBooleanType; use _ContaoManager\PHPStan\Type\Constant\ConstantStringType; use _ContaoManager\PHPStan\Type\DynamicMethodReturnTypeExtension; use _ContaoManager\PHPStan\Type\IntegerRangeType; use _ContaoManager\PHPStan\Type\IntegerType; use _ContaoManager\PHPStan\Type\MixedType; use _ContaoManager\PHPStan\Type\StringType; use _ContaoManager\PHPStan\Type\Type; use _ContaoManager\PHPStan\Type\TypeCombinator; use _ContaoManager\PHPStan\Type\TypeUtils; use _ContaoManager\PHPStan\Type\UnionType; final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension { /** @var array */ private $properties = []; public function __construct() { $schema = JsonFile::parseJson((string) \file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); /** * @var string $prop */ foreach ($schema['properties']['config']['properties'] as $prop => $conf) { $type = $this->parseType($conf, $prop); $this->properties[$prop] = $type; } } public function getClass() : string { return Config::class; } public function isMethodSupported(MethodReflection $methodReflection) : bool { return \strtolower($methodReflection->getName()) === 'get'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope) : Type { $args = $methodCall->getArgs(); $defaultReturn = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); if (\count($args) < 1) { return $defaultReturn; } $keyType = $scope->getType($args[0]->value); if (\method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore-line - depending on PHPStan version, this method will always exist, or not. $strings = $keyType->getConstantStrings(); } else { // for compat with old phpstan versions, we use a deprecated phpstan method. $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore-line ignore deprecation } if ($strings !== []) { $types = []; foreach ($strings as $string) { if (!isset($this->properties[$string->getValue()])) { return $defaultReturn; } $types[] = $this->properties[$string->getValue()]; } return TypeCombinator::union(...$types); } return $defaultReturn; } /** * @param array $def */ private function parseType(array $def, string $path) : Type { if (isset($def['type'])) { $types = []; foreach ((array) $def['type'] as $type) { switch ($type) { case 'integer': if (\in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], \true)) { $types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0); } else { $types[] = new IntegerType(); } break; case 'string': if ($path === 'cache-files-maxsize') { // passthru, skip as it is always converted to int } elseif ($path === 'discard-changes') { $types[] = new ConstantStringType('stash'); } elseif ($path === 'use-parent-dir') { $types[] = new ConstantStringType('prompt'); } elseif ($path === 'store-auths') { $types[] = new ConstantStringType('prompt'); } elseif ($path === 'platform-check') { $types[] = new ConstantStringType('php-only'); } elseif ($path === 'github-protocols') { $types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]); } elseif (\str_starts_with($path, 'preferred-install')) { $types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]); } else { $types[] = new StringType(); } break; case 'boolean': if ($path === 'platform.additionalProperties') { $types[] = new ConstantBooleanType(\false); } else { $types[] = new BooleanType(); } break; case 'object': $addlPropType = null; if (isset($def['additionalProperties'])) { $addlPropType = $this->parseType($def['additionalProperties'], $path . '.additionalProperties'); } if (isset($def['properties'])) { $keyNames = []; $valTypes = []; $optionalKeys = []; $propIndex = 0; foreach ($def['properties'] as $propName => $propdef) { $keyNames[] = new ConstantStringType($propName); $valType = $this->parseType($propdef, $path . '.' . $propName); if (!isset($def['required']) || !\in_array($propName, $def['required'], \true)) { $valType = TypeCombinator::addNull($valType); $optionalKeys[] = $propIndex; } $valTypes[] = $valType; $propIndex++; } if ($addlPropType !== null) { $types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes)); } else { $types[] = new ConstantArrayType($keyNames, $valTypes, [0], $optionalKeys); } } else { $types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType()); } break; case 'array': if (isset($def['items'])) { $valType = $this->parseType($def['items'], $path . '.items'); } else { $valType = new MixedType(); } $types[] = new ArrayType(new IntegerType(), $valType); break; default: $types[] = new MixedType(); } } $type = TypeCombinator::union(...$types); } elseif (isset($def['enum'])) { $type = TypeCombinator::union(...\array_map(static function (string $value) : ConstantStringType { return new ConstantStringType($value); }, $def['enum'])); } else { $type = new MixedType(); } // allow-plugins defaults to null until July 1st 2022 for some BC hackery, but after that it is not nullable anymore if ($path === 'allow-plugins' && \time() < \strtotime('2022-07-01')) { $type = TypeCombinator::addNull($type); } // default null props if (\in_array($path, ['autoloader-suffix', 'gitlab-protocol'], \true)) { $type = TypeCombinator::addNull($type); } return $type; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Config\JsonConfigSource; use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Package\Archiver; use Composer\Package\Version\VersionGuesser; use Composer\Package\RootPackageInterface; use Composer\Repository\FilesystemRepository; use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryFactory; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\Util\Loop; use Composer\Util\Silencer; use Composer\Plugin\PluginEvents; use Composer\EventDispatcher\Event; use Phar; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterStyle; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutput; use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Version\VersionParser; use Composer\Downloader\TransportException; use Composer\Json\JsonValidationException; use Composer\Repository\InstalledRepositoryInterface; use UnexpectedValueException; use ZipArchive; /** * Creates a configured instance of composer. * * @author Ryan Weaver * @author Jordi Boggiano * @author Igor Wiedler * @author Nils Adermann */ class Factory { /** * @throws \RuntimeException */ protected static function getHomeDir() : string { $home = Platform::getEnv('COMPOSER_HOME'); if ($home) { return $home; } if (Platform::isWindows()) { if (!Platform::getEnv('APPDATA')) { throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); } return \rtrim(\strtr(Platform::getEnv('APPDATA'), '\\', '/'), '/') . '/Composer'; } $userDir = self::getUserDir(); $dirs = []; if (self::useXdg()) { // XDG Base Directory Specifications $xdgConfig = Platform::getEnv('XDG_CONFIG_HOME'); if (!$xdgConfig) { $xdgConfig = $userDir . '/.config'; } $dirs[] = $xdgConfig . '/composer'; } $dirs[] = $userDir . '/.composer'; // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer foreach ($dirs as $dir) { if (Silencer::call('is_dir', $dir)) { return $dir; } } // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise) return $dirs[0]; } protected static function getCacheDir(string $home) : string { $cacheDir = Platform::getEnv('COMPOSER_CACHE_DIR'); if ($cacheDir) { return $cacheDir; } $homeEnv = Platform::getEnv('COMPOSER_HOME'); if ($homeEnv) { return $homeEnv . '/cache'; } if (Platform::isWindows()) { if ($cacheDir = Platform::getEnv('LOCALAPPDATA')) { $cacheDir .= '/Composer'; } else { $cacheDir = $home . '/cache'; } return \rtrim(\strtr($cacheDir, '\\', '/'), '/'); } $userDir = self::getUserDir(); if (\PHP_OS === 'Darwin') { // Migrate existing cache dir in old location if present if (\is_dir($home . '/cache') && !\is_dir($userDir . '/Library/Caches/composer')) { Silencer::call('rename', $home . '/cache', $userDir . '/Library/Caches/composer'); } return $userDir . '/Library/Caches/composer'; } if ($home === $userDir . '/.composer' && \is_dir($home . '/cache')) { return $home . '/cache'; } if (self::useXdg()) { $xdgCache = Platform::getEnv('XDG_CACHE_HOME') ?: $userDir . '/.cache'; return $xdgCache . '/composer'; } return $home . '/cache'; } protected static function getDataDir(string $home) : string { $homeEnv = Platform::getEnv('COMPOSER_HOME'); if ($homeEnv) { return $homeEnv; } if (Platform::isWindows()) { return \strtr($home, '\\', '/'); } $userDir = self::getUserDir(); if ($home !== $userDir . '/.composer' && self::useXdg()) { $xdgData = Platform::getEnv('XDG_DATA_HOME') ?: $userDir . '/.local/share'; return $xdgData . '/composer'; } return $home; } public static function createConfig(?IOInterface $io = null, ?string $cwd = null) : \Composer\Config { $cwd = $cwd ?? Platform::getCwd(\true); $config = new \Composer\Config(\true, $cwd); // determine and add main dirs to the config $home = self::getHomeDir(); $config->merge(['config' => ['home' => $home, 'cache-dir' => self::getCacheDir($home), 'data-dir' => self::getDataDir($home)]], \Composer\Config::SOURCE_DEFAULT); // load global config $file = new JsonFile($config->get('home') . '/config.json'); if ($file->exists()) { if ($io instanceof IOInterface) { $io->writeError('Loading config file ' . $file->getPath(), \true, IOInterface::DEBUG); } self::validateJsonSchema($io, $file); $config->merge($file->read(), $file->getPath()); } $config->setConfigSource(new JsonConfigSource($file)); $htaccessProtect = $config->get('htaccess-protect'); if ($htaccessProtect) { // Protect directory against web access. Since HOME could be // the www-data's user home and be web-accessible it is a // potential security risk $dirs = [$config->get('home'), $config->get('cache-dir'), $config->get('data-dir')]; foreach ($dirs as $dir) { if (!\file_exists($dir . '/.htaccess')) { if (!\is_dir($dir)) { Silencer::call('mkdir', $dir, 0777, \true); } Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all'); } } } // load global auth file $file = new JsonFile($config->get('home') . '/auth.json'); if ($file->exists()) { if ($io instanceof IOInterface) { $io->writeError('Loading config file ' . $file->getPath(), \true, IOInterface::DEBUG); } self::validateJsonSchema($io, $file, JsonFile::AUTH_SCHEMA); $config->merge(['config' => $file->read()], $file->getPath()); } $config->setAuthConfigSource(new JsonConfigSource($file, \true)); // load COMPOSER_AUTH environment variable if set if ($composerAuthEnv = Platform::getEnv('COMPOSER_AUTH')) { $authData = \json_decode($composerAuthEnv); if (null === $authData) { throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); } else { if ($io instanceof IOInterface) { $io->writeError('Loading auth config from COMPOSER_AUTH', \true, IOInterface::DEBUG); } self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH'); $authData = \json_decode($composerAuthEnv, \true); if (null !== $authData) { $config->merge(['config' => $authData], 'COMPOSER_AUTH'); } } } return $config; } public static function getComposerFile() : string { return \trim((string) Platform::getEnv('COMPOSER')) ?: './composer.json'; } public static function getLockFile(string $composerFile) : string { return "json" === \pathinfo($composerFile, \PATHINFO_EXTENSION) ? \substr($composerFile, 0, -4) . 'lock' : $composerFile . '.lock'; } /** * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle} */ public static function createAdditionalStyles() : array { return ['highlight' => new OutputFormatterStyle('red'), 'warning' => new OutputFormatterStyle('black', 'yellow')]; } public static function createOutput() : ConsoleOutput { $styles = self::createAdditionalStyles(); $formatter = new OutputFormatter(\false, $styles); return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); } /** * Creates a Composer instance * * @param IOInterface $io IO instance * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will * read from the default filename * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins * @param bool $disableScripts Whether scripts should not be run * @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer) * @throws \InvalidArgumentException * @throws \UnexpectedValueException * @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer * @phpstan-return ($fullLoad is true ? Composer : PartialComposer) */ public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = \false, ?string $cwd = null, bool $fullLoad = \true, bool $disableScripts = \false) { // if a custom composer.json path is given, we change the default cwd to be that file's directory if (\is_string($localConfig) && \is_file($localConfig) && null === $cwd) { $cwd = \dirname($localConfig); } $cwd = $cwd ?? Platform::getCwd(\true); // load Composer configuration if (null === $localConfig) { $localConfig = static::getComposerFile(); } $localConfigSource = \Composer\Config::SOURCE_UNKNOWN; if (\is_string($localConfig)) { $composerFile = $localConfig; $file = new JsonFile($localConfig, null, $io); if (!$file->exists()) { if ($localConfig === './composer.json' || $localConfig === 'composer.json') { $message = 'Composer could not find a composer.json file in ' . $cwd; } else { $message = 'Composer could not find the config file: ' . $localConfig; } $instructions = $fullLoad ? 'To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage' : ''; throw new \InvalidArgumentException($message . \PHP_EOL . $instructions); } if (!Platform::isInputCompletionProcess()) { try { $file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { $errors = ' - ' . \implode(\PHP_EOL . ' - ', $e->getErrors()); $message = $e->getMessage() . ':' . \PHP_EOL . $errors; throw new JsonValidationException($message); } } $localConfig = $file->read(); $localConfigSource = $file->getPath(); } // Load config and override with local config/auth config $config = static::createConfig($io, $cwd); $config->merge($localConfig, $localConfigSource); if (isset($composerFile)) { $io->writeError('Loading config file ' . $composerFile . ' (' . \realpath($composerFile) . ')', \true, IOInterface::DEBUG); $config->setConfigSource(new JsonConfigSource(new JsonFile(\realpath($composerFile), null, $io))); $localAuthFile = new JsonFile(\dirname(\realpath($composerFile)) . '/auth.json', null, $io); if ($localAuthFile->exists()) { $io->writeError('Loading config file ' . $localAuthFile->getPath(), \true, IOInterface::DEBUG); self::validateJsonSchema($io, $localAuthFile, JsonFile::AUTH_SCHEMA); $config->merge(['config' => $localAuthFile->read()], $localAuthFile->getPath()); $config->setLocalAuthConfigSource(new JsonConfigSource($localAuthFile, \true)); } } $vendorDir = $config->get('vendor-dir'); // initialize composer $composer = $fullLoad ? new \Composer\Composer() : new \Composer\PartialComposer(); $composer->setConfig($config); if ($fullLoad) { // load auth configs into the IO instance $io->loadConfiguration($config); // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once if (\false === $disablePlugins && \false === $disableScripts && !\class_exists('Composer\\InstalledVersions', \false) && \file_exists($installedVersionsPath = $config->get('vendor-dir') . '/composer/installed.php')) { // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir // as we cannot guarantee integrity of that file if (\class_exists('Composer\\InstalledVersions')) { FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath); } } } $httpDownloader = self::createHttpDownloader($io, $config); $process = new ProcessExecutor($io); $loop = new Loop($httpDownloader, $process); $composer->setLoop($loop); // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io, $process); $dispatcher->setRunScripts(!$disableScripts); $composer->setEventDispatcher($dispatcher); // initialize repository manager $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process); $composer->setRepositoryManager($rm); // force-set the version of the global package if not defined as // guessing it adds no value and only takes time if (!$fullLoad && !isset($localConfig['version'])) { $localConfig['version'] = '1.0.0'; } // load package $parser = new VersionParser(); $guesser = new VersionGuesser($config, $process, $parser); $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io); $package = $loader->load($localConfig, 'Composer\\Package\\RootPackage', $cwd); $composer->setPackage($package); // load local repository $this->addLocalRepository($io, $rm, $vendorDir, $package, $process); // initialize installation manager $im = $this->createInstallationManager($loop, $io, $dispatcher); $composer->setInstallationManager($im); if ($composer instanceof \Composer\Composer) { // initialize download manager $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher); $composer->setDownloadManager($dm); // initialize autoload generator $generator = new AutoloadGenerator($dispatcher, $io); $composer->setAutoloadGenerator($generator); // initialize archive manager $am = $this->createArchiveManager($config, $dm, $loop); $composer->setArchiveManager($am); } // add installers to the manager (must happen after download manager is created since they read it out of $composer) $this->createDefaultInstallers($im, $composer, $io, $process); // init locker if possible if ($composer instanceof \Composer\Composer && isset($composerFile)) { $lockFile = self::getLockFile($composerFile); if (!$config->get('lock') && \file_exists($lockFile)) { $io->writeError('' . $lockFile . ' is present but ignored as the "lock" config option is disabled.'); } $locker = new \Composer\Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, \file_get_contents($composerFile), $process); $composer->setLocker($locker); } elseif ($composer instanceof \Composer\Composer) { $locker = new \Composer\Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process); $composer->setLocker($locker); } if ($composer instanceof \Composer\Composer) { $globalComposer = null; if (\realpath($config->get('home')) !== $cwd) { $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts); } $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins); $composer->setPluginManager($pm); if (\realpath($config->get('home')) === $cwd) { $pm->setRunningInGlobalDir(\true); } $pm->loadInstalledPlugins(); } if ($fullLoad) { $initEvent = new Event(PluginEvents::INIT); $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent); // once everything is initialized we can // purge packages from local repos if they have been deleted on the filesystem $this->purgePackages($rm->getLocalRepository(), $im); } return $composer; } /** * @param bool $disablePlugins Whether plugins should not be loaded * @param bool $disableScripts Whether scripts should not be executed */ public static function createGlobal(IOInterface $io, bool $disablePlugins = \false, bool $disableScripts = \false) : ?\Composer\Composer { $factory = new static(); return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, \true); } /** * @param Repository\RepositoryManager $rm */ protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, string $vendorDir, RootPackageInterface $rootPackage, ?ProcessExecutor $process = null) : void { $fs = null; if ($process) { $fs = new Filesystem($process); } $rm->setLocalRepository(new \Composer\Repository\InstalledFilesystemRepository(new JsonFile($vendorDir . '/composer/installed.json', null, $io), \true, $rootPackage, $fs)); } /** * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins * @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true * @phpstan-return ($fullLoad is true ? Composer|null : PartialComposer|null) */ protected function createGlobalComposer(IOInterface $io, \Composer\Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = \false) : ?\Composer\PartialComposer { // make sure if disable plugins was 'local' it is now turned off $disablePlugins = $disablePlugins === 'global' || $disablePlugins === \true; $composer = null; try { $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts); } catch (\Exception $e) { $io->writeError('Failed to initialize global composer: ' . $e->getMessage(), \true, IOInterface::DEBUG); } return $composer; } /** * @param IO\IOInterface $io * @param EventDispatcher $eventDispatcher */ public function createDownloadManager(IOInterface $io, \Composer\Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, ?EventDispatcher $eventDispatcher = null) : \Composer\Downloader\DownloadManager { $cache = null; if ($config->get('cache-files-ttl') > 0) { $cache = new \Composer\Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); $cache->setReadOnly($config->get('cache-read-only')); } $fs = new Filesystem($process); $dm = new \Composer\Downloader\DownloadManager($io, \false, $fs); switch ($preferred = $config->get('preferred-install')) { case 'dist': $dm->setPreferDist(\true); break; case 'source': $dm->setPreferSource(\true); break; case 'auto': default: // noop break; } if (\is_array($preferred)) { $dm->setPreferences($preferred); } $dm->setDownloader('git', new \Composer\Downloader\GitDownloader($io, $config, $process, $fs)); $dm->setDownloader('svn', new \Composer\Downloader\SvnDownloader($io, $config, $process, $fs)); $dm->setDownloader('fossil', new \Composer\Downloader\FossilDownloader($io, $config, $process, $fs)); $dm->setDownloader('hg', new \Composer\Downloader\HgDownloader($io, $config, $process, $fs)); $dm->setDownloader('perforce', new \Composer\Downloader\PerforceDownloader($io, $config, $process, $fs)); $dm->setDownloader('zip', new \Composer\Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('rar', new \Composer\Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('tar', new \Composer\Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('gzip', new \Composer\Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('xz', new \Composer\Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('phar', new \Composer\Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('file', new \Composer\Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('path', new \Composer\Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); return $dm; } /** * @param Config $config The configuration * @param Downloader\DownloadManager $dm Manager use to download sources * @return Archiver\ArchiveManager */ public function createArchiveManager(\Composer\Config $config, \Composer\Downloader\DownloadManager $dm, Loop $loop) { $am = new Archiver\ArchiveManager($dm, $loop); if (\class_exists(ZipArchive::class)) { $am->addArchiver(new Archiver\ZipArchiver()); } if (\class_exists(Phar::class)) { $am->addArchiver(new Archiver\PharArchiver()); } return $am; } /** * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins */ protected function createPluginManager(IOInterface $io, \Composer\Composer $composer, ?\Composer\PartialComposer $globalComposer = null, $disablePlugins = \false) : \Composer\Plugin\PluginManager { return new \Composer\Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); } public function createInstallationManager(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null) : \Composer\Installer\InstallationManager { return new \Composer\Installer\InstallationManager($loop, $io, $eventDispatcher); } protected function createDefaultInstallers(\Composer\Installer\InstallationManager $im, \Composer\PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null) : void { $fs = new Filesystem($process); $binaryInstaller = new \Composer\Installer\BinaryInstaller($io, \rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, \rtrim($composer->getConfig()->get('vendor-dir'), '/')); $im->addInstaller(new \Composer\Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller)); $im->addInstaller(new \Composer\Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller)); $im->addInstaller(new \Composer\Installer\MetapackageInstaller($io)); } /** * @param InstalledRepositoryInterface $repo repository to purge packages from * @param Installer\InstallationManager $im manager to check whether packages are still installed */ protected function purgePackages(InstalledRepositoryInterface $repo, \Composer\Installer\InstallationManager $im) : void { foreach ($repo->getPackages() as $package) { if (!$im->isPackageInstalled($repo, $package)) { $repo->removePackage($package); } } } protected function loadRootPackage(RepositoryManager $rm, \Composer\Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io) : \Composer\Package\Loader\RootPackageLoader { return new \Composer\Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); } /** * @param IOInterface $io IO instance * @param mixed $config either a configuration array or a filename to read from, if null it will read from * the default filename * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins * @param bool $disableScripts Whether scripts should not be run */ public static function create(IOInterface $io, $config = null, $disablePlugins = \false, bool $disableScripts = \false) : \Composer\Composer { $factory = new static(); // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path // we disable local plugins as they really should not be loaded from CWD // If you want to avoid this behavior, you should be calling createComposer directly with a $cwd arg set correctly // to the path where the composer.json being loaded resides if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === \false) { $disablePlugins = 'local'; } return $factory->createComposer($io, $config, $disablePlugins, null, \true, $disableScripts); } /** * If you are calling this in a plugin, you probably should instead use $composer->getLoop()->getHttpDownloader() * * @param IOInterface $io IO instance * @param Config $config Config instance * @param mixed[] $options Array of options passed directly to HttpDownloader constructor */ public static function createHttpDownloader(IOInterface $io, \Composer\Config $config, array $options = []) : HttpDownloader { static $warned = \false; $disableTls = \false; // allow running the config command if disable-tls is in the arg list, even if openssl is missing, to allow disabling it via the config command if (isset($_SERVER['argv']) && \in_array('disable-tls', $_SERVER['argv']) && (\in_array('conf', $_SERVER['argv']) || \in_array('config', $_SERVER['argv']))) { $warned = \true; $disableTls = !\extension_loaded('openssl'); } elseif ($config->get('disable-tls') === \true) { if (!$warned) { $io->writeError('You are running Composer with SSL/TLS protection disabled.'); } $warned = \true; $disableTls = \true; } elseif (!\extension_loaded('openssl')) { throw new \Composer\Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } $httpDownloaderOptions = []; if ($disableTls === \false) { if ('' !== $config->get('cafile')) { $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile'); } if ('' !== $config->get('capath')) { $httpDownloaderOptions['ssl']['capath'] = $config->get('capath'); } $httpDownloaderOptions = \array_replace_recursive($httpDownloaderOptions, $options); } try { $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls); } catch (TransportException $e) { if (\false !== \strpos($e->getMessage(), 'cafile')) { $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); $io->write('A valid CA certificate file is required for SSL/TLS protection.'); $io->write('You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } throw $e; } return $httpDownloader; } private static function useXdg() : bool { foreach (\array_keys($_SERVER) as $key) { if (\strpos($key, 'XDG_') === 0) { return \true; } } if (Silencer::call('is_dir', '/etc/xdg')) { return \true; } return \false; } /** * @throws \RuntimeException */ private static function getUserDir() : string { $home = Platform::getEnv('HOME'); if (!$home) { throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); } return \rtrim(\strtr($home, '\\', '/'), '/'); } /** * @param mixed $fileOrData * @param JsonFile::*_SCHEMA $schema */ private static function validateJsonSchema(?IOInterface $io, $fileOrData, int $schema = JsonFile::LAX_SCHEMA, ?string $source = null) : void { if (Platform::isInputCompletionProcess()) { return; } try { if ($fileOrData instanceof JsonFile) { $fileOrData->validateSchema($schema); } else { if (null === $source) { throw new \InvalidArgumentException('$source is required to be provided if $fileOrData is arbitrary data'); } JsonFile::validateJsonSchema($source, $fileOrData, $schema); } } catch (JsonValidationException $e) { $msg = $e->getMessage() . ', this may result in errors and should be resolved:' . \PHP_EOL . ' - ' . \implode(\PHP_EOL . ' - ', $e->getErrors()); if ($io instanceof IOInterface) { $io->writeError('' . $msg . ''); } else { throw new UnexpectedValueException($msg); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; use Composer\Package\BasePackage; use Composer\Semver\Constraint\ConstraintInterface; /** * Repository interface. * * @author Nils Adermann * @author Konstantin Kudryashov * @author Jordi Boggiano */ interface RepositoryInterface extends \Countable { public const SEARCH_FULLTEXT = 0; public const SEARCH_NAME = 1; public const SEARCH_VENDOR = 2; /** * Checks if specified package registered (installed). * * @param PackageInterface $package package instance * * @return bool */ public function hasPackage(PackageInterface $package); /** * Searches for the first match of a package by name and version. * * @param string $name package name * @param string|ConstraintInterface $constraint package version or version constraint to match against * * @return BasePackage|null */ public function findPackage(string $name, $constraint); /** * Searches for all packages matching a name and optionally a version. * * @param string $name package name * @param string|ConstraintInterface $constraint package version or version constraint to match against * * @return BasePackage[] */ public function findPackages(string $name, $constraint = null); /** * Returns list of registered packages. * * @return BasePackage[] */ public function getPackages(); /** * Returns list of registered packages with the supplied name * * - The packages returned are the packages found which match the constraints, acceptable stability and stability flags provided * - The namesFound returned are names which should be considered as canonically found in this repository, that should not be looked up in any further lower priority repositories * * @param ConstraintInterface[] $packageNameMap package names pointing to constraints * @param array $acceptableStabilities array of stability => BasePackage::STABILITY_* value * @param array $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @param array> $alreadyLoaded an array of package name => package version => package * * @return array * * @phpstan-param array $packageNameMap * @phpstan-return array{namesFound: array, packages: array} */ public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []); /** * Searches the repository for packages containing the query * * @param string $query search query, for SEARCH_NAME and SEARCH_VENDOR regular expressions metacharacters are supported by implementations, and user input should be escaped through preg_quote by callers * @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only, default is SEARCH_FULLTEXT * @param ?string $type The type of package to search for. Defaults to all types of packages * * @return array[] an array of array('name' => '...', 'description' => '...'|null, 'abandoned' => 'string'|true|unset) For SEARCH_VENDOR the name will be in "vendor" form * @phpstan-return list */ public function search(string $query, int $mode = 0, ?string $type = null); /** * Returns a list of packages providing a given package name * * Packages which have the same name as $packageName should not be returned, only those that have a "provide" on it. * * @param string $packageName package name which must be provided * * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') * @phpstan-return array */ public function getProviders(string $packageName); /** * Returns a name representing this repository to the user * * This is best effort and definitely can not always be very precise * * @return string */ public function getRepoName(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Installed filesystem repository. * * @author Jordi Boggiano */ class InstalledFilesystemRepository extends \Composer\Repository\FilesystemRepository implements \Composer\Repository\InstalledRepositoryInterface { public function getRepoName() { return 'installed ' . parent::getRepoName(); } /** * @inheritDoc */ public function isFresh() { return !$this->file->exists(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Pcre\Preg; /** * Package repository. * * @author Jordi Boggiano */ class PackageRepository extends \Composer\Repository\ArrayRepository { /** @var mixed[] */ private $config; /** * Initializes filesystem repository. * * @param array{package: mixed[]} $config package definition */ public function __construct(array $config) { parent::__construct(); $this->config = $config['package']; // make sure we have an array of package definitions if (!\is_numeric(\key($this->config))) { $this->config = [$this->config]; } } /** * Initializes repository (reads file, or remote address). */ protected function initialize() : void { parent::initialize(); $loader = new ValidatingArrayLoader(new ArrayLoader(null, \true), \true); foreach ($this->config as $package) { try { $package = $loader->load($package); } catch (\Exception $e) { throw new \Composer\Repository\InvalidRepositoryException('A repository of type "package" contains an invalid package definition: ' . $e->getMessage() . "\n\nInvalid package definition:\n" . \json_encode($package)); } $this->addPackage($package); } } public function getRepoName() : string { return Preg::replace('{^array }', 'package ', parent::getRepoName()); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Lock array repository. * * Regular array repository, only uses a different type to identify the lock file as the source of info * * @author Nils Adermann */ class LockArrayRepository extends \Composer\Repository\ArrayRepository { use \Composer\Repository\CanonicalPackagesTrait; public function getRepoName() : string { return 'lock repo'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Composer; use Composer\Package\CompletePackage; use Composer\Package\CompletePackageInterface; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Pcre\Preg; use Composer\Platform\HhvmDetector; use Composer\Platform\Runtime; use Composer\Platform\Version; use Composer\Plugin\PluginInterface; use Composer\Semver\Constraint\Constraint; use Composer\Util\Silencer; use Composer\XdebugHandler\XdebugHandler; /** * @author Jordi Boggiano */ class PlatformRepository extends \Composer\Repository\ArrayRepository { /** * @deprecated use PlatformRepository::isPlatformPackage(string $name) instead * @private */ public const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD'; /** * @var ?string */ private static $lastSeenPlatformPhp = null; /** * @var VersionParser */ private $versionParser; /** * Defines overrides so that the platform can be mocked * * Keyed by package name (lowercased) * * @var array */ private $overrides = []; /** * Stores which packages have been disabled and their actual version * * @var array */ private $disabledPackages = []; /** @var Runtime */ private $runtime; /** @var HhvmDetector */ private $hhvmDetector; /** * @param array $overrides */ public function __construct(array $packages = [], array $overrides = [], ?Runtime $runtime = null, ?HhvmDetector $hhvmDetector = null) { $this->runtime = $runtime ?: new Runtime(); $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector(); foreach ($overrides as $name => $version) { if (!\is_string($version) && \false !== $version) { // @phpstan-ignore-line throw new \UnexpectedValueException('config.platform.' . $name . ' should be a string or false, but got ' . \gettype($version) . ' ' . \var_export($version, \true)); } if ($name === 'php' && $version === \false) { throw new \UnexpectedValueException('config.platform.' . $name . ' cannot be set to false as you cannot disable php entirely.'); } $this->overrides[\strtolower($name)] = ['name' => $name, 'version' => $version]; } parent::__construct($packages); } public function getRepoName() : string { return 'platform repo'; } public function isPlatformPackageDisabled(string $name) : bool { return isset($this->disabledPackages[$name]); } /** * @return array */ public function getDisabledPackages() : array { return $this->disabledPackages; } protected function initialize() : void { parent::initialize(); $this->versionParser = new VersionParser(); // Add each of the override versions as options. // Later we might even replace the extensions instead. foreach ($this->overrides as $override) { // Check that it's a platform package. if (!self::isPlatformPackage($override['name'])) { throw new \InvalidArgumentException('Invalid platform package name in config.platform: ' . $override['name']); } if ($override['version'] !== \false) { $this->addOverriddenPackage($override); } } $prettyVersion = Composer::getVersion(); $version = $this->versionParser->normalize($prettyVersion); $composer = new CompletePackage('composer', $version, $prettyVersion); $composer->setDescription('Composer package'); $this->addPackage($composer); $prettyVersion = PluginInterface::PLUGIN_API_VERSION; $version = $this->versionParser->normalize($prettyVersion); $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); $composerPluginApi->setDescription('The Composer Plugin API'); $this->addPackage($composerPluginApi); $prettyVersion = Composer::RUNTIME_API_VERSION; $version = $this->versionParser->normalize($prettyVersion); $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion); $composerRuntimeApi->setDescription('The Composer Runtime API'); $this->addPackage($composerRuntimeApi); try { $prettyVersion = $this->runtime->getConstant('PHP_VERSION'); $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION')); $version = $this->versionParser->normalize($prettyVersion); } $php = new CompletePackage('php', $version, $prettyVersion); $php->setDescription('The PHP interpreter'); $this->addPackage($php); if ($this->runtime->getConstant('PHP_DEBUG')) { $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); $this->addPackage($phpdebug); } if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) { $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); $this->addPackage($phpzts); } if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) { $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); $php64->setDescription('The PHP interpreter, 64bit'); $this->addPackage($php64); } // The AF_INET6 constant is only defined if ext-sockets is available but // IPv6 support might still be available. if ($this->runtime->hasConstant('AF_INET6') || Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) !== \false) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); } $loadedExtensions = $this->runtime->getExtensions(); // Extensions scanning foreach ($loadedExtensions as $name) { if (\in_array($name, ['standard', 'Core'])) { continue; } $this->addExtension($name, $this->runtime->getExtensionVersion($name)); } // Check for Xdebug in a restarted process if (!\in_array('xdebug', $loadedExtensions, \true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) { $this->addExtension('xdebug', $prettyVersion); } // Another quick loop, just for possible libraries // Doing it this way to know that functions or constants exist before // relying on them. foreach ($loadedExtensions as $name) { switch ($name) { case 'amqp': $info = $this->runtime->getExtensionInfo($name); // librabbitmq version => 0.9.0 if (Preg::isMatch('/^librabbitmq version => (?.+)$/im', $info, $librabbitmqMatches)) { $this->addLibrary($name . '-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); } // AMQP protocol version => 0-9-1 if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?.+)$/im', $info, $protocolMatches)) { $this->addLibrary($name . '-protocol', \str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); } break; case 'bz2': $info = $this->runtime->getExtensionInfo($name); // BZip2 Version => 1.0.6, 6-Sept-2010 if (Preg::isMatch('/^BZip2 Version => (?.*),/im', $info, $matches)) { $this->addLibrary($name, $matches['version']); } break; case 'curl': $curlVersion = $this->runtime->invoke('curl_version'); $this->addLibrary($name, $curlVersion['version']); $info = $this->runtime->getExtensionInfo($name); // SSL Version => OpenSSL/1.0.1t if (Preg::isMatchStrictGroups('{^SSL Version => (?[^/]+)/(?.+)$}im', $info, $sslMatches)) { $library = \strtolower($sslMatches['library']); if ($library === 'openssl') { $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); $this->addLibrary($name . '-openssl' . ($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version (' . $parsedVersion . ')', [], $isFips ? ['curl-openssl'] : []); } else { if ($library === '(securetransport) openssl') { $shortlib = 'securetransport'; } else { $shortlib = $library; } $this->addLibrary($name . '-' . $shortlib, $sslMatches['version'], 'curl ' . $library . ' version (' . $sslMatches['version'] . ')', ['curl-openssl']); } } // libSSH Version => libssh2/1.4.3 if (Preg::isMatchStrictGroups('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}im', $info, $sshMatches)) { $this->addLibrary($name . '-' . \strtolower($sshMatches['library']), $sshMatches['version'], 'curl ' . $sshMatches['library'] . ' version'); } // ZLib Version => 1.2.8 if (Preg::isMatchStrictGroups('{^ZLib Version => (?.+)$}im', $info, $zlibMatches)) { $this->addLibrary($name . '-zlib', $zlibMatches['version'], 'curl zlib version'); } break; case 'date': $info = $this->runtime->getExtensionInfo($name); // timelib version => 2018.03 if (Preg::isMatchStrictGroups('/^timelib version => (?.+)$/im', $info, $timelibMatches)) { $this->addLibrary($name . '-timelib', $timelibMatches['version'], 'date timelib version'); } // Timezone Database => internal if (Preg::isMatchStrictGroups('/^Timezone Database => (?internal|external)$/im', $info, $zoneinfoSourceMatches)) { $external = $zoneinfoSourceMatches['source'] === 'external'; if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?.+?)(?:\\.system)?$/im', $info, $zoneinfoMatches)) { // If the timezonedb is provided by ext/timezonedb, register that version as a replacement if ($external && \in_array('timezonedb', $loadedExtensions, \true)) { $this->addLibrary('timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name . '-zoneinfo']); } else { $this->addLibrary($name . '-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); } } } break; case 'fileinfo': $info = $this->runtime->getExtensionInfo($name); // libmagic => 537 if (Preg::isMatch('/^libmagic => (?.+)$/im', $info, $magicMatches)) { $this->addLibrary($name . '-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); } break; case 'gd': $this->addLibrary($name, $this->runtime->getConstant('GD_VERSION')); $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^libJPEG Version => (?.+?)(?: compatible)?$/im', $info, $libjpegMatches)) { $this->addLibrary($name . '-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); } if (Preg::isMatchStrictGroups('/^libPNG Version => (?.+)$/im', $info, $libpngMatches)) { $this->addLibrary($name . '-libpng', $libpngMatches['version'], 'libpng version for gd'); } if (Preg::isMatchStrictGroups('/^FreeType Version => (?.+)$/im', $info, $freetypeMatches)) { $this->addLibrary($name . '-freetype', $freetypeMatches['version'], 'freetype version for gd'); } if (Preg::isMatchStrictGroups('/^libXpm Version => (?\\d+)$/im', $info, $libxpmMatches)) { $this->addLibrary($name . '-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd'); } break; case 'gmp': $this->addLibrary($name, $this->runtime->getConstant('GMP_VERSION')); break; case 'iconv': $this->addLibrary($name, $this->runtime->getConstant('ICONV_VERSION')); break; case 'intl': $info = $this->runtime->getExtensionInfo($name); $description = 'The ICU unicode and globalization support library'; // Truthy check is for testing only so we can make the condition fail if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { $this->addLibrary('icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); } elseif (Preg::isMatch('/^ICU version => (?.+)$/im', $info, $matches)) { $this->addLibrary('icu', $matches['version'], $description); } // ICU TZData version => 2019c if (Preg::isMatchStrictGroups('/^ICU TZData version => (?.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) { $this->addLibrary('icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu'); } // Add a separate version for the CLDR library version if ($this->runtime->hasClass('ResourceBundle')) { $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', \false]); if ($resourceBundle !== null) { $this->addLibrary('icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version'); } } if ($this->runtime->hasClass('IntlChar')) { $this->addLibrary('icu-unicode', \implode('.', \array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version'); } break; case 'imagick': $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org Preg::match('/^ImageMagick (?[\\d.]+)(?:-(?\\d+))?/', $imageMagickVersion['versionString'], $matches); $version = $matches['version']; if (isset($matches['patch'])) { $version .= '.' . $matches['patch']; } $this->addLibrary($name . '-imagemagick', $version, null, ['imagick']); break; case 'ldap': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^Vendor Version => (?\\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?.+)$/im', $info, $vendorMatches)) { $this->addLibrary($name . '-' . \strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'] . ' version of ldap'); } break; case 'libxml': // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml $libxmlProvides = \array_map(static function ($extension) : string { return $extension . '-libxml'; }, \array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'])); $this->addLibrary($name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides); break; case 'mbstring': $info = $this->runtime->getExtensionInfo($name); // libmbfl version => 1.3.2 if (Preg::isMatch('/^libmbfl version => (?.+)$/im', $info, $libmbflMatches)) { $this->addLibrary($name . '-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); } if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { $this->addLibrary($name . '-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); // Multibyte regex (oniguruma) version => 5.9.5 // oniguruma version => 6.9.0 } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \\(oniguruma\\)) version => (?.+)$/im', $info, $onigurumaMatches)) { $this->addLibrary($name . '-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); } break; case 'memcached': $info = $this->runtime->getExtensionInfo($name); // libmemcached version => 1.0.18 if (Preg::isMatch('/^libmemcached version => (?.+)$/im', $info, $matches)) { $this->addLibrary($name . '-libmemcached', $matches['version'], 'libmemcached version'); } break; case 'openssl': // OpenSSL 1.1.1g 21 Apr 2020 if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\\s*(?\\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); $this->addLibrary($name . ($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []); } break; case 'pcre': $this->addLibrary($name, Preg::replace('{^(\\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); $info = $this->runtime->getExtensionInfo($name); // PCRE Unicode Version => 12.1.0 if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?.+)$/im', $info, $pcreUnicodeMatches)) { $this->addLibrary($name . '-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); } break; case 'mysqlnd': case 'pdo_mysql': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { $this->addLibrary($name . '-mysqlnd', $matches['version'], 'mysqlnd library version for ' . $name); } break; case 'mongodb': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?.+)$/im', $info, $libmongocMatches)) { $this->addLibrary($name . '-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); } if (Preg::isMatchStrictGroups('/^libbson bundled version => (?.+)$/im', $info, $libbsonMatches)) { $this->addLibrary($name . '-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); } break; case 'pgsql': if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) { $this->addLibrary('pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql'); break; } // intentional fall-through to next case... case 'pdo_pgsql': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^PostgreSQL\\(libpq\\) Version => (?.*)$/im', $info, $matches)) { $this->addLibrary($name . '-libpq', $matches['version'], 'libpq for ' . $name); } break; case 'pq': $info = $this->runtime->getExtensionInfo($name); // Used Library => Compiled => Linked // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2 if (Preg::isMatch('/^libpq => (?.+) => (?.+)$/im', $info, $matches)) { $this->addLibrary($name . '-libpq', $matches['linked'], 'libpq for ' . $name); } break; case 'rdkafka': if ($this->runtime->hasConstant('RD_KAFKA_VERSION')) { /** * Interpreted as hex \c MM.mm.rr.xx: * - MM = Major * - mm = minor * - rr = revision * - xx = pre-release id (0xff is the final release) * * pre-release ID in practice is always 0xff even for RCs etc, so we ignore it */ $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION'); $this->addLibrary($name . '-librdkafka', \sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0xff000000) >> 24, ($libRdKafkaVersionInt & 0xff0000) >> 16, ($libRdKafkaVersionInt & 0xff00) >> 8), 'librdkafka for ' . $name); } break; case 'libsodium': case 'sodium': if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) { $this->addLibrary('libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); } break; case 'sqlite3': case 'pdo_sqlite': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^SQLite Library => (?.+)$/im', $info, $matches)) { $this->addLibrary($name . '-sqlite', $matches['version']); } break; case 'ssh2': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libssh2 version => (?.+)$/im', $info, $matches)) { $this->addLibrary($name . '-libssh2', $matches['version']); } break; case 'xsl': $this->addLibrary('libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']); $info = $this->runtime->getExtensionInfo('xsl'); if (Preg::isMatch('/^libxslt compiled against libxml Version => (?.+)$/im', $info, $matches)) { $this->addLibrary('libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); } break; case 'yaml': $info = $this->runtime->getExtensionInfo('yaml'); if (Preg::isMatch('/^LibYAML Version => (?.+)$/im', $info, $matches)) { $this->addLibrary($name . '-libyaml', $matches['version'], 'libyaml version of yaml'); } break; case 'zip': if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { $this->addLibrary($name . '-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']); } break; case 'zlib': if ($this->runtime->hasConstant('ZLIB_VERSION')) { $this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION')); // Linked Version => 1.2.8 } elseif (Preg::isMatch('/^Linked Version => (?.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) { $this->addLibrary($name, $matches['version']); } break; default: break; } } $hhvmVersion = $this->hhvmDetector->getVersion(); if ($hhvmVersion) { try { $prettyVersion = $hhvmVersion; $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $hhvmVersion); $version = $this->versionParser->normalize($prettyVersion); } $hhvm = new CompletePackage('hhvm', $version, $prettyVersion); $hhvm->setDescription('The HHVM Runtime (64bit)'); $this->addPackage($hhvm); } } /** * @inheritDoc */ public function addPackage(PackageInterface $package) : void { if (!$package instanceof CompletePackage) { throw new \UnexpectedValueException('Expected CompletePackage but got ' . \get_class($package)); } // Skip if overridden if (isset($this->overrides[$package->getName()])) { if ($this->overrides[$package->getName()]['version'] === \false) { $this->addDisabledPackage($package); return; } $overrider = $this->findPackage($package->getName(), '*'); if ($package->getVersion() === $overrider->getVersion()) { $actualText = 'same as actual'; } else { $actualText = 'actual: ' . $package->getPrettyVersion(); } if ($overrider instanceof CompletePackageInterface) { $overrider->setDescription($overrider->getDescription() . ', ' . $actualText); } return; } // Skip if PHP is overridden and we are adding a php-* package if (isset($this->overrides['php']) && 0 === \strpos($package->getName(), 'php-')) { $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName()); if ($package->getVersion() === $overrider->getVersion()) { $actualText = 'same as actual'; } else { $actualText = 'actual: ' . $package->getPrettyVersion(); } $overrider->setDescription($overrider->getDescription() . ', ' . $actualText); return; } parent::addPackage($package); } /** * @param array{version: string, name: string} $override */ private function addOverriddenPackage(array $override, ?string $name = null) : CompletePackage { $version = $this->versionParser->normalize($override['version']); $package = new CompletePackage($name ?: $override['name'], $version, $override['version']); $package->setDescription('Package overridden via config.platform'); $package->setExtra(['config.platform' => \true]); parent::addPackage($package); if ($package->getName() === 'php') { self::$lastSeenPlatformPhp = \implode('.', \array_slice(\explode('.', $package->getVersion()), 0, 3)); } return $package; } private function addDisabledPackage(CompletePackage $package) : void { $package->setDescription($package->getDescription() . '. Package disabled via config.platform'); $package->setExtra(['config.platform' => \true]); $this->disabledPackages[$package->getName()] = $package; } /** * Parses the version and adds a new package to the repository */ private function addExtension(string $name, string $prettyVersion) : void { $extraDescription = null; try { $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $extraDescription = ' (actual version: ' . $prettyVersion . ')'; if (Preg::isMatchStrictGroups('{^(\\d+\\.\\d+\\.\\d+(?:\\.\\d+)?)}', $prettyVersion, $match)) { $prettyVersion = $match[1]; } else { $prettyVersion = '0'; } $version = $this->versionParser->normalize($prettyVersion); } $packageName = $this->buildPackageName($name); $ext = new CompletePackage($packageName, $version, $prettyVersion); $ext->setDescription('The ' . $name . ' PHP extension' . $extraDescription); if ($name === 'uuid') { $ext->setReplaces(['lib-uuid' => new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version), Link::TYPE_REPLACE, $ext->getPrettyVersion())]); } $this->addPackage($ext); } private function buildPackageName(string $name) : string { return 'ext-' . \str_replace(' ', '-', \strtolower($name)); } /** * @param string[] $replaces * @param string[] $provides */ private function addLibrary(string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []) : void { if (null === $prettyVersion) { return; } try { $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { return; } if ($description === null) { $description = 'The ' . $name . ' library'; } $lib = new CompletePackage('lib-' . $name, $version, $prettyVersion); $lib->setDescription($description); $replaceLinks = []; foreach ($replaces as $replace) { $replace = \strtolower($replace); $replaceLinks[$replace] = new Link('lib-' . $name, 'lib-' . $replace, new Constraint('=', $version), Link::TYPE_REPLACE, $lib->getPrettyVersion()); } $provideLinks = []; foreach ($provides as $provide) { $provide = \strtolower($provide); $provideLinks[$provide] = new Link('lib-' . $name, 'lib-' . $provide, new Constraint('=', $version), Link::TYPE_PROVIDE, $lib->getPrettyVersion()); } $lib->setReplaces($replaceLinks); $lib->setProvides($provideLinks); $this->addPackage($lib); } /** * Check if a package name is a platform package. */ public static function isPlatformPackage(string $name) : bool { static $cache = []; if (isset($cache[$name])) { return $cache[$name]; } return $cache[$name] = Preg::isMatch(\Composer\Repository\PlatformRepository::PLATFORM_PACKAGE_REGEX, $name); } /** * Returns the last seen config.platform.php version if defined * * This is a best effort attempt for internal purposes, retrieve the real * packages from a PlatformRepository instance if you need a version guaranteed to * be correct. * * @internal */ public static function getPlatformPhpVersion() : ?string { return self::$lastSeenPlatformPhp; } public function search(string $query, int $mode = 0, ?string $type = null) : array { // suppress vendor search as there are no vendors to match in platform packages if ($mode === self::SEARCH_VENDOR) { return []; } return parent::search($query, $mode, $type); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Pcre\Preg; use Composer\Util\HttpDownloader; use Composer\Util\ProcessExecutor; use Composer\Json\JsonFile; /** * @author Jordi Boggiano */ class RepositoryFactory { /** * @return array|mixed */ public static function configFromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = \false) { if (0 === \strpos($repository, 'http')) { $repoConfig = ['type' => 'composer', 'url' => $repository]; } elseif ("json" === \pathinfo($repository, \PATHINFO_EXTENSION)) { $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config)); $data = $json->read(); if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) { $repoConfig = ['type' => 'composer', 'url' => 'file://' . \strtr(\realpath($repository), '\\', '/')]; } elseif ($allowFilesystem) { $repoConfig = ['type' => 'filesystem', 'json' => $json]; } else { throw new \InvalidArgumentException("Invalid repository URL ({$repository}) given. This file does not contain a valid composer repository."); } } elseif (\strpos($repository, '{') === 0) { // assume it is a json object that makes a repo config $repoConfig = JsonFile::parseJson($repository); } else { throw new \InvalidArgumentException("Invalid repository url ({$repository}) given. Has to be a .json file, an http url or a JSON object."); } return $repoConfig; } public static function fromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = \false, ?\Composer\Repository\RepositoryManager $rm = null) : \Composer\Repository\RepositoryInterface { $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem); return static::createRepo($io, $config, $repoConfig, $rm); } /** * @param array $repoConfig */ public static function createRepo(IOInterface $io, Config $config, array $repoConfig, ?\Composer\Repository\RepositoryManager $rm = null) : \Composer\Repository\RepositoryInterface { if (!$rm) { @\trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', \E_USER_DEPRECATED); $rm = static::manager($io, $config); } $repos = self::createRepos($rm, [$repoConfig]); return \reset($repos); } /** * @return RepositoryInterface[] */ public static function defaultRepos(?IOInterface $io = null, ?Config $config = null, ?\Composer\Repository\RepositoryManager $rm = null) : array { if (null === $rm) { @\trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', \E_USER_DEPRECATED); } if (null === $config) { $config = Factory::createConfig($io); } if (null !== $io) { $io->loadConfiguration($config); } if (null === $rm) { if (null === $io) { throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); } $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); } return self::createRepos($rm, $config->getRepositories()); } /** * @param EventDispatcher $eventDispatcher * @param HttpDownloader $httpDownloader */ public static function manager(IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null) : \Composer\Repository\RepositoryManager { if ($httpDownloader === null) { $httpDownloader = Factory::createHttpDownloader($io, $config); } if ($process === null) { $process = new ProcessExecutor($io); $process->enableAsync(); } $rm = new \Composer\Repository\RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process); $rm->setRepositoryClass('composer', 'Composer\\Repository\\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\\Repository\\PackageRepository'); $rm->setRepositoryClass('pear', 'Composer\\Repository\\PearRepository'); $rm->setRepositoryClass('git', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('bitbucket', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('git-bitbucket', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('github', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('gitlab', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('svn', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('fossil', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('perforce', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('hg', 'Composer\\Repository\\VcsRepository'); $rm->setRepositoryClass('artifact', 'Composer\\Repository\\ArtifactRepository'); $rm->setRepositoryClass('path', 'Composer\\Repository\\PathRepository'); return $rm; } /** * @return RepositoryInterface[] */ public static function defaultReposWithDefaultManager(IOInterface $io) : array { $manager = \Composer\Repository\RepositoryFactory::manager($io, $config = Factory::createConfig($io)); $io->loadConfiguration($config); return \Composer\Repository\RepositoryFactory::defaultRepos($io, $config, $manager); } /** * @param array $repoConfigs * * @return RepositoryInterface[] */ private static function createRepos(\Composer\Repository\RepositoryManager $rm, array $repoConfigs) : array { $repos = []; foreach ($repoConfigs as $index => $repo) { if (\is_string($repo)) { throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given'); } if (!\is_array($repo)) { throw new \UnexpectedValueException('Repository "' . $index . '" (' . \json_encode($repo) . ') should be an array, ' . \gettype($repo) . ' given'); } if (!isset($repo['type'])) { throw new \UnexpectedValueException('Repository "' . $index . '" (' . \json_encode($repo) . ') must have a type defined'); } $name = self::generateRepositoryName($index, $repo, $repos); if ($repo['type'] === 'filesystem') { $repos[$name] = new \Composer\Repository\FilesystemRepository($repo['json']); } else { $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index); } } return $repos; } /** * @param int|string $index * @param array{url?: string} $repo * @param array $existingRepos */ public static function generateRepositoryName($index, array $repo, array $existingRepos) : string { $name = \is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i', '', $repo['url']) : (string) $index; while (isset($existingRepos[$name])) { $name .= '2'; } return $name; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Installable repository interface. * * Just used to tag installed repositories so the base classes can act differently on Alias packages * * @author Jordi Boggiano */ interface InstalledRepositoryInterface extends \Composer\Repository\WritableRepositoryInterface { /** * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown */ public function getDevMode(); /** * @return bool true if packages were never installed in this repository */ public function isFresh(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; use Composer\Installer\InstallationManager; /** * Writable repository interface. * * @author Konstantin Kudryashov */ interface WritableRepositoryInterface extends \Composer\Repository\RepositoryInterface { /** * Writes repository (f.e. to the disc). * * @param bool $devMode Whether dev requirements were included or not in this installation * @return void */ public function write(bool $devMode, InstallationManager $installationManager); /** * Adds package to the repository. * * @param PackageInterface $package package instance * @return void */ public function addPackage(PackageInterface $package); /** * Removes package from the repository. * * @param PackageInterface $package package instance * @return void */ public function removePackage(PackageInterface $package); /** * Get unique packages (at most one package of each name), with aliases resolved and removed. * * @return PackageInterface[] */ public function getCanonicalPackages(); /** * Forces a reload of all packages. * * @return void */ public function reload(); /** * @param string[] $devPackageNames * @return void */ public function setDevPackageNames(array $devPackageNames); /** * @return string[] Names of dependencies installed through require-dev */ public function getDevPackageNames(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; /** * @author Jordi Boggiano * * @see RepositorySet for ways to work with sets of repos */ class RepositoryUtils { /** * Find all of $packages which are required by $requirer, either directly or transitively * * Require-dev is ignored * * @template T of PackageInterface * @param array $packages * @param list $bucket Do not pass this in, only used to avoid recursion with circular deps * @return list */ public static function filterRequiredPackages(array $packages, PackageInterface $requirer, array $bucket = []) : array { $requires = $requirer->getRequires(); foreach ($packages as $candidate) { foreach ($candidate->getNames() as $name) { if (isset($requires[$name])) { if (!\in_array($candidate, $bucket, \true)) { $bucket[] = $candidate; $bucket = self::filterRequiredPackages($packages, $candidate, $bucket); } break; } } } return $bucket; } /** * Unwraps CompositeRepository, InstalledRepository and optionally FilterRepository to get a flat array of pure repository instances * * @return RepositoryInterface[] */ public static function flattenRepositories(\Composer\Repository\RepositoryInterface $repo, bool $unwrapFilterRepos = \true) : array { // unwrap filter repos if ($unwrapFilterRepos && $repo instanceof \Composer\Repository\FilterRepository) { $repo = $repo->getRepository(); } if (!$repo instanceof \Composer\Repository\CompositeRepository) { return [$repo]; } $repos = []; foreach ($repo->getRepositories() as $r) { foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) { $repos[] = $r2; } } return $repos; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Thrown when a security problem, like a broken or missing signature * * @author Eric Daspet */ class RepositorySecurityException extends \Exception { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; /** * Composite repository. * * @author Beau Simensen */ class CompositeRepository implements \Composer\Repository\RepositoryInterface { /** * List of repositories * @var RepositoryInterface[] */ private $repositories; /** * Constructor * @param RepositoryInterface[] $repositories */ public function __construct(array $repositories) { $this->repositories = []; foreach ($repositories as $repo) { $this->addRepository($repo); } } public function getRepoName() : string { return 'composite repo (' . \implode(', ', \array_map(static function ($repo) : string { return $repo->getRepoName(); }, $this->repositories)) . ')'; } /** * Returns all the wrapped repositories * * @return RepositoryInterface[] */ public function getRepositories() : array { return $this->repositories; } /** * @inheritDoc */ public function hasPackage(PackageInterface $package) : bool { foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ if ($repository->hasPackage($package)) { return \true; } } return \false; } /** * @inheritDoc */ public function findPackage($name, $constraint) : ?BasePackage { foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $package = $repository->findPackage($name, $constraint); if (null !== $package) { return $package; } } return null; } /** * @inheritDoc */ public function findPackages($name, $constraint = null) : array { $packages = []; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $packages[] = $repository->findPackages($name, $constraint); } return $packages ? \array_merge(...$packages) : []; } /** * @inheritDoc */ public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) : array { $packages = []; $namesFound = []; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $result = $repository->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); $packages[] = $result['packages']; $namesFound[] = $result['namesFound']; } return ['packages' => $packages ? \array_merge(...$packages) : [], 'namesFound' => $namesFound ? \array_unique(\array_merge(...$namesFound)) : []]; } /** * @inheritDoc */ public function search(string $query, int $mode = 0, ?string $type = null) : array { $matches = []; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $matches[] = $repository->search($query, $mode, $type); } return \count($matches) > 0 ? \array_merge(...$matches) : []; } /** * @inheritDoc */ public function getPackages() : array { $packages = []; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $packages[] = $repository->getPackages(); } return $packages ? \array_merge(...$packages) : []; } /** * @inheritDoc */ public function getProviders($packageName) : array { $results = []; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $results[] = $repository->getProviders($packageName); } return $results ? \array_merge(...$results) : []; } public function removePackage(PackageInterface $package) : void { foreach ($this->repositories as $repository) { if ($repository instanceof \Composer\Repository\WritableRepositoryInterface) { $repository->removePackage($package); } } } /** * @inheritDoc */ public function count() : int { $total = 0; foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ $total += $repository->count(); } return $total; } /** * Add a repository. */ public function addRepository(\Composer\Repository\RepositoryInterface $repository) : void { if ($repository instanceof self) { foreach ($repository->getRepositories() as $repo) { $this->addRepository($repo); } } else { $this->repositories[] = $repository; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; use Composer\Package\Version\StabilityFilter; use Composer\Pcre\Preg; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; /** * A repository implementation that simply stores packages in an array * * @author Nils Adermann */ class ArrayRepository implements \Composer\Repository\RepositoryInterface { /** @var ?array */ protected $packages = null; /** * @var ?array indexed by package unique name and used to cache hasPackage calls */ protected $packageMap = null; /** * @param array $packages */ public function __construct(array $packages = []) { foreach ($packages as $package) { $this->addPackage($package); } } public function getRepoName() { return 'array repo (defining ' . $this->count() . ' package' . ($this->count() > 1 ? 's' : '') . ')'; } /** * @inheritDoc */ public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) { $packages = $this->getPackages(); $result = []; $namesFound = []; foreach ($packages as $package) { if (\array_key_exists($package->getName(), $packageNameMap)) { if ((!$packageNameMap[$package->getName()] || $packageNameMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability()) && !isset($alreadyLoaded[$package->getName()][$package->getVersion()])) { // add selected packages which match stability requirements $result[\spl_object_hash($package)] = $package; // add the aliased package for packages where the alias matches if ($package instanceof AliasPackage && !isset($result[\spl_object_hash($package->getAliasOf())])) { $result[\spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } $namesFound[$package->getName()] = \true; } } // add aliases of packages that were selected, even if the aliases did not match foreach ($packages as $package) { if ($package instanceof AliasPackage) { if (isset($result[\spl_object_hash($package->getAliasOf())])) { $result[\spl_object_hash($package)] = $package; } } } return ['namesFound' => \array_keys($namesFound), 'packages' => $result]; } /** * @inheritDoc */ public function findPackage(string $name, $constraint) { $name = \strtolower($name); if (!$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { return $package; } } } return null; } /** * @inheritDoc */ public function findPackages(string $name, $constraint = null) { // normalize name $name = \strtolower($name); $packages = []; if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { if (null === $constraint || $constraint->matches(new Constraint('==', $package->getVersion()))) { $packages[] = $package; } } } return $packages; } /** * @inheritDoc */ public function search(string $query, int $mode = 0, ?string $type = null) { if ($mode === self::SEARCH_FULLTEXT) { $regex = '{(?:' . \implode('|', Preg::split('{\\s+}', \preg_quote($query))) . ')}i'; } else { // vendor/name searches expect the caller to have preg_quoted the query $regex = '{(?:' . \implode('|', Preg::split('{\\s+}', $query)) . ')}i'; } $matches = []; foreach ($this->getPackages() as $package) { $name = $package->getName(); if ($mode === self::SEARCH_VENDOR) { [$name] = \explode('/', $name); } if (isset($matches[$name])) { continue; } if (null !== $type && $package->getType() !== $type) { continue; } if (Preg::isMatch($regex, $name) || $mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && Preg::isMatch($regex, \implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription())) { if ($mode === self::SEARCH_VENDOR) { $matches[$name] = ['name' => $name, 'description' => null]; } else { $matches[$name] = ['name' => $package->getPrettyName(), 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null]; if ($package instanceof CompletePackageInterface && $package->isAbandoned()) { $matches[$name]['abandoned'] = $package->getReplacementPackage() ?: \true; } } } } return \array_values($matches); } /** * @inheritDoc */ public function hasPackage(PackageInterface $package) { if ($this->packageMap === null) { $this->packageMap = []; foreach ($this->getPackages() as $repoPackage) { $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage; } } return isset($this->packageMap[$package->getUniqueName()]); } /** * Adds a new package to the repository * * @return void */ public function addPackage(PackageInterface $package) { if (!$package instanceof BasePackage) { throw new \InvalidArgumentException('Only subclasses of BasePackage are supported'); } if (null === $this->packages) { $this->initialize(); } $package->setRepository($this); $this->packages[] = $package; if ($package instanceof AliasPackage) { $aliasedPackage = $package->getAliasOf(); if (null === $aliasedPackage->getRepository()) { $this->addPackage($aliasedPackage); } } // invalidate package map cache $this->packageMap = null; } /** * @inheritDoc */ public function getProviders(string $packageName) { $result = []; foreach ($this->getPackages() as $candidate) { if (isset($result[$candidate->getName()])) { continue; } foreach ($candidate->getProvides() as $link) { if ($packageName === $link->getTarget()) { $result[$candidate->getName()] = ['name' => $candidate->getName(), 'description' => $candidate instanceof CompletePackageInterface ? $candidate->getDescription() : null, 'type' => $candidate->getType()]; continue 2; } } } return $result; } /** * @return AliasPackage|CompleteAliasPackage */ protected function createAliasPackage(BasePackage $package, string $alias, string $prettyAlias) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if ($package instanceof CompletePackage) { return new CompleteAliasPackage($package, $alias, $prettyAlias); } return new AliasPackage($package, $alias, $prettyAlias); } /** * Removes package from repository. * * @param PackageInterface $package package instance * * @return void */ public function removePackage(PackageInterface $package) { $packageId = $package->getUniqueName(); foreach ($this->getPackages() as $key => $repoPackage) { if ($packageId === $repoPackage->getUniqueName()) { \array_splice($this->packages, $key, 1); // invalidate package map cache $this->packageMap = null; return; } } } /** * @inheritDoc */ public function getPackages() { if (null === $this->packages) { $this->initialize(); } if (null === $this->packages) { throw new \LogicException('initialize failed to initialize the packages array'); } return $this->packages; } /** * Returns the number of packages in this repository * * @return 0|positive-int Number of packages */ public function count() : int { if (null === $this->packages) { $this->initialize(); } return \count($this->packages); } /** * Initializes the packages array. Mostly meant as an extension point. * * @return void */ protected function initialize() { $this->packages = []; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Installer\InstallationManager; /** * Writable array repository. * * @author Jordi Boggiano */ class WritableArrayRepository extends \Composer\Repository\ArrayRepository implements \Composer\Repository\WritableRepositoryInterface { use \Composer\Repository\CanonicalPackagesTrait; /** * @var string[] */ protected $devPackageNames = []; /** @var bool|null */ private $devMode = null; /** * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown */ public function getDevMode() { return $this->devMode; } /** * @inheritDoc */ public function setDevPackageNames(array $devPackageNames) { $this->devPackageNames = $devPackageNames; } /** * @inheritDoc */ public function getDevPackageNames() { return $this->devPackageNames; } /** * @inheritDoc */ public function write(bool $devMode, InstallationManager $installationManager) { $this->devMode = $devMode; } /** * @inheritDoc */ public function reload() { $this->devMode = null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; use Composer\Repository\Vcs\VcsDriverInterface; use Composer\Package\Version\VersionParser; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; use Composer\Package\Loader\LoaderInterface; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\Util\Url; use Composer\Semver\Constraint\Constraint; use Composer\IO\IOInterface; use Composer\Config; /** * @author Jordi Boggiano */ class VcsRepository extends \Composer\Repository\ArrayRepository implements \Composer\Repository\ConfigurableRepositoryInterface { /** @var string */ protected $url; /** @var ?string */ protected $packageName; /** @var bool */ protected $isVerbose; /** @var bool */ protected $isVeryVerbose; /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var VersionParser */ protected $versionParser; /** @var string */ protected $type; /** @var ?LoaderInterface */ protected $loader; /** @var array */ protected $repoConfig; /** @var HttpDownloader */ protected $httpDownloader; /** @var ProcessExecutor */ protected $processExecutor; /** @var bool */ protected $branchErrorOccurred = \false; /** @var array> */ private $drivers; /** @var ?VcsDriverInterface */ private $driver; /** @var ?VersionCacheInterface */ private $versionCache; /** @var string[] */ private $emptyReferences = []; /** @var array<'tags'|'branches', array> */ private $versionTransportExceptions = []; /** * @param array{url: string, type?: string}&array $repoConfig * @param array>|null $drivers */ public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null, ?array $drivers = null, ?\Composer\Repository\VersionCacheInterface $versionCache = null) { parent::__construct(); $this->drivers = $drivers ?: [ 'github' => 'Composer\\Repository\\Vcs\\GitHubDriver', 'gitlab' => 'Composer\\Repository\\Vcs\\GitLabDriver', 'bitbucket' => 'Composer\\Repository\\Vcs\\GitBitbucketDriver', 'git-bitbucket' => 'Composer\\Repository\\Vcs\\GitBitbucketDriver', 'git' => 'Composer\\Repository\\Vcs\\GitDriver', 'hg' => 'Composer\\Repository\\Vcs\\HgDriver', 'perforce' => 'Composer\\Repository\\Vcs\\PerforceDriver', 'fossil' => 'Composer\\Repository\\Vcs\\FossilDriver', // svn must be last because identifying a subversion server for sure is practically impossible 'svn' => 'Composer\\Repository\\Vcs\\SvnDriver', ]; $this->url = $repoConfig['url'] = Platform::expandPath($repoConfig['url']); $this->io = $io; $this->type = $repoConfig['type'] ?? 'vcs'; $this->isVerbose = $io->isVerbose(); $this->isVeryVerbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; $this->versionCache = $versionCache; $this->httpDownloader = $httpDownloader; $this->processExecutor = $process ?? new ProcessExecutor($io); } public function getRepoName() { $driverClass = \get_class($this->getDriver()); $driverType = \array_search($driverClass, $this->drivers); if (!$driverType) { $driverType = $driverClass; } return 'vcs repo (' . $driverType . ' ' . Url::sanitize($this->url) . ')'; } public function getRepoConfig() { return $this->repoConfig; } public function setLoader(LoaderInterface $loader) : void { $this->loader = $loader; } public function getDriver() : ?VcsDriverInterface { if ($this->driver) { return $this->driver; } if (isset($this->drivers[$this->type])) { $class = $this->drivers[$this->type]; $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url)) { $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; } } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url, \true)) { $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; } } return null; } public function hadInvalidBranches() : bool { return $this->branchErrorOccurred; } /** * @return string[] */ public function getEmptyReferences() : array { return $this->emptyReferences; } /** * @return array<'tags'|'branches', array> */ public function getVersionTransportExceptions() : array { return $this->versionTransportExceptions; } protected function initialize() { parent::initialize(); $isVerbose = $this->isVerbose; $isVeryVerbose = $this->isVeryVerbose; $driver = $this->getDriver(); if (!$driver) { throw new \InvalidArgumentException('No driver found to handle VCS repository ' . $this->url); } $this->versionParser = new VersionParser(); if (!$this->loader) { $this->loader = new ArrayLoader($this->versionParser); } $hasRootIdentifierComposerJson = \false; try { $hasRootIdentifierComposerJson = $driver->hasComposerFile($driver->getRootIdentifier()); if ($hasRootIdentifierComposerJson) { $data = $driver->getComposerInformation($driver->getRootIdentifier()); $this->packageName = !empty($data['name']) ? $data['name'] : null; } } catch (\Exception $e) { if ($e instanceof TransportException && $this->shouldRethrowTransportException($e)) { throw $e; } if ($isVeryVerbose) { $this->io->writeError('Skipped parsing ' . $driver->getRootIdentifier() . ', ' . $e->getMessage() . ''); } } foreach ($driver->getTags() as $tag => $identifier) { $tag = (string) $tag; $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; if ($isVeryVerbose) { $this->io->writeError($msg); } elseif ($isVerbose) { $this->io->overwriteError($msg, \false); } // strip the release- prefix from tags if present $tag = \str_replace('release-', '', $tag); $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose); if ($cachedPackage) { $this->addPackage($cachedPackage); continue; } if ($cachedPackage === \false) { $this->emptyReferences[] = $identifier; continue; } if (!($parsedTag = $this->validateTag($tag))) { if ($isVeryVerbose) { $this->io->writeError('Skipped tag ' . $tag . ', invalid tag name'); } continue; } try { $data = $driver->getComposerInformation($identifier); if (null === $data) { if ($isVeryVerbose) { $this->io->writeError('Skipped tag ' . $tag . ', no composer file'); } $this->emptyReferences[] = $identifier; continue; } // manually versioned package if (isset($data['version'])) { $data['version_normalized'] = $this->versionParser->normalize($data['version']); } else { // auto-versioned package, read value from tag $data['version'] = $tag; $data['version_normalized'] = $parsedTag; } // make sure tag packages have no -dev flag $data['version'] = Preg::replace('{[.-]?dev$}i', '', $data['version']); $data['version_normalized'] = Preg::replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); // make sure tag do not contain the default-branch marker unset($data['default-branch']); // broken package, version doesn't match tag if ($data['version_normalized'] !== $parsedTag) { if ($isVeryVerbose) { if (Preg::isMatch('{(^dev-|[.-]?dev$)}i', $parsedTag)) { $this->io->writeError('Skipped tag ' . $tag . ', invalid tag name, tags can not use dev prefixes or suffixes'); } else { $this->io->writeError('Skipped tag ' . $tag . ', tag (' . $parsedTag . ') does not match version (' . $data['version_normalized'] . ') in composer.json'); } } continue; } $tagPackageName = $this->packageName ?: $data['name'] ?? ''; if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { if ($isVeryVerbose) { $this->io->writeError('Skipped tag ' . $tag . ', it conflicts with an another tag (' . $existingPackage->getPrettyVersion() . ') as both resolve to ' . $data['version_normalized'] . ' internally'); } continue; } if ($isVeryVerbose) { $this->io->writeError('Importing tag ' . $tag . ' (' . $data['version_normalized'] . ')'); } $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); } catch (\Exception $e) { if ($e instanceof TransportException) { $this->versionTransportExceptions['tags'][$tag] = $e; if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } if ($this->shouldRethrowTransportException($e)) { throw $e; } } if ($isVeryVerbose) { $this->io->writeError('Skipped tag ' . $tag . ', ' . ($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()) . ''); } continue; } } if (!$isVeryVerbose) { $this->io->overwriteError('', \false); } $branches = $driver->getBranches(); // make sure the root identifier branch gets loaded first if ($hasRootIdentifierComposerJson && isset($branches[$driver->getRootIdentifier()])) { $branches = [$driver->getRootIdentifier() => $branches[$driver->getRootIdentifier()]] + $branches; } foreach ($branches as $branch => $identifier) { $branch = (string) $branch; $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; if ($isVeryVerbose) { $this->io->writeError($msg); } elseif ($isVerbose) { $this->io->overwriteError($msg, \false); } if (!($parsedBranch = $this->validateBranch($branch))) { if ($isVeryVerbose) { $this->io->writeError('Skipped branch ' . $branch . ', invalid name'); } continue; } // make sure branch packages have a dev flag if (\strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) { $version = 'dev-' . $branch; } else { $prefix = \strpos($branch, 'v') === 0 ? 'v' : ''; $version = $prefix . Preg::replace('{(\\.9{7})+}', '.x', $parsedBranch); } $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose, $driver->getRootIdentifier() === $branch); if ($cachedPackage) { $this->addPackage($cachedPackage); continue; } if ($cachedPackage === \false) { $this->emptyReferences[] = $identifier; continue; } try { $data = $driver->getComposerInformation($identifier); if (null === $data) { if ($isVeryVerbose) { $this->io->writeError('Skipped branch ' . $branch . ', no composer file'); } $this->emptyReferences[] = $identifier; continue; } // branches are always auto-versioned, read value from branch name $data['version'] = $version; $data['version_normalized'] = $parsedBranch; unset($data['default-branch']); if ($driver->getRootIdentifier() === $branch) { $data['default-branch'] = \true; } if ($isVeryVerbose) { $this->io->writeError('Importing branch ' . $branch . ' (' . $data['version'] . ')'); } $packageData = $this->preProcess($driver, $data, $identifier); $package = $this->loader->load($packageData); if ($this->loader instanceof ValidatingArrayLoader && \count($this->loader->getWarnings()) > 0) { throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData); } $this->addPackage($package); } catch (TransportException $e) { $this->versionTransportExceptions['branches'][$branch] = $e; if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } if ($this->shouldRethrowTransportException($e)) { throw $e; } if ($isVeryVerbose) { $this->io->writeError('Skipped branch ' . $branch . ', no composer file was found (' . $e->getCode() . ' HTTP status code)'); } continue; } catch (\Exception $e) { if (!$isVeryVerbose) { $this->io->writeError(''); } $this->branchErrorOccurred = \true; $this->io->writeError('Skipped branch ' . $branch . ', ' . $e->getMessage() . ''); $this->io->writeError(''); continue; } } $driver->cleanup(); if (!$isVeryVerbose) { $this->io->overwriteError('', \false); } if (!$this->getPackages()) { throw new \Composer\Repository\InvalidRepositoryException('No valid composer.json was found in any branch or tag of ' . $this->url . ', could not load a package from it.'); } } /** * @param array{name?: string, dist?: array{type: string, url: string, reference: string, shasum: string}, source?: array{type: string, url: string, reference: string}} $data * * @return array{name: string|null, dist: array{type: string, url: string, reference: string, shasum: string}|null, source: array{type: string, url: string, reference: string}} */ protected function preProcess(VcsDriverInterface $driver, array $data, string $identifier) : array { // keep the name of the main identifier for all packages // this ensures that a package can be renamed in one place and that all old tags // will still be installable using that new name without requiring re-tagging $dataPackageName = $data['name'] ?? null; $data['name'] = $this->packageName ?: $dataPackageName; if (!isset($data['dist'])) { $data['dist'] = $driver->getDist($identifier); } if (!isset($data['source'])) { $data['source'] = $driver->getSource($identifier); } return $data; } /** * @return string|false */ private function validateBranch(string $branch) { try { $normalizedBranch = $this->versionParser->normalizeBranch($branch); // validate that the branch name has no weird characters conflicting with constraints $this->versionParser->parseConstraints($normalizedBranch); return $normalizedBranch; } catch (\Exception $e) { } return \false; } /** * @return string|false */ private function validateTag(string $version) { try { return $this->versionParser->normalize($version); } catch (\Exception $e) { } return \false; } /** * @return \Composer\Package\CompletePackage|\Composer\Package\CompleteAliasPackage|null|false null if no cache present, false if the absence of a version was cached */ private function getCachedPackageVersion(string $version, string $identifier, bool $isVerbose, bool $isVeryVerbose, bool $isDefaultBranch = \false) { if (!$this->versionCache) { return null; } $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); if ($cachedPackage === \false) { if ($isVeryVerbose) { $this->io->writeError('Skipped ' . $version . ', no composer file (cached from ref ' . $identifier . ')'); } return \false; } if ($cachedPackage) { $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; if ($isVeryVerbose) { $this->io->writeError($msg); } elseif ($isVerbose) { $this->io->overwriteError($msg, \false); } unset($cachedPackage['default-branch']); if ($isDefaultBranch) { $cachedPackage['default-branch'] = \true; } if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) { if ($isVeryVerbose) { $this->io->writeError('Skipped cached version ' . $version . ', it conflicts with an another tag (' . $existingPackage->getPrettyVersion() . ') as both resolve to ' . $cachedPackage['version_normalized'] . ' internally'); } $cachedPackage = null; } } if ($cachedPackage) { return $this->loader->load($cachedPackage); } return null; } private function shouldRethrowTransportException(TransportException $e) : bool { return \in_array($e->getCode(), [401, 403, 429], \true) || $e->getCode() >= 500; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Advisory\PartialSecurityAdvisory; use Composer\Advisory\SecurityAdvisory; use Composer\Package\BasePackage; use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Package\CompletePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\Version\VersionParser; use Composer\Package\Version\StabilityFilter; use Composer\Json\JsonFile; use Composer\Cache; use Composer\Config; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Plugin\PostFileDownloadEvent; use Composer\Semver\CompilingMatcher; use Composer\Util\HttpDownloader; use Composer\Util\Loop; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Downloader\TransportException; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Util\Http\Response; use Composer\MetadataMinifier\MetadataMinifier; use Composer\Util\Url; use React\Promise\PromiseInterface; /** * @author Jordi Boggiano */ class ComposerRepository extends \Composer\Repository\ArrayRepository implements \Composer\Repository\ConfigurableRepositoryInterface, \Composer\Repository\AdvisoryProviderInterface { /** * @var mixed[] * @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} */ private $repoConfig; /** @var mixed[] */ private $options; /** @var non-empty-string */ private $url; /** @var non-empty-string */ private $baseUrl; /** @var IOInterface */ private $io; /** @var HttpDownloader */ private $httpDownloader; /** @var Loop */ private $loop; /** @var Cache */ protected $cache; /** @var ?non-empty-string */ protected $notifyUrl = null; /** @var ?non-empty-string */ protected $searchUrl = null; /** @var ?non-empty-string a URL containing %package% which can be queried to get providers of a given name */ protected $providersApiUrl = null; /** @var bool */ protected $hasProviders = \false; /** @var ?non-empty-string */ protected $providersUrl = null; /** @var ?non-empty-string */ protected $listUrl = null; /** @var bool Indicates whether a comprehensive list of packages this repository might provide is expressed in the repository root. **/ protected $hasAvailablePackageList = \false; /** @var ?array */ protected $availablePackages = null; /** @var ?array */ protected $availablePackagePatterns = null; /** @var ?non-empty-string */ protected $lazyProvidersUrl = null; /** @var ?array */ protected $providerListing; /** @var ArrayLoader */ protected $loader; /** @var bool */ private $allowSslDowngrade = \false; /** @var ?EventDispatcher */ private $eventDispatcher; /** @var ?array> */ private $sourceMirrors; /** @var ?list */ private $distMirrors; /** @var bool */ private $degradedMode = \false; /** @var mixed[]|true */ private $rootData; /** @var bool */ private $hasPartialPackages = \false; /** @var ?array */ private $partialPackagesByName = null; /** @var bool */ private $displayedWarningAboutNonMatchingPackageIndex = \false; /** @var array{metadata: bool, api-url: string|null}|null */ private $securityAdvisoryConfig = null; /** * @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times * useful for v2 metadata repositories with lazy providers * @phpstan-var array */ private $freshMetadataUrls = []; /** * @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times * useful for v2 metadata repositories with lazy providers * @phpstan-var array */ private $packagesNotFoundCache = []; /** * @var VersionParser */ private $versionParser; /** * @param array $repoConfig * @phpstan-param array{url: non-empty-string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} $repoConfig */ public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null) { parent::__construct(); if (!Preg::isMatch('{^[\\w.]+\\??://}', $repoConfig['url'])) { if (($localFilePath = \realpath($repoConfig['url'])) !== \false) { // it is a local path, add file scheme $repoConfig['url'] = 'file://' . $localFilePath; } else { // otherwise, assume http as the default protocol $repoConfig['url'] = 'http://' . $repoConfig['url']; } } $repoConfig['url'] = \rtrim($repoConfig['url'], '/'); if ($repoConfig['url'] === '') { throw new \InvalidArgumentException('The repository url must not be an empty string'); } if (\str_starts_with($repoConfig['url'], 'https?')) { $repoConfig['url'] = (\extension_loaded('openssl') ? 'https' : 'http') . \substr($repoConfig['url'], 6); } $urlBits = \parse_url(\strtr($repoConfig['url'], '\\', '/')); if ($urlBits === \false || empty($urlBits['scheme'])) { throw new \UnexpectedValueException('Invalid url given for Composer repository: ' . $repoConfig['url']); } if (!isset($repoConfig['options'])) { $repoConfig['options'] = []; } if (isset($repoConfig['allow_ssl_downgrade']) && \true === $repoConfig['allow_ssl_downgrade']) { $this->allowSslDowngrade = \true; } $this->options = $repoConfig['options']; $this->url = $repoConfig['url']; // force url for packagist.org to repo.packagist.org if (Preg::isMatch('{^(?Phttps?)://packagist\\.org/?$}i', $this->url, $match)) { $this->url = $match['proto'] . '://repo.packagist.org'; } $baseUrl = \rtrim(Preg::replace('{(?:/[^/\\\\]+\\.json)?(?:[?#].*)?$}', '', $this->url), '/'); \assert($baseUrl !== ''); $this->baseUrl = $baseUrl; $this->io = $io; $this->cache = new Cache($io, $config->get('cache-repo-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)), 'a-z0-9.$~_'); $this->cache->setReadOnly($config->get('cache-read-only')); $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->repoConfig = $repoConfig; $this->loop = new Loop($this->httpDownloader); } public function getRepoName() { return 'composer repo (' . Url::sanitize($this->url) . ')'; } public function getRepoConfig() { return $this->repoConfig; } /** * @inheritDoc */ public function findPackage(string $name, $constraint) { // this call initializes loadRootServerFile which is needed for the rest below to work $hasProviders = $this->hasProviders(); $name = \strtolower($name); if (!$constraint instanceof ConstraintInterface) { $constraint = $this->versionParser->parseConstraints($constraint); } if ($this->lazyProvidersUrl) { if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { return $this->filterPackages($this->whatProvides($name), $constraint, \true); } if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { return null; } $packages = $this->loadAsyncPackages([$name => $constraint]); if (\count($packages['packages']) > 0) { return \reset($packages['packages']); } return null; } if ($hasProviders) { foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { return $this->filterPackages($this->whatProvides($providerName), $constraint, \true); } } return null; } return parent::findPackage($name, $constraint); } /** * @inheritDoc */ public function findPackages(string $name, $constraint = null) { // this call initializes loadRootServerFile which is needed for the rest below to work $hasProviders = $this->hasProviders(); $name = \strtolower($name); if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $constraint = $this->versionParser->parseConstraints($constraint); } if ($this->lazyProvidersUrl) { if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { return $this->filterPackages($this->whatProvides($name), $constraint); } if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { return []; } $result = $this->loadAsyncPackages([$name => $constraint]); return $result['packages']; } if ($hasProviders) { foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { return $this->filterPackages($this->whatProvides($providerName), $constraint); } } return []; } return parent::findPackages($name, $constraint); } /** * @param array $packages * * @return BasePackage|array|null */ private function filterPackages(array $packages, ?ConstraintInterface $constraint = null, bool $returnFirstMatch = \false) { if (null === $constraint) { if ($returnFirstMatch) { return \reset($packages); } return $packages; } $filteredPackages = []; foreach ($packages as $package) { $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { if ($returnFirstMatch) { return $package; } $filteredPackages[] = $package; } } if ($returnFirstMatch) { return null; } return $filteredPackages; } public function getPackages() { $hasProviders = $this->hasProviders(); if ($this->lazyProvidersUrl) { if (\is_array($this->availablePackages) && !$this->availablePackagePatterns) { $packageMap = []; foreach ($this->availablePackages as $name) { $packageMap[$name] = new MatchAllConstraint(); } $result = $this->loadAsyncPackages($packageMap); return \array_values($result['packages']); } if ($this->hasPartialPackages()) { if (!\is_array($this->partialPackagesByName)) { throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); } return $this->createPackages($this->partialPackagesByName, 'packages.json inline packages'); } throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.'); } if ($hasProviders) { throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.'); } return parent::getPackages(); } /** * @param string|null $packageFilter Package pattern filter which can include "*" as a wildcard * * @return string[] */ public function getPackageNames(?string $packageFilter = null) { $hasProviders = $this->hasProviders(); $filterResults = static function (array $results) : array { return $results; }; if (null !== $packageFilter && '' !== $packageFilter) { $packageFilterRegex = BasePackage::packageNameToRegexp($packageFilter); $filterResults = static function (array $results) use($packageFilterRegex) : array { /** @var list $results */ return Preg::grep($packageFilterRegex, $results); }; } if ($this->lazyProvidersUrl) { if (\is_array($this->availablePackages)) { return $filterResults(\array_keys($this->availablePackages)); } if ($this->listUrl) { // no need to call $filterResults here as the $packageFilter is applied in the function itself return $this->loadPackageList($packageFilter); } if ($this->hasPartialPackages() && $this->partialPackagesByName !== null) { return $filterResults(\array_keys($this->partialPackagesByName)); } return []; } if ($hasProviders) { return $filterResults($this->getProviderNames()); } $names = []; foreach ($this->getPackages() as $package) { $names[] = $package->getPrettyName(); } return $filterResults($names); } /** * @return list */ private function getVendorNames() : array { $cacheKey = 'vendor-list.txt'; $cacheAge = $this->cache->getAge($cacheKey); if (\false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== \false) { $cachedData = \explode("\n", $cachedData); return $cachedData; } $names = $this->getPackageNames(); $uniques = []; foreach ($names as $name) { // @phpstan-ignore-next-line $uniques[\substr($name, 0, \strpos($name, '/'))] = \true; } $vendors = \array_keys($uniques); if (!$this->cache->isReadOnly()) { $this->cache->write($cacheKey, \implode("\n", $vendors)); } return $vendors; } /** * @return list */ private function loadPackageList(?string $packageFilter = null) : array { if (null === $this->listUrl) { throw new \LogicException('Make sure to call loadRootServerFile before loadPackageList'); } $url = $this->listUrl; if (\is_string($packageFilter) && $packageFilter !== '') { $url .= '?filter=' . \urlencode($packageFilter); $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); return $result['packageNames']; } $cacheKey = 'package-list.txt'; $cacheAge = $this->cache->getAge($cacheKey); if (\false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== \false) { $cachedData = \explode("\n", $cachedData); return $cachedData; } $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); if (!$this->cache->isReadOnly()) { $this->cache->write($cacheKey, \implode("\n", $result['packageNames'])); } return $result['packageNames']; } public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) { // this call initializes loadRootServerFile which is needed for the rest below to work $hasProviders = $this->hasProviders(); if (!$hasProviders && !$this->hasPartialPackages() && null === $this->lazyProvidersUrl) { return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); } $packages = []; $namesFound = []; if ($hasProviders || $this->hasPartialPackages()) { foreach ($packageNameMap as $name => $constraint) { $matches = []; // if a repo has no providers but only partial packages and the partial packages are missing // then we don't want to call whatProvides as it would try to load from the providers and fail if (!$hasProviders && !isset($this->partialPackagesByName[$name])) { continue; } $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); foreach ($candidates as $candidate) { if ($candidate->getName() !== $name) { throw new \LogicException('whatProvides should never return a package with a different name than the requested one'); } $namesFound[$name] = \true; if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { $matches[\spl_object_hash($candidate)] = $candidate; if ($candidate instanceof AliasPackage && !isset($matches[\spl_object_hash($candidate->getAliasOf())])) { $matches[\spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); } } } // add aliases of matched packages even if they did not match the constraint foreach ($candidates as $candidate) { if ($candidate instanceof AliasPackage) { if (isset($matches[\spl_object_hash($candidate->getAliasOf())])) { $matches[\spl_object_hash($candidate)] = $candidate; } } } $packages = \array_merge($packages, $matches); unset($packageNameMap[$name]); } } if ($this->lazyProvidersUrl && \count($packageNameMap)) { if ($this->hasAvailablePackageList) { foreach ($packageNameMap as $name => $constraint) { if (!$this->lazyProvidersRepoContains(\strtolower($name))) { unset($packageNameMap[$name]); } } } $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); $packages = \array_merge($packages, $result['packages']); $namesFound = \array_merge($namesFound, $result['namesFound']); } return ['namesFound' => \array_keys($namesFound), 'packages' => $packages]; } /** * @inheritDoc */ public function search(string $query, int $mode = 0, ?string $type = null) { $this->loadRootServerFile(600); if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = \str_replace(['%query%', '%type%'], [\urlencode($query), $type], $this->searchUrl); $search = $this->httpDownloader->get($url, $this->options)->decodeJson(); if (empty($search['results'])) { return []; } $results = []; foreach ($search['results'] as $result) { // do not show virtual packages in results as they are not directly useful from a composer perspective if (!empty($result['virtual'])) { continue; } $results[] = $result; } return $results; } if ($mode === self::SEARCH_VENDOR) { $results = []; $regex = '{(?:' . \implode('|', Preg::split('{\\s+}', $query)) . ')}i'; $vendorNames = $this->getVendorNames(); foreach (Preg::grep($regex, $vendorNames) as $name) { $results[] = ['name' => $name, 'description' => '']; } return $results; } if ($this->hasProviders() || $this->lazyProvidersUrl) { // optimize search for "^foo/bar" where at least "^foo/" is present by loading this directly from the listUrl if present if (Preg::isMatchStrictGroups('{^\\^(?P(?P[a-z0-9_.-]+)/[a-z0-9_.-]*)\\*?$}i', $query, $match) && $this->listUrl !== null) { $url = $this->listUrl . '?vendor=' . \urlencode($match['vendor']) . '&filter=' . \urlencode($match['query'] . '*'); $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); $results = []; foreach ($result['packageNames'] as $name) { $results[] = ['name' => $name, 'description' => '']; } return $results; } $results = []; $regex = '{(?:' . \implode('|', Preg::split('{\\s+}', $query)) . ')}i'; $packageNames = $this->getPackageNames(); foreach (Preg::grep($regex, $packageNames) as $name) { $results[] = ['name' => $name, 'description' => '']; } return $results; } return parent::search($query, $mode); } public function hasSecurityAdvisories() : bool { $this->loadRootServerFile(600); return $this->securityAdvisoryConfig !== null && ($this->securityAdvisoryConfig['metadata'] || $this->securityAdvisoryConfig['api-url'] !== null); } /** * @inheritDoc */ public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = \false) : array { $this->loadRootServerFile(600); if (null === $this->securityAdvisoryConfig) { return ['namesFound' => [], 'advisories' => []]; } $advisories = []; $namesFound = []; $apiUrl = $this->securityAdvisoryConfig['api-url']; // respect available-package-patterns / available-packages directives from the repo if ($this->hasAvailablePackageList) { foreach ($packageConstraintMap as $name => $constraint) { if (!$this->lazyProvidersRepoContains(\strtolower($name))) { unset($packageConstraintMap[$name]); } } } $parser = new VersionParser(); /** * @param array $data * @param string $name * @return ($allowPartialAdvisories is false ? SecurityAdvisory|null : PartialSecurityAdvisory|SecurityAdvisory|null) */ $create = function (array $data, string $name) use($parser, $allowPartialAdvisories, &$packageConstraintMap) : ?PartialSecurityAdvisory { $advisory = PartialSecurityAdvisory::create($name, $data, $parser); if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) { throw new \RuntimeException('Advisory for ' . $name . ' could not be loaded as a full advisory from ' . $this->getRepoName() . \PHP_EOL . \var_export($data, \true)); } if (!$advisory->affectedVersions->matches($packageConstraintMap[$name])) { return null; } return $advisory; }; if ($this->securityAdvisoryConfig['metadata'] && ($allowPartialAdvisories || $apiUrl === null)) { $promises = []; foreach ($packageConstraintMap as $name => $constraint) { $name = \strtolower($name); // skip platform packages, root package and composer-plugin-api if (\Composer\Repository\PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { continue; } $promises[] = $this->startCachedAsyncDownload($name, $name)->then(static function (array $spec) use(&$advisories, &$namesFound, &$packageConstraintMap, $name, $create) : void { [$response] = $spec; if (!isset($response['security-advisories']) || !\is_array($response['security-advisories'])) { return; } $namesFound[$name] = \true; if (\count($response['security-advisories']) > 0) { $advisories[$name] = \array_filter(\array_map(static function ($data) use($name, $create) { return $create($data, $name); }, $response['security-advisories'])); } unset($packageConstraintMap[$name]); }); } $this->loop->wait($promises); } if ($apiUrl !== null && \count($packageConstraintMap) > 0) { $options = $this->options; $options['http']['method'] = 'POST'; if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = 'Content-type: application/x-www-form-urlencoded'; $options['http']['timeout'] = 10; $options['http']['content'] = \http_build_query(['packages' => \array_keys($packageConstraintMap)]); $response = $this->httpDownloader->get($apiUrl, $options); $warned = \false; /** @var string $name */ foreach ($response->decodeJson()['advisories'] as $name => $list) { if (!isset($packageConstraintMap[$name])) { if (!$warned) { $this->io->writeError('' . $this->getRepoName() . ' returned names which were not requested in response to the security-advisories API. ' . $name . ' was not requested but is present in the response. Requested names were: ' . \implode(', ', \array_keys($packageConstraintMap)) . ''); $warned = \true; } continue; } if (\count($list) > 0) { $advisories[$name] = \array_filter(\array_map(static function ($data) use($name, $create) { return $create($data, $name); }, $list)); } $namesFound[$name] = \true; } } return ['namesFound' => \array_keys($namesFound), 'advisories' => \array_filter($advisories)]; } public function getProviders(string $packageName) { $this->loadRootServerFile(); $result = []; if ($this->providersApiUrl) { try { $apiResult = $this->httpDownloader->get(\str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson(); } catch (TransportException $e) { if ($e->getStatusCode() === 404) { return $result; } throw $e; } foreach ($apiResult['providers'] as $provider) { $result[$provider['name']] = $provider; } return $result; } if ($this->hasPartialPackages()) { if (!\is_array($this->partialPackagesByName)) { throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); } foreach ($this->partialPackagesByName as $versions) { foreach ($versions as $candidate) { if (isset($result[$candidate['name']]) || !isset($candidate['provide'][$packageName])) { continue; } $result[$candidate['name']] = ['name' => $candidate['name'], 'description' => $candidate['description'] ?? '', 'type' => $candidate['type'] ?? '']; } } } if ($this->packages) { $result = \array_merge($result, parent::getProviders($packageName)); } return $result; } /** * @return string[] */ private function getProviderNames() : array { $this->loadRootServerFile(); if (null === $this->providerListing) { $data = $this->loadRootServerFile(); if (\is_array($data)) { $this->loadProviderListings($data); } } if ($this->lazyProvidersUrl) { // Can not determine list of provided packages for lazy repositories return []; } if (null !== $this->providersUrl && null !== $this->providerListing) { return \array_keys($this->providerListing); } return []; } protected function configurePackageTransportOptions(PackageInterface $package) : void { foreach ($package->getDistUrls() as $url) { if (\strpos($url, $this->baseUrl) === 0) { $package->setTransportOptions($this->options); return; } } } private function hasProviders() : bool { $this->loadRootServerFile(); return $this->hasProviders; } /** * @param string $name package name * @param array|null $acceptableStabilities * @phpstan-param array|null $acceptableStabilities * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array|null $stabilityFlags * @param array> $alreadyLoaded * * @return array */ private function whatProvides(string $name, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []) : array { $packagesSource = null; if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { // skip platform packages, root package and composer-plugin-api if (\Composer\Repository\PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { return []; } if (null === $this->providerListing) { $data = $this->loadRootServerFile(); if (\is_array($data)) { $this->loadProviderListings($data); } } $useLastModifiedCheck = \false; if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) { $hash = null; $url = \str_replace('%package%', $name, $this->lazyProvidersUrl); $cacheKey = 'provider-' . \strtr($name, '/', '$') . '.json'; $useLastModifiedCheck = \true; } elseif ($this->providersUrl) { // package does not exist in this repo if (!isset($this->providerListing[$name])) { return []; } $hash = $this->providerListing[$name]['sha256']; $url = \str_replace(['%package%', '%hash%'], [$name, $hash], $this->providersUrl); $cacheKey = 'provider-' . \strtr($name, '/', '$') . '.json'; } else { return []; } $packages = null; if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) { $packages = \json_decode($this->cache->read($cacheKey), \true); $packagesSource = 'cached file (' . $cacheKey . ' originating from ' . Url::sanitize($url) . ')'; } elseif ($useLastModifiedCheck) { if ($contents = $this->cache->read($cacheKey)) { $contents = \json_decode($contents, \true); // we already loaded some packages from this file, so assume it is fresh and avoid fetching it again if (isset($alreadyLoaded[$name])) { $packages = $contents; $packagesSource = 'cached file (' . $cacheKey . ' originating from ' . Url::sanitize($url) . ')'; } elseif (isset($contents['last-modified'])) { $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']); $packages = \true === $response ? $contents : $response; $packagesSource = \true === $response ? 'cached file (' . $cacheKey . ' originating from ' . Url::sanitize($url) . ')' : 'downloaded file (' . Url::sanitize($url) . ')'; } } } if (!$packages) { try { $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck); $packagesSource = 'downloaded file (' . Url::sanitize($url) . ')'; } catch (TransportException $e) { // 404s are acceptable for lazy provider repos if ($this->lazyProvidersUrl && \in_array($e->getStatusCode(), [404, 499], \true)) { $packages = ['packages' => []]; $packagesSource = 'not-found file (' . Url::sanitize($url) . ')'; if ($e->getStatusCode() === 499) { $this->io->error('' . $e->getMessage() . ''); } } else { throw $e; } } } $loadingPartialPackage = \false; } else { $packages = ['packages' => ['versions' => $this->partialPackagesByName[$name]]]; $packagesSource = 'root file (' . Url::sanitize($this->getPackagesJsonUrl()) . ')'; $loadingPartialPackage = \true; } $result = []; $versionsToLoad = []; foreach ($packages['packages'] as $versions) { foreach ($versions as $version) { $normalizedName = \strtolower($version['name']); // only load the actual named package, not other packages that might find themselves in the same file if ($normalizedName !== $name) { continue; } if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) { continue; } if (!isset($versionsToLoad[$version['uid']])) { if (!isset($version['version_normalized'])) { $version['version_normalized'] = $this->versionParser->normalize($version['version']); } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it $version['version_normalized'] = $this->versionParser->normalize($version['version']); } // avoid loading packages which have already been loaded if (isset($alreadyLoaded[$name][$version['version_normalized']])) { continue; } if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) { $versionsToLoad[$version['uid']] = $version; } } } } // load acceptable packages in the providers $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); $uids = \array_keys($versionsToLoad); foreach ($loadedPackages as $index => $package) { $package->setRepository($this); $uid = $uids[$index]; if ($package instanceof AliasPackage) { $aliased = $package->getAliasOf(); $aliased->setRepository($this); $result[$uid] = $aliased; $result[$uid . '-alias'] = $package; } else { $result[$uid] = $package; } } return $result; } /** * @inheritDoc */ protected function initialize() { parent::initialize(); $repoData = $this->loadDataFromServer(); foreach ($this->createPackages($repoData, 'root file (' . Url::sanitize($this->getPackagesJsonUrl()) . ')') as $package) { $this->addPackage($package); } } /** * Adds a new package to the repository */ public function addPackage(PackageInterface $package) { parent::addPackage($package); $this->configurePackageTransportOptions($package); } /** * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only * packages matching it will be loaded * @param array|null $acceptableStabilities * @phpstan-param array|null $acceptableStabilities * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array|null $stabilityFlags * @param array> $alreadyLoaded * * @return array{namesFound: array, packages: array} */ private function loadAsyncPackages(array $packageNames, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []) : array { $this->loadRootServerFile(); $packages = []; $namesFound = []; $promises = []; if (null === $this->lazyProvidersUrl) { throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); } // load ~dev versions of the packages as well if needed foreach ($packageNames as $name => $constraint) { if ($acceptableStabilities === null || $stabilityFlags === null || StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], 'dev')) { $packageNames[$name . '~dev'] = $constraint; } // if only dev stability is requested, we skip loading the non dev file if (isset($acceptableStabilities['dev']) && \count($acceptableStabilities) === 1 && \count($stabilityFlags) === 0) { unset($packageNames[$name]); } } foreach ($packageNames as $name => $constraint) { $name = \strtolower($name); $realName = Preg::replace('{~dev$}', '', $name); // skip platform packages, root package and composer-plugin-api if (\Composer\Repository\PlatformRepository::isPlatformPackage($realName) || '__root__' === $realName) { continue; } $promises[] = $this->startCachedAsyncDownload($name, $realName)->then(function (array $spec) use(&$packages, &$namesFound, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded) : void { [$response, $packagesSource] = $spec; if (null === $response || !isset($response['packages'][$realName])) { return; } $versions = $response['packages'][$realName]; if (isset($response['minified']) && $response['minified'] === 'composer/2.0') { $versions = MetadataMinifier::expand($versions); } $namesFound[$realName] = \true; $versionsToLoad = []; foreach ($versions as $version) { if (!isset($version['version_normalized'])) { $version['version_normalized'] = $this->versionParser->normalize($version['version']); } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it $version['version_normalized'] = $this->versionParser->normalize($version['version']); } // avoid loading packages which have already been loaded if (isset($alreadyLoaded[$realName][$version['version_normalized']])) { continue; } if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) { $versionsToLoad[] = $version; } } $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); foreach ($loadedPackages as $package) { $package->setRepository($this); $packages[\spl_object_hash($package)] = $package; if ($package instanceof AliasPackage && !isset($packages[\spl_object_hash($package->getAliasOf())])) { $package->getAliasOf()->setRepository($this); $packages[\spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } }); } $this->loop->wait($promises); return ['namesFound' => $namesFound, 'packages' => $packages]; } /** * @phpstan-return PromiseInterface */ private function startCachedAsyncDownload(string $fileName, ?string $packageName = null) : PromiseInterface { if (null === $this->lazyProvidersUrl) { throw new \LogicException('startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url'); } $name = \strtolower($fileName); $packageName = $packageName ?? $name; $url = \str_replace('%package%', $name, $this->lazyProvidersUrl); $cacheKey = 'provider-' . \strtr($name, '/', '~') . '.json'; $lastModified = null; if ($contents = $this->cache->read($cacheKey)) { $contents = \json_decode($contents, \true); $lastModified = $contents['last-modified'] ?? null; } return $this->asyncFetchFile($url, $cacheKey, $lastModified)->then(static function ($response) use($url, $cacheKey, $contents, $packageName) : array { $packagesSource = 'downloaded file (' . Url::sanitize($url) . ')'; if (\true === $response) { $packagesSource = 'cached file (' . $cacheKey . ' originating from ' . Url::sanitize($url) . ')'; $response = $contents; } if (!isset($response['packages'][$packageName]) && !isset($response['security-advisories'])) { return [null, $packagesSource]; } return [$response, $packagesSource]; }); } /** * @param string $name package name (must be lowercased already) * @param array $versionData * @param array|null $acceptableStabilities * @phpstan-param array|null $acceptableStabilities * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array|null $stabilityFlags */ private function isVersionAcceptable(?ConstraintInterface $constraint, string $name, array $versionData, ?array $acceptableStabilities = null, ?array $stabilityFlags = null) : bool { $versions = [$versionData['version_normalized']]; if ($alias = $this->loader->getBranchAlias($versionData)) { $versions[] = $alias; } foreach ($versions as $version) { if (null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], VersionParser::parseStability($version))) { continue; } if ($constraint && !CompilingMatcher::match($constraint, Constraint::OP_EQ, $version)) { continue; } return \true; } return \false; } private function getPackagesJsonUrl() : string { $jsonUrlParts = \parse_url(\strtr($this->url, '\\', '/')); if (isset($jsonUrlParts['path']) && \false !== \strpos($jsonUrlParts['path'], '.json')) { return $this->url; } return $this->url . '/packages.json'; } /** * @return array<'providers'|'provider-includes'|'packages'|'providers-url'|'notify-batch'|'search'|'mirrors'|'providers-lazy-url'|'metadata-url'|'available-packages'|'available-package-patterns', mixed>|true */ protected function loadRootServerFile(?int $rootMaxAge = null) { if (null !== $this->rootData) { return $this->rootData; } if (!\extension_loaded('openssl') && \strpos($this->url, 'https') === 0) { throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from ' . $this->url); } if ($cachedData = $this->cache->read('packages.json')) { $cachedData = \json_decode($cachedData, \true); if ($rootMaxAge !== null && ($age = $this->cache->getAge('packages.json')) !== \false && $age <= $rootMaxAge) { $data = $cachedData; } elseif (isset($cachedData['last-modified'])) { $response = $this->fetchFileIfLastModified($this->getPackagesJsonUrl(), 'packages.json', $cachedData['last-modified']); $data = \true === $response ? $cachedData : $response; } } if (!isset($data)) { $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json', null, \true); } if (!empty($data['notify-batch'])) { $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']); } elseif (!empty($data['notify'])) { $this->notifyUrl = $this->canonicalizeUrl($data['notify']); } if (!empty($data['search'])) { $this->searchUrl = $this->canonicalizeUrl($data['search']); } if (!empty($data['mirrors'])) { foreach ($data['mirrors'] as $mirror) { if (!empty($mirror['git-url'])) { $this->sourceMirrors['git'][] = ['url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])]; } if (!empty($mirror['hg-url'])) { $this->sourceMirrors['hg'][] = ['url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])]; } if (!empty($mirror['dist-url'])) { $this->distMirrors[] = ['url' => $this->canonicalizeUrl($mirror['dist-url']), 'preferred' => !empty($mirror['preferred'])]; } } } if (!empty($data['providers-lazy-url'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']); $this->hasProviders = \true; $this->hasPartialPackages = !empty($data['packages']) && \is_array($data['packages']); } // metadata-url indicates V2 repo protocol so it takes over from all the V1 types // V2 only has lazyProviders and possibly partial packages, but no ability to process anything else, // V2 also supports async loading if (!empty($data['metadata-url'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']); $this->providersUrl = null; $this->hasProviders = \false; $this->hasPartialPackages = !empty($data['packages']) && \is_array($data['packages']); $this->allowSslDowngrade = \false; // provides a list of package names that are available in this repo // this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo // while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it if (!empty($data['available-packages'])) { $availPackages = \array_map('strtolower', $data['available-packages']); $this->availablePackages = \array_combine($availPackages, $availPackages); $this->hasAvailablePackageList = \true; } // Provides a list of package name patterns (using * wildcards to match any substring, e.g. "vendor/*") that are available in this repo // Disables lazy-provider behavior as with available-packages, but may allow much more compact expression of packages covered by this repository. // Over-specifying covered packages is safe, but may result in increased traffic to your repository. if (!empty($data['available-package-patterns'])) { $this->availablePackagePatterns = \array_map(static function ($pattern) : string { return BasePackage::packageNameToRegexp($pattern); }, $data['available-package-patterns']); $this->hasAvailablePackageList = \true; } // Remove legacy keys as most repos need to be compatible with Composer v1 // as well but we are not interested in the old format anymore at this point unset($data['providers-url'], $data['providers'], $data['providers-includes']); if (isset($data['security-advisories']) && \is_array($data['security-advisories'])) { $this->securityAdvisoryConfig = ['metadata' => $data['security-advisories']['metadata'] ?? \false, 'api-url' => isset($data['security-advisories']['api-url']) && \is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null]; if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) { throw new \UnexpectedValueException('Invalid security advisory configuration on ' . $this->getRepoName() . ': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.'); } } } if ($this->allowSslDowngrade) { $this->url = \str_replace('https://', 'http://', $this->url); $this->baseUrl = \str_replace('https://', 'http://', $this->baseUrl); } if (!empty($data['providers-url'])) { $this->providersUrl = $this->canonicalizeUrl($data['providers-url']); $this->hasProviders = \true; } if (!empty($data['list'])) { $this->listUrl = $this->canonicalizeUrl($data['list']); } if (!empty($data['providers']) || !empty($data['providers-includes'])) { $this->hasProviders = \true; } if (!empty($data['providers-api'])) { $this->providersApiUrl = $this->canonicalizeUrl($data['providers-api']); } return $this->rootData = $data; } /** * @param string $url * @return non-empty-string */ private function canonicalizeUrl(string $url) : string { if (\strlen($url) === 0) { throw new \InvalidArgumentException('Expected a string with a value and not an empty string'); } if (\str_starts_with($url, '/')) { if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) { return $matches[0] . $url; } return $this->url; } return $url; } /** * @return mixed[] */ private function loadDataFromServer() : array { $data = $this->loadRootServerFile(); if (\true === $data) { throw new \LogicException('loadRootServerFile should not return true during initialization'); } return $this->loadIncludes($data); } private function hasPartialPackages() : bool { if ($this->hasPartialPackages && null === $this->partialPackagesByName) { $this->initializePartialPackages(); } return $this->hasPartialPackages; } /** * @param array{providers?: mixed[], provider-includes?: mixed[]} $data */ private function loadProviderListings($data) : void { if (isset($data['providers'])) { if (!\is_array($this->providerListing)) { $this->providerListing = []; } $this->providerListing = \array_merge($this->providerListing, $data['providers']); } if ($this->providersUrl && isset($data['provider-includes'])) { $includes = $data['provider-includes']; foreach ($includes as $include => $metadata) { $url = $this->baseUrl . '/' . \str_replace('%hash%', $metadata['sha256'], $include); $cacheKey = \str_replace(['%hash%', '$'], '', $include); if ($this->cache->sha256($cacheKey) === $metadata['sha256']) { $includedData = \json_decode($this->cache->read($cacheKey), \true); } else { $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']); } $this->loadProviderListings($includedData); } } } /** * @param mixed[] $data * * @return mixed[] */ private function loadIncludes(array $data) : array { $packages = []; // legacy repo handling if (!isset($data['packages']) && !isset($data['includes'])) { foreach ($data as $pkg) { if (isset($pkg['versions']) && \is_array($pkg['versions'])) { foreach ($pkg['versions'] as $metadata) { $packages[] = $metadata; } } } return $packages; } if (isset($data['packages'])) { foreach ($data['packages'] as $package => $versions) { $packageName = \strtolower((string) $package); foreach ($versions as $version => $metadata) { $packages[] = $metadata; if (!$this->displayedWarningAboutNonMatchingPackageIndex && $packageName !== \strtolower((string) ($metadata['name'] ?? ''))) { $this->displayedWarningAboutNonMatchingPackageIndex = \true; $this->io->writeError(\sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $metadata['name'] ?? '', $this->baseUrl)); } } } } if (isset($data['includes'])) { foreach ($data['includes'] as $include => $metadata) { if (isset($metadata['sha1']) && $this->cache->sha1((string) $include) === $metadata['sha1']) { $includedData = \json_decode($this->cache->read((string) $include), \true); } else { $includedData = $this->fetchFile($include); } $packages = \array_merge($packages, $this->loadIncludes($includedData)); } } return $packages; } /** * @param mixed[] $packages * * @return list */ private function createPackages(array $packages, ?string $source = null) : array { if (!$packages) { return []; } try { foreach ($packages as &$data) { if (!isset($data['notification-url'])) { $data['notification-url'] = $this->notifyUrl; } } $packageInstances = $this->loader->loadPackages($packages); foreach ($packageInstances as $package) { if (isset($this->sourceMirrors[$package->getSourceType()])) { $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); } $package->setDistMirrors($this->distMirrors); $this->configurePackageTransportOptions($package); } return $packageInstances; } catch (\Exception $e) { throw new \RuntimeException('Could not load packages ' . ($packages[0]['name'] ?? \json_encode($packages)) . ' in ' . $this->getRepoName() . ($source ? ' from ' . $source : '') . ': [' . \get_class($e) . '] ' . $e->getMessage(), 0, $e); } } /** * @return array */ protected function fetchFile(string $filename, ?string $cacheKey = null, ?string $sha256 = null, bool $storeLastModifiedTime = \false) { if ('' === $filename) { throw new \InvalidArgumentException('$filename should not be an empty string'); } if (null === $cacheKey) { $cacheKey = $filename; $filename = $this->baseUrl . '/' . $filename; } // url-encode $ signs in URLs as bad proxies choke on them if (($pos = \strpos($filename, '$')) && Preg::isMatch('{^https?://}i', $filename)) { $filename = \substr($filename, 0, $pos) . '%24' . \substr($filename, $pos + 1); } $retries = 3; while ($retries--) { try { $options = $this->options; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); $preFileDownloadEvent->setTransportOptions($this->options); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $filename = $preFileDownloadEvent->getProcessedUrl(); $options = $preFileDownloadEvent->getTransportOptions(); } $response = $this->httpDownloader->get($filename, $options); $json = (string) $response->getBody(); if ($sha256 && $sha256 !== \hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow if ($this->allowSslDowngrade) { $this->url = \str_replace('http://', 'https://', $this->url); $this->baseUrl = \str_replace('http://', 'https://', $this->baseUrl); $filename = \str_replace('http://', 'https://', $filename); } if ($retries > 0) { \usleep(100000); continue; } // TODO use scarier wording once we know for sure it doesn't do false positives anymore throw new \Composer\Repository\RepositorySecurityException('The contents of ' . $filename . ' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.'); } if ($this->eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, $sha256, $filename, 'metadata', ['response' => $response, 'repository' => $this]); $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } $data = $response->decodeJson(); HttpDownloader::outputWarnings($this->io, $this->url, $data); if ($cacheKey && !$this->cache->isReadOnly()) { if ($storeLastModifiedTime) { $lastModifiedDate = $response->getHeader('last-modified'); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, 0); } } $this->cache->write($cacheKey, $json); } $response->collect(); break; } catch (\Exception $e) { if ($e instanceof \LogicException) { throw $e; } if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } if ($e instanceof \Composer\Repository\RepositorySecurityException) { throw $e; } if ($cacheKey && ($contents = $this->cache->read($cacheKey))) { if (!$this->degradedMode) { $this->io->writeError('' . $this->url . ' could not be fully loaded (' . $e->getMessage() . '), package information was loaded from the local cache and may be out of date'); } $this->degradedMode = \true; $data = JsonFile::parseJson($contents, $this->cache->getRoot() . $cacheKey); break; } throw $e; } } if (!isset($data)) { throw new \LogicException("ComposerRepository: Undefined \$data. Please report at https://github.com/composer/composer/issues/new."); } return $data; } /** * @return array|true */ private function fetchFileIfLastModified(string $filename, string $cacheKey, string $lastModifiedTime) { if ('' === $filename) { throw new \InvalidArgumentException('$filename should not be an empty string'); } try { $options = $this->options; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); $preFileDownloadEvent->setTransportOptions($this->options); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $filename = $preFileDownloadEvent->getProcessedUrl(); $options = $preFileDownloadEvent->getTransportOptions(); } if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = 'If-Modified-Since: ' . $lastModifiedTime; $response = $this->httpDownloader->get($filename, $options); $json = (string) $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { return \true; } if ($this->eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } $data = $response->decodeJson(); HttpDownloader::outputWarnings($this->io, $this->url, $data); $lastModifiedDate = $response->getHeader('last-modified'); $response->collect(); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, 0); } if (!$this->cache->isReadOnly()) { $this->cache->write($cacheKey, $json); } return $data; } catch (\Exception $e) { if ($e instanceof \LogicException) { throw $e; } if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } if (!$this->degradedMode) { $this->io->writeError('' . $this->url . ' could not be fully loaded (' . $e->getMessage() . '), package information was loaded from the local cache and may be out of date'); } $this->degradedMode = \true; return \true; } } /** * @phpstan-return PromiseInterface|true> true if the response was a 304 and the cache is fresh, otherwise it returns the decoded json */ private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null) : PromiseInterface { if ('' === $filename) { throw new \InvalidArgumentException('$filename should not be an empty string'); } if (isset($this->packagesNotFoundCache[$filename])) { return \React\Promise\resolve(['packages' => []]); } if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) { // make it look like we got a 304 response /** @var PromiseInterface $promise */ $promise = \React\Promise\resolve(\true); return $promise; } $httpDownloader = $this->httpDownloader; $options = $this->options; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); $preFileDownloadEvent->setTransportOptions($this->options); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $filename = $preFileDownloadEvent->getProcessedUrl(); $options = $preFileDownloadEvent->getTransportOptions(); } if ($lastModifiedTime) { if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = 'If-Modified-Since: ' . $lastModifiedTime; } $io = $this->io; $url = $this->url; $cache = $this->cache; $degradedMode =& $this->degradedMode; $eventDispatcher = $this->eventDispatcher; /** * @return array|true true if the response was a 304 and the cache is fresh */ $accept = function ($response) use($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) { // package not found is acceptable for a v2 protocol repository if ($response->getStatusCode() === 404) { $this->packagesNotFoundCache[$filename] = \true; return ['packages' => []]; } $json = (string) $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { $this->freshMetadataUrls[$filename] = \true; return \true; } if ($eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } $data = $response->decodeJson(); HttpDownloader::outputWarnings($io, $url, $data); $lastModifiedDate = $response->getHeader('last-modified'); $response->collect(); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); } if (!$cache->isReadOnly()) { $cache->write($cacheKey, $json); } $this->freshMetadataUrls[$filename] = \true; return $data; }; $reject = function ($e) use($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) { if ($e instanceof TransportException && $e->getStatusCode() === 404) { $this->packagesNotFoundCache[$filename] = \true; return \false; } if (!$degradedMode) { $io->writeError('' . $url . ' could not be fully loaded (' . $e->getMessage() . '), package information was loaded from the local cache and may be out of date'); } $degradedMode = \true; // if the file is in the cache, we fake a 304 Not Modified to allow the process to continue if ($lastModifiedTime) { return $accept(new Response(['url' => $url], 304, [], '')); } // special error code returned when network is being artificially disabled if ($e instanceof TransportException && $e->getStatusCode() === 499) { return $accept(new Response(['url' => $url], 404, [], '')); } throw $e; }; return $httpDownloader->add($filename, $options)->then($accept, $reject); } /** * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url * * This should only be called once */ private function initializePartialPackages() : void { $rootData = $this->loadRootServerFile(); if ($rootData === \true) { return; } $this->partialPackagesByName = []; foreach ($rootData['packages'] as $package => $versions) { foreach ($versions as $version) { $versionPackageName = \strtolower((string) ($version['name'] ?? '')); $this->partialPackagesByName[$versionPackageName][] = $version; if (!$this->displayedWarningAboutNonMatchingPackageIndex && $versionPackageName !== \strtolower($package)) { $this->io->writeError(\sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $version['name'] ?? '', $this->baseUrl)); $this->displayedWarningAboutNonMatchingPackageIndex = \true; } } } // wipe rootData as it is fully consumed at this point and this saves some memory $this->rootData = \true; } /** * Checks if the package name is present in this lazy providers repo * * @return bool true if the package name is present in availablePackages or matched by availablePackagePatterns */ protected function lazyProvidersRepoContains(string $name) { if (!$this->hasAvailablePackageList) { throw new \LogicException('lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true'); } if (\is_array($this->availablePackages) && isset($this->availablePackages[$name])) { return \true; } if (\is_array($this->availablePackagePatterns)) { foreach ($this->availablePackagePatterns as $providerRegex) { if (Preg::isMatch($providerRegex, $name)) { return \true; } } } return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Builds list of package from PEAR channel. * * Packages read from channel are named as 'pear-{channelName}/{packageName}' * and has aliased as 'pear-{channelAlias}/{packageName}' * * @author Benjamin Eberlei * @author Jordi Boggiano * @deprecated * @private */ class PearRepository extends \Composer\Repository\ArrayRepository { public function __construct() { throw new \InvalidArgumentException('The PEAR repository has been removed from Composer 2.x'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\DependencyResolver\PoolOptimizer; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\PoolBuilder; use Composer\DependencyResolver\Request; use Composer\EventDispatcher\EventDispatcher; use Composer\Advisory\SecurityAdvisory; use Composer\Advisory\PartialSecurityAdvisory; use Composer\IO\IOInterface; use Composer\IO\NullIO; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; use Composer\Package\PackageInterface; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Package\Version\StabilityFilter; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MultiConstraint; /** * @author Nils Adermann * * @see RepositoryUtils for ways to work with single repos */ class RepositorySet { /** * Packages are returned even though their stability does not match the required stability */ public const ALLOW_UNACCEPTABLE_STABILITIES = 1; /** * Packages will be looked up in all repositories, even after they have been found in a higher prio one */ public const ALLOW_SHADOWED_REPOSITORIES = 2; /** * @var array[] * @phpstan-var array> */ private $rootAliases; /** * @var string[] * @phpstan-var array */ private $rootReferences; /** @var RepositoryInterface[] */ private $repositories = []; /** * @var int[] array of stability => BasePackage::STABILITY_* value * @phpstan-var array */ private $acceptableStabilities; /** * @var int[] array of package name => BasePackage::STABILITY_* value * @phpstan-var array */ private $stabilityFlags; /** * @var ConstraintInterface[] * @phpstan-var array */ private $rootRequires; /** * @var array */ private $temporaryConstraints; /** @var bool */ private $locked = \false; /** @var bool */ private $allowInstalledRepositories = \false; /** * In most cases if you are looking to use this class as a way to find packages from repositories * passing minimumStability is all you need to worry about. The rest is for advanced pool creation including * aliases, pinned references and other special cases. * * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array $stabilityFlags * @param array[] $rootAliases * @phpstan-param list $rootAliases * @param string[] $rootReferences an array of package name => source reference * @phpstan-param array $rootReferences * @param ConstraintInterface[] $rootRequires an array of package name => constraint from the root package * @phpstan-param array $rootRequires * @param array $temporaryConstraints Runtime temporary constraints that will be used to filter packages */ public function __construct(string $minimumStability = 'stable', array $stabilityFlags = [], array $rootAliases = [], array $rootReferences = [], array $rootRequires = [], array $temporaryConstraints = []) { $this->rootAliases = self::getRootAliasesPerPackage($rootAliases); $this->rootReferences = $rootReferences; $this->acceptableStabilities = []; foreach (BasePackage::$stabilities as $stability => $value) { if ($value <= BasePackage::$stabilities[$minimumStability]) { $this->acceptableStabilities[$stability] = $value; } } $this->stabilityFlags = $stabilityFlags; $this->rootRequires = $rootRequires; foreach ($rootRequires as $name => $constraint) { if (\Composer\Repository\PlatformRepository::isPlatformPackage($name)) { unset($this->rootRequires[$name]); } } $this->temporaryConstraints = $temporaryConstraints; } public function allowInstalledRepositories(bool $allow = \true) : void { $this->allowInstalledRepositories = $allow; } /** * @return ConstraintInterface[] an array of package name => constraint from the root package, platform requirements excluded * @phpstan-return array */ public function getRootRequires() : array { return $this->rootRequires; } /** * @return array Runtime temporary constraints that will be used to filter packages */ public function getTemporaryConstraints() : array { return $this->temporaryConstraints; } /** * Adds a repository to this repository set * * The first repos added have a higher priority. As soon as a package is found in any * repository the search for that package ends, and following repos will not be consulted. * * @param RepositoryInterface $repo A package repository */ public function addRepository(\Composer\Repository\RepositoryInterface $repo) : void { if ($this->locked) { throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore."); } if ($repo instanceof \Composer\Repository\CompositeRepository) { $repos = $repo->getRepositories(); } else { $repos = [$repo]; } foreach ($repos as $repo) { $this->repositories[] = $repo; } } /** * Find packages providing or matching a name and optionally meeting a constraint in all repositories * * Returned in the order of repositories, matching priority * * @param int $flags any of the ALLOW_* constants from this class to tweak what is returned * @return BasePackage[] */ public function findPackages(string $name, ?ConstraintInterface $constraint = null, int $flags = 0) : array { $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0; $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0; $packages = []; if ($loadFromAllRepos) { foreach ($this->repositories as $repository) { $packages[] = $repository->findPackages($name, $constraint) ?: []; } } else { foreach ($this->repositories as $repository) { $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::$stabilities : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags); $packages[] = $result['packages']; foreach ($result['namesFound'] as $nameFound) { // avoid loading the same package again from other repositories once it has been found if ($name === $nameFound) { break 2; } } } } $candidates = $packages ? \array_merge(...$packages) : []; // when using loadPackages above (!$loadFromAllRepos) the repos already filter for stability so no need to do it again if ($ignoreStability || !$loadFromAllRepos) { return $candidates; } $result = []; foreach ($candidates as $candidate) { if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) { $result[] = $candidate; } } return $result; } /** * @param string[] $packageNames * @return ($allowPartialAdvisories is true ? array> : array>) */ public function getSecurityAdvisories(array $packageNames, bool $allowPartialAdvisories = \false) : array { $map = []; foreach ($packageNames as $name) { $map[$name] = new MatchAllConstraint(); } return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories); } /** * @param PackageInterface[] $packages * @return ($allowPartialAdvisories is true ? array> : array>) */ public function getMatchingSecurityAdvisories(array $packages, bool $allowPartialAdvisories = \false) : array { $map = []; foreach ($packages as $package) { // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { continue; } if (isset($map[$package->getName()])) { $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], \false); } else { $map[$package->getName()] = new Constraint('=', $package->getVersion()); } } return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories); } /** * @param array $packageConstraintMap * @return ($allowPartialAdvisories is true ? array> : array>) */ private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap, bool $allowPartialAdvisories) : array { $repoAdvisories = []; foreach ($this->repositories as $repository) { if (!$repository instanceof \Composer\Repository\AdvisoryProviderInterface || !$repository->hasSecurityAdvisories()) { continue; } $repoAdvisories[] = $repository->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories)['advisories']; } $advisories = \array_merge_recursive([], ...$repoAdvisories); \ksort($advisories); return $advisories; } /** * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') * @phpstan-return array */ public function getProviders(string $packageName) : array { $providers = []; foreach ($this->repositories as $repository) { if ($repoProviders = $repository->getProviders($packageName)) { $providers = \array_merge($providers, $repoProviders); } } return $providers; } /** * Check for each given package name whether it would be accepted by this RepositorySet in the given $stability * * @param string[] $names * @param string $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' */ public function isPackageAcceptable(array $names, string $stability) : bool { return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability); } /** * Create a pool for dependency resolution from the packages in this repository set. */ public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null) : Pool { $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints); foreach ($this->repositories as $repo) { if (($repo instanceof \Composer\Repository\InstalledRepositoryInterface || $repo instanceof \Composer\Repository\InstalledRepository) && !$this->allowInstalledRepositories) { throw new \LogicException('The pool can not accept packages from an installed repository'); } } $this->locked = \true; return $poolBuilder->buildPool($this->repositories, $request); } /** * Create a pool for dependency resolution from the packages in this repository set. */ public function createPoolWithAllPackages() : Pool { foreach ($this->repositories as $repo) { if (($repo instanceof \Composer\Repository\InstalledRepositoryInterface || $repo instanceof \Composer\Repository\InstalledRepository) && !$this->allowInstalledRepositories) { throw new \LogicException('The pool can not accept packages from an installed repository'); } } $this->locked = \true; $packages = []; foreach ($this->repositories as $repository) { foreach ($repository->getPackages() as $package) { $packages[] = $package; if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { $alias = $this->rootAliases[$package->getName()][$package->getVersion()]; while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if ($package instanceof CompletePackage) { $aliasPackage = new CompleteAliasPackage($package, $alias['alias_normalized'], $alias['alias']); } else { $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); } $aliasPackage->setRootPackageAlias(\true); $packages[] = $aliasPackage; } } } return new Pool($packages); } public function createPoolForPackage(string $packageName, ?\Composer\Repository\LockArrayRepository $lockedRepo = null) : Pool { // TODO unify this with above in some simpler version without "request"? return $this->createPoolForPackages([$packageName], $lockedRepo); } /** * @param string[] $packageNames */ public function createPoolForPackages(array $packageNames, ?\Composer\Repository\LockArrayRepository $lockedRepo = null) : Pool { $request = new Request($lockedRepo); $allowedPackages = []; foreach ($packageNames as $packageName) { if (\Composer\Repository\PlatformRepository::isPlatformPackage($packageName)) { throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.'); } $request->requireName($packageName); $allowedPackages[] = \strtolower($packageName); } if (\count($allowedPackages) > 0) { $request->restrictPackages($allowedPackages); } return $this->createPool($request, new NullIO()); } /** * @param array[] $aliases * @phpstan-param list $aliases * * @return array> */ private static function getRootAliasesPerPackage(array $aliases) : array { $normalizedAliases = []; foreach ($aliases as $alias) { $normalizedAliases[$alias['package']][$alias['version']] = ['alias' => $alias['alias'], 'alias_normalized' => $alias['alias_normalized']]; } return $normalizedAliases; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\BasePackage; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\LoaderInterface; use Composer\Util\Platform; use Composer\Util\Tar; use Composer\Util\Zip; /** * @author Serge Smertin */ class ArtifactRepository extends \Composer\Repository\ArrayRepository implements \Composer\Repository\ConfigurableRepositoryInterface { /** @var LoaderInterface */ protected $loader; /** @var string */ protected $lookup; /** @var array{url: string} */ protected $repoConfig; /** @var IOInterface */ private $io; /** * @param array{url: string} $repoConfig */ public function __construct(array $repoConfig, IOInterface $io) { parent::__construct(); if (!\extension_loaded('zip')) { throw new \RuntimeException('The artifact repository requires PHP\'s zip extension'); } $this->loader = new ArrayLoader(); $this->lookup = Platform::expandPath($repoConfig['url']); $this->io = $io; $this->repoConfig = $repoConfig; } public function getRepoName() { return 'artifact repo (' . $this->lookup . ')'; } public function getRepoConfig() { return $this->repoConfig; } protected function initialize() { parent::initialize(); $this->scanDirectory($this->lookup); } private function scanDirectory(string $path) : void { $io = $this->io; $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); $iterator = new \RecursiveIteratorIterator($directory); $regex = new \RegexIterator($iterator, '/^.+\\.(zip|tar|gz|tgz)$/i'); foreach ($regex as $file) { /* @var $file \SplFileInfo */ if (!$file->isFile()) { continue; } $package = $this->getComposerInformation($file); if (!$package) { $io->writeError("File {$file->getBasename()} doesn't seem to hold a package", \true, IOInterface::VERBOSE); continue; } $template = 'Found package %s (%s) in file %s'; $io->writeError(\sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), \true, IOInterface::VERBOSE); $this->addPackage($package); } } /** * @return ?BasePackage */ private function getComposerInformation(\SplFileInfo $file) : ?BasePackage { $json = null; $fileType = null; $fileExtension = \pathinfo($file->getPathname(), \PATHINFO_EXTENSION); if (\in_array($fileExtension, ['gz', 'tar', 'tgz'], \true)) { $fileType = 'tar'; } elseif ($fileExtension === 'zip') { $fileType = 'zip'; } else { throw new \RuntimeException('Files with "' . $fileExtension . '" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.'); } try { if ($fileType === 'tar') { $json = Tar::getComposerJson($file->getPathname()); } else { $json = Zip::getComposerJson($file->getPathname()); } } catch (\Exception $exception) { $this->io->write('Failed loading package ' . $file->getPathname() . ': ' . $exception->getMessage(), \false, IOInterface::VERBOSE); } if (null === $json) { return null; } $package = JsonFile::parseJson($json, $file->getPathname() . '#composer.json'); $package['dist'] = ['type' => $fileType, 'url' => \strtr($file->getPathname(), '\\', '/'), 'shasum' => \sha1_file($file->getRealPath())]; try { $package = $this->loader->load($package); } catch (\UnexpectedValueException $e) { throw new \UnexpectedValueException('Failed loading package in ' . $file . ': ' . $e->getMessage(), 0, $e); } return $package; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\RootPackageInterface; /** * Root package repository. * * This is used for serving the RootPackage inside an in-memory InstalledRepository * * @author Jordi Boggiano */ class RootPackageRepository extends \Composer\Repository\ArrayRepository { public function __construct(RootPackageInterface $package) { parent::__construct([$package]); } public function getRepoName() : string { return 'root package repo'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\HttpDownloader; use Composer\Util\ProcessExecutor; /** * Repositories manager. * * @author Jordi Boggiano * @author Konstantin Kudryashov * @author François Pluchino */ class RepositoryManager { /** @var InstalledRepositoryInterface */ private $localRepository; /** @var list */ private $repositories = []; /** @var array> */ private $repositoryClasses = []; /** @var IOInterface */ private $io; /** @var Config */ private $config; /** @var HttpDownloader */ private $httpDownloader; /** @var ?EventDispatcher */ private $eventDispatcher; /** @var ProcessExecutor */ private $process; public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null) { $this->io = $io; $this->config = $config; $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->process = $process ?? new ProcessExecutor($io); } /** * Searches for a package by its name and version in managed repositories. * * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against */ public function findPackage(string $name, $constraint) : ?PackageInterface { foreach ($this->repositories as $repository) { /** @var RepositoryInterface $repository */ if ($package = $repository->findPackage($name, $constraint)) { return $package; } } return null; } /** * Searches for all packages matching a name and optionally a version in managed repositories. * * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface[] */ public function findPackages(string $name, $constraint) : array { $packages = []; foreach ($this->getRepositories() as $repository) { $packages = \array_merge($packages, $repository->findPackages($name, $constraint)); } return $packages; } /** * Adds repository * * @param RepositoryInterface $repository repository instance */ public function addRepository(\Composer\Repository\RepositoryInterface $repository) : void { $this->repositories[] = $repository; } /** * Adds a repository to the beginning of the chain * * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin. * * @param RepositoryInterface $repository repository instance */ public function prependRepository(\Composer\Repository\RepositoryInterface $repository) : void { \array_unshift($this->repositories, $repository); } /** * Returns a new repository for a specific installation type. * * @param string $type repository type * @param array $config repository configuration * @param string $name repository name * @throws \InvalidArgumentException if repository for provided type is not registered */ public function createRepository(string $type, array $config, ?string $name = null) : \Composer\Repository\RepositoryInterface { if (!isset($this->repositoryClasses[$type])) { throw new \InvalidArgumentException('Repository type is not registered: ' . $type); } if (isset($config['packagist']) && \false === $config['packagist']) { $this->io->writeError('Repository "' . $name . '" (' . \json_encode($config) . ') has a packagist key which should be in its own repository definition'); } $class = $this->repositoryClasses[$type]; if (isset($config['only']) || isset($config['exclude']) || isset($config['canonical'])) { $filterConfig = $config; unset($config['only'], $config['exclude'], $config['canonical']); } $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process); if (isset($filterConfig)) { $repository = new \Composer\Repository\FilterRepository($repository, $filterConfig); } return $repository; } /** * Stores repository class for a specific installation type. * * @param string $type installation type * @param class-string $class class name of the repo implementation */ public function setRepositoryClass(string $type, $class) : void { $this->repositoryClasses[$type] = $class; } /** * Returns all repositories, except local one. * * @return RepositoryInterface[] */ public function getRepositories() : array { return $this->repositories; } /** * Sets local repository for the project. * * @param InstalledRepositoryInterface $repository repository instance */ public function setLocalRepository(\Composer\Repository\InstalledRepositoryInterface $repository) : void { $this->localRepository = $repository; } /** * Returns local repository for the project. */ public function getLocalRepository() : \Composer\Repository\InstalledRepositoryInterface { return $this->localRepository; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Util\GitHub; use Composer\Util\Http\Response; /** * @author Jordi Boggiano */ class GitHubDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var string */ protected $owner; /** @var string */ protected $repository; /** @var array Map of tag name to identifier */ protected $tags; /** @var array Map of branch name to identifier */ protected $branches; /** @var string */ protected $rootIdentifier; /** @var mixed[] */ protected $repoData; /** @var bool */ protected $hasIssues = \false; /** @var bool */ protected $isPrivate = \false; /** @var bool */ private $isArchived = \false; /** @var array|false|null */ private $fundingInfo; /** * Git Driver * * @var ?GitDriver */ protected $gitDriver = null; /** * @inheritDoc */ public function initialize() : void { if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\\.git|/)?$#', $this->url, $match)) { throw new \InvalidArgumentException(\sprintf('The GitHub repository URL %s is invalid.', $this->url)); } \assert(\is_string($match[3])); \assert(\is_string($match[4])); $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = \strtolower($match[1] ?? (string) $match[2]); if ($this->originUrl === 'www.github.com') { $this->originUrl = 'github.com'; } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir') . '/' . $this->originUrl . '/' . $this->owner . '/' . $this->repository); $this->cache->setReadOnly($this->config->get('cache-read-only')); if ($this->config->get('use-github-api') === \false || isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) { $this->setupGitDriver($this->url); return; } $this->fetchRootIdentifier(); } public function getRepositoryUrl() : string { return 'https://' . $this->originUrl . '/' . $this->owner . '/' . $this->repository; } /** * @inheritDoc */ public function getRootIdentifier() : string { if ($this->gitDriver) { return $this->gitDriver->getRootIdentifier(); } return $this->rootIdentifier; } /** * @inheritDoc */ public function getUrl() : string { if ($this->gitDriver) { return $this->gitDriver->getUrl(); } return 'https://' . $this->originUrl . '/' . $this->owner . '/' . $this->repository . '.git'; } protected function getApiUrl() : string { if ('github.com' === $this->originUrl) { $apiUrl = 'api.github.com'; } else { $apiUrl = $this->originUrl . '/api/v3'; } return 'https://' . $apiUrl; } /** * @inheritDoc */ public function getSource(string $identifier) : array { if ($this->gitDriver) { return $this->gitDriver->getSource($identifier); } if ($this->isPrivate) { // Private GitHub repositories should be accessed using the // SSH version of the URL. $url = $this->generateSshUrl(); } else { $url = $this->getUrl(); } return ['type' => 'git', 'url' => $url, 'reference' => $identifier]; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { $url = $this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository . '/zipball/' . $identifier; return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } /** * @inheritDoc */ public function getComposerInformation(string $identifier) : ?array { if ($this->gitDriver) { return $this->gitDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && ($res = $this->cache->read($identifier))) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, \json_encode($composer)); } } if ($composer !== null) { // specials for github if (isset($composer['support']) && !\is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source'])) { $label = (\array_search($identifier, $this->getTags()) ?: \array_search($identifier, $this->getBranches())) ?: $identifier; $composer['support']['source'] = \sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); } if (!isset($composer['support']['issues']) && $this->hasIssues) { $composer['support']['issues'] = \sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); } if (!isset($composer['abandoned']) && $this->isArchived) { $composer['abandoned'] = \true; } if (!isset($composer['funding']) && ($funding = $this->getFundingInfo())) { $composer['funding'] = $funding; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * @return array|false */ private function getFundingInfo() { if (null !== $this->fundingInfo) { return $this->fundingInfo; } if ($this->originUrl !== 'github.com') { return $this->fundingInfo = \false; } foreach ([$this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository . '/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/' . $this->owner . '/.github/contents/FUNDING.yml'] as $file) { try { $response = $this->httpDownloader->get($file, ['retry-auth-failure' => \false])->decodeJson(); } catch (TransportException $e) { continue; } if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = \base64_decode($response['content']))) { continue; } break; } if (empty($funding)) { return $this->fundingInfo = \false; } $result = []; $key = null; foreach (Preg::split('{\\r?\\n}', $funding) as $line) { $line = \trim($line); if (Preg::isMatchStrictGroups('{^(\\w+)\\s*:\\s*(.+)$}', $line, $match)) { if ($match[2] === '[') { $key = $match[1]; continue; } if (Preg::isMatchStrictGroups('{^\\[(.*)\\](?:\\s*#.*)?$}', $match[2], $match2)) { foreach (\array_map('trim', Preg::split('{[\'"]?\\s*,\\s*[\'"]?}', $match2[1])) as $item) { $result[] = ['type' => $match[1], 'url' => \trim($item, '"\' ')]; } } elseif (Preg::isMatchStrictGroups('{^([^#].*?)(?:\\s+#.*)?$}', $match[2], $match2)) { $result[] = ['type' => $match[1], 'url' => \trim($match2[1], '"\' ')]; } $key = null; } elseif (Preg::isMatchStrictGroups('{^(\\w+)\\s*:\\s*#\\s*$}', $line, $match)) { $key = $match[1]; } elseif ($key !== null && (Preg::isMatchStrictGroups('{^-\\s*(.+)(?:\\s+#.*)?$}', $line, $match) || Preg::isMatchStrictGroups('{^(.+),(?:\\s*#.*)?$}', $line, $match))) { $result[] = ['type' => $key, 'url' => \trim($match[1], '"\' ')]; } elseif ($key !== null && $line === ']') { $key = null; } } foreach ($result as $key => $item) { switch ($item['type']) { case 'tidelift': $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url']; break; case 'github': $result[$key]['url'] = 'https://github.com/' . \basename($item['url']); break; case 'patreon': $result[$key]['url'] = 'https://www.patreon.com/' . \basename($item['url']); break; case 'otechie': $result[$key]['url'] = 'https://otechie.com/' . \basename($item['url']); break; case 'open_collective': $result[$key]['url'] = 'https://opencollective.com/' . \basename($item['url']); break; case 'liberapay': $result[$key]['url'] = 'https://liberapay.com/' . \basename($item['url']); break; case 'ko_fi': $result[$key]['url'] = 'https://ko-fi.com/' . \basename($item['url']); break; case 'issuehunt': $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url']; break; case 'community_bridge': $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . \basename($item['url']); break; } } return $this->fundingInfo = $result; } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { if ($this->gitDriver) { return $this->gitDriver->getFileContent($file, $identifier); } $resource = $this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository . '/contents/' . $file . '?ref=' . \urlencode($identifier); $resource = $this->getContents($resource)->decodeJson(); // The GitHub contents API only returns files up to 1MB as base64 encoded files // larger files either need be fetched with a raw accept header or by using the git blob endpoint if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { $resource = $this->getContents($resource['git_url'])->decodeJson(); } if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || \false === ($content = \base64_decode($resource['content']))) { throw new \RuntimeException('Could not retrieve ' . $file . ' for ' . $identifier); } return $content; } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { if ($this->gitDriver) { return $this->gitDriver->getChangeDate($identifier); } $resource = $this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository . '/commits/' . \urlencode($identifier); $commit = $this->getContents($resource)->decodeJson(); return new \DateTimeImmutable($commit['commit']['committer']['date']); } /** * @inheritDoc */ public function getTags() : array { if ($this->gitDriver) { return $this->gitDriver->getTags(); } if (null === $this->tags) { $tags = []; $resource = $this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository . '/tags?per_page=100'; do { $response = $this->getContents($resource); $tagsData = $response->decodeJson(); foreach ($tagsData as $tag) { $tags[$tag['name']] = $tag['commit']['sha']; } $resource = $this->getNextPage($response); } while ($resource); $this->tags = $tags; } return $this->tags; } /** * @inheritDoc */ public function getBranches() : array { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $branches = []; $resource = $this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository . '/git/refs/heads?per_page=100'; do { $response = $this->getContents($resource); $branchData = $response->decodeJson(); foreach ($branchData as $branch) { $name = \substr($branch['ref'], 11); if ($name !== 'gh-pages') { $branches[$name] = $branch['object']['sha']; } } $resource = $this->getNextPage($response); } while ($resource); $this->branches = $branches; } return $this->branches; } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if (!Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\\.git|/)?$#', $url, $matches)) { return \false; } $originUrl = $matches[2] ?? (string) $matches[3]; if (!\in_array(\strtolower(Preg::replace('{^www\\.}i', '', $originUrl)), $config->get('github-domains'))) { return \false; } if (!\extension_loaded('openssl')) { $io->writeError('Skipping GitHub driver for ' . $url . ' because the OpenSSL PHP extension is missing.', \true, IOInterface::VERBOSE); return \false; } return \true; } /** * Gives back the loaded /repos// result * * @return mixed[]|null */ public function getRepoData() : ?array { $this->fetchRootIdentifier(); return $this->repoData; } /** * Generate an SSH URL */ protected function generateSshUrl() : string { if (\false !== \strpos($this->originUrl, ':')) { return 'ssh://git@' . $this->originUrl . '/' . $this->owner . '/' . $this->repository . '.git'; } return 'git@' . $this->originUrl . ':' . $this->owner . '/' . $this->repository . '.git'; } /** * @inheritDoc */ protected function getContents(string $url, bool $fetchingRepoData = \false) : Response { try { return parent::getContents($url); } catch (TransportException $e) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader); switch ($e->getCode()) { case 401: case 404: // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 if (!$fetchingRepoData) { throw $e; } if ($gitHubUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive()) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } $scopesIssued = []; $scopesNeeded = []; if ($headers = $e->getHeaders()) { if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) { $scopesIssued = \explode(' ', $scopes); } if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { $scopesNeeded = \explode(' ', $scopes); } } $scopesFailed = \array_diff($scopesNeeded, $scopesIssued); // non-authenticated requests get no scopesNeeded, so ask for credentials // authenticated requests which failed some scopes should ask for new credentials too if (!$headers || !\count($scopesNeeded) || \count($scopesFailed)) { $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata (' . $this->url . ')'); } return parent::getContents($url); case 403: if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } $rateLimited = $gitHubUtil->isRateLimited((array) $e->getHeaders()); if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the ' . $this->url . ' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); throw $e; } $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit (' . $this->url . ')'); return parent::getContents($url); } if ($rateLimited) { $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders()); $this->io->writeError(\sprintf('GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', $rateLimit['limit'], $rateLimit['reset'])); } throw $e; default: throw $e; } } } /** * Fetch root identifier from GitHub * * @throws TransportException */ protected function fetchRootIdentifier() : void { if ($this->repoData) { return; } $repoDataUrl = $this->getApiUrl() . '/repos/' . $this->owner . '/' . $this->repository; try { $this->repoData = $this->getContents($repoDataUrl, \true)->decodeJson(); } catch (TransportException $e) { if ($e->getCode() === 499) { $this->attemptCloneFallback(); } else { throw $e; } } if (null === $this->repoData && null !== $this->gitDriver) { return; } $this->owner = $this->repoData['owner']['login']; $this->repository = $this->repoData['name']; $this->isPrivate = !empty($this->repoData['private']); if (isset($this->repoData['default_branch'])) { $this->rootIdentifier = $this->repoData['default_branch']; } elseif (isset($this->repoData['master_branch'])) { $this->rootIdentifier = $this->repoData['master_branch']; } else { $this->rootIdentifier = 'master'; } $this->hasIssues = !empty($this->repoData['has_issues']); $this->isArchived = !empty($this->repoData['archived']); } /** * @phpstan-impure * * @return true * @throws \RuntimeException */ protected function attemptCloneFallback() : bool { $this->isPrivate = \true; try { // If this repository may be private (hard to say for sure, // GitHub returns 404 for private repositories) and we // cannot ask for authentication credentials (because we // are not interactive) then we fallback to GitDriver. $this->setupGitDriver($this->generateSshUrl()); return \true; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode so that you can enter your GitHub credentials'); throw $e; } } protected function setupGitDriver(string $url) : void { $this->gitDriver = new \Composer\Repository\Vcs\GitDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $this->gitDriver->initialize(); } protected function getNextPage(Response $response) : ?string { $header = $response->getHeader('link'); if (!$header) { return null; } $links = \explode(',', $header); foreach ($links as $link) { if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Config; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\Util\Filesystem; use Composer\Util\Http\Response; /** * A driver implementation for driver with authentication interaction. * * @author François Pluchino */ abstract class VcsDriver implements \Composer\Repository\Vcs\VcsDriverInterface { /** @var string */ protected $url; /** @var string */ protected $originUrl; /** @var array */ protected $repoConfig; /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var HttpDownloader */ protected $httpDownloader; /** @var array */ protected $infoCache = []; /** @var ?Cache */ protected $cache; /** * Constructor. * * @param array{url: string}&array $repoConfig The repository configuration * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking * @param ProcessExecutor $process Process instance, injectable for mocking */ public final function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process) { if (Filesystem::isLocalPath($repoConfig['url'])) { $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']); } $this->url = $repoConfig['url']; $this->originUrl = $repoConfig['url']; $this->repoConfig = $repoConfig; $this->io = $io; $this->config = $config; $this->httpDownloader = $httpDownloader; $this->process = $process; } /** * Returns whether or not the given $identifier should be cached or not. */ protected function shouldCache(string $identifier) : bool { return $this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD', $identifier); } /** * @inheritDoc */ public function getComposerInformation(string $identifier) : ?array { if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && ($res = $this->cache->read($identifier))) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, JsonFile::encode($composer, 0)); } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * @return array|null */ protected function getBaseComposerInformation(string $identifier) : ?array { $composerFileContent = $this->getFileContent('composer.json', $identifier); if (!$composerFileContent) { return null; } $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); if ([] === $composer || !\is_array($composer)) { return null; } if (empty($composer['time']) && null !== ($changeDate = $this->getChangeDate($identifier))) { $composer['time'] = $changeDate->format(\DATE_RFC3339); } return $composer; } /** * @inheritDoc */ public function hasComposerFile(string $identifier) : bool { try { return null !== $this->getComposerInformation($identifier); } catch (TransportException $e) { } return \false; } /** * Get the https or http protocol depending on SSL support. * * Call this only if you know that the server supports both. * * @return string The correct type of protocol */ protected function getScheme() : string { if (\extension_loaded('openssl')) { return 'https'; } return 'http'; } /** * Get the remote content. * * @param string $url The URL of content * * @throws TransportException */ protected function getContents(string $url) : Response { $options = $this->repoConfig['options'] ?? []; return $this->httpDownloader->get($url, $options); } /** * @inheritDoc */ public function cleanup() : void { } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Cache; use Composer\Config; use Composer\Json\JsonFile; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\Util\Url; use Composer\Util\Svn as SvnUtil; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; /** * @author Jordi Boggiano * @author Till Klampaeckel */ class SvnDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var string */ protected $baseUrl; /** @var array Map of tag name to identifier */ protected $tags; /** @var array Map of branch name to identifier */ protected $branches; /** @var ?string */ protected $rootIdentifier; /** @var string|false */ protected $trunkPath = 'trunk'; /** @var string */ protected $branchesPath = 'branches'; /** @var string */ protected $tagsPath = 'tags'; /** @var string */ protected $packagePath = ''; /** @var bool */ protected $cacheCredentials = \true; /** * @var \Composer\Util\Svn */ private $util; /** * @inheritDoc */ public function initialize() : void { $this->url = $this->baseUrl = \rtrim(self::normalizeUrl($this->url), '/'); SvnUtil::cleanEnv(); if (isset($this->repoConfig['trunk-path'])) { $this->trunkPath = $this->repoConfig['trunk-path']; } if (isset($this->repoConfig['branches-path'])) { $this->branchesPath = $this->repoConfig['branches-path']; } if (isset($this->repoConfig['tags-path'])) { $this->tagsPath = $this->repoConfig['tags-path']; } if (\array_key_exists('svn-cache-credentials', $this->repoConfig)) { $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials']; } if (isset($this->repoConfig['package-path'])) { $this->packagePath = '/' . \trim($this->repoConfig['package-path'], '/'); } if (\false !== ($pos = \strrpos($this->url, '/' . $this->trunkPath))) { $this->baseUrl = \substr($this->url, 0, $pos); } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->baseUrl))); $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->getBranches(); $this->getTags(); } /** * @inheritDoc */ public function getRootIdentifier() : string { return $this->rootIdentifier ?: $this->trunkPath; } /** * @inheritDoc */ public function getUrl() : string { return $this->url; } /** * @inheritDoc */ public function getSource(string $identifier) : array { return ['type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier]; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { return null; } /** * @inheritDoc */ protected function shouldCache(string $identifier) : bool { return $this->cache && Preg::isMatch('{@\\d+$}', $identifier); } /** * @inheritDoc */ public function getComposerInformation(string $identifier) : ?array { if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && ($res = $this->cache->read($identifier . '.json'))) { // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array // and fix outdated invalid cache files if ($res === '""') { $res = 'null'; $this->cache->write($identifier . '.json', \json_encode(null)); } return $this->infoCache[$identifier] = JsonFile::parseJson($res); } try { $composer = $this->getBaseComposerInformation($identifier); } catch (TransportException $e) { $message = $e->getMessage(); if (\stripos($message, 'path not found') === \false && \stripos($message, 'svn: warning: W160013') === \false) { throw $e; } // remember a not-existent composer.json $composer = null; } if ($this->shouldCache($identifier)) { $this->cache->write($identifier . '.json', \json_encode($composer)); } $this->infoCache[$identifier] = $composer; } // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array if (!\is_array($this->infoCache[$identifier])) { return null; } return $this->infoCache[$identifier]; } public function getFileContent(string $file, string $identifier) : ?string { $identifier = '/' . \trim($identifier, '/') . '/'; Preg::match('{^(.+?)(@\\d+)?/$}', $identifier, $match); if (!empty($match[2])) { $path = $match[1]; $rev = $match[2]; } else { $path = $identifier; $rev = ''; } try { $resource = $path . $file; $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); if (!\trim($output)) { return null; } } catch (\RuntimeException $e) { throw new TransportException($e->getMessage()); } return $output; } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { $identifier = '/' . \trim($identifier, '/') . '/'; Preg::match('{^(.+?)(@\\d+)?/$}', $identifier, $match); if (null !== $match[2] && null !== $match[1]) { $path = $match[1]; $rev = $match[2]; } else { $path = $identifier; $rev = ''; } $output = $this->execute('svn info', $this->baseUrl . $path . $rev); foreach ($this->process->splitLines($output) as $line) { if ($line && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); } } return null; } /** * @inheritDoc */ public function getTags() : array { if (null === $this->tags) { $tags = []; if ($this->tagsPath !== \false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath); if ($output) { $lastRev = 0; foreach ($this->process->splitLines($output) as $line) { $line = \trim($line); if ($line && Preg::isMatch('{^\\s*(\\S+).*?(\\S+)\\s*$}', $line, $match)) { if (isset($match[1], $match[2])) { if ($match[2] === './') { $lastRev = (int) $match[1]; } else { $tags[\rtrim($match[2], '/')] = $this->buildIdentifier('/' . $this->tagsPath . '/' . $match[2], \max($lastRev, (int) $match[1])); } } } } } } $this->tags = $tags; } return $this->tags; } /** * @inheritDoc */ public function getBranches() : array { if (null === $this->branches) { $branches = []; if (\false === $this->trunkPath) { $trunkParent = $this->baseUrl . '/'; } else { $trunkParent = $this->baseUrl . '/' . $this->trunkPath; } $output = $this->execute('svn ls --verbose', $trunkParent); if ($output) { foreach ($this->process->splitLines($output) as $line) { $line = \trim($line); if ($line && Preg::isMatch('{^\\s*(\\S+).*?(\\S+)\\s*$}', $line, $match)) { if (isset($match[1], $match[2]) && $match[2] === './') { $branches['trunk'] = $this->buildIdentifier('/' . $this->trunkPath, (int) $match[1]); $this->rootIdentifier = $branches['trunk']; break; } } } } unset($output); if ($this->branchesPath !== \false) { $output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath); if ($output) { $lastRev = 0; foreach ($this->process->splitLines(\trim($output)) as $line) { $line = \trim($line); if ($line && Preg::isMatch('{^\\s*(\\S+).*?(\\S+)\\s*$}', $line, $match)) { if (isset($match[1], $match[2])) { if ($match[2] === './') { $lastRev = (int) $match[1]; } else { $branches[\rtrim($match[2], '/')] = $this->buildIdentifier('/' . $this->branchesPath . '/' . $match[2], \max($lastRev, (int) $match[1])); } } } } } } $this->branches = $branches; } return $this->branches; } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { $url = self::normalizeUrl($url); if (Preg::isMatch('#(^svn://|^svn\\+ssh://|svn\\.)#i', $url)) { return \true; } // proceed with deep check for local urls since they are fast to process if (!$deep && !Filesystem::isLocalPath($url)) { return \false; } $process = new ProcessExecutor($io); $exit = $process->execute("svn info --non-interactive -- " . ProcessExecutor::escape($url), $ignoredOutput); if ($exit === 0) { // This is definitely a Subversion repository. return \true; } // Subversion client 1.7 and older if (\false !== \stripos($process->getErrorOutput(), 'authorization failed:')) { // This is likely a remote Subversion repository that requires // authentication. We will handle actual authentication later. return \true; } // Subversion client 1.8 and newer if (\false !== \stripos($process->getErrorOutput(), 'Authentication failed')) { // This is likely a remote Subversion or newer repository that requires // authentication. We will handle actual authentication later. return \true; } return \false; } /** * An absolute path (leading '/') is converted to a file:// url. */ protected static function normalizeUrl(string $url) : string { $fs = new Filesystem(); if ($fs->isAbsolutePath($url)) { return 'file://' . \strtr($url, '\\', '/'); } return $url; } /** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command The svn command to run. * @param string $url The SVN URL. * @throws \RuntimeException */ protected function execute(string $command, string $url) : string { if (null === $this->util) { $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process); $this->util->setCacheCredentials($this->cacheCredentials); } try { return $this->util->execute($command, $url); } catch (\RuntimeException $e) { if (null === $this->util->binaryVersion()) { throw new \RuntimeException('Failed to load ' . $this->url . ', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException('Repository ' . $this->url . ' could not be processed, ' . $e->getMessage()); } } /** * Build the identifier respecting "package-path" config option * * @param string $baseDir The path to trunk/branch/tag * @param int $revision The revision mark to add to identifier */ protected function buildIdentifier(string $baseDir, int $revision) : string { return \rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Cache; use Composer\Pcre\Preg; use Composer\Util\Hg as HgUtils; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; /** * @author Per Bernhardt */ class HgDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var array Map of tag name to identifier */ protected $tags; /** @var array Map of branch name to identifier */ protected $branches; /** @var string */ protected $rootIdentifier; /** @var string */ protected $repoDir; /** * @inheritDoc */ public function initialize() : void { if (Filesystem::isLocalPath($this->url)) { $this->repoDir = $this->url; } else { if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $cacheDir = $this->config->get('cache-vcs-dir'); $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', $this->url) . '/'; $fs = new Filesystem(); $fs->ensureDirectoryExists($cacheDir); if (!\is_writable(\dirname($this->repoDir))) { throw new \RuntimeException('Can not clone ' . $this->url . ' to access package information. The "' . $cacheDir . '" directory is not writable by the current user.'); } // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($this->url, $this->io); $hgUtils = new HgUtils($this->io, $this->config, $this->process); // update the repo if it is a valid hg repository if (\is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) { if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) { $this->io->writeError('Failed to update ' . $this->url . ', package information from this repository may be outdated (' . $this->process->getErrorOutput() . ')'); } } else { // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); $repoDir = $this->repoDir; $command = static function ($url) use($repoDir) : string { return \sprintf('hg clone --noupdate -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir)); }; $hgUtils->runCommand($command, $this->url, null); } } $this->getTags(); $this->getBranches(); } /** * @inheritDoc */ public function getRootIdentifier() : string { if (null === $this->rootIdentifier) { $this->process->execute('hg tip --template "{node}"', $output, $this->repoDir); $output = $this->process->splitLines($output); $this->rootIdentifier = $output[0]; } return $this->rootIdentifier; } /** * @inheritDoc */ public function getUrl() : string { return $this->url; } /** * @inheritDoc */ public function getSource(string $identifier) : array { return ['type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier]; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { return null; } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); } $resource = \sprintf('hg cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute($resource, $content, $this->repoDir); if (!\trim($content)) { return null; } return $content; } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { $this->process->execute(\sprintf('hg log --template "{date|rfc3339date}" -r %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir); return new \DateTimeImmutable(\trim($output), new \DateTimeZone('UTC')); } /** * @inheritDoc */ public function getTags() : array { if (null === $this->tags) { $tags = []; $this->process->execute('hg tags', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag && Preg::isMatchStrictGroups('(^([^\\s]+)\\s+\\d+:(.*)$)', $tag, $match)) { $tags[$match[1]] = $match[2]; } } unset($tags['tip']); $this->tags = $tags; } return $this->tags; } /** * @inheritDoc */ public function getBranches() : array { if (null === $this->branches) { $branches = []; $bookmarks = []; $this->process->execute('hg branches', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('(^([^\\s]+)\\s+\\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } } $this->process->execute('hg bookmarks', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('(^(?:[\\s*]*)([^\\s]+)\\s+\\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { $bookmarks[$match[1]] = $match[2]; } } // Branches will have preference over bookmarks $this->branches = \array_merge($bookmarks, $branches); } return $this->branches; } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\\.kilnhg.com)#i', $url)) { return \true; } // local filesystem if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!\is_dir($url)) { return \false; } $process = new ProcessExecutor($io); // check whether there is a hg repo in that path if ($process->execute('hg summary', $output, $url) === 0) { return \true; } } if (!$deep) { return \false; } $process = new ProcessExecutor($io); $exit = $process->execute(\sprintf('hg identify -- %s', ProcessExecutor::escape($url)), $ignored); return $exit === 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; use Composer\Util\Perforce; use Composer\Util\Http\Response; /** * @author Matt Whittom */ class PerforceDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var string */ protected $depot; /** @var string */ protected $branch; /** @var ?Perforce */ protected $perforce = null; /** * @inheritDoc */ public function initialize() : void { $this->depot = $this->repoConfig['depot']; $this->branch = ''; if (!empty($this->repoConfig['branch'])) { $this->branch = $this->repoConfig['branch']; } $this->initPerforce($this->repoConfig); $this->perforce->p4Login(); $this->perforce->checkStream(); $this->perforce->writeP4ClientSpec(); $this->perforce->connectClient(); } /** * @param array $repoConfig */ private function initPerforce(array $repoConfig) : void { if (!empty($this->perforce)) { return; } if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { return $this->perforce->getFileContent($file, $identifier); } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { return null; } /** * @inheritDoc */ public function getRootIdentifier() : string { return $this->branch; } /** * @inheritDoc */ public function getBranches() : array { return $this->perforce->getBranches(); } /** * @inheritDoc */ public function getTags() : array { return $this->perforce->getTags(); } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { return null; } /** * @inheritDoc */ public function getSource(string $identifier) : array { return ['type' => 'perforce', 'url' => $this->repoConfig['url'], 'reference' => $identifier, 'p4user' => $this->perforce->getUser()]; } /** * @inheritDoc */ public function getUrl() : string { return $this->url; } /** * @inheritDoc */ public function hasComposerFile(string $identifier) : bool { $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); return !empty($composerInfo); } /** * @inheritDoc */ public function getContents(string $url) : Response { throw new \BadMethodCallException('Not implemented/used in PerforceDriver'); } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if ($deep || Preg::isMatch('#\\b(perforce|p4)\\b#i', $url)) { return Perforce::checkServerExists($url, new ProcessExecutor($io)); } return \false; } /** * @inheritDoc */ public function cleanup() : void { $this->perforce->cleanupClientSpec(); $this->perforce = null; } public function getDepot() : string { return $this->depot; } public function getBranch() : string { return $this->branch; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\IO\IOInterface; use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\Pcre\Preg; use Composer\Util\Bitbucket; use Composer\Util\Http\Response; /** * @author Per Bernhardt */ class GitBitbucketDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var string */ protected $owner; /** @var string */ protected $repository; /** @var bool */ private $hasIssues = \false; /** @var ?string */ private $rootIdentifier; /** @var array Map of tag name to identifier */ private $tags; /** @var array Map of branch name to identifier */ private $branches; /** @var string */ private $branchesUrl = ''; /** @var string */ private $tagsUrl = ''; /** @var string */ private $homeUrl = ''; /** @var string */ private $website = ''; /** @var string */ private $cloneHttpsUrl = ''; /** @var array */ private $repoData; /** * @var ?VcsDriver */ protected $fallbackDriver = null; /** @var string|null if set either git or hg */ private $vcsType; /** * @inheritDoc */ public function initialize() : void { if (!Preg::isMatchStrictGroups('#^https?://bitbucket\\.org/([^/]+)/([^/]+?)(?:\\.git|/?)?$#i', $this->url, $match)) { throw new \InvalidArgumentException(\sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url)); } $this->owner = $match[1]; $this->repository = $match[2]; $this->originUrl = 'bitbucket.org'; $this->cache = new Cache($this->io, \implode('/', [$this->config->get('cache-repo-dir'), $this->originUrl, $this->owner, $this->repository])); $this->cache->setReadOnly($this->config->get('cache-read-only')); } /** * @inheritDoc */ public function getUrl() : string { if ($this->fallbackDriver) { return $this->fallbackDriver->getUrl(); } return $this->cloneHttpsUrl; } /** * Attempts to fetch the repository data via the BitBucket API and * sets some parameters which are used in other methods * * @phpstan-impure */ protected function getRepoData() : bool { $resource = \sprintf('https://api.bitbucket.org/2.0/repositories/%s/%s?%s', $this->owner, $this->repository, \http_build_query(['fields' => '-project,-owner'], '', '&')); $repoData = $this->fetchWithOAuthCredentials($resource, \true)->decodeJson(); if ($this->fallbackDriver) { return \false; } $this->parseCloneUrls($repoData['links']['clone']); $this->hasIssues = !empty($repoData['has_issues']); $this->branchesUrl = $repoData['links']['branches']['href']; $this->tagsUrl = $repoData['links']['tags']['href']; $this->homeUrl = $repoData['links']['html']['href']; $this->website = $repoData['website']; $this->vcsType = $repoData['scm']; $this->repoData = $repoData; return \true; } /** * @inheritDoc */ public function getComposerInformation(string $identifier) : ?array { if ($this->fallbackDriver) { return $this->fallbackDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && ($res = $this->cache->read($identifier))) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, \json_encode($composer)); } } if ($composer !== null) { // specials for bitbucket if (isset($composer['support']) && !\is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source'])) { $label = (\array_search($identifier, $this->getTags()) ?: \array_search($identifier, $this->getBranches())) ?: $identifier; if (\array_key_exists($label, $tags = $this->getTags())) { $hash = $tags[$label]; } elseif (\array_key_exists($label, $branches = $this->getBranches())) { $hash = $branches[$label]; } if (!isset($hash)) { $composer['support']['source'] = \sprintf('https://%s/%s/%s/src', $this->originUrl, $this->owner, $this->repository); } else { $composer['support']['source'] = \sprintf('https://%s/%s/%s/src/%s/?at=%s', $this->originUrl, $this->owner, $this->repository, $hash, $label); } } if (!isset($composer['support']['issues']) && $this->hasIssues) { $composer['support']['issues'] = \sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); } if (!isset($composer['homepage'])) { $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { if ($this->fallbackDriver) { return $this->fallbackDriver->getFileContent($file, $identifier); } if (\strpos($identifier, '/') !== \false) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = \sprintf('https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s', $this->owner, $this->repository, $identifier, $file); return $this->fetchWithOAuthCredentials($resource)->getBody(); } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { if ($this->fallbackDriver) { return $this->fallbackDriver->getChangeDate($identifier); } if (\strpos($identifier, '/') !== \false) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = \sprintf('https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', $this->owner, $this->repository, $identifier); $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson(); return new \DateTimeImmutable($commit['date']); } /** * @inheritDoc */ public function getSource(string $identifier) : array { if ($this->fallbackDriver) { return $this->fallbackDriver->getSource($identifier); } return ['type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier]; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { if ($this->fallbackDriver) { return $this->fallbackDriver->getDist($identifier); } $url = \sprintf('https://bitbucket.org/%s/%s/get/%s.zip', $this->owner, $this->repository, $identifier); return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } /** * @inheritDoc */ public function getTags() : array { if ($this->fallbackDriver) { return $this->fallbackDriver->getTags(); } if (null === $this->tags) { $tags = []; $resource = \sprintf('%s?%s', $this->tagsUrl, \http_build_query(['pagelen' => 100, 'fields' => 'values.name,values.target.hash,next', 'sort' => '-target.date'], '', '&')); $hasNext = \true; while ($hasNext) { $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); foreach ($tagsData['values'] as $data) { $tags[$data['name']] = $data['target']['hash']; } if (empty($tagsData['next'])) { $hasNext = \false; } else { $resource = $tagsData['next']; } } $this->tags = $tags; } return $this->tags; } /** * @inheritDoc */ public function getBranches() : array { if ($this->fallbackDriver) { return $this->fallbackDriver->getBranches(); } if (null === $this->branches) { $branches = []; $resource = \sprintf('%s?%s', $this->branchesUrl, \http_build_query(['pagelen' => 100, 'fields' => 'values.name,values.target.hash,values.heads,next', 'sort' => '-target.date'], '', '&')); $hasNext = \true; while ($hasNext) { $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); foreach ($branchData['values'] as $data) { $branches[$data['name']] = $data['target']['hash']; } if (empty($branchData['next'])) { $hasNext = \false; } else { $resource = $branchData['next']; } } $this->branches = $branches; } return $this->branches; } /** * Get the remote content. * * @param string $url The URL of content * * @return Response The result * * @phpstan-impure */ protected function fetchWithOAuthCredentials(string $url, bool $fetchingRepoData = \false) : Response { try { return parent::getContents($url); } catch (TransportException $e) { $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); if (\in_array($e->getCode(), [403, 404], \true) || 401 === $e->getCode() && \strpos($e->getMessage(), 'Could not authenticate against') === 0) { if (!$this->io->hasAuthentication($this->originUrl) && $bitbucketUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } } throw $e; } } /** * Generate an SSH URL */ protected function generateSshUrl() : string { return 'git@' . $this->originUrl . ':' . $this->owner . '/' . $this->repository . '.git'; } /** * @phpstan-impure * * @return true * @throws \RuntimeException */ protected function attemptCloneFallback() : bool { try { $this->setupFallbackDriver($this->generateSshUrl()); return \true; } catch (\RuntimeException $e) { $this->fallbackDriver = null; $this->io->writeError('Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode' . ' so that you can enter your Bitbucket OAuth consumer credentials'); throw $e; } } protected function setupFallbackDriver(string $url) : void { $this->fallbackDriver = new \Composer\Repository\Vcs\GitDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $this->fallbackDriver->initialize(); } /** * @param array $cloneLinks */ protected function parseCloneUrls(array $cloneLinks) : void { foreach ($cloneLinks as $cloneLink) { if ($cloneLink['name'] === 'https') { // Format: https://(user@)bitbucket.org/{user}/{repo} // Strip username from URL (only present in clone URL's for private repositories) $this->cloneHttpsUrl = Preg::replace('/https:\\/\\/([^@]+@)?/', 'https://', $cloneLink['href']); } } } /** * @inheritDoc */ public function getRootIdentifier() : string { if ($this->fallbackDriver) { return $this->fallbackDriver->getRootIdentifier(); } if (null === $this->rootIdentifier) { if (!$this->getRepoData()) { if (!$this->fallbackDriver) { throw new \LogicException('A fallback driver should be setup if getRepoData returns false'); } return $this->fallbackDriver->getRootIdentifier(); } if ($this->vcsType !== 'git') { throw new \RuntimeException($this->url . ' does not appear to be a git repository, use ' . $this->cloneHttpsUrl . ' but remember that Bitbucket no longer supports the mercurial repositories. ' . 'https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket'); } $this->rootIdentifier = $this->repoData['mainbranch']['name'] ?? 'master'; } return $this->rootIdentifier; } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if (!Preg::isMatch('#^https?://bitbucket\\.org/([^/]+)/([^/]+?)(\\.git|/?)?$#i', $url)) { return \false; } if (!\extension_loaded('openssl')) { $io->writeError('Skipping Bitbucket git driver for ' . $url . ' because the OpenSSL PHP extension is missing.', \true, IOInterface::VERBOSE); return \false; } return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\Util\Url; use Composer\Util\Git as GitUtil; use Composer\IO\IOInterface; use Composer\Cache; use Composer\Config; /** * @author Jordi Boggiano */ class GitDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var array Map of tag name (can be turned to an int by php if it is a numeric name) to identifier */ protected $tags; /** @var array Map of branch name (can be turned to an int by php if it is a numeric name) to identifier */ protected $branches; /** @var string */ protected $rootIdentifier; /** @var string */ protected $repoDir; /** * @inheritDoc */ public function initialize() : void { if (Filesystem::isLocalPath($this->url)) { $this->url = Preg::replace('{[\\/]\\.git/?$}', '', $this->url); if (!\is_dir($this->url)) { throw new \RuntimeException('Failed to read package information from ' . $this->url . ' as the path does not exist'); } $this->repoDir = $this->url; $cacheUrl = \realpath($this->url); } else { if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; GitUtil::cleanEnv(); $fs = new Filesystem(); $fs->ensureDirectoryExists(\dirname($this->repoDir)); if (!\is_writable(\dirname($this->repoDir))) { throw new \RuntimeException('Can not clone ' . $this->url . ' to access package information. The "' . \dirname($this->repoDir) . '" directory is not writable by the current user.'); } if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) { throw new \InvalidArgumentException('The source URL ' . $this->url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); if (!$gitUtil->syncMirror($this->url, $this->repoDir)) { if (!\is_dir($this->repoDir)) { throw new \RuntimeException('Failed to clone ' . $this->url . ' to read package information from it'); } $this->io->writeError('Failed to update ' . $this->url . ', package information from this repository may be outdated'); } $cacheUrl = $this->url; } $this->getTags(); $this->getBranches(); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($cacheUrl))); $this->cache->setReadOnly($this->config->get('cache-read-only')); } /** * @inheritDoc */ public function getRootIdentifier() : string { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'master'; $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem()); if (!Filesystem::isLocalPath($this->url)) { $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, \false); if ($defaultBranch !== null) { return $this->rootIdentifier = $defaultBranch; } } // select currently checked out branch as default branch $this->process->execute('git branch --no-color', $output, $this->repoDir); $branches = $this->process->splitLines($output); if (!\in_array('* master', $branches)) { foreach ($branches as $branch) { if ($branch && Preg::isMatchStrictGroups('{^\\* +(\\S+)}', $branch, $match)) { $this->rootIdentifier = $match[1]; break; } } } } return $this->rootIdentifier; } /** * @inheritDoc */ public function getUrl() : string { return $this->url; } /** * @inheritDoc */ public function getSource(string $identifier) : array { return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier]; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { return null; } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); } $resource = \sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute(\sprintf('git show %s', $resource), $content, $this->repoDir); if (!\trim($content)) { return null; } return $content; } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { $this->process->execute(\sprintf('git -c log.showSignature=false log -1 --format=%%at %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir); return new \DateTimeImmutable('@' . \trim($output), new \DateTimeZone('UTC')); } /** * @inheritDoc */ public function getTags() : array { if (null === $this->tags) { $this->tags = []; $this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir); foreach ($output = $this->process->splitLines($output) as $tag) { if ($tag && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\\S+?)(\\^\\{\\})?$}', $tag, $match)) { $this->tags[$match[2]] = (string) $match[1]; } } } return $this->tags; } /** * @inheritDoc */ public function getBranches() : array { if (null === $this->branches) { $branches = []; $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\\* )? *(\\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } } } $this->branches = $branches; } return $this->branches; } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if (Preg::isMatch('#(^git://|\\.git/?$|git(?:olite)?@|//git\\.|//github.com/)#i', $url)) { return \true; } // local filesystem if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!\is_dir($url)) { return \false; } $process = new ProcessExecutor($io); // check whether there is a git repo in that path if ($process->execute('git tag', $output, $url) === 0) { return \true; } } if (!$deep) { return \false; } $gitUtil = new GitUtil($io, $config, new ProcessExecutor($io), new Filesystem()); GitUtil::cleanEnv(); try { $gitUtil->runCommand(static function ($url) : string { return 'git ls-remote --heads -- ' . ProcessExecutor::escape($url); }, $url, \sys_get_temp_dir()); } catch (\RuntimeException $e) { return \false; } return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; use Composer\Util\HttpDownloader; use Composer\Util\GitLab; use Composer\Util\Http\Response; /** * Driver for GitLab API, use the Git driver for local checkouts. * * @author Henrik Bjørnskov * @author Jérôme Tamarelle */ class GitLabDriver extends \Composer\Repository\Vcs\VcsDriver { /** * @var string * @phpstan-var 'https'|'http' */ private $scheme; /** @var string */ private $namespace; /** @var string */ private $repository; /** * @var mixed[] Project data returned by GitLab API */ private $project = null; /** * @var array Keeps commits returned by GitLab API as commit id => info */ private $commits = []; /** @var array Map of tag name to identifier */ private $tags; /** @var array Map of branch name to identifier */ private $branches; /** * Git Driver * * @var ?GitDriver */ protected $gitDriver = null; /** * Protocol to force use of for repository URLs. * * @var string One of ssh, http */ protected $protocol; /** * Defaults to true unless we can make sure it is public * * @var bool defines whether the repo is private or not */ private $isPrivate = \true; /** * @var bool true if the origin has a port number or a path component in it */ private $hasNonstandardOrigin = \false; public const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\\.git|/)?$#'; /** * Extracts information from the repository url. * * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. * * @inheritDoc */ public function initialize() : void { if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) { throw new \InvalidArgumentException(\sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); } \assert(\is_string($match['parts'])); \assert(\is_string($match['repo'])); $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $configuredDomains = $this->config->get('gitlab-domains'); $urlParts = \explode('/', $match['parts']); $this->scheme = \in_array($match['scheme'], ['https', 'http'], \true) ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === \false ? 'http' : 'https'); $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); if (\false === $origin) { throw new \LogicException('It should not be possible to create a gitlab driver with an unparseable origin URL (' . $this->url . ')'); } $this->originUrl = $origin; if (\is_string($protocol = $this->config->get('gitlab-protocol'))) { // https treated as a synonym for http. if (!\in_array($protocol, ['git', 'http', 'https'])) { throw new \RuntimeException('gitlab-protocol must be one of git, http.'); } $this->protocol = $protocol === 'git' ? 'ssh' : 'http'; } if (\false !== \strpos($this->originUrl, ':') || \false !== \strpos($this->originUrl, '/')) { $this->hasNonstandardOrigin = \true; } $this->namespace = \implode('/', $urlParts); $this->repository = Preg::replace('#(\\.git)$#', '', $match['repo']); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir') . '/' . $this->originUrl . '/' . $this->namespace . '/' . $this->repository); $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->fetchProject(); } /** * Updates the HttpDownloader instance. * Mainly useful for tests. * * @internal */ public function setHttpDownloader(HttpDownloader $httpDownloader) : void { $this->httpDownloader = $httpDownloader; } /** * @inheritDoc */ public function getComposerInformation(string $identifier) : ?array { if ($this->gitDriver) { return $this->gitDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && ($res = $this->cache->read($identifier))) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, \json_encode($composer)); } } if (null !== $composer) { // specials for gitlab (this data is only available if authentication is provided) if (isset($composer['support']) && !\is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source']) && isset($this->project['web_url'])) { $label = (\array_search($identifier, $this->getTags(), \true) ?: \array_search($identifier, $this->getBranches(), \true)) ?: $identifier; $composer['support']['source'] = \sprintf('%s/-/tree/%s', $this->project['web_url'], $label); } if (!isset($composer['support']['issues']) && !empty($this->project['issues_enabled']) && isset($this->project['web_url'])) { $composer['support']['issues'] = \sprintf('%s/-/issues', $this->project['web_url']); } if (!isset($composer['abandoned']) && !empty($this->project['archived'])) { $composer['abandoned'] = \true; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { if ($this->gitDriver) { return $this->gitDriver->getFileContent($file, $identifier); } // Convert the root identifier to a cacheable commit id if (!Preg::isMatch('{[a-f0-9]{40}}i', $identifier)) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = $this->getApiUrl() . '/repository/files/' . $this->urlEncodeAll($file) . '/raw?ref=' . $identifier; try { $content = $this->getContents($resource)->getBody(); } catch (TransportException $e) { if ($e->getCode() !== 404) { throw $e; } return null; } return $content; } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { if ($this->gitDriver) { return $this->gitDriver->getChangeDate($identifier); } if (isset($this->commits[$identifier])) { return new \DateTimeImmutable($this->commits[$identifier]['committed_date']); } return null; } public function getRepositoryUrl() : string { if ($this->protocol) { return $this->project["{$this->protocol}_url_to_repo"]; } return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo']; } /** * @inheritDoc */ public function getUrl() : string { if ($this->gitDriver) { return $this->gitDriver->getUrl(); } return $this->project['web_url']; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { $url = $this->getApiUrl() . '/repository/archive.zip?sha=' . $identifier; return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } /** * @inheritDoc */ public function getSource(string $identifier) : array { if ($this->gitDriver) { return $this->gitDriver->getSource($identifier); } return ['type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier]; } /** * @inheritDoc */ public function getRootIdentifier() : string { if ($this->gitDriver) { return $this->gitDriver->getRootIdentifier(); } return $this->project['default_branch']; } /** * @inheritDoc */ public function getBranches() : array { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $this->branches = $this->getReferences('branches'); } return $this->branches; } /** * @inheritDoc */ public function getTags() : array { if ($this->gitDriver) { return $this->gitDriver->getTags(); } if (null === $this->tags) { $this->tags = $this->getReferences('tags'); } return $this->tags; } /** * @return string Base URL for GitLab API v3 */ public function getApiUrl() : string { return $this->scheme . '://' . $this->originUrl . '/api/v4/projects/' . $this->urlEncodeAll($this->namespace) . '%2F' . $this->urlEncodeAll($this->repository); } /** * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.` */ private function urlEncodeAll(string $string) : string { $encoded = ''; for ($i = 0; isset($string[$i]); $i++) { $character = $string[$i]; if (!\ctype_alnum($character) && !\in_array($character, ['-', '_'], \true)) { $character = '%' . \sprintf('%02X', \ord($character)); } $encoded .= $character; } return $encoded; } /** * @return string[] where keys are named references like tags or branches and the value a sha */ protected function getReferences(string $type) : array { $perPage = 100; $resource = $this->getApiUrl() . '/repository/' . $type . '?per_page=' . $perPage; $references = []; do { $response = $this->getContents($resource); $data = $response->decodeJson(); foreach ($data as $datum) { $references[$datum['name']] = $datum['commit']['id']; // Keep the last commit date of a reference to avoid // unnecessary API call when retrieving the composer file. $this->commits[$datum['commit']['id']] = $datum['commit']; } if (\count($data) >= $perPage) { $resource = $this->getNextPage($response); } else { $resource = \false; } } while ($resource); return $references; } protected function fetchProject() : void { if (!\is_null($this->project)) { return; } // we need to fetch the default branch from the api $resource = $this->getApiUrl(); $this->project = $this->getContents($resource, \true)->decodeJson(); if (isset($this->project['visibility'])) { $this->isPrivate = $this->project['visibility'] !== 'public'; } else { // client is not authenticated, therefore repository has to be public $this->isPrivate = \false; } } /** * @phpstan-impure * * @return true * @throws \RuntimeException */ protected function attemptCloneFallback() : bool { if ($this->isPrivate === \false) { $url = $this->generatePublicUrl(); } else { $url = $this->generateSshUrl(); } try { // If this repository may be private and we // cannot ask for authentication credentials (because we // are not interactive) then we fallback to GitDriver. $this->setupGitDriver($url); return \true; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the ' . $url . ' repository, try running in interactive mode so that you can enter your credentials'); throw $e; } } /** * Generate an SSH URL */ protected function generateSshUrl() : string { if ($this->hasNonstandardOrigin) { return 'ssh://git@' . $this->originUrl . '/' . $this->namespace . '/' . $this->repository . '.git'; } return 'git@' . $this->originUrl . ':' . $this->namespace . '/' . $this->repository . '.git'; } protected function generatePublicUrl() : string { return $this->scheme . '://' . $this->originUrl . '/' . $this->namespace . '/' . $this->repository . '.git'; } protected function setupGitDriver(string $url) : void { $this->gitDriver = new \Composer\Repository\Vcs\GitDriver(['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process); $this->gitDriver->initialize(); } /** * @inheritDoc */ protected function getContents(string $url, bool $fetchingRepoData = \false) : Response { try { $response = parent::getContents($url); if ($fetchingRepoData) { $json = $response->decodeJson(); // Accessing the API with a token with Guest (10) access will return // more data than unauthenticated access but no default_branch data // accessing files via the API will then also fail if (!isset($json['default_branch']) && isset($json['permissions'])) { $this->isPrivate = $json['visibility'] !== 'public'; $moreThanGuestAccess = \false; // Check both access levels (e.g. project, group) // - value will be null if no access is set // - value will be array with key access_level if set foreach ($json['permissions'] as $permission) { if ($permission && $permission['access_level'] > 10) { $moreThanGuestAccess = \true; } } if (!$moreThanGuestAccess) { $this->io->writeError('GitLab token with Guest only access detected'); $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } } // force auth as the unauthenticated version of the API is broken if (!isset($json['default_branch'])) { // GitLab allows you to disable the repository inside a project to use a project only for issues and wiki if (isset($json['repository_access_level']) && $json['repository_access_level'] === 'disabled') { throw new TransportException('The GitLab repository is disabled in the project', 400); } if (!empty($json['id'])) { $this->isPrivate = \false; } throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401); } } return $response; } catch (TransportException $e) { $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader); switch ($e->getCode()) { case 401: case 404: // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 if (!$fetchingRepoData) { throw $e; } if ($gitLabUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if ($gitLabUtil->isOAuthExpired($this->originUrl) && $gitLabUtil->authorizeOAuthRefresh($this->scheme, $this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive()) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata (' . $this->url . ')'); return parent::getContents($url); case 403: if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } throw $e; default: throw $e; } } } /** * Uses the config `gitlab-domains` to see if the driver supports the url for the * repository given. * * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if (!Preg::isMatch(self::URL_REGEX, $url, $match)) { return \false; } \assert(\is_string($match['parts'])); \assert(\is_string($match['repo'])); $scheme = $match['scheme']; $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $urlParts = \explode('/', $match['parts']); if (\false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { return \false; } if ('https' === $scheme && !\extension_loaded('openssl')) { $io->writeError('Skipping GitLab driver for ' . $url . ' because the OpenSSL PHP extension is missing.', \true, IOInterface::VERBOSE); return \false; } return \true; } /** * Gives back the loaded /projects// result * * @return mixed[]|null */ public function getRepoData() : ?array { $this->fetchProject(); return $this->project; } protected function getNextPage(Response $response) : ?string { $header = $response->getHeader('link'); $links = \explode(',', $header); foreach ($links as $link) { if (Preg::isMatchStrictGroups('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } return null; } /** * @param array $configuredDomains * @param array $urlParts * @param string $portNumber * * @return string|false */ private static function determineOrigin(array $configuredDomains, string $guessedDomain, array &$urlParts, ?string $portNumber) { $guessedDomain = \strtolower($guessedDomain); if (\in_array($guessedDomain, $configuredDomains) || null !== $portNumber && \in_array($guessedDomain . ':' . $portNumber, $configuredDomains)) { if (null !== $portNumber) { return $guessedDomain . ':' . $portNumber; } return $guessedDomain; } if (null !== $portNumber) { $guessedDomain .= ':' . $portNumber; } while (null !== ($part = \array_shift($urlParts))) { $guessedDomain .= '/' . $part; if (\in_array($guessedDomain, $configuredDomains) || null !== $portNumber && \in_array(Preg::replace('{:\\d+}', '', $guessedDomain), $configuredDomains)) { return $guessedDomain; } } return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Config; use Composer\IO\IOInterface; /** * @author Jordi Boggiano * @internal */ interface VcsDriverInterface { /** * Initializes the driver (git clone, svn checkout, fetch info etc) */ public function initialize() : void; /** * Return the composer.json file information * * @param string $identifier Any identifier to a specific branch/tag/commit * @return mixed[]|null Array containing all infos from the composer.json file, or null to denote that no file was present */ public function getComposerInformation(string $identifier) : ?array; /** * Return the content of $file or null if the file does not exist. */ public function getFileContent(string $file, string $identifier) : ?string; /** * Get the changedate for $identifier. */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable; /** * Return the root identifier (trunk, master, default/tip ..) * * @return string Identifier */ public function getRootIdentifier() : string; /** * Return list of branches in the repository * * @return array Branch names as keys, identifiers as values */ public function getBranches() : array; /** * Return list of tags in the repository * * @return array Tag names as keys, identifiers as values */ public function getTags() : array; /** * @param string $identifier Any identifier to a specific branch/tag/commit * * @return array{type: string, url: string, reference: string, shasum: string}|null */ public function getDist(string $identifier) : ?array; /** * @param string $identifier Any identifier to a specific branch/tag/commit * * @return array{type: string, url: string, reference: string} */ public function getSource(string $identifier) : array; /** * Return the URL of the repository */ public function getUrl() : string; /** * Return true if the repository has a composer file for a given identifier, * false otherwise. * * @param string $identifier Any identifier to a specific branch/tag/commit * @return bool Whether the repository has a composer file for a given identifier. */ public function hasComposerFile(string $identifier) : bool; /** * Performs any cleanup necessary as the driver is not longer needed */ public function cleanup() : void; /** * Checks if this driver can handle a given url * * @param IOInterface $io IO instance * @param Config $config current $config * @param string $url URL to validate/check * @param bool $deep unless true, only shallow checks (url matching typically) should be done */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository\Vcs; use Composer\Cache; use Composer\Config; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; /** * @author BohwaZ */ class FossilDriver extends \Composer\Repository\Vcs\VcsDriver { /** @var array Map of tag name to identifier */ protected $tags; /** @var array Map of branch name to identifier */ protected $branches; /** @var ?string */ protected $rootIdentifier = null; /** @var ?string */ protected $repoFile = null; /** @var string */ protected $checkoutDir; /** * @inheritDoc */ public function initialize() : void { // Make sure fossil is installed and reachable. $this->checkFossil(); // Ensure we are allowed to use this URL by config. $this->config->prohibitUrlByConfig($this->url, $this->io); // Only if url points to a locally accessible directory, assume it's the checkout directory. // Otherwise, it should be something fossil can clone from. if (Filesystem::isLocalPath($this->url) && \is_dir($this->url)) { $this->checkoutDir = $this->url; } else { if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $localName = Preg::replace('{[^a-z0-9]}i', '-', $this->url); $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil'; $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/'; $this->updateLocalRepo(); } $this->getTags(); $this->getBranches(); } /** * Check that fossil can be invoked via command line. */ protected function checkFossil() : void { if (0 !== $this->process->execute('fossil version', $ignoredOutput)) { throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput()); } } /** * Clone or update existing local fossil repository. */ protected function updateLocalRepo() : void { $fs = new Filesystem(); $fs->ensureDirectoryExists($this->checkoutDir); if (!\is_writable(\dirname($this->checkoutDir))) { throw new \RuntimeException('Can not clone ' . $this->url . ' to access package information. The "' . $this->checkoutDir . '" directory is not writable by the current user.'); } // update the repo if it is a valid fossil repository if (\is_file($this->repoFile) && \is_dir($this->checkoutDir) && 0 === $this->process->execute('fossil info', $output, $this->checkoutDir)) { if (0 !== $this->process->execute('fossil pull', $output, $this->checkoutDir)) { $this->io->writeError('Failed to update ' . $this->url . ', package information from this repository may be outdated (' . $this->process->getErrorOutput() . ')'); } } else { // clean up directory and do a fresh clone into it $fs->removeDirectory($this->checkoutDir); $fs->remove($this->repoFile); $fs->ensureDirectoryExists($this->checkoutDir); if (0 !== $this->process->execute(\sprintf('fossil clone -- %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoFile)), $output)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to clone ' . $this->url . ' to repository ' . $this->repoFile . "\n\n" . $output); } if (0 !== $this->process->execute(\sprintf('fossil open --nested -- %s', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to open repository ' . $this->repoFile . ' in ' . $this->checkoutDir . "\n\n" . $output); } } } /** * @inheritDoc */ public function getRootIdentifier() : string { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'trunk'; } return $this->rootIdentifier; } /** * @inheritDoc */ public function getUrl() : string { return $this->url; } /** * @inheritDoc */ public function getSource(string $identifier) : array { return ['type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier]; } /** * @inheritDoc */ public function getDist(string $identifier) : ?array { return null; } /** * @inheritDoc */ public function getFileContent(string $file, string $identifier) : ?string { $command = \sprintf('fossil cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute($command, $content, $this->checkoutDir); if (!\trim($content)) { return null; } return $content; } /** * @inheritDoc */ public function getChangeDate(string $identifier) : ?\DateTimeImmutable { $this->process->execute('fossil finfo -b -n 1 composer.json', $output, $this->checkoutDir); [, $date] = \explode(' ', \trim($output), 3); return new \DateTimeImmutable($date, new \DateTimeZone('UTC')); } /** * @inheritDoc */ public function getTags() : array { if (null === $this->tags) { $tags = []; $this->process->execute('fossil tag list', $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $tag) { $tags[$tag] = $tag; } $this->tags = $tags; } return $this->tags; } /** * @inheritDoc */ public function getBranches() : array { if (null === $this->branches) { $branches = []; $this->process->execute('fossil branch list', $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $branch) { $branch = \trim(Preg::replace('/^\\*/', '', \trim($branch))); $branches[$branch] = $branch; } $this->branches = $branches; } return $this->branches; } /** * @inheritDoc */ public static function supports(IOInterface $io, Config $config, string $url, bool $deep = \false) : bool { if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\\.com|fossil\\.))#i', $url)) { return \true; } if (Preg::isMatch('!/fossil/|\\.fossil!', $url)) { return \true; } // local filesystem if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!\is_dir($url)) { return \false; } $process = new ProcessExecutor($io); // check whether there is a fossil repo in that path if ($process->execute('fossil info', $output, $url) === 0) { return \true; } } return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Pcre\Preg; use Composer\Util\HttpDownloader; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\Util\Url; use Composer\Util\Git as GitUtil; /** * This repository allows installing local packages that are not necessarily under their own VCS. * * The local packages will be symlinked when possible, else they will be copied. * * @code * "require": { * "/": "*" * }, * "repositories": [ * { * "type": "path", * "url": "../../relative/path/to/package/" * }, * { * "type": "path", * "url": "/absolute/path/to/package/" * }, * { * "type": "path", * "url": "/absolute/path/to/several/packages/*" * }, * { * "type": "path", * "url": "../../relative/path/to/package/", * "options": { * "symlink": false * } * }, * { * "type": "path", * "url": "../../relative/path/to/package/", * "options": { * "reference": "none" * } * }, * ] * @endcode * * @author Samuel Roze * @author Johann Reinke */ class PathRepository extends \Composer\Repository\ArrayRepository implements \Composer\Repository\ConfigurableRepositoryInterface { /** * @var ArrayLoader */ private $loader; /** * @var VersionGuesser */ private $versionGuesser; /** * @var string */ private $url; /** * @var mixed[] * @phpstan-var array{url: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array}} */ private $repoConfig; /** * @var ProcessExecutor */ private $process; /** * @var array{symlink?: bool, reference?: string, relative?: bool, versions?: array} */ private $options; /** * Initializes path repository. * * @param array{url?: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array}} $repoConfig */ public function __construct(array $repoConfig, IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null) { if (!isset($repoConfig['url'])) { throw new \RuntimeException('You must specify the `url` configuration for the path repository'); } $this->loader = new ArrayLoader(null, \true); $this->url = Platform::expandPath($repoConfig['url']); $this->process = $process ?? new ProcessExecutor($io); $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser()); $this->repoConfig = $repoConfig; $this->options = $repoConfig['options'] ?? []; if (!isset($this->options['relative'])) { $filesystem = new Filesystem(); $this->options['relative'] = !$filesystem->isAbsolutePath($this->url); } parent::__construct(); } public function getRepoName() : string { return 'path repo (' . Url::sanitize($this->repoConfig['url']) . ')'; } public function getRepoConfig() : array { return $this->repoConfig; } /** * Initializes path repository. * * This method will basically read the folder and add the found package. */ protected function initialize() : void { parent::initialize(); $urlMatches = $this->getUrlMatches(); if (empty($urlMatches)) { if (Preg::isMatch('{[*{}]}', $this->url)) { $url = $this->url; while (Preg::isMatch('{[*{}]}', $url)) { $url = \dirname($url); } // the parent directory before any wildcard exists, so we assume it is correctly configured but simply empty if (\is_dir($url)) { return; } } throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist'); } foreach ($urlMatches as $url) { $path = \realpath($url) . \DIRECTORY_SEPARATOR; $composerFilePath = $path . 'composer.json'; if (!\file_exists($composerFilePath)) { continue; } $json = \file_get_contents($composerFilePath); $package = JsonFile::parseJson($json, $composerFilePath); $package['dist'] = ['type' => 'path', 'url' => $url]; $reference = $this->options['reference'] ?? 'auto'; if ('none' === $reference) { $package['dist']['reference'] = null; } elseif ('config' === $reference || 'auto' === $reference) { $package['dist']['reference'] = \sha1($json . \serialize($this->options)); } // copy symlink/relative options to transport options $package['transport-options'] = \array_intersect_key($this->options, ['symlink' => \true, 'relative' => \true]); // use the version provided as option if available if (isset($package['name'], $this->options['versions'][$package['name']])) { $package['version'] = $this->options['versions'][$package['name']]; } // carry over the root package version if this path repo is in the same git repository as root package if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) { if (0 === $this->process->execute('git rev-parse HEAD', $ref1, $path) && 0 === $this->process->execute('git rev-parse HEAD', $ref2) && $ref1 === $ref2) { $package['version'] = $rootVersion; } } $output = ''; if ('auto' === $reference && \is_dir($path . \DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H' . GitUtil::getNoShowSignatureFlag($this->process), $output, $path)) { $package['dist']['reference'] = \trim($output); } if (!isset($package['version'])) { $versionData = $this->versionGuesser->guessVersion($package, $path); if (\is_array($versionData) && $versionData['pretty_version']) { // if there is a feature branch detected, we add a second packages with the feature branch version if (!empty($versionData['feature_pretty_version'])) { $package['version'] = $versionData['feature_pretty_version']; $this->addPackage($this->loader->load($package)); } $package['version'] = $versionData['pretty_version']; } else { $package['version'] = 'dev-main'; } } try { $this->addPackage($this->loader->load($package)); } catch (\Exception $e) { throw new \RuntimeException('Failed loading the package in ' . $composerFilePath, 0, $e); } } } /** * Get a list of all (possibly relative) path names matching given url (supports globbing). * * @return string[] */ private function getUrlMatches() : array { $flags = \GLOB_MARK | \GLOB_ONLYDIR; if (\defined('GLOB_BRACE')) { $flags |= \GLOB_BRACE; } elseif (\strpos($this->url, '{') !== \false || \strpos($this->url, '}') !== \false) { throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url ' . $this->url); } // Ensure environment-specific path separators are normalized to URL separators return \array_map(static function ($val) : string { return \rtrim(\str_replace(\DIRECTORY_SEPARATOR, '/', $val), '/'); }, \glob($this->url, $flags)); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; interface VersionCacheInterface { /** * @return mixed[]|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier */ public function getVersionPackage(string $version, string $identifier); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; /** * Provides getCanonicalPackages() to various repository implementations * * @internal */ trait CanonicalPackagesTrait { /** * Get unique packages (at most one package of each name), with aliases resolved and removed. * * @return PackageInterface[] */ public function getCanonicalPackages() { $packages = $this->getPackages(); // get at most one package of each name, preferring non-aliased ones $packagesByName = []; foreach ($packages as $package) { if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) { $packagesByName[$package->getName()] = $package; } } $canonicalPackages = []; // unfold aliased packages foreach ($packagesByName as $package) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $canonicalPackages[] = $package; } return $canonicalPackages; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\PackageInterface; use Composer\Package\BasePackage; use Composer\Pcre\Preg; /** * Filters which packages are seen as canonical on this repo by loadPackages * * @author Jordi Boggiano */ class FilterRepository implements \Composer\Repository\RepositoryInterface, \Composer\Repository\AdvisoryProviderInterface { /** @var ?string */ private $only = null; /** @var ?non-empty-string */ private $exclude = null; /** @var bool */ private $canonical = \true; /** @var RepositoryInterface */ private $repo; /** * @param array{only?: array, exclude?: array, canonical?: bool} $options */ public function __construct(\Composer\Repository\RepositoryInterface $repo, array $options) { if (isset($options['only'])) { if (!\is_array($options['only'])) { throw new \InvalidArgumentException('"only" key for repository ' . $repo->getRepoName() . ' should be an array'); } $this->only = BasePackage::packageNamesToRegexp($options['only']); } if (isset($options['exclude'])) { if (!\is_array($options['exclude'])) { throw new \InvalidArgumentException('"exclude" key for repository ' . $repo->getRepoName() . ' should be an array'); } $this->exclude = BasePackage::packageNamesToRegexp($options['exclude']); } if ($this->exclude && $this->only) { throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository ' . $repo->getRepoName()); } if (isset($options['canonical'])) { if (!\is_bool($options['canonical'])) { throw new \InvalidArgumentException('"canonical" key for repository ' . $repo->getRepoName() . ' should be a boolean'); } $this->canonical = $options['canonical']; } $this->repo = $repo; } public function getRepoName() : string { return $this->repo->getRepoName(); } /** * Returns the wrapped repositories */ public function getRepository() : \Composer\Repository\RepositoryInterface { return $this->repo; } /** * @inheritDoc */ public function hasPackage(PackageInterface $package) : bool { return $this->repo->hasPackage($package); } /** * @inheritDoc */ public function findPackage($name, $constraint) : ?BasePackage { if (!$this->isAllowed($name)) { return null; } return $this->repo->findPackage($name, $constraint); } /** * @inheritDoc */ public function findPackages($name, $constraint = null) : array { if (!$this->isAllowed($name)) { return []; } return $this->repo->findPackages($name, $constraint); } /** * @inheritDoc */ public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) : array { foreach ($packageNameMap as $name => $constraint) { if (!$this->isAllowed($name)) { unset($packageNameMap[$name]); } } if (!$packageNameMap) { return ['namesFound' => [], 'packages' => []]; } $result = $this->repo->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); if (!$this->canonical) { $result['namesFound'] = []; } return $result; } /** * @inheritDoc */ public function search(string $query, int $mode = 0, ?string $type = null) : array { $result = []; foreach ($this->repo->search($query, $mode, $type) as $package) { if ($this->isAllowed($package['name'])) { $result[] = $package; } } return $result; } /** * @inheritDoc */ public function getPackages() : array { $result = []; foreach ($this->repo->getPackages() as $package) { if ($this->isAllowed($package->getName())) { $result[] = $package; } } return $result; } /** * @inheritDoc */ public function getProviders($packageName) : array { $result = []; foreach ($this->repo->getProviders($packageName) as $name => $provider) { if ($this->isAllowed($provider['name'])) { $result[$name] = $provider; } } return $result; } /** * @inheritDoc */ public function count() : int { if ($this->repo->count() > 0) { return \count($this->getPackages()); } return 0; } public function hasSecurityAdvisories() : bool { if (!$this->repo instanceof \Composer\Repository\AdvisoryProviderInterface) { return \false; } return $this->repo->hasSecurityAdvisories(); } /** * @inheritDoc */ public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = \false) : array { if (!$this->repo instanceof \Composer\Repository\AdvisoryProviderInterface) { return ['namesFound' => [], 'advisories' => []]; } foreach ($packageConstraintMap as $name => $constraint) { if (!$this->isAllowed($name)) { unset($packageConstraintMap[$name]); } } return $this->repo->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories); } private function isAllowed(string $name) : bool { if (!$this->only && !$this->exclude) { return \true; } if ($this->only) { return Preg::isMatch($this->only, $name); } if ($this->exclude === null) { return \true; } return !Preg::isMatch($this->exclude, $name); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Package\RootPackageInterface; use Composer\Package\Link; /** * Installed repository is a composite of all installed repo types. * * The main use case is tagging a repo as an "installed" repository, and offering a way to get providers/replacers easily. * * Installed repos are LockArrayRepository, InstalledRepositoryInterface, RootPackageRepository and PlatformRepository * * @author Jordi Boggiano */ class InstalledRepository extends \Composer\Repository\CompositeRepository { /** * @param ConstraintInterface|string|null $constraint * * @return BasePackage[] */ public function findPackagesWithReplacersAndProviders(string $name, $constraint = null) : array { $name = \strtolower($name); if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } $matches = []; foreach ($this->getRepositories() as $repo) { foreach ($repo->getPackages() as $candidate) { if ($name === $candidate->getName()) { if (null === $constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { $matches[] = $candidate; } continue; } foreach (\array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { if ($name === $link->getTarget() && ($constraint === null || $constraint->matches($link->getConstraint()))) { $matches[] = $candidate; continue 2; } } } } return $matches; } /** * Returns a list of links causing the requested needle packages to be installed, as an associative array with the * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship * as values. If recursive lookup was requested a third value is returned containing an identically formed array up * to the root package. That third value will be false in case a circular recursion was detected. * * @param string|string[] $needle The package name(s) to inspect. * @param ConstraintInterface|null $constraint Optional constraint to filter by. * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed. * @param bool $recurse Whether to recursively expand the requirement tree up to the root package. * @param string[] $packagesFound Used internally when recurring * * @return array[] An associative array of arrays as described above. * @phpstan-return array */ public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = \false, bool $recurse = \true, ?array $packagesFound = null) : array { $needles = \array_map('strtolower', (array) $needle); $results = []; // initialize the array with the needles before any recursion occurs if (null === $packagesFound) { $packagesFound = $needles; } // locate root package for use below $rootPackage = null; foreach ($this->getPackages() as $package) { if ($package instanceof RootPackageInterface) { $rootPackage = $package; break; } } // Loop over all currently installed packages. foreach ($this->getPackages() as $package) { $links = $package->getRequires(); // each loop needs its own "tree" as we want to show the complete dependent set of every needle // without warning all the time about finding circular deps $packagesInTree = $packagesFound; // Replacements are considered valid reasons for a package to be installed during forward resolution if (!$invert) { $links += $package->getReplaces(); // On forward search, check if any replaced package was required and add the replaced // packages to the list of needles. Contrary to the cross-reference link check below, // replaced packages are the target of links. foreach ($package->getReplaces() as $link) { foreach ($needles as $needle) { if ($link->getSource() === $needle) { if ($constraint === null || $link->getConstraint()->matches($constraint) === \true) { // already displayed this node's dependencies, cutting short if (\in_array($link->getTarget(), $packagesInTree)) { $results[] = [$package, $link, \false]; continue; } $packagesInTree[] = $link->getTarget(); $dependents = $recurse ? $this->getDependents($link->getTarget(), null, \false, \true, $packagesInTree) : []; $results[] = [$package, $link, $dependents]; $needles[] = $link->getTarget(); } } } } } // Require-dev is only relevant for the root package if ($package instanceof RootPackageInterface) { $links += $package->getDevRequires(); } // Cross-reference all discovered links to the needles foreach ($links as $link) { foreach ($needles as $needle) { if ($link->getTarget() === $needle) { if ($constraint === null || $link->getConstraint()->matches($constraint) === !$invert) { // already displayed this node's dependencies, cutting short if (\in_array($link->getSource(), $packagesInTree)) { $results[] = [$package, $link, \false]; continue; } $packagesInTree[] = $link->getSource(); $dependents = $recurse ? $this->getDependents($link->getSource(), null, \false, \true, $packagesInTree) : []; $results[] = [$package, $link, $dependents]; } } } } // When inverting, we need to check for conflicts of the needles against installed packages if ($invert && \in_array($package->getName(), $needles)) { foreach ($package->getConflicts() as $link) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); if ($link->getConstraint()->matches($version) === $invert) { $results[] = [$package, $link, \false]; } } } } // List conflicts against X as they may explain why the current version was selected, or explain why it is rejected if the conflict matched when inverting foreach ($package->getConflicts() as $link) { if (\in_array($link->getTarget(), $needles)) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); if ($link->getConstraint()->matches($version) === $invert) { $results[] = [$package, $link, \false]; } } } } // When inverting, we need to check for conflicts of the needles' requirements against installed packages if ($invert && $constraint && \in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) { foreach ($package->getRequires() as $link) { if (\Composer\Repository\PlatformRepository::isPlatformPackage($link->getTarget())) { if ($this->findPackage($link->getTarget(), $link->getConstraint())) { continue; } $platformPkg = $this->findPackage($link->getTarget(), '*'); $description = $platformPkg ? 'but ' . $platformPkg->getPrettyVersion() . ' is installed' : 'but it is missing'; $results[] = [$package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraint(), Link::TYPE_REQUIRE, $link->getPrettyConstraint() . ' ' . $description), \false]; continue; } foreach ($this->getPackages() as $pkg) { if (!\in_array($link->getTarget(), $pkg->getNames())) { continue; } $version = new Constraint('=', $pkg->getVersion()); if ($link->getTarget() !== $pkg->getName()) { foreach (\array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) { if ($link->getTarget() === $prov->getTarget()) { $version = $prov->getConstraint(); break; } } } if (!$link->getConstraint()->matches($version)) { // if we have a root package (we should but can not guarantee..) we show // the root requires as well to perhaps allow to find an issue there if ($rootPackage) { foreach (\array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) { if (\in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) { $results[] = [$package, $link, \false]; $results[] = [$rootPackage, $rootReq, \false]; continue 3; } } $results[] = [$package, $link, \false]; $results[] = [$rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraint(), Link::TYPE_DOES_NOT_REQUIRE, 'but ' . $pkg->getPrettyVersion() . ' is installed'), \false]; } else { // no root so let's just print whatever we found $results[] = [$package, $link, \false]; } } continue 2; } } } } \ksort($results); return $results; } public function getRepoName() : string { return 'installed repo (' . \implode(', ', \array_map(static function ($repo) : string { return $repo->getRepoName(); }, $this->getRepositories())) . ')'; } /** * @inheritDoc */ public function addRepository(\Composer\Repository\RepositoryInterface $repository) : void { if ($repository instanceof \Composer\Repository\LockArrayRepository || $repository instanceof \Composer\Repository\InstalledRepositoryInterface || $repository instanceof \Composer\Repository\RootPackageRepository || $repository instanceof \Composer\Repository\PlatformRepository) { parent::addRepository($repository); return; } throw new \LogicException('An InstalledRepository can not contain a repository of type ' . \get_class($repository) . ' (' . $repository->getRepoName() . ')'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Advisory\PartialSecurityAdvisory; use Composer\Advisory\SecurityAdvisory; /** * Repositories that allow fetching security advisory data * * @author Jordi Boggiano * @internal */ interface AdvisoryProviderInterface { public function hasSecurityAdvisories() : bool; /** * @param array $packageConstraintMap Map of package name to constraint (can be MatchAllConstraint to fetch all advisories) * @return ($allowPartialAdvisories is true ? array{namesFound: string[], advisories: array>} : array{namesFound: string[], advisories: array>}) */ public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = \false) : array; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; use Composer\Package\RootAliasPackage; use Composer\Package\RootPackageInterface; use Composer\Package\AliasPackage; use Composer\Package\Dumper\ArrayDumper; use Composer\Installer\InstallationManager; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; /** * Filesystem repository. * * @author Konstantin Kudryashov * @author Jordi Boggiano */ class FilesystemRepository extends \Composer\Repository\WritableArrayRepository { /** @var JsonFile */ protected $file; /** @var bool */ private $dumpVersions; /** @var ?RootPackageInterface */ private $rootPackage; /** @var Filesystem */ private $filesystem; /** @var bool|null */ private $devMode = null; /** * Initializes filesystem repository. * * @param JsonFile $repositoryFile repository json file * @param ?RootPackageInterface $rootPackage Must be provided if $dumpVersions is true */ public function __construct(JsonFile $repositoryFile, bool $dumpVersions = \false, ?RootPackageInterface $rootPackage = null, ?Filesystem $filesystem = null) { parent::__construct(); $this->file = $repositoryFile; $this->dumpVersions = $dumpVersions; $this->rootPackage = $rootPackage; $this->filesystem = $filesystem ?: new Filesystem(); if ($dumpVersions && !$rootPackage) { throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true'); } } /** * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown */ public function getDevMode() { return $this->devMode; } /** * Initializes repository (reads file, or remote address). */ protected function initialize() { parent::initialize(); if (!$this->file->exists()) { return; } try { $data = $this->file->read(); if (isset($data['packages'])) { $packages = $data['packages']; } else { $packages = $data; } if (isset($data['dev-package-names'])) { $this->setDevPackageNames($data['dev-package-names']); } if (isset($data['dev'])) { $this->devMode = $data['dev']; } if (!\is_array($packages)) { throw new \UnexpectedValueException('Could not parse package list from the repository'); } } catch (\Exception $e) { throw new \Composer\Repository\InvalidRepositoryException('Invalid repository data in ' . $this->file->getPath() . ', packages could not be loaded: [' . \get_class($e) . '] ' . $e->getMessage()); } $loader = new ArrayLoader(null, \true); foreach ($packages as $packageData) { $package = $loader->load($packageData); $this->addPackage($package); } } public function reload() { $this->packages = null; $this->initialize(); } /** * Writes writable repository. */ public function write(bool $devMode, InstallationManager $installationManager) { $data = ['packages' => [], 'dev' => $devMode, 'dev-package-names' => []]; $dumper = new ArrayDumper(); // make sure the directory is created so we can realpath it // as realpath() does some additional normalizations with network paths that normalizePath does not // and we need to find shortest path correctly $repoDir = \dirname($this->file->getPath()); $this->filesystem->ensureDirectoryExists($repoDir); $repoDir = $this->filesystem->normalizePath(\realpath($repoDir)); $installPaths = []; foreach ($this->getCanonicalPackages() as $package) { $pkgArray = $dumper->dump($package); $path = $installationManager->getInstallPath($package); $installPath = null; if ('' !== $path && null !== $path) { $normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path); $installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, \true); } $installPaths[$package->getName()] = $installPath; $pkgArray['install-path'] = $installPath; $data['packages'][] = $pkgArray; // only write to the files the names which are really installed, as we receive the full list // of dev package names before they get installed during composer install if (\in_array($package->getName(), $this->devPackageNames, \true)) { $data['dev-package-names'][] = $package->getName(); } } \sort($data['dev-package-names']); \usort($data['packages'], static function ($a, $b) : int { return \strcmp($a['name'], $b['name']); }); $this->file->write($data); if ($this->dumpVersions) { $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir); $this->filesystem->filePutContentsIfModified($repoDir . '/installed.php', 'dumpToPhpCode($versions) . ';' . "\n"); $installedVersionsClass = \file_get_contents(__DIR__ . '/../InstalledVersions.php'); // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility if ($installedVersionsClass !== \false) { $this->filesystem->filePutContentsIfModified($repoDir . '/InstalledVersions.php', $installedVersionsClass); \Composer\InstalledVersions::reload($versions); } } } /** * As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it * * @internal */ public static function safelyLoadInstalledVersions(string $path) : bool { $installedVersionsData = @\file_get_contents($path); $pattern = <<<'REGEX' {(?(DEFINE) (? -? \s*+ \d++ (?:\.\d++)? ) (? true | false | null ) (? (?&string) (?: \s*+ \. \s*+ (?&string))*+ ) (? (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) ) (? array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+ \s*+ \) ) (? (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) ) ) ^<\?php\s++return\s++(?&array)\s*+;$}ix REGEX; if (\is_string($installedVersionsData) && Preg::isMatch($pattern, \trim($installedVersionsData))) { \Composer\InstalledVersions::reload(eval('?>' . Preg::replace('{=>\\s*+__DIR__\\s*+\\.\\s*+([\'"])}', '=> ' . \var_export(\dirname($path), \true) . ' . $1', $installedVersionsData))); return \true; } return \false; } /** * @param array $array */ private function dumpToPhpCode(array $array = [], int $level = 0) : string { $lines = "array(\n"; $level++; foreach ($array as $key => $value) { $lines .= \str_repeat(' ', $level); $lines .= \is_int($key) ? $key . ' => ' : \var_export($key, \true) . ' => '; if (\is_array($value)) { if (!empty($value)) { $lines .= $this->dumpToPhpCode($value, $level); } else { $lines .= "array(),\n"; } } elseif ($key === 'install_path' && \is_string($value)) { if ($this->filesystem->isAbsolutePath($value)) { $lines .= \var_export($value, \true) . ",\n"; } else { $lines .= "__DIR__ . " . \var_export('/' . $value, \true) . ",\n"; } } elseif (\is_string($value)) { $lines .= \var_export($value, \true) . ",\n"; } elseif (\is_bool($value)) { $lines .= ($value ? 'true' : 'false') . ",\n"; } elseif (\is_null($value)) { $lines .= "null,\n"; } else { throw new \UnexpectedValueException('Unexpected type ' . \gettype($value)); } } $lines .= \str_repeat(' ', $level - 1) . ')' . ($level - 1 === 0 ? '' : ",\n"); return $lines; } /** * @param array $installPaths * * @return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths, bool $devMode, string $repoDir) : array { $devPackages = \array_flip($this->devPackageNames); $packages = $this->getPackages(); if (null === $this->rootPackage) { throw new \LogicException('It should not be possible to dump packages if no root package is given'); } $packages[] = $rootPackage = $this->rootPackage; while ($rootPackage instanceof RootAliasPackage) { $rootPackage = $rootPackage->getAliasOf(); $packages[] = $rootPackage; } $versions = ['root' => $this->dumpRootPackage($rootPackage, $installPaths, $devMode, $repoDir, $devPackages), 'versions' => []]; // add real installed packages foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $versions['versions'][$package->getName()] = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); } // add provided/replaced packages foreach ($packages as $package) { $isDevPackage = isset($devPackages[$package->getName()]); foreach ($package->getReplaces() as $replace) { // exclude platform replaces as when they are really there we can not check for their presence if (\Composer\Repository\PlatformRepository::isPlatformPackage($replace->getTarget())) { continue; } if (!isset($versions['versions'][$replace->getTarget()]['dev_requirement'])) { $versions['versions'][$replace->getTarget()]['dev_requirement'] = $isDevPackage; } elseif (!$isDevPackage) { $versions['versions'][$replace->getTarget()]['dev_requirement'] = \false; } $replaced = $replace->getPrettyConstraint(); if ($replaced === 'self.version') { $replaced = $package->getPrettyVersion(); } if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !\in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], \true)) { $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced; } } foreach ($package->getProvides() as $provide) { // exclude platform provides as when they are really there we can not check for their presence if (\Composer\Repository\PlatformRepository::isPlatformPackage($provide->getTarget())) { continue; } if (!isset($versions['versions'][$provide->getTarget()]['dev_requirement'])) { $versions['versions'][$provide->getTarget()]['dev_requirement'] = $isDevPackage; } elseif (!$isDevPackage) { $versions['versions'][$provide->getTarget()]['dev_requirement'] = \false; } $provided = $provide->getPrettyConstraint(); if ($provided === 'self.version') { $provided = $package->getPrettyVersion(); } if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !\in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], \true)) { $versions['versions'][$provide->getTarget()]['provided'][] = $provided; } } } // add aliases foreach ($packages as $package) { if (!$package instanceof AliasPackage) { continue; } $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion(); if ($package instanceof RootPackageInterface) { $versions['root']['aliases'][] = $package->getPrettyVersion(); } } \ksort($versions['versions']); \ksort($versions); return $versions; } /** * @param array $installPaths * @param array $devPackages * @return array{pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev_requirement: bool} */ private function dumpInstalledPackage(PackageInterface $package, array $installPaths, string $repoDir, array $devPackages) : array { $reference = null; if ($package->getInstallationSource()) { $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference(); } if (null === $reference) { $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null; } if ($package instanceof RootPackageInterface) { $to = $this->filesystem->normalizePath(\realpath(Platform::getCwd())); $installPath = $this->filesystem->findShortestPath($repoDir, $to, \true); } else { $installPath = $installPaths[$package->getName()]; } $data = ['pretty_version' => $package->getPrettyVersion(), 'version' => $package->getVersion(), 'reference' => $reference, 'type' => $package->getType(), 'install_path' => $installPath, 'aliases' => [], 'dev_requirement' => isset($devPackages[$package->getName()])]; return $data; } /** * @param array $installPaths * @param array $devPackages * @return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ private function dumpRootPackage(RootPackageInterface $package, array $installPaths, bool $devMode, string $repoDir, array $devPackages) { $data = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); return ['name' => $package->getName(), 'pretty_version' => $data['pretty_version'], 'version' => $data['version'], 'reference' => $data['reference'], 'type' => $data['type'], 'install_path' => $data['install_path'], 'aliases' => $data['aliases'], 'dev' => $devMode]; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Installed array repository. * * This is used as an in-memory InstalledRepository mostly for testing purposes * * @author Jordi Boggiano */ class InstalledArrayRepository extends \Composer\Repository\WritableArrayRepository implements \Composer\Repository\InstalledRepositoryInterface { public function getRepoName() : string { return 'installed ' . parent::getRepoName(); } /** * @inheritDoc */ public function isFresh() : bool { // this is not a completely correct implementation but there is no way to // distinguish an empty repo and a newly created one given this is all in-memory return $this->count() === 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Configurable repository interface. * * @author Lukas Homza */ interface ConfigurableRepositoryInterface { /** * @return mixed[] */ public function getRepoConfig(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Repository; /** * Exception thrown when a package repository is utterly broken * * @author Jordi Boggiano */ class InvalidRepositoryException extends \Exception { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; class PackageSorter { /** * Returns the most recent version of a set of packages * * This is ideally the default branch version, or failing that it will return the package with the highest version * * @template T of PackageInterface * @param array $packages * @return ($packages is non-empty-array ? T : T|null) */ public static function getMostCurrentVersion(array $packages) : ?PackageInterface { if (\count($packages) === 0) { return null; } $highest = \reset($packages); foreach ($packages as $candidate) { if ($candidate->isDefaultBranch()) { return $candidate; } if (\version_compare($highest->getVersion(), $candidate->getVersion(), '<')) { $highest = $candidate; } } return $highest; } /** * Sorts packages by name * * @template T of PackageInterface * @param array $packages * @return array */ public static function sortPackagesAlphabetically(array $packages) : array { \usort($packages, static function (PackageInterface $a, PackageInterface $b) { return $a->getName() <=> $b->getName(); }); return $packages; } /** * Sorts packages by dependency weight * * Packages of equal weight are sorted alphabetically * * @param PackageInterface[] $packages * @param array $weights Pre-set weights for some packages to give them more (negative number) or less (positive) weight offsets * @return PackageInterface[] sorted array */ public static function sortPackages(array $packages, array $weights = []) : array { $usageList = []; foreach ($packages as $package) { $links = $package->getRequires(); if ($package instanceof RootPackageInterface) { $links = \array_merge($links, $package->getDevRequires()); } foreach ($links as $link) { $target = $link->getTarget(); $usageList[$target][] = $package->getName(); } } $computing = []; $computed = []; $computeImportance = static function ($name) use(&$computeImportance, &$computing, &$computed, $usageList, $weights) { // reusing computed importance if (isset($computed[$name])) { return $computed[$name]; } // canceling circular dependency if (isset($computing[$name])) { return 0; } $computing[$name] = \true; $weight = $weights[$name] ?? 0; if (isset($usageList[$name])) { foreach ($usageList[$name] as $user) { $weight -= 1 - $computeImportance($user); } } unset($computing[$name]); $computed[$name] = $weight; return $weight; }; $weightedPackages = []; foreach ($packages as $index => $package) { $name = $package->getName(); $weight = $computeImportance($name); $weightedPackages[] = ['name' => $name, 'weight' => $weight, 'index' => $index]; } \usort($weightedPackages, static function (array $a, array $b) : int { if ($a['weight'] !== $b['weight']) { return $a['weight'] - $b['weight']; } return \strnatcasecmp($a['name'], $b['name']); }); $sortedPackages = []; foreach ($weightedPackages as $pkg) { $sortedPackages[] = $packages[$pkg['index']]; } return $sortedPackages; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; use Composer\Json\JsonValidationException; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Pcre\Preg; use Composer\Spdx\SpdxLicenses; use _ContaoManager\Seld\JsonLint\DuplicateKeyException; use _ContaoManager\Seld\JsonLint\JsonParser; /** * Validates a composer configuration. * * @author Robert Schönthal * @author Jordi Boggiano */ class ConfigValidator { public const CHECK_VERSION = 1; /** @var IOInterface */ private $io; public function __construct(IOInterface $io) { $this->io = $io; } /** * Validates the config, and returns the result. * * @param string $file The path to the file * @param int $arrayLoaderValidationFlags Flags for ArrayLoader validation * @param int $flags Flags for validation * * @return array{list, list, list} a triple containing the errors, publishable errors, and warnings */ public function validate(string $file, int $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL, int $flags = self::CHECK_VERSION) : array { $errors = []; $publishErrors = []; $warnings = []; // validate json schema $laxValid = \false; $manifest = null; try { $json = new JsonFile($file, null, $this->io); $manifest = $json->read(); $json->validateSchema(JsonFile::LAX_SCHEMA); $laxValid = \true; $json->validateSchema(); } catch (JsonValidationException $e) { foreach ($e->getErrors() as $message) { if ($laxValid) { $publishErrors[] = $message; } else { $errors[] = $message; } } } catch (\Exception $e) { $errors[] = $e->getMessage(); return [$errors, $publishErrors, $warnings]; } if (\is_array($manifest)) { $jsonParser = new JsonParser(); try { $jsonParser->parse((string) \file_get_contents($file), JsonParser::DETECT_KEY_CONFLICTS); } catch (DuplicateKeyException $e) { $details = $e->getDetails(); $warnings[] = 'Key ' . $details['key'] . ' is a duplicate in ' . $file . ' at line ' . $details['line']; } } // validate actual data if (empty($manifest['license'])) { $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; } else { $licenses = (array) $manifest['license']; // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer foreach ($licenses as $key => $license) { if ('proprietary' === $license) { unset($licenses[$key]); } } $licenseValidator = new SpdxLicenses(); foreach ($licenses as $license) { $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); if ($spdxLicense && $spdxLicense[3]) { if (Preg::isMatch('{^[AL]?GPL-[123](\\.[01])?\\+$}i', $license)) { $warnings[] = \sprintf('License "%s" is a deprecated SPDX license identifier, use "' . \str_replace('+', '', $license) . '-or-later" instead', $license); } elseif (Preg::isMatch('{^[AL]?GPL-[123](\\.[01])?$}i', $license)) { $warnings[] = \sprintf('License "%s" is a deprecated SPDX license identifier, use "' . $license . '-only" or "' . $license . '-or-later" instead', $license); } else { $warnings[] = \sprintf('License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', $license); } } } } if ($flags & self::CHECK_VERSION && isset($manifest['version'])) { $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.'; } if (!empty($manifest['name']) && Preg::isMatch('{[A-Z]}', $manifest['name'])) { $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '_ContaoManager\\1\\3-\\2\\4', $manifest['name']); $suggestName = \strtolower($suggestName); $publishErrors[] = \sprintf('Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.', $manifest['name'], $suggestName); } if (!empty($manifest['type']) && $manifest['type'] === 'composer-installer') { $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation."; } // check for require-dev overrides if (isset($manifest['require'], $manifest['require-dev'])) { $requireOverrides = \array_intersect_key($manifest['require'], $manifest['require-dev']); if (!empty($requireOverrides)) { $plural = \count($requireOverrides) > 1 ? 'are' : 'is'; $warnings[] = \implode(', ', \array_keys($requireOverrides)) . " {$plural} required both in require and require-dev, this can lead to unexpected behavior"; } } // check for meaningless provide/replace satisfying requirements foreach (['provide', 'replace'] as $linkType) { if (isset($manifest[$linkType])) { foreach (['require', 'require-dev'] as $requireType) { if (isset($manifest[$requireType])) { foreach ($manifest[$linkType] as $provide => $constraint) { if (isset($manifest[$requireType][$provide])) { $warnings[] = 'The package ' . $provide . ' in ' . $requireType . ' is also listed in ' . $linkType . ' which satisfies the requirement. Remove it from ' . $linkType . ' if you wish to install it.'; } } } } } } // check for commit references $require = $manifest['require'] ?? []; $requireDev = $manifest['require-dev'] ?? []; $packages = \array_merge($require, $requireDev); foreach ($packages as $package => $version) { if (Preg::isMatch('/#/', $version)) { $warnings[] = \sprintf('The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', $package); } } // report scripts-descriptions for non-existent scripts $scriptsDescriptions = $manifest['scripts-descriptions'] ?? []; $scripts = $manifest['scripts'] ?? []; foreach ($scriptsDescriptions as $scriptName => $scriptDescription) { if (!\array_key_exists($scriptName, $scripts)) { $warnings[] = \sprintf('Description for non-existent script "%s" found in "scripts-descriptions"', $scriptName); } } // report scripts-aliases for non-existent scripts $scriptAliases = $manifest['scripts-aliases'] ?? []; foreach ($scriptAliases as $scriptName => $scriptAlias) { if (!\array_key_exists($scriptName, $scripts)) { $warnings[] = \sprintf('Aliases for non-existent script "%s" found in "scripts-aliases"', $scriptName); } } // check for empty psr-0/psr-4 namespace prefixes if (isset($manifest['autoload']['psr-0'][''])) { $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; } if (isset($manifest['autoload']['psr-4'][''])) { $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; } $loader = new ValidatingArrayLoader(new ArrayLoader(), \true, null, $arrayLoaderValidationFlags); try { if (!isset($manifest['version'])) { $manifest['version'] = '1.0.0'; } if (!isset($manifest['name'])) { $manifest['name'] = 'dummy/dummy'; } $loader->load($manifest); } catch (InvalidPackageException $e) { $errors = \array_merge($errors, $e->getErrors()); } $warnings = \array_merge($warnings, $loader->getWarnings()); return [$errors, $publishErrors, $warnings]; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; use Composer\Pcre\Preg; /** * @author Jordi Boggiano */ class Git { /** @var string|false|null */ private static $version = \false; /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var Filesystem */ protected $filesystem; public function __construct(IOInterface $io, Config $config, \Composer\Util\ProcessExecutor $process, \Composer\Util\Filesystem $fs) { $this->io = $io; $this->config = $config; $this->process = $process; $this->filesystem = $fs; } /** * @param mixed $commandOutput the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler */ public function runCommand(callable $commandCallable, string $url, ?string $cwd, bool $initialClone = \false, &$commandOutput = null) : void { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); if ($initialClone) { $origCwd = $cwd; $cwd = null; } if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } if (!$initialClone) { // capture username/password from URL if there is one and we have no auth configured yet $this->process->execute('git remote -v', $output, $cwd); if (Preg::isMatchStrictGroups('{^(?:composer|origin)\\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) { $this->io->setAuthentication($match[3], \rawurldecode($match[1]), \rawurldecode($match[2])); } } $protocols = $this->config->get('github-protocols'); // public github, autoswitch protocols if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { $messages = []; foreach ($protocols as $protocol) { if ('ssh' === $protocol) { $protoUrl = "git@" . $match[1] . ":" . $match[2]; } else { $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; } if (0 === $this->process->execute($commandCallable($protoUrl), $commandOutput, $cwd)) { return; } $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput()); if ($initialClone && isset($origCwd)) { $this->filesystem->removeDirectory($origCwd); } } // failed to checkout, first check git accessibility if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) { $this->throwException('Failed to clone ' . $url . ' via ' . \implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . \implode("\n", $messages), $url); } } // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\\.git$}i', $url) && !\in_array('ssh', $protocols, \true); $command = $commandCallable($url); $auth = null; $credentials = []; if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) { $errorMsg = $this->process->getErrorOutput(); // private github repository without ssh key access, try https with auth if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\\.git$}i', $url, $match) || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\\.git)?$}i', $url, $match)) { if (!$this->io->hasAuthentication($match[1])) { $gitHubUtil = new \Composer\Util\GitHub($this->io, $this->config, $this->process); $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $gitHubUtil->authorizeOAuthInteractively($match[1], $message); } } if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . \rawurlencode($auth['username']) . ':' . \rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { return; } $credentials = [\rawurlencode($auth['username']), \rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } elseif (Preg::isMatchStrictGroups('{^https://(bitbucket\\.org)/(.*?)(?:\\.git)?$}i', $url, $match)) { //bitbucket oauth $bitbucketUtil = new \Composer\Util\Bitbucket($this->io, $this->config, $this->process); if (!$this->io->hasAuthentication($match[1])) { $message = 'Enter your Bitbucket credentials to access private repos'; if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $bitbucketUtil->authorizeOAuthInteractively($match[1], $message); $accessToken = $bitbucketUtil->getToken(); $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); } } else { //We're authenticating with a locally stored consumer. $auth = $this->io->getAuthentication($match[1]); //We already have an access_token from a previous request. if ($auth['username'] !== 'x-token-auth') { $accessToken = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); if (!empty($accessToken)) { $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); } } } if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . \rawurlencode($auth['username']) . ':' . \rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { return; } $credentials = [\rawurlencode($auth['username']), \rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } else { // Falling back to ssh $sshUrl = 'git@bitbucket.org:' . $match[2] . '.git'; $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); $command = $commandCallable($sshUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { return; } $errorMsg = $this->process->getErrorOutput(); } } elseif (Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\\.git)$}i', $url, $match) || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match)) { if ($match[1] === 'git') { $match[1] = 'https'; } if (!$this->io->hasAuthentication($match[2])) { $gitLabUtil = new \Composer\Util\GitLab($this->io, $this->config, $this->process); $message = 'Cloning failed, enter your GitLab credentials to access private repos'; if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) { $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message); } } if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') { $authUrl = $match[1] . '://' . \rawurlencode($auth['password']) . ':' . \rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password } else { $authUrl = $match[1] . '://' . \rawurlencode($auth['username']) . ':' . \rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3]; } $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { return; } $credentials = [\rawurlencode($auth['username']), \rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate if (\strpos($match[2], '@')) { [$authParts, $match[2]] = \explode('@', $match[2], 2); } $storeAuth = \false; if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); } elseif ($this->io->isInteractive()) { $defaultUsername = null; if (isset($authParts) && $authParts) { if (\false !== \strpos($authParts, ':')) { [$defaultUsername] = \explode(':', $authParts, 2); } else { $defaultUsername = $authParts; } } $this->io->writeError(' Authentication required (' . $match[2] . '):'); $this->io->writeError('' . \trim($errorMsg) . '', \true, IOInterface::VERBOSE); $auth = ['username' => $this->io->ask(' Username: ', $defaultUsername), 'password' => $this->io->askAndHideAnswer(' Password: ')]; $storeAuth = $this->config->get('store-auths'); } if (null !== $auth) { $authUrl = $match[1] . \rawurlencode($auth['username']) . ':' . \rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; $command = $commandCallable($authUrl); if (0 === $this->process->execute($command, $commandOutput, $cwd)) { $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); $authHelper = new \Composer\Util\AuthHelper($this->io, $this->config); $authHelper->storeAuth($match[2], $storeAuth); return; } $credentials = [\rawurlencode($auth['username']), \rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } if ($initialClone && isset($origCwd)) { $this->filesystem->removeDirectory($origCwd); } if (\count($credentials) > 0) { $command = $this->maskCredentials($command, $credentials); $errorMsg = $this->maskCredentials($errorMsg, $credentials); } $this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url); } } public function syncMirror(string $url, string $dir) : bool { if (\Composer\Util\Platform::getEnv('COMPOSER_DISABLE_NETWORK') && \Composer\Util\Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { $this->io->writeError('Aborting git mirror sync of ' . $url . ' as network is disabled'); return \false; } // update the repo if it is a valid git repository if (\is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && \trim($output) === '.') { try { $commandCallable = static function ($url) : string { $sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url); return \sprintf('git remote set-url origin -- %s && git remote update --prune origin && git remote set-url origin -- %s && git gc --auto', \Composer\Util\ProcessExecutor::escape($url), \Composer\Util\ProcessExecutor::escape($sanitizedUrl)); }; $this->runCommand($commandCallable, $url, $dir); } catch (\Exception $e) { $this->io->writeError('Sync mirror failed: ' . $e->getMessage() . '', \true, IOInterface::DEBUG); return \false; } return \true; } // clean up directory and do a fresh clone into it $this->filesystem->removeDirectory($dir); $commandCallable = static function ($url) use($dir) : string { return \sprintf('git clone --mirror -- %s %s', \Composer\Util\ProcessExecutor::escape($url), \Composer\Util\ProcessExecutor::escape($dir)); }; $this->runCommand($commandCallable, $url, $dir, \true); return \true; } public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, string $prettyVersion = null) : bool { if ($this->checkRefIsInMirror($dir, $ref)) { if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) { $branch = Preg::replace('{(?:^dev-|(?:\\.x)?-dev$)}i', '', $prettyVersion); $branches = null; $tags = null; if (0 === $this->process->execute('git branch', $output, $dir)) { $branches = $output; } if (0 === $this->process->execute('git tag', $output, $dir)) { $tags = $output; } // if the pretty version cannot be found as a branch (nor branch with 'v' in front of the branch as it may have been stripped when generating pretty name), // nor as a tag, then we sync the mirror as otherwise it will likely fail during install. // this can occur if a git tag gets created *after* the reference is already put into the cache, as the ref check above will then not sync the new tags // see https://github.com/composer/composer/discussions/11002 if (null !== $branches && !Preg::isMatch('{^[\\s*]*v?' . \preg_quote($branch) . '$}m', $branches) && null !== $tags && !Preg::isMatch('{^[\\s*]*' . \preg_quote($branch) . '$}m', $tags)) { $this->syncMirror($url, $dir); } } return \true; } if ($this->syncMirror($url, $dir)) { return $this->checkRefIsInMirror($dir, $ref); } return \false; } public static function getNoShowSignatureFlag(\Composer\Util\ProcessExecutor $process) : string { $gitVersion = self::getVersion($process); if ($gitVersion && \version_compare($gitVersion, '2.10.0-rc0', '>=')) { return ' --no-show-signature'; } return ''; } private function checkRefIsInMirror(string $dir, string $ref) : bool { if (\is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && \trim($output) === '.') { $escapedRef = \Composer\Util\ProcessExecutor::escape($ref . '^{commit}'); $exitCode = $this->process->execute(\sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir); if ($exitCode === 0) { return \true; } } return \false; } /** * @param string[] $match */ private function isAuthenticationFailure(string $url, array &$match) : bool { if (!Preg::isMatch('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { return \false; } $authFailures = ['fatal: Authentication failed', 'remote error: Invalid username or password.', 'error: 401 Unauthorized', 'fatal: unable to access', 'fatal: could not read Username']; $errorOutput = $this->process->getErrorOutput(); foreach ($authFailures as $authFailure) { if (\strpos($errorOutput, $authFailure) !== \false) { return \true; } } return \false; } public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository) : ?string { if ((bool) \Composer\Util\Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { return null; } try { if ($isLocalPathRepository) { $this->process->execute('git remote show origin', $output, $dir); } else { $commandCallable = static function ($url) : string { $sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url); return \sprintf('git remote set-url origin -- %s && git remote show origin && git remote set-url origin -- %s', \Composer\Util\ProcessExecutor::escape($url), \Composer\Util\ProcessExecutor::escape($sanitizedUrl)); }; $this->runCommand($commandCallable, $url, $dir, \false, $output); } $lines = $this->process->splitLines($output); foreach ($lines as $line) { if (Preg::match('{^\\s*HEAD branch:\\s(.+)\\s*$}m', $line, $matches) > 0) { return $matches[1]; } } } catch (\Exception $e) { $this->io->writeError('Failed to fetch root identifier from remote: ' . $e->getMessage() . '', \true, IOInterface::DEBUG); } return null; } public static function cleanEnv() : void { // added in git 1.7.1, prevents prompting the user for username/password if (\Composer\Util\Platform::getEnv('GIT_ASKPASS') !== 'echo') { \Composer\Util\Platform::putEnv('GIT_ASKPASS', 'echo'); } // clean up rogue git env vars in case this is running in a git hook if (\Composer\Util\Platform::getEnv('GIT_DIR')) { \Composer\Util\Platform::clearEnv('GIT_DIR'); } if (\Composer\Util\Platform::getEnv('GIT_WORK_TREE')) { \Composer\Util\Platform::clearEnv('GIT_WORK_TREE'); } // Run processes with predictable LANGUAGE if (\Composer\Util\Platform::getEnv('LANGUAGE') !== 'C') { \Composer\Util\Platform::putEnv('LANGUAGE', 'C'); } // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 \Composer\Util\Platform::clearEnv('DYLD_LIBRARY_PATH'); } /** * @return non-empty-string */ public static function getGitHubDomainsRegex(Config $config) : string { return '(' . \implode('|', \array_map('preg_quote', $config->get('github-domains'))) . ')'; } /** * @return non-empty-string */ public static function getGitLabDomainsRegex(Config $config) : string { return '(' . \implode('|', \array_map('preg_quote', $config->get('gitlab-domains'))) . ')'; } /** * @param non-empty-string $message * * @return never */ private function throwException($message, string $url) : void { // git might delete a directory when it fails and php will not know \clearstatcache(); if (0 !== $this->process->execute('git --version', $ignoredOutput)) { throw new \RuntimeException(\Composer\Util\Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); } throw new \RuntimeException(\Composer\Util\Url::sanitize($message)); } /** * Retrieves the current git version. * * @return string|null The git version number, if present. */ public static function getVersion(\Composer\Util\ProcessExecutor $process) : ?string { if (\false === self::$version) { self::$version = null; if (0 === $process->execute('git --version', $output) && Preg::isMatch('/^git version (\\d+(?:\\.\\d+)+)/m', $output, $matches)) { self::$version = $matches[1]; } } return self::$version; } /** * @param string[] $credentials */ private function maskCredentials(string $error, array $credentials) : string { $maskedCredentials = []; foreach ($credentials as $credential) { if (\in_array($credential, ['private-token', 'x-token-auth', 'oauth2', 'gitlab-ci-token', 'x-oauth-basic'])) { $maskedCredentials[] = $credential; } elseif (\strlen($credential) > 6) { $maskedCredentials[] = \substr($credential, 0, 3) . '...' . \substr($credential, -3); } elseif (\strlen($credential) > 3) { $maskedCredentials[] = \substr($credential, 0, 3) . '...'; } else { $maskedCredentials[] = 'XXX'; } } return \str_replace($credentials, $maskedCredentials, $error); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; use Composer\Pcre\Preg; /** * @author Till Klampaeckel * @author Jordi Boggiano */ class Svn { private const MAX_QTY_AUTH_TRIES = 5; /** * @var ?array{username: string, password: string} */ protected $credentials; /** * @var bool */ protected $hasAuth; /** * @var \Composer\IO\IOInterface */ protected $io; /** * @var string */ protected $url; /** * @var bool */ protected $cacheCredentials = \true; /** * @var ProcessExecutor */ protected $process; /** * @var int */ protected $qtyAuthTries = 0; /** * @var \Composer\Config */ protected $config; /** * @var string|null */ private static $version; /** * @param ProcessExecutor $process */ public function __construct(string $url, IOInterface $io, Config $config, ?\Composer\Util\ProcessExecutor $process = null) { $this->url = $url; $this->io = $io; $this->config = $config; $this->process = $process ?: new \Composer\Util\ProcessExecutor($io); } public static function cleanEnv() : void { // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 \Composer\Util\Platform::clearEnv('DYLD_LIBRARY_PATH'); } /** * Execute an SVN remote command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param ?string $cwd Working directory * @param ?string $path Target for a checkout * @param bool $verbose Output all output to the user * * @throws \RuntimeException */ public function execute(string $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = \false) : string { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose); } /** * Execute an SVN local command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $path Path argument passed thru to the command * @param string $cwd Working directory * @param bool $verbose Output all output to the user * * @throws \RuntimeException */ public function executeLocal(string $command, string $path, ?string $cwd = null, bool $verbose = \false) : string { // A local command has no remote url return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); } private function executeWithAuthRetry(string $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose) : ?string { // Regenerate the command at each try, to use the newly user-provided credentials $command = $this->getCommand($svnCommand, $url, $path); $output = null; $io = $this->io; $handler = static function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return null; } if (\strpos($buffer, 'Redirecting to URL ') === 0) { return null; } $output .= $buffer; if ($verbose) { $io->writeError($buffer, \false); } }; $status = $this->process->execute($command, $handler, $cwd); if (0 === $status) { return $output; } $errorOutput = $this->process->getErrorOutput(); $fullOutput = \trim(\implode("\n", [$output, $errorOutput])); // the error is not auth-related if (\false === \stripos($fullOutput, 'Could not authenticate to server:') && \false === \stripos($fullOutput, 'authorization failed') && \false === \stripos($fullOutput, 'svn: E170001:') && \false === \stripos($fullOutput, 'svn: E215004:')) { throw new \RuntimeException($fullOutput); } if (!$this->hasAuth()) { $this->doAuthDance(); } // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { // restart the process return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $fullOutput . ')'); } public function setCacheCredentials(bool $cacheCredentials) : void { $this->cacheCredentials = $cacheCredentials; } /** * Repositories requests credentials, let's put them in. * * @throws \RuntimeException * @return \Composer\Util\Svn */ protected function doAuthDance() : \Composer\Util\Svn { // cannot ask for credentials in non interactive mode if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode'); } $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); $this->hasAuth = \true; $this->credentials = ['username' => (string) $this->io->ask("Username: ", ''), 'password' => (string) $this->io->askAndHideAnswer("Password: ")]; $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) "); return $this; } /** * A method to create the svn commands run. * * @param string $cmd Usually 'svn ls' or something like that. * @param string $url Repo URL. * @param string $path Target for a checkout */ protected function getCommand(string $cmd, string $url, ?string $path = null) : string { $cmd = \sprintf('%s %s%s -- %s', $cmd, '--non-interactive ', $this->getCredentialString(), \Composer\Util\ProcessExecutor::escape($url)); if ($path) { $cmd .= ' ' . \Composer\Util\ProcessExecutor::escape($path); } return $cmd; } /** * Return the credential string for the svn command. * * Adds --no-auth-cache when credentials are present. */ protected function getCredentialString() : string { if (!$this->hasAuth()) { return ''; } return \sprintf(' %s--username %s --password %s ', $this->getAuthCache(), \Composer\Util\ProcessExecutor::escape($this->getUsername()), \Composer\Util\ProcessExecutor::escape($this->getPassword())); } /** * Get the password for the svn command. Can be empty. * * @throws \LogicException */ protected function getPassword() : string { if ($this->credentials === null) { throw new \LogicException("No svn auth detected."); } return $this->credentials['password']; } /** * Get the username for the svn command. * * @throws \LogicException */ protected function getUsername() : string { if ($this->credentials === null) { throw new \LogicException("No svn auth detected."); } return $this->credentials['username']; } /** * Detect Svn Auth. */ protected function hasAuth() : bool { if (null !== $this->hasAuth) { return $this->hasAuth; } if (\false === $this->createAuthFromConfig()) { $this->createAuthFromUrl(); } return (bool) $this->hasAuth; } /** * Return the no-auth-cache switch. */ protected function getAuthCache() : string { return $this->cacheCredentials ? '' : '--no-auth-cache '; } /** * Create the auth params from the configuration file. */ private function createAuthFromConfig() : bool { if (!$this->config->has('http-basic')) { return $this->hasAuth = \false; } $authConfig = $this->config->get('http-basic'); $host = \parse_url($this->url, \PHP_URL_HOST); if (isset($authConfig[$host])) { $this->credentials = ['username' => $authConfig[$host]['username'], 'password' => $authConfig[$host]['password']]; return $this->hasAuth = \true; } return $this->hasAuth = \false; } /** * Create the auth params from the url */ private function createAuthFromUrl() : bool { $uri = \parse_url($this->url); if (empty($uri['user'])) { return $this->hasAuth = \false; } $this->credentials = ['username' => $uri['user'], 'password' => !empty($uri['pass']) ? $uri['pass'] : '']; return $this->hasAuth = \true; } /** * Returns the version of the svn binary contained in PATH */ public function binaryVersion() : ?string { if (!self::$version) { if (0 === $this->process->execute('svn --version', $output)) { if (Preg::isMatch('{(\\d+(?:\\.\\d+)+)}', $output, $match)) { self::$version = $match[1]; } } } return self::$version; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Pcre\Preg; use stdClass; /** * Tests URLs against NO_PROXY patterns */ class NoProxyPattern { /** * @var string[] */ protected $hostNames = []; /** * @var (null|object)[] */ protected $rules = []; /** * @var bool */ protected $noproxy; /** * @param string $pattern NO_PROXY pattern */ public function __construct(string $pattern) { $this->hostNames = Preg::split('{[\\s,]+}', $pattern, -1, \PREG_SPLIT_NO_EMPTY); $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0]; } /** * Returns true if a URL matches the NO_PROXY pattern */ public function test(string $url) : bool { if ($this->noproxy) { return \true; } if (!($urlData = $this->getUrlData($url))) { return \false; } foreach ($this->hostNames as $index => $hostName) { if ($this->match($index, $hostName, $urlData)) { return \true; } } return \false; } /** * Returns false is the url cannot be parsed, otherwise a data object * * @return bool|stdClass */ protected function getUrlData(string $url) { if (!($host = \parse_url($url, \PHP_URL_HOST))) { return \false; } $port = \parse_url($url, \PHP_URL_PORT); if (empty($port)) { switch (\parse_url($url, \PHP_URL_SCHEME)) { case 'http': $port = 80; break; case 'https': $port = 443; break; } } $hostName = $host . ($port ? ':' . $port : ''); [$host, $port, $err] = $this->splitHostPort($hostName); if ($err || !$this->ipCheckData($host, $ipdata)) { return \false; } return $this->makeData($host, $port, $ipdata); } /** * Returns true if the url is matched by a rule */ protected function match(int $index, string $hostName, stdClass $url) : bool { if (!($rule = $this->getRule($index, $hostName))) { // Data must have been misformatted return \false; } if ($rule->ipdata) { // Match ipdata first if (!$url->ipdata) { return \false; } if ($rule->ipdata->netmask) { return $this->matchRange($rule->ipdata, $url->ipdata); } $match = $rule->ipdata->ip === $url->ipdata->ip; } else { // Match host and port $haystack = \substr($url->name, -\strlen($rule->name)); $match = \stripos($haystack, $rule->name) === 0; } if ($match && $rule->port) { $match = $rule->port === $url->port; } return $match; } /** * Returns true if the target ip is in the network range */ protected function matchRange(stdClass $network, stdClass $target) : bool { $net = \unpack('C*', $network->ip); $mask = \unpack('C*', $network->netmask); $ip = \unpack('C*', $target->ip); if (\false === $net) { throw new \RuntimeException('Could not parse network IP ' . $network->ip); } if (\false === $mask) { throw new \RuntimeException('Could not parse netmask ' . $network->netmask); } if (\false === $ip) { throw new \RuntimeException('Could not parse target IP ' . $target->ip); } for ($i = 1; $i < 17; ++$i) { if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) { return \false; } } return \true; } /** * Finds or creates rule data for a hostname * * @return null|stdClass Null if the hostname is invalid */ private function getRule(int $index, string $hostName) : ?stdClass { if (\array_key_exists($index, $this->rules)) { return $this->rules[$index]; } $this->rules[$index] = null; [$host, $port, $err] = $this->splitHostPort($hostName); if ($err || !$this->ipCheckData($host, $ipdata, \true)) { return null; } $this->rules[$index] = $this->makeData($host, $port, $ipdata); return $this->rules[$index]; } /** * Creates an object containing IP data if the host is an IP address * * @param null|stdClass $ipdata Set by method if IP address found * @param bool $allowPrefix Whether a CIDR prefix-length is expected * * @return bool False if the host contains invalid data */ private function ipCheckData(string $host, ?stdClass &$ipdata, bool $allowPrefix = \false) : bool { $ipdata = null; $netmask = null; $prefix = null; $modified = \false; // Check for a CIDR prefix-length if (\strpos($host, '/') !== \false) { [$host, $prefix] = \explode('/', $host); if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) { return \false; } $prefix = (int) $prefix; $modified = \true; } // See if this is an ip address if (!\filter_var($host, \FILTER_VALIDATE_IP)) { return !$modified; } [$ip, $size] = $this->ipGetAddr($host); if ($prefix !== null) { // Check for a valid prefix if ($prefix > $size * 8) { return \false; } [$ip, $netmask] = $this->ipGetNetwork($ip, $size, $prefix); } $ipdata = $this->makeIpData($ip, $size, $netmask); return \true; } /** * Returns an array of the IP in_addr and its byte size * * IPv4 addresses are always mapped to IPv6, which simplifies handling * and comparison. * * @return mixed[] in_addr, size */ private function ipGetAddr(string $host) : array { $ip = \inet_pton($host); $size = \strlen($ip); $mapped = $this->ipMapTo6($ip, $size); return [$mapped, $size]; } /** * Returns the binary network mask mapped to IPv6 * * @param int $prefix CIDR prefix-length * @param int $size Byte size of in_addr */ private function ipGetMask(int $prefix, int $size) : string { $mask = ''; if ($ones = \floor($prefix / 8)) { $mask = \str_repeat(\chr(255), (int) $ones); } if ($remainder = $prefix % 8) { $mask .= \chr(0xff ^ 0xff >> $remainder); } $mask = \str_pad($mask, $size, \chr(0)); return $this->ipMapTo6($mask, $size); } /** * Calculates and returns the network and mask * * @param string $rangeIp IP in_addr * @param int $size Byte size of in_addr * @param int $prefix CIDR prefix-length * * @return string[] network in_addr, binary mask */ private function ipGetNetwork(string $rangeIp, int $size, int $prefix) : array { $netmask = $this->ipGetMask($prefix, $size); // Get the network from the address and mask $mask = \unpack('C*', $netmask); $ip = \unpack('C*', $rangeIp); $net = ''; if (\false === $mask) { throw new \RuntimeException('Could not parse netmask ' . $netmask); } if (\false === $ip) { throw new \RuntimeException('Could not parse range IP ' . $rangeIp); } for ($i = 1; $i < 17; ++$i) { $net .= \chr($ip[$i] & $mask[$i]); } return [$net, $netmask]; } /** * Maps an IPv4 address to IPv6 * * @param string $binary in_addr * @param int $size Byte size of in_addr * * @return string Mapped or existing in_addr */ private function ipMapTo6(string $binary, int $size) : string { if ($size === 4) { $prefix = \str_repeat(\chr(0), 10) . \str_repeat(\chr(255), 2); $binary = $prefix . $binary; } return $binary; } /** * Creates a rule data object */ private function makeData(string $host, int $port, ?stdClass $ipdata) : stdClass { return (object) ['host' => $host, 'name' => '.' . \ltrim($host, '.'), 'port' => $port, 'ipdata' => $ipdata]; } /** * Creates an ip data object * * @param string $ip in_addr * @param int $size Byte size of in_addr * @param null|string $netmask Network mask */ private function makeIpData(string $ip, int $size, ?string $netmask) : stdClass { return (object) ['ip' => $ip, 'size' => $size, 'netmask' => $netmask]; } /** * Splits the hostname into host and port components * * @return mixed[] host, port, if there was error */ private function splitHostPort(string $hostName) : array { // host, port, err $error = ['', '', \true]; $port = 0; $ip6 = ''; // Check for square-bracket notation if ($hostName[0] === '[') { $index = \strpos($hostName, ']'); // The smallest ip6 address is :: if (\false === $index || $index < 3) { return $error; } $ip6 = \substr($hostName, 1, $index - 1); $hostName = \substr($hostName, $index + 1); if (\strpbrk($hostName, '[]') !== \false || \substr_count($hostName, ':') > 1) { return $error; } } if (\substr_count($hostName, ':') === 1) { $index = \strpos($hostName, ':'); $port = \substr($hostName, $index + 1); $hostName = \substr($hostName, 0, $index); if (!$this->validateInt($port, 1, 65535)) { return $error; } $port = (int) $port; } $host = $ip6 . $hostName; return [$host, $port, \false]; } /** * Wrapper around filter_var FILTER_VALIDATE_INT */ private function validateInt(string $int, int $min, int $max) : bool { $options = ['options' => ['min_range' => $min, 'max_range' => $max]]; return \false !== \filter_var($int, \FILTER_VALIDATE_INT, $options); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Pcre\Preg; /** * Composer mirror utilities * * @author Jordi Boggiano */ class ComposerMirror { /** * @param non-empty-string $mirrorUrl * @return non-empty-string */ public static function processUrl(string $mirrorUrl, string $packageName, string $version, ?string $reference, ?string $type, ?string $prettyVersion = null) : string { if ($reference) { $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : \md5($reference); } $version = \strpos($version, '/') === \false ? $version : \md5($version); $from = ['%package%', '%version%', '%reference%', '%type%']; $to = [$packageName, $version, $reference, $type]; if (null !== $prettyVersion) { $from[] = '%prettyVersion%'; $to[] = $prettyVersion; } $url = \str_replace($from, $to, $mirrorUrl); \assert($url !== ''); return $url; } /** * @param non-empty-string $mirrorUrl * @return string */ public static function processGitUrl(string $mirrorUrl, string $packageName, string $url, ?string $type) : string { if (Preg::isMatch('#^(?:(?:https?|git)://github\\.com/|git@github\\.com:)([^/]+)/(.+?)(?:\\.git)?$#', $url, $match)) { $url = 'gh-' . $match[1] . '/' . $match[2]; } elseif (Preg::isMatch('#^https://bitbucket\\.org/([^/]+)/(.+?)(?:\\.git)?/?$#', $url, $match)) { $url = 'bb-' . $match[1] . '/' . $match[2]; } else { $url = Preg::replace('{[^a-z0-9_.-]}i', '-', \trim($url, '/')); } return \str_replace(['%package%', '%normalizedUrl%', '%type%'], [$packageName, $url, $type], $mirrorUrl); } /** * @param non-empty-string $mirrorUrl * @return string */ public static function processHgUrl(string $mirrorUrl, string $packageName, string $url, string $type) : string { return self::processGitUrl($mirrorUrl, $packageName, $url, $type); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Package\CompletePackageInterface; use Composer\Package\PackageInterface; class PackageInfo { public static function getViewSourceUrl(PackageInterface $package) : ?string { if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source']) && '' !== $package->getSupport()['source']) { return $package->getSupport()['source']; } return $package->getSourceUrl(); } public static function getViewSourceOrHomepageUrl(PackageInterface $package) : ?string { $url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null); if ($url === '') { return null; } return $url; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; /** * @author Jordi Boggiano */ class AuthHelper { /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var array Map of origins to message displayed */ private $displayedOriginAuthentications = []; /** @var array Map of URLs and whether they already retried with authentication from Bitbucket */ private $bitbucketRetry = []; public function __construct(IOInterface $io, Config $config) { $this->io = $io; $this->config = $config; } /** * @param 'prompt'|bool $storeAuth */ public function storeAuth(string $origin, $storeAuth) : void { $store = \false; $configSource = $this->config->getAuthConfigSource(); if ($storeAuth === \true) { $store = $configSource; } elseif ($storeAuth === 'prompt') { $answer = $this->io->askAndValidate('Do you want to store credentials for ' . $origin . ' in ' . $configSource->getName() . ' ? [Yn] ', static function ($value) : string { $input = \strtolower(\substr(\trim($value), 0, 1)); if (\in_array($input, ['y', 'n'])) { return $input; } throw new \RuntimeException('Please answer (y)es or (n)o'); }, null, 'y'); if ($answer === 'y') { $store = $configSource; } } if ($store) { $store->addConfigSetting('http-basic.' . $origin, $this->io->getAuthentication($origin)); } } /** * @param int $statusCode HTTP status code that triggered this call * @param string|null $reason a message/description explaining why this was called * @param string[] $headers * @param int $retryCount the amount of retries already done on this URL * @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json * @phpstan-return array{retry: bool, storeAuth: 'prompt'|bool} */ public function promptAuthIfNeeded(string $url, string $origin, int $statusCode, ?string $reason = null, array $headers = [], int $retryCount = 0) : array { $storeAuth = \false; if (\in_array($origin, $this->config->get('github-domains'), \true)) { $gitHubUtil = new \Composer\Util\GitHub($this->io, $this->config, null); $message = "\n"; $rateLimited = $gitHubUtil->isRateLimited($headers); $requiresSso = $gitHubUtil->requiresSso($headers); if ($requiresSso) { $ssoUrl = $gitHubUtil->getSsoUrl($headers); $message = 'GitHub API token requires SSO authorization. Authorize this token at ' . $ssoUrl . "\n"; $this->io->writeError($message); if (!$this->io->isInteractive()) { throw new TransportException('Could not authenticate against ' . $origin, 403); } $this->io->ask('After authorizing your token, confirm that you would like to retry the request'); return ['retry' => \true, 'storeAuth' => $storeAuth]; } if ($rateLimited) { $rateLimit = $gitHubUtil->getRateLimit($headers); if ($this->io->hasAuthentication($origin)) { $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; } else { $message = 'Create a GitHub OAuth token to go over the API rate limit.'; } $message = \sprintf('GitHub API limit (%d calls/hr) is exhausted, could not fetch ' . $url . '. ' . $message . ' You can also wait until %s for the rate limit to reset.', $rateLimit['limit'], $rateLimit['reset']) . "\n"; } else { $message .= 'Could not fetch ' . $url . ', please '; if ($this->io->hasAuthentication($origin)) { $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; } else { $message .= 'create a GitHub OAuth token to access private repos'; } } if (!$gitHubUtil->authorizeOAuth($origin) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message))) { throw new TransportException('Could not authenticate against ' . $origin, 401); } } elseif (\in_array($origin, $this->config->get('gitlab-domains'), \true)) { $message = "\n" . 'Could not fetch ' . $url . ', enter your ' . $origin . ' credentials ' . ($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit'); $gitLabUtil = new \Composer\Util\GitLab($this->io, $this->config, null); $auth = null; if ($this->io->hasAuthentication($origin)) { $auth = $this->io->getAuthentication($origin); if (\in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], \true)) { throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); } } if (!$gitLabUtil->authorizeOAuth($origin) && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(\parse_url($url, \PHP_URL_SCHEME), $origin, $message))) { throw new TransportException('Could not authenticate against ' . $origin, 401); } if ($auth !== null && $this->io->hasAuthentication($origin)) { if ($auth === $this->io->getAuthentication($origin)) { throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); } } } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') { $askForOAuthToken = \true; $origin = 'bitbucket.org'; if ($this->io->hasAuthentication($origin)) { $auth = $this->io->getAuthentication($origin); if ($auth['username'] !== 'x-token-auth') { $bitbucketUtil = new \Composer\Util\Bitbucket($this->io, $this->config); $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']); if (!empty($accessToken)) { $this->io->setAuthentication($origin, 'x-token-auth', $accessToken); $askForOAuthToken = \false; } } elseif (!isset($this->bitbucketRetry[$url])) { // when multiple requests fire at the same time, they will all fail and the first one resets the token to be correct above but then the others // reach the code path and without this fallback they would end up throwing below // see https://github.com/composer/composer/pull/11464 for more details $askForOAuthToken = \false; $this->bitbucketRetry[$url] = \true; } else { throw new TransportException('Could not authenticate against ' . $origin, 401); } } if ($askForOAuthToken) { $message = "\n" . 'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . ($statusCode === 401 || $statusCode === 403 ? 'access private repos' : 'go over the API rate limit'); $bitBucketUtil = new \Composer\Util\Bitbucket($this->io, $this->config); if (!$bitBucketUtil->authorizeOAuth($origin) && (!$this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message))) { throw new TransportException('Could not authenticate against ' . $origin, 401); } } } else { // 404s are only handled for github if ($statusCode === 404) { return ['retry' => \false, 'storeAuth' => \false]; } // fail if the console is not interactive if (!$this->io->isInteractive()) { if ($statusCode === 401) { $message = "The '" . $url . "' URL required authentication (HTTP 401).\nYou must be using the interactive console to authenticate"; } elseif ($statusCode === 403) { $message = "The '" . $url . "' URL could not be accessed (HTTP 403): " . $reason; } else { $message = "Unknown error code '" . $statusCode . "', reason: " . $reason; } throw new TransportException($message, $statusCode); } // fail if we already have auth if ($this->io->hasAuthentication($origin)) { // if two or more requests are started together for the same host, and the first // received authentication already, we let the others retry before failing them if ($retryCount === 0) { return ['retry' => \true, 'storeAuth' => \false]; } throw new TransportException("Invalid credentials (HTTP {$statusCode}) for '{$url}', aborting.", $statusCode); } $this->io->writeError(' Authentication required (' . $origin . '):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthentication($origin, $username, $password); $storeAuth = $this->config->get('store-auths'); } return ['retry' => \true, 'storeAuth' => $storeAuth]; } /** * @param string[] $headers * * @return string[] updated headers array */ public function addAuthenticationHeader(array $headers, string $origin, string $url) : array { if ($this->io->hasAuthentication($origin)) { $authenticationDisplayMessage = null; $auth = $this->io->getAuthentication($origin); if ($auth['password'] === 'bearer') { $headers[] = 'Authorization: Bearer ' . $auth['username']; } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) { // only add the access_token if it is actually a github API URL if (Preg::isMatch('{^https?://api\\.github\\.com/}', $url)) { $headers[] = 'Authorization: token ' . $auth['username']; $authenticationDisplayMessage = 'Using GitHub token authentication'; } } elseif (\in_array($origin, $this->config->get('gitlab-domains'), \true) && \in_array($auth['password'], ['oauth2', 'private-token', 'gitlab-ci-token'], \true)) { if ($auth['password'] === 'oauth2') { $headers[] = 'Authorization: Bearer ' . $auth['username']; $authenticationDisplayMessage = 'Using GitLab OAuth token authentication'; } else { $headers[] = 'PRIVATE-TOKEN: ' . $auth['username']; $authenticationDisplayMessage = 'Using GitLab private token authentication'; } } elseif ('bitbucket.org' === $origin && $url !== \Composer\Util\Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username']) { if (!$this->isPublicBitBucketDownload($url)) { $headers[] = 'Authorization: Bearer ' . $auth['password']; $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication'; } } else { $authStr = \base64_encode($auth['username'] . ':' . $auth['password']); $headers[] = 'Authorization: Basic ' . $authStr; $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"'; } if ($authenticationDisplayMessage && (!isset($this->displayedOriginAuthentications[$origin]) || $this->displayedOriginAuthentications[$origin] !== $authenticationDisplayMessage)) { $this->io->writeError($authenticationDisplayMessage, \true, IOInterface::DEBUG); $this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage; } } elseif (\in_array($origin, ['api.bitbucket.org', 'api.github.com'], \true)) { return $this->addAuthenticationHeader($headers, \str_replace('api.', '', $origin), $url); } return $headers; } /** * @link https://github.com/composer/composer/issues/5584 * * @param string $urlToBitBucketFile URL to a file at bitbucket.org. * * @return bool Whether the given URL is a public BitBucket download which requires no authentication. */ public function isPublicBitBucketDownload(string $urlToBitBucketFile) : bool { $domain = \parse_url($urlToBitBucketFile, \PHP_URL_HOST); if (\strpos($domain, 'bitbucket.org') === \false) { // Bitbucket downloads are hosted on amazonaws. // We do not need to authenticate there at all return \true; } $path = \parse_url($urlToBitBucketFile, \PHP_URL_PATH); // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} $pathParts = \explode('/', $path); return \count($pathParts) >= 4 && $pathParts[3] === 'downloads'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * @author Andreas Schempp */ class Zip { /** * Gets content of the root composer.json inside a ZIP archive. */ public static function getComposerJson(string $pathToZip) : ?string { if (!\extension_loaded('zip')) { throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); } $zip = new \ZipArchive(); if ($zip->open($pathToZip) !== \true) { return null; } if (0 === $zip->numFiles) { $zip->close(); return null; } $foundFileIndex = self::locateFile($zip, 'composer.json'); $content = null; $configurationFileName = $zip->getNameIndex($foundFileIndex); $stream = $zip->getStream($configurationFileName); if (\false !== $stream) { $content = \stream_get_contents($stream); } $zip->close(); return $content; } /** * Find a file by name, returning the one that has the shortest path. * * @throws \RuntimeException */ private static function locateFile(\ZipArchive $zip, string $filename) : int { // return root composer.json if it is there and is a file if (\false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== \false) { return $index; } $topLevelPaths = []; for ($i = 0; $i < $zip->numFiles; $i++) { $name = $zip->getNameIndex($i); $dirname = \dirname($name); // ignore OSX specific resource fork folder if (\strpos($name, '__MACOSX') !== \false) { continue; } // handle archives with proper TOC if ($dirname === '.') { $topLevelPaths[$name] = \true; if (\count($topLevelPaths) > 1) { throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: ' . \implode(',', \array_keys($topLevelPaths))); } continue; } // handle archives which do not have a TOC record for the directory itself if (\false === \strpos($dirname, '\\') && \false === \strpos($dirname, '/')) { $topLevelPaths[$dirname . '/'] = \true; if (\count($topLevelPaths) > 1) { throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: ' . \implode(',', \array_keys($topLevelPaths))); } } } if ($topLevelPaths && \false !== ($index = $zip->locateName(\key($topLevelPaths) . $filename)) && $zip->getFromIndex($index) !== \false) { return $index; } throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * Temporarily suppress PHP error reporting, usually warnings and below. * * @author Niels Keurentjes */ class Silencer { /** * @var int[] Unpop stack */ private static $stack = []; /** * Suppresses given mask or errors. * * @param int|null $mask Error levels to suppress, default value NULL indicates all warnings and below. * @return int The old error reporting level. */ public static function suppress(?int $mask = null) : int { if (!isset($mask)) { $mask = \E_WARNING | \E_NOTICE | \E_USER_WARNING | \E_USER_NOTICE | \E_DEPRECATED | \E_USER_DEPRECATED | \E_STRICT; } $old = \error_reporting(); self::$stack[] = $old; \error_reporting($old & ~$mask); return $old; } /** * Restores a single state. */ public static function restore() : void { if (!empty(self::$stack)) { \error_reporting(\array_pop(self::$stack)); } } /** * Calls a specified function while silencing warnings and below. * * @param callable $callable Function to execute. * @param mixed $parameters Function to execute. * @throws \Exception Any exceptions from the callback are rethrown. * @return mixed Return value of the callback. */ public static function call(callable $callable, ...$parameters) { try { self::suppress(); $result = $callable(...$parameters); self::restore(); return $result; } catch (\Exception $e) { // Use a finally block for this when requirements are raised to PHP 5.5 self::restore(); throw $e; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Pcre\Preg; use React\Promise\PromiseInterface; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use _ContaoManager\Symfony\Component\Filesystem\Exception\IOException; use _ContaoManager\Symfony\Component\Finder\Finder; /** * @author Jordi Boggiano * @author Johannes M. Schmitt */ class Filesystem { /** @var ?ProcessExecutor */ private $processExecutor; public function __construct(?\Composer\Util\ProcessExecutor $executor = null) { $this->processExecutor = $executor; } /** * @return bool */ public function remove(string $file) { if (\is_dir($file)) { return $this->removeDirectory($file); } if (\file_exists($file)) { return $this->unlink($file); } return \false; } /** * Checks if a directory is empty * * @return bool */ public function isDirEmpty(string $dir) { $finder = Finder::create()->ignoreVCS(\false)->ignoreDotFiles(\false)->depth(0)->in($dir); return \count($finder) === 0; } /** * @return void */ public function emptyDirectory(string $dir, bool $ensureDirectoryExists = \true) { if (\is_link($dir) && \file_exists($dir)) { $this->unlink($dir); } if ($ensureDirectoryExists) { $this->ensureDirectoryExists($dir); } if (\is_dir($dir)) { $finder = Finder::create()->ignoreVCS(\false)->ignoreDotFiles(\false)->depth(0)->in($dir); foreach ($finder as $path) { $this->remove((string) $path); } } } /** * Recursively remove a directory * * Uses the process component if proc_open is enabled on the PHP * installation. * * @throws \RuntimeException * @return bool */ public function removeDirectory(string $directory) { $edgeCaseResult = $this->removeEdgeCases($directory); if ($edgeCaseResult !== null) { return $edgeCaseResult; } if (\Composer\Util\Platform::isWindows()) { $cmd = \sprintf('rmdir /S /Q %s', \Composer\Util\ProcessExecutor::escape(\realpath($directory))); } else { $cmd = \sprintf('rm -rf %s', \Composer\Util\ProcessExecutor::escape($directory)); } $result = $this->getProcess()->execute($cmd, $output) === 0; // clear stat cache because external processes aren't tracked by the php stat cache \clearstatcache(); if ($result && !\is_dir($directory)) { return \true; } return $this->removeDirectoryPhp($directory); } /** * Recursively remove a directory asynchronously * * Uses the process component if proc_open is enabled on the PHP * installation. * * @throws \RuntimeException * @return PromiseInterface * @phpstan-return PromiseInterface */ public function removeDirectoryAsync(string $directory) { $edgeCaseResult = $this->removeEdgeCases($directory); if ($edgeCaseResult !== null) { return \React\Promise\resolve($edgeCaseResult); } if (\Composer\Util\Platform::isWindows()) { $cmd = \sprintf('rmdir /S /Q %s', \Composer\Util\ProcessExecutor::escape(\realpath($directory))); } else { $cmd = \sprintf('rm -rf %s', \Composer\Util\ProcessExecutor::escape($directory)); } $promise = $this->getProcess()->executeAsync($cmd); return $promise->then(function ($process) use($directory) { // clear stat cache because external processes aren't tracked by the php stat cache \clearstatcache(); if ($process->isSuccessful()) { if (!\is_dir($directory)) { return \React\Promise\resolve(\true); } } return \React\Promise\resolve($this->removeDirectoryPhp($directory)); }); } /** * @return bool|null Returns null, when no edge case was hit. Otherwise a bool whether removal was successful */ private function removeEdgeCases(string $directory, bool $fallbackToPhp = \true) : ?bool { if ($this->isSymlinkedDirectory($directory)) { return $this->unlinkSymlinkedDirectory($directory); } if ($this->isJunction($directory)) { return $this->removeJunction($directory); } if (\is_link($directory)) { return \unlink($directory); } if (!\is_dir($directory) || !\file_exists($directory)) { return \true; } if (Preg::isMatch('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) { throw new \RuntimeException('Aborting an attempted deletion of ' . $directory . ', this was probably not intended, if it is a real use case please report it.'); } if (!\function_exists('proc_open') && $fallbackToPhp) { return $this->removeDirectoryPhp($directory); } return null; } /** * Recursively delete directory using PHP iterators. * * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files * before directories, creating a single non-recursive loop * to delete files/directories in the correct order. * * @return bool */ public function removeDirectoryPhp(string $directory) { $edgeCaseResult = $this->removeEdgeCases($directory, \false); if ($edgeCaseResult !== null) { return $edgeCaseResult; } try { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); } catch (\UnexpectedValueException $e) { // re-try once after clearing the stat cache if it failed as it // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009 \clearstatcache(); \usleep(100000); if (!\is_dir($directory)) { return \true; } $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); } $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($ri as $file) { if ($file->isDir()) { $this->rmdir($file->getPathname()); } else { $this->unlink($file->getPathname()); } } // release locks on the directory, see https://github.com/composer/composer/issues/9945 unset($ri, $it, $file); return $this->rmdir($directory); } /** * @return void */ public function ensureDirectoryExists(string $directory) { if (!\is_dir($directory)) { if (\file_exists($directory)) { throw new \RuntimeException($directory . ' exists and is not a directory.'); } if (\is_link($directory) && !@$this->unlinkImplementation($directory)) { throw new \RuntimeException('Could not delete symbolic link ' . $directory . ': ' . (\error_get_last()['message'] ?? '')); } if (!@\mkdir($directory, 0777, \true)) { throw new \RuntimeException($directory . ' does not exist and could not be created: ' . (\error_get_last()['message'] ?? '')); } } } /** * Attempts to unlink a file and in case of failure retries after 350ms on windows * * @throws \RuntimeException * @return bool */ public function unlink(string $path) { $unlinked = @$this->unlinkImplementation($path); if (!$unlinked) { // retry after a bit on windows since it tends to be touchy with mass removals if (\Composer\Util\Platform::isWindows()) { \usleep(350000); $unlinked = @$this->unlinkImplementation($path); } if (!$unlinked) { $error = \error_get_last(); $message = 'Could not delete ' . $path . ': ' . ($error['message'] ?? ''); if (\Composer\Util\Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } throw new \RuntimeException($message); } } return \true; } /** * Attempts to rmdir a file and in case of failure retries after 350ms on windows * * @throws \RuntimeException * @return bool */ public function rmdir(string $path) { $deleted = @\rmdir($path); if (!$deleted) { // retry after a bit on windows since it tends to be touchy with mass removals if (\Composer\Util\Platform::isWindows()) { \usleep(350000); $deleted = @\rmdir($path); } if (!$deleted) { $error = \error_get_last(); $message = 'Could not delete ' . $path . ': ' . ($error['message'] ?? ''); if (\Composer\Util\Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } throw new \RuntimeException($message); } } return \true; } /** * Copy then delete is a non-atomic version of {@link rename}. * * Some systems can't rename and also don't have proc_open, * which requires this solution. * * @return void */ public function copyThenRemove(string $source, string $target) { $this->copy($source, $target); if (!\is_dir($source)) { $this->unlink($source); return; } $this->removeDirectoryPhp($source); } /** * Copies a file or directory from $source to $target. * * @return bool */ public function copy(string $source, string $target) { if (!\is_dir($source)) { return \copy($source, $target); } $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); $this->ensureDirectoryExists($target); $result = \true; foreach ($ri as $file) { $targetPath = $target . \DIRECTORY_SEPARATOR . $ri->getSubPathname(); if ($file->isDir()) { $this->ensureDirectoryExists($targetPath); } else { $result = $result && \copy($file->getPathname(), $targetPath); } } return $result; } /** * @return void */ public function rename(string $source, string $target) { if (\true === @\rename($source, $target)) { return; } if (!\function_exists('proc_open')) { $this->copyThenRemove($source, $target); return; } if (\Composer\Util\Platform::isWindows()) { // Try to copy & delete - this is a workaround for random "Access denied" errors. $command = \sprintf('xcopy %s %s /E /I /Q /Y', \Composer\Util\ProcessExecutor::escape($source), \Composer\Util\ProcessExecutor::escape($target)); $result = $this->getProcess()->execute($command, $output); // clear stat cache because external processes aren't tracked by the php stat cache \clearstatcache(); if (0 === $result) { $this->remove($source); return; } } else { // We do not use PHP's "rename" function here since it does not support // the case where $source, and $target are located on different partitions. $command = \sprintf('mv %s %s', \Composer\Util\ProcessExecutor::escape($source), \Composer\Util\ProcessExecutor::escape($target)); $result = $this->getProcess()->execute($command, $output); // clear stat cache because external processes aren't tracked by the php stat cache \clearstatcache(); if (0 === $result) { return; } } $this->copyThenRemove($source, $target); } /** * Returns the shortest path from $from to $to * * @param bool $directories if true, the source/target are considered to be directories * @throws \InvalidArgumentException * @return string */ public function findShortestPath(string $from, string $to, bool $directories = \false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(\sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } $from = $this->normalizePath($from); $to = $this->normalizePath($to); if ($directories) { $from = \rtrim($from, '/') . '/dummy_file'; } if (\dirname($from) === \dirname($to)) { return './' . \basename($to); } $commonPath = $to; while (\strpos($from . '/', $commonPath . '/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) { $commonPath = \strtr(\dirname($commonPath), '\\', '/'); } // no commonality at all if (0 !== \strpos($from, $commonPath)) { return $to; } $commonPath = \rtrim($commonPath, '/') . '/'; $sourcePathDepth = \substr_count((string) \substr($from, \strlen($commonPath)), '/'); $commonPathCode = \str_repeat('../', $sourcePathDepth); // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups if ('/' === $commonPath && $sourcePathDepth > 1) { return $to; } $result = $commonPathCode . \substr($to, \strlen($commonPath)); if (\strlen($result) === 0) { return './'; } return $result; } /** * Returns PHP code that, when executed in $from, will return the path to $to * * @param bool $directories if true, the source/target are considered to be directories * @throws \InvalidArgumentException * @return string */ public function findShortestPathCode(string $from, string $to, bool $directories = \false, bool $staticCode = \false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(\sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } $from = $this->normalizePath($from); $to = $this->normalizePath($to); if ($from === $to) { return $directories ? '__DIR__' : '__FILE__'; } $commonPath = $to; while (\strpos($from . '/', $commonPath . '/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) { $commonPath = \strtr(\dirname($commonPath), '\\', '/'); } // no commonality at all if (0 !== \strpos($from, $commonPath) || '.' === $commonPath) { return \var_export($to, \true); } $commonPath = \rtrim($commonPath, '/') . '/'; if (\str_starts_with($to, $from . '/')) { return '__DIR__ . ' . \var_export((string) \substr($to, \strlen($from)), \true); } $sourcePathDepth = \substr_count((string) \substr($from, \strlen($commonPath)), '/') + (int) $directories; // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups if ('/' === $commonPath && $sourcePathDepth > 1) { return \var_export($to, \true); } if ($staticCode) { $commonPathCode = "__DIR__ . '" . \str_repeat('/..', $sourcePathDepth) . "'"; } else { $commonPathCode = \str_repeat('dirname(', $sourcePathDepth) . '__DIR__' . \str_repeat(')', $sourcePathDepth); } $relTarget = (string) \substr($to, \strlen($commonPath)); return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . \var_export('/' . $relTarget, \true) : ''); } /** * Checks if the given path is absolute * * @return bool */ public function isAbsolutePath(string $path) { return \strpos($path, '/') === 0 || \substr($path, 1, 1) === ':' || \strpos($path, '\\\\') === 0; } /** * Returns size of a file or directory specified by path. If a directory is * given, its size will be computed recursively. * * @param string $path Path to the file or directory * @throws \RuntimeException * @return int */ public function size(string $path) { if (!\file_exists($path)) { throw new \RuntimeException("{$path} does not exist."); } if (\is_dir($path)) { return $this->directorySize($path); } return (int) \filesize($path); } /** * Normalize a path. This replaces backslashes with slashes, removes ending * slash and collapses redundant separators and up-level references. * * @param string $path Path to the file or directory * @return string */ public function normalizePath(string $path) { $parts = []; $path = \strtr($path, '\\', '/'); $prefix = ''; $absolute = ''; // extract windows UNC paths e.g. \\foo\bar if (\strpos($path, '//') === 0 && \strlen($path) > 2) { $absolute = '//'; $path = \substr($path, 2); } // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { $prefix = $match[1]; $path = \substr($path, \strlen($prefix)); } if (\strpos($path, '/') === 0) { $absolute = '/'; $path = \substr($path, 1); } $up = \false; foreach (\explode('/', $path) as $chunk) { if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { \array_pop($parts); $up = !(\count($parts) === 0 || '..' === \end($parts)); } elseif ('.' !== $chunk && '' !== $chunk) { $parts[] = $chunk; $up = '..' !== $chunk; } } // ensure c: is normalized to C: $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) { \assert(\is_string($m[0])); return \strtoupper($m[0]); }, $prefix); return $prefix . $absolute . \implode('/', $parts); } /** * Remove trailing slashes if present to avoid issues with symlinks * * And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422 * * @return string */ public static function trimTrailingSlash(string $path) { if (!Preg::isMatch('{^[/\\\\]+$}', $path)) { $path = \rtrim($path, '/\\'); } return $path; } /** * Return if the given path is local * * @return bool */ public static function isLocalPath(string $path) { return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\\.\\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); } /** * @return string */ public static function getPlatformPath(string $path) { if (\Composer\Util\Platform::isWindows()) { $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path); } return Preg::replace('{^file://}i', '', $path); } /** * Cross-platform safe version of is_readable() * * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 * * @return bool */ public static function isReadable(string $path) { if (\is_readable($path)) { return \true; } if (\is_file($path)) { return \false !== \Composer\Util\Silencer::call('file_get_contents', $path, \false, null, 0, 1); } if (\is_dir($path)) { return \false !== \Composer\Util\Silencer::call('opendir', $path); } // assume false otherwise return \false; } /** * @return int */ protected function directorySize(string $directory) { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); $size = 0; foreach ($ri as $file) { if ($file->isFile()) { $size += $file->getSize(); } } return $size; } /** * @return ProcessExecutor */ protected function getProcess() { if (null === $this->processExecutor) { $this->processExecutor = new \Composer\Util\ProcessExecutor(); } return $this->processExecutor; } /** * delete symbolic link implementation (commonly known as "unlink()") * * symbolic links on windows which link to directories need rmdir instead of unlink */ private function unlinkImplementation(string $path) : bool { if (\Composer\Util\Platform::isWindows() && \is_dir($path) && \is_link($path)) { return \rmdir($path); } return \unlink($path); } /** * Creates a relative symlink from $link to $target * * @param string $target The path of the binary file to be symlinked * @param string $link The path where the symlink should be created * @return bool */ public function relativeSymlink(string $target, string $link) { if (!\function_exists('symlink')) { return \false; } $cwd = \Composer\Util\Platform::getCwd(); $relativePath = $this->findShortestPath($link, $target); \chdir(\dirname($link)); $result = @\symlink($relativePath, $link); \chdir($cwd); return $result; } /** * return true if that directory is a symlink. * * @return bool */ public function isSymlinkedDirectory(string $directory) { if (!\is_dir($directory)) { return \false; } $resolved = $this->resolveSymlinkedDirectorySymlink($directory); return \is_link($resolved); } private function unlinkSymlinkedDirectory(string $directory) : bool { $resolved = $this->resolveSymlinkedDirectorySymlink($directory); return $this->unlink($resolved); } /** * resolve pathname to symbolic link of a directory * * @param string $pathname directory path to resolve * * @return string resolved path to symbolic link or original pathname (unresolved) */ private function resolveSymlinkedDirectorySymlink(string $pathname) : string { if (!\is_dir($pathname)) { return $pathname; } $resolved = \rtrim($pathname, '/'); if (0 === \strlen($resolved)) { return $pathname; } return $resolved; } /** * Creates an NTFS junction. * * @return void */ public function junction(string $target, string $junction) { if (!\Composer\Util\Platform::isWindows()) { throw new \LogicException(\sprintf('Function %s is not available on non-Windows platform', __CLASS__)); } if (!\is_dir($target)) { throw new IOException(\sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target); } // Removing any previously junction to ensure clean execution. if (!\is_dir($junction) || $this->isJunction($junction)) { @\rmdir($junction); } $cmd = \sprintf('mklink /J %s %s', \Composer\Util\ProcessExecutor::escape(\str_replace('/', \DIRECTORY_SEPARATOR, $junction)), \Composer\Util\ProcessExecutor::escape(\realpath($target))); if ($this->getProcess()->execute($cmd, $output) !== 0) { throw new IOException(\sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); } \clearstatcache(\true, $junction); } /** * Returns whether the target directory is a Windows NTFS Junction. * * We test if the path is a directory and not an ordinary link, then check * that the mode value returned from lstat (which gives the status of the * link itself) is not a directory, by replicating the POSIX S_ISDIR test. * * This logic works because PHP does not set the mode value for a junction, * since there is no universal file type flag for it. Unfortunately an * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a * random value to be returned. See https://bugs.php.net/bug.php?id=77552 * * If this random value passes the S_ISDIR test, then a junction will not be * detected and a recursive delete operation could lead to loss of data in * the target directory. Note that Windows rmdir can handle this situation * and will only delete the junction (from Windows 7 onwards). * * @param string $junction Path to check. * @return bool */ public function isJunction(string $junction) { if (!\Composer\Util\Platform::isWindows()) { return \false; } // Important to clear all caches first \clearstatcache(\true, $junction); if (!\is_dir($junction) || \is_link($junction)) { return \false; } $stat = \lstat($junction); // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) return \is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xf000) : \false; } /** * Removes a Windows NTFS junction. * * @return bool */ public function removeJunction(string $junction) { if (!\Composer\Util\Platform::isWindows()) { return \false; } $junction = \rtrim(\str_replace('/', \DIRECTORY_SEPARATOR, $junction), \DIRECTORY_SEPARATOR); if (!$this->isJunction($junction)) { throw new IOException(\sprintf('%s is not a junction and thus cannot be removed as one', $junction)); } return $this->rmdir($junction); } /** * @return int|false */ public function filePutContentsIfModified(string $path, string $content) { $currentContent = \Composer\Util\Silencer::call('file_get_contents', $path); if (\false === $currentContent || $currentContent !== $content) { return \file_put_contents($path, $content); } return 0; } /** * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 */ public function safeCopy(string $source, string $target) : void { if (!\file_exists($target) || !\file_exists($source) || !$this->filesAreEqual($source, $target)) { $sourceHandle = \fopen($source, 'r'); \assert($sourceHandle !== \false, 'Could not open "' . $source . '" for reading.'); $targetHandle = \fopen($target, 'w+'); \assert($targetHandle !== \false, 'Could not open "' . $target . '" for writing.'); \stream_copy_to_stream($sourceHandle, $targetHandle); \fclose($sourceHandle); \fclose($targetHandle); \touch($target, (int) \filemtime($source), (int) \fileatime($source)); } } /** * compare 2 files * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files */ private function filesAreEqual(string $a, string $b) : bool { // Check if filesize is different if (\filesize($a) !== \filesize($b)) { return \false; } // Check if content is different $aHandle = \fopen($a, 'rb'); \assert($aHandle !== \false, 'Could not open "' . $a . '" for reading.'); $bHandle = \fopen($b, 'rb'); \assert($bHandle !== \false, 'Could not open "' . $b . '" for reading.'); $result = \true; while (!\feof($aHandle)) { if (\fread($aHandle, 8192) !== \fread($bHandle, 8192)) { $result = \false; break; } } \fclose($aHandle); \fclose($bHandle); return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; /** * @author Wissem Riahi */ class Tar { public static function getComposerJson(string $pathToArchive) : ?string { $phar = new \PharData($pathToArchive); if (!$phar->valid()) { return null; } return self::extractComposerJsonFromFolder($phar); } /** * @throws \RuntimeException */ private static function extractComposerJsonFromFolder(\PharData $phar) : string { if (isset($phar['composer.json'])) { return $phar['composer.json']->getContent(); } $topLevelPaths = []; foreach ($phar as $folderFile) { $name = $folderFile->getBasename(); if ($folderFile->isDir()) { $topLevelPaths[$name] = \true; if (\count($topLevelPaths) > 1) { throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: ' . \implode(',', \array_keys($topLevelPaths))); } } } $composerJsonPath = \key($topLevelPaths) . '/composer.json'; if ($topLevelPaths && isset($phar[$composerJsonPath])) { return $phar[$composerJsonPath]->getContent(); } throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; /** * @author Jordi Boggiano */ class GitHub { /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var HttpDownloader */ protected $httpDownloader; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking */ public function __construct(IOInterface $io, Config $config, ?\Composer\Util\ProcessExecutor $process = null, ?\Composer\Util\HttpDownloader $httpDownloader = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new \Composer\Util\ProcessExecutor($io); $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } /** * Attempts to authorize a GitHub domain via OAuth * * @param string $originUrl The host this GitHub instance is located at * @return bool true on success */ public function authorizeOAuth(string $originUrl) : bool { if (!\in_array($originUrl, $this->config->get('github-domains'))) { return \false; } // if available use token from git config if (0 === $this->process->execute('git config github.accesstoken', $output)) { $this->io->setAuthentication($originUrl, \trim($output), 'x-oauth-basic'); return \true; } return \false; } /** * Authorizes a GitHub domain interactively via OAuth * * @param string $originUrl The host this GitHub instance is located at * @param string $message The reason this authorization is required * @throws \RuntimeException * @throws TransportException|\Exception * @return bool true on success */ public function authorizeOAuthInteractively(string $originUrl, ?string $message = null) : bool { if ($message) { $this->io->writeError($message); } $note = 'Composer'; if ($this->config->get('github-expose-hostname') === \true && 0 === $this->process->execute('hostname', $output)) { $note .= ' on ' . \trim($output); } $note .= ' ' . \date('Y-m-d Hi'); $url = 'https://' . $originUrl . '/settings/tokens/new?scopes=&description=' . \str_replace('%20', '+', \rawurlencode($note)); $this->io->writeError('When working with _public_ GitHub repositories only, head here to retrieve a token:'); $this->io->writeError($url); $this->io->writeError('This token will have read-only permission for public information only.'); $localAuthConfig = $this->config->getLocalAuthConfigSource(); $url = 'https://' . $originUrl . '/settings/tokens/new?scopes=repo&description=' . \str_replace('%20', '+', \rawurlencode($note)); $this->io->writeError('When you need to access _private_ GitHub repositories as well, go to:'); $this->io->writeError($url); $this->io->writeError('Note that such tokens have broad read/write permissions on your behalf, even if not needed by Composer.'); $this->io->writeError(\sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth'); $storeInLocalAuthConfig = \false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', \true); } $token = \trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); if ($token === '') { $this->io->writeError('No token given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return \false; } $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic'); try { $apiUrl = 'github.com' === $originUrl ? 'api.github.com/' : $originUrl . '/api/v3/'; $this->httpDownloader->get('https://' . $apiUrl, ['retry-auth-failure' => \false]); } catch (TransportException $e) { if (\in_array($e->getCode(), [403, 401])) { $this->io->writeError('Invalid token provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return \false; } throw $e; } // store value in local/user config $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); $this->config->getConfigSource()->removeConfigSetting('github-oauth.' . $originUrl); $authConfigSource->addConfigSetting('github-oauth.' . $originUrl, $token); $this->io->writeError('Token stored successfully.'); return \true; } /** * Extract rate limit from response. * * @param string[] $headers Headers from Composer\Downloader\TransportException. * * @return array{limit: int|'?', reset: string} */ public function getRateLimit(array $headers) : array { $rateLimit = ['limit' => '?', 'reset' => '?']; foreach ($headers as $header) { $header = \trim($header); if (\false === \stripos($header, 'x-ratelimit-')) { continue; } [$type, $value] = \explode(':', $header, 2); switch (\strtolower($type)) { case 'x-ratelimit-limit': $rateLimit['limit'] = (int) \trim($value); break; case 'x-ratelimit-reset': $rateLimit['reset'] = \date('Y-m-d H:i:s', (int) \trim($value)); break; } } return $rateLimit; } /** * Extract SSO URL from response. * * @param string[] $headers Headers from Composer\Downloader\TransportException. */ public function getSsoUrl(array $headers) : ?string { foreach ($headers as $header) { $header = \trim($header); if (\false === \stripos($header, 'x-github-sso: required')) { continue; } if (Preg::isMatch('{\\burl=(?P[^\\s;]+)}', $header, $match)) { return $match['url']; } } return null; } /** * Finds whether a request failed due to rate limiting * * @param string[] $headers Headers from Composer\Downloader\TransportException. */ public function isRateLimited(array $headers) : bool { foreach ($headers as $header) { if (Preg::isMatch('{^x-ratelimit-remaining: *0$}i', \trim($header))) { return \true; } } return \false; } /** * Finds whether a request failed due to lacking SSO authorization * * @see https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso * * @param string[] $headers Headers from Composer\Downloader\TransportException. */ public function requiresSso(array $headers) : bool { foreach ($headers as $header) { if (Preg::isMatch('{^x-github-sso: required}i', \trim($header))) { return \true; } } return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; use Composer\Config; use Composer\Factory; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; /** * @author Roshan Gautam */ class GitLab { /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var HttpDownloader */ protected $httpDownloader; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking */ public function __construct(IOInterface $io, Config $config, ?\Composer\Util\ProcessExecutor $process = null, ?\Composer\Util\HttpDownloader $httpDownloader = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new \Composer\Util\ProcessExecutor($io); $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } /** * Attempts to authorize a GitLab domain via OAuth. * * @param string $originUrl The host this GitLab instance is located at * * @return bool true on success */ public function authorizeOAuth(string $originUrl) : bool { // before composer 1.9, origin URLs had no port number in them $bcOriginUrl = Preg::replace('{:\\d+}', '', $originUrl); if (!\in_array($originUrl, $this->config->get('gitlab-domains'), \true) && !\in_array($bcOriginUrl, $this->config->get('gitlab-domains'), \true)) { return \false; } // if available use token from git config if (0 === $this->process->execute('git config gitlab.accesstoken', $output)) { $this->io->setAuthentication($originUrl, \trim($output), 'oauth2'); return \true; } // if available use deploy token from git config if (0 === $this->process->execute('git config gitlab.deploytoken.user', $tokenUser) && 0 === $this->process->execute('git config gitlab.deploytoken.token', $tokenPassword)) { $this->io->setAuthentication($originUrl, \trim($tokenUser), \trim($tokenPassword)); return \true; } // if available use token from composer config $authTokens = $this->config->get('gitlab-token'); if (isset($authTokens[$originUrl])) { $token = $authTokens[$originUrl]; } if (isset($authTokens[$bcOriginUrl])) { $token = $authTokens[$bcOriginUrl]; } if (isset($token)) { $username = \is_array($token) ? $token["username"] : $token; $password = \is_array($token) ? $token["token"] : 'private-token'; // Composer expects the GitLab token to be stored as username and 'private-token' or 'gitlab-ci-token' to be stored as password // Detect cases where this is reversed and resolve automatically resolve it if (\in_array($username, ['private-token', 'gitlab-ci-token', 'oauth2'], \true)) { $this->io->setAuthentication($originUrl, $password, $username); } else { $this->io->setAuthentication($originUrl, $username, $password); } return \true; } return \false; } /** * Authorizes a GitLab domain interactively via OAuth. * * @param string $scheme Scheme used in the origin URL * @param string $originUrl The host this GitLab instance is located at * @param string $message The reason this authorization is required * * @throws \RuntimeException * @throws TransportException|\Exception * * @return bool true on success */ public function authorizeOAuthInteractively(string $scheme, string $originUrl, ?string $message = null) : bool { if ($message) { $this->io->writeError($message); } $localAuthConfig = $this->config->getLocalAuthConfigSource(); $personalAccessTokenLink = $scheme . '://' . $originUrl . '/-/profile/personal_access_tokens'; $revokeLink = $scheme . '://' . $originUrl . '/-/profile/applications'; $this->io->writeError(\sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('To revoke access to this token you can visit:'); $this->io->writeError($revokeLink); $this->io->writeError('Alternatively you can setup an personal access token on:'); $this->io->writeError($personalAccessTokenLink); $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.'); $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token'); $this->io->writeError('for more details.'); $storeInLocalAuthConfig = \false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', \true); } $attemptCounter = 0; while ($attemptCounter++ < 5) { try { $response = $this->createToken($scheme, $originUrl); } catch (TransportException $e) { // 401 is bad credentials, // 403 is max login attempts exceeded if (\in_array($e->getCode(), [403, 401])) { if (401 === $e->getCode()) { $response = \json_decode($e->getResponse(), \true); if (isset($response['error']) && $response['error'] === 'invalid_grant') { $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token'); } else { $this->io->writeError('Bad credentials.'); } } else { $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:'); $this->io->writeError($scheme . '://' . $originUrl . '/profile/personal_access_tokens'); $this->io->writeError('Add it using "composer config --global --auth gitlab-token.' . $originUrl . ' "'); continue; } throw $e; } $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); // store value in user config in auth file if (isset($response['expires_in'])) { $authConfigSource->addConfigSetting('gitlab-oauth.' . $originUrl, ['expires-at' => \intval($response['created_at']) + \intval($response['expires_in']), 'refresh-token' => $response['refresh_token'], 'token' => $response['access_token']]); } else { $authConfigSource->addConfigSetting('gitlab-oauth.' . $originUrl, $response['access_token']); } return \true; } throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); } /** * Authorizes a GitLab domain interactively via OAuth. * * @param string $scheme Scheme used in the origin URL * @param string $originUrl The host this GitLab instance is located at * * @throws \RuntimeException * @throws TransportException|\Exception * * @return bool true on success */ public function authorizeOAuthRefresh(string $scheme, string $originUrl) : bool { try { $response = $this->refreshToken($scheme, $originUrl); } catch (TransportException $e) { $this->io->writeError("Couldn't refresh access token: " . $e->getMessage()); return \false; } $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); // store value in user config in auth file $this->config->getAuthConfigSource()->addConfigSetting('gitlab-oauth.' . $originUrl, ['expires-at' => \intval($response['created_at']) + \intval($response['expires_in']), 'refresh-token' => $response['refresh_token'], 'token' => $response['access_token']]); return \true; } /** * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in?: positive-int, created_at: positive-int} * * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow */ private function createToken(string $scheme, string $originUrl) : array { $username = $this->io->ask('Username: '); $password = $this->io->askAndHideAnswer('Password: '); $headers = ['Content-Type: application/x-www-form-urlencoded']; $apiUrl = $originUrl; $data = \http_build_query(['username' => $username, 'password' => $password, 'grant_type' => 'password'], '', '&'); $options = ['retry-auth-failure' => \false, 'http' => ['method' => 'POST', 'header' => $headers, 'content' => $data]]; $token = $this->httpDownloader->get($scheme . '://' . $apiUrl . '/oauth/token', $options)->decodeJson(); $this->io->writeError('Token successfully created'); return $token; } /** * Is the OAuth access token expired? * * @return bool true on expired token, false if token is fresh or expiration date is not set */ public function isOAuthExpired(string $originUrl) : bool { $authTokens = $this->config->get('gitlab-oauth'); if (isset($authTokens[$originUrl]['expires-at'])) { if ($authTokens[$originUrl]['expires-at'] < \time()) { return \true; } } return \false; } /** * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in: positive-int, created_at: positive-int} * * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow */ private function refreshToken(string $scheme, string $originUrl) : array { $authTokens = $this->config->get('gitlab-oauth'); if (!isset($authTokens[$originUrl]['refresh-token'])) { throw new \RuntimeException('No GitLab refresh token present for ' . $originUrl . '.'); } $refreshToken = $authTokens[$originUrl]['refresh-token']; $headers = ['Content-Type: application/x-www-form-urlencoded']; $data = \http_build_query(['refresh_token' => $refreshToken, 'grant_type' => 'refresh_token'], '', '&'); $options = ['retry-auth-failure' => \false, 'http' => ['method' => 'POST', 'header' => $headers, 'content' => $data]]; $token = $this->httpDownloader->get($scheme . '://' . $originUrl . '/oauth/token', $options)->decodeJson(); $this->io->writeError('GitLab token successfully refreshed', \true, IOInterface::VERY_VERBOSE); $this->io->writeError('To revoke access to this token you can visit ' . $scheme . '://' . $originUrl . '/-/profile/applications', \true, IOInterface::VERY_VERBOSE); return $token; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\Downloader\TransportException; /** * @author Paul Wenke */ class Bitbucket { /** @var IOInterface */ private $io; /** @var Config */ private $config; /** @var ProcessExecutor */ private $process; /** @var HttpDownloader */ private $httpDownloader; /** @var array{access_token: string, expires_in?: int}|null */ private $token = null; /** @var int|null */ private $time; public const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking * @param int $time Timestamp, injectable for mocking */ public function __construct(IOInterface $io, Config $config, ?\Composer\Util\ProcessExecutor $process = null, ?\Composer\Util\HttpDownloader $httpDownloader = null, ?int $time = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new \Composer\Util\ProcessExecutor($io); $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); $this->time = $time; } public function getToken() : string { if (!isset($this->token['access_token'])) { return ''; } return $this->token['access_token']; } /** * Attempts to authorize a Bitbucket domain via OAuth * * @param string $originUrl The host this Bitbucket instance is located at * @return bool true on success */ public function authorizeOAuth(string $originUrl) : bool { if ($originUrl !== 'bitbucket.org') { return \false; } // if available use token from git config if (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) { $this->io->setAuthentication($originUrl, 'x-token-auth', \trim($output)); return \true; } return \false; } private function requestAccessToken() : bool { try { $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, ['retry-auth-failure' => \false, 'http' => ['method' => 'POST', 'content' => 'grant_type=client_credentials']]); $token = $response->decodeJson(); if (!isset($token['expires_in']) || !isset($token['access_token'])) { throw new \LogicException('Expected a token configured with expires_in and access_token present, got ' . \json_encode($token)); } $this->token = $token; } catch (TransportException $e) { if ($e->getCode() === 400) { $this->io->writeError('Invalid OAuth consumer provided.'); $this->io->writeError('This can have three reasons:'); $this->io->writeError('1. You are authenticating with a bitbucket username/password combination'); $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'); $this->io->writeError('3. You are using an OAuth consumer, but didn\'t configure it as private consumer'); return \false; } if (\in_array($e->getCode(), [403, 401])) { $this->io->writeError('Invalid OAuth consumer provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return \false; } throw $e; } return \true; } /** * Authorizes a Bitbucket domain interactively via OAuth * * @param string $originUrl The host this Bitbucket instance is located at * @param string $message The reason this authorization is required * @throws \RuntimeException * @throws TransportException|\Exception * @return bool true on success */ public function authorizeOAuthInteractively(string $originUrl, ?string $message = null) : bool { if ($message) { $this->io->writeError($message); } $localAuthConfig = $this->config->getLocalAuthConfigSource(); $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/'; $this->io->writeError('Follow the instructions here:'); $this->io->writeError($url); $this->io->writeError(\sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)'); $storeInLocalAuthConfig = \false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', \true); } $consumerKey = \trim((string) $this->io->askAndHideAnswer('Consumer Key (hidden): ')); if (!$consumerKey) { $this->io->writeError('No consumer key given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return \false; } $consumerSecret = \trim((string) $this->io->askAndHideAnswer('Consumer Secret (hidden): ')); if (!$consumerSecret) { $this->io->writeError('No consumer secret given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return \false; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); if (!$this->requestAccessToken()) { return \false; } // store value in user config $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); $this->storeInAuthConfig($authConfigSource, $originUrl, $consumerKey, $consumerSecret); // Remove conflicting basic auth credentials (if available) $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl); $this->io->writeError('Consumer stored successfully.'); return \true; } /** * Retrieves an access token from Bitbucket. */ public function requestToken(string $originUrl, string $consumerKey, string $consumerSecret) : string { if ($this->token !== null || $this->getTokenFromConfig($originUrl)) { return $this->token['access_token']; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); if (!$this->requestAccessToken()) { return ''; } $this->storeInAuthConfig($this->config->getLocalAuthConfigSource() ?? $this->config->getAuthConfigSource(), $originUrl, $consumerKey, $consumerSecret); if (!isset($this->token['access_token'])) { throw new \LogicException('Failed to initialize token above'); } return $this->token['access_token']; } /** * Store the new/updated credentials to the configuration */ private function storeInAuthConfig(Config\ConfigSourceInterface $authConfigSource, string $originUrl, string $consumerKey, string $consumerSecret) : void { $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.' . $originUrl); if (null === $this->token || !isset($this->token['expires_in'])) { throw new \LogicException('Expected a token configured with expires_in present, got ' . \json_encode($this->token)); } $time = null === $this->time ? \time() : $this->time; $consumer = ["consumer-key" => $consumerKey, "consumer-secret" => $consumerSecret, "access-token" => $this->token['access_token'], "access-token-expiration" => $time + $this->token['expires_in']]; $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.' . $originUrl, $consumer); } /** * @phpstan-assert-if-true array{access_token: string} $this->token */ private function getTokenFromConfig(string $originUrl) : bool { $authConfig = $this->config->get('bitbucket-oauth'); if (!isset($authConfig[$originUrl]['access-token'], $authConfig[$originUrl]['access-token-expiration']) || \time() > $authConfig[$originUrl]['access-token-expiration']) { return \false; } $this->token = ['access_token' => $authConfig[$originUrl]['access-token']]; return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; use Composer\Util\Http\Response; use Composer\Util\Http\CurlDownloader; use Composer\Composer; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\Constraint; use Composer\Exception\IrrecoverableDownloadException; use React\Promise\Promise; use React\Promise\PromiseInterface; /** * @author Jordi Boggiano * @phpstan-type Request array{url: non-empty-string, options: mixed[], copyTo: string|null} * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: \Throwable} */ class HttpDownloader { private const STATUS_QUEUED = 1; private const STATUS_STARTED = 2; private const STATUS_COMPLETED = 3; private const STATUS_FAILED = 4; private const STATUS_ABORTED = 5; /** @var IOInterface */ private $io; /** @var Config */ private $config; /** @var array */ private $jobs = []; /** @var mixed[] */ private $options = []; /** @var int */ private $runningJobs = 0; /** @var int */ private $maxJobs = 12; /** @var ?CurlDownloader */ private $curl; /** @var ?RemoteFilesystem */ private $rfs; /** @var int */ private $idGen = 0; /** @var bool */ private $disabled; /** @var bool */ private $allowAsync = \false; /** * @param IOInterface $io The IO instance * @param Config $config The config * @param mixed[] $options The options */ public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = \false) { $this->io = $io; $this->disabled = (bool) \Composer\Util\Platform::getEnv('COMPOSER_DISABLE_NETWORK'); // Setup TLS options // The cafile option can be set via config.json if ($disableTls === \false) { $this->options = \Composer\Util\StreamContextFactory::getTlsDefaults($options, $io); } // handle the other externally set options normally. $this->options = \array_replace_recursive($this->options, $options); $this->config = $config; if (self::isCurlEnabled()) { $this->curl = new CurlDownloader($io, $config, $options, $disableTls); } $this->rfs = new \Composer\Util\RemoteFilesystem($io, $config, $options, $disableTls); if (\is_numeric($maxJobs = \Composer\Util\Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) { $this->maxJobs = \max(1, \min(50, (int) $maxJobs)); } } /** * Download a file synchronously * * @param string $url URL to download * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException * @return Response */ public function get(string $url, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null], \true); $promise->then(null, function (\Throwable $e) { // suppress error as it is rethrown to the caller by getResponse() a few lines below }); $this->wait($job['id']); $response = $this->getResponse($job['id']); return $response; } /** * Create an async download operation * * @param string $url URL to download * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException * @return PromiseInterface * @phpstan-return PromiseInterface */ public function add(string $url, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null]); return $promise; } /** * Copy a file synchronously * * @param string $url URL to download * @param string $to Path to copy to * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException * @return Response */ public function copy(string $url, string $to, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [$job] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to], \true); $this->wait($job['id']); return $this->getResponse($job['id']); } /** * Create an async copy operation * * @param string $url URL to download * @param string $to Path to copy to * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException * @return PromiseInterface * @phpstan-return PromiseInterface */ public function addCopy(string $url, string $to, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to]); return $promise; } /** * Retrieve the options set in the constructor * * @return mixed[] Options */ public function getOptions() { return $this->options; } /** * Merges new options * * @param mixed[] $options * @return void */ public function setOptions(array $options) { $this->options = \array_replace_recursive($this->options, $options); } /** * @phpstan-param Request $request * @return array{Job, PromiseInterface} * @phpstan-return array{Job, PromiseInterface} */ private function addJob(array $request, bool $sync = \false) : array { $request['options'] = \array_replace_recursive($this->options, $request['options']); /** @var Job */ $job = ['id' => $this->idGen++, 'status' => self::STATUS_QUEUED, 'request' => $request, 'sync' => $sync, 'origin' => \Composer\Util\Url::getOrigin($this->config, $request['url'])]; if (!$sync && !$this->allowAsync) { throw new \LogicException('You must use the HttpDownloader instance which is part of a Composer\\Loop instance to be able to run async http requests'); } // capture username/password from URL if there is one if (Preg::isMatchStrictGroups('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { $this->io->setAuthentication($job['origin'], \rawurldecode($match[1]), \rawurldecode($match[2])); } $rfs = $this->rfs; if ($this->canUseCurl($job)) { $resolver = static function ($resolve, $reject) use(&$job) : void { $job['status'] = \Composer\Util\HttpDownloader::STATUS_QUEUED; $job['resolve'] = $resolve; $job['reject'] = $reject; }; } else { $resolver = static function ($resolve, $reject) use(&$job, $rfs) : void { // start job $url = $job['request']['url']; $options = $job['request']['options']; $job['status'] = \Composer\Util\HttpDownloader::STATUS_STARTED; if ($job['request']['copyTo']) { $rfs->copy($job['origin'], $url, $job['request']['copyTo'], \false, $options); $headers = $rfs->getLastHeaders(); $response = new \Composer\Util\Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'] . '~'); $resolve($response); } else { $body = $rfs->getContents($job['origin'], $url, \false, $options); $headers = $rfs->getLastHeaders(); $response = new \Composer\Util\Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $body); $resolve($response); } }; } $curl = $this->curl; $canceler = static function () use(&$job, $curl) : void { if ($job['status'] === \Composer\Util\HttpDownloader::STATUS_QUEUED) { $job['status'] = \Composer\Util\HttpDownloader::STATUS_ABORTED; } if ($job['status'] !== \Composer\Util\HttpDownloader::STATUS_STARTED) { return; } $job['status'] = \Composer\Util\HttpDownloader::STATUS_ABORTED; if (isset($job['curl_id'])) { $curl->abortRequest($job['curl_id']); } throw new IrrecoverableDownloadException('Download of ' . \Composer\Util\Url::sanitize($job['request']['url']) . ' canceled'); }; $promise = new Promise($resolver, $canceler); $promise = $promise->then(function ($response) use(&$job) { $job['status'] = \Composer\Util\HttpDownloader::STATUS_COMPLETED; $job['response'] = $response; $this->markJobDone(); return $response; }, function ($e) use(&$job) : void { $job['status'] = \Composer\Util\HttpDownloader::STATUS_FAILED; $job['exception'] = $e; $this->markJobDone(); throw $e; }); $this->jobs[$job['id']] =& $job; if ($this->runningJobs < $this->maxJobs) { $this->startJob($job['id']); } return [$job, $promise]; } private function startJob(int $id) : void { $job =& $this->jobs[$id]; if ($job['status'] !== self::STATUS_QUEUED) { return; } // start job $job['status'] = self::STATUS_STARTED; $this->runningJobs++; \assert(isset($job['resolve'])); \assert(isset($job['reject'])); $resolve = $job['resolve']; $reject = $job['reject']; $url = $job['request']['url']; $options = $job['request']['options']; $origin = $job['origin']; if ($this->disabled) { if (isset($job['request']['options']['http']['header']) && \false !== \stripos(\implode('', $job['request']['options']['http']['header']), 'if-modified-since')) { $resolve(new Response(['url' => $url], 304, [], '')); } else { $e = new TransportException('Network disabled, request canceled: ' . \Composer\Util\Url::sanitize($url), 499); $e->setStatusCode(499); $reject($e); } return; } try { if ($job['request']['copyTo']) { $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); } else { $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options); } } catch (\Exception $exception) { $reject($exception); } } private function markJobDone() : void { $this->runningJobs--; } /** * Wait for current async download jobs to complete * * @param int|null $index For internal use only, the job id * * @return void */ public function wait(?int $index = null) { do { $jobCount = $this->countActiveJobs($index); } while ($jobCount); } /** * @internal */ public function enableAsync() : void { $this->allowAsync = \true; } /** * @internal * * @param int|null $index For internal use only, the job id * @return int number of active (queued or started) jobs */ public function countActiveJobs(?int $index = null) : int { if ($this->runningJobs < $this->maxJobs) { foreach ($this->jobs as $job) { if ($job['status'] === self::STATUS_QUEUED && $this->runningJobs < $this->maxJobs) { $this->startJob($job['id']); } } } if ($this->curl) { $this->curl->tick(); } if (null !== $index) { return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; } $active = 0; foreach ($this->jobs as $job) { if ($job['status'] < self::STATUS_COMPLETED) { $active++; } elseif (!$job['sync']) { unset($this->jobs[$job['id']]); } } return $active; } /** * @param int $index Job id */ private function getResponse(int $index) : Response { if (!isset($this->jobs[$index])) { throw new \LogicException('Invalid request id'); } if ($this->jobs[$index]['status'] === self::STATUS_FAILED) { \assert(isset($this->jobs[$index]['exception'])); throw $this->jobs[$index]['exception']; } if (!isset($this->jobs[$index]['response'])) { throw new \LogicException('Response not available yet, call wait() first'); } $resp = $this->jobs[$index]['response']; unset($this->jobs[$index]); return $resp; } /** * @internal * * @param array{warning?: string, info?: string, warning-versions?: string, info-versions?: string, warnings?: array, infos?: array} $data */ public static function outputWarnings(IOInterface $io, string $url, $data) : void { $cleanMessage = static function ($msg) use($io) { if (!$io->isDecorated()) { $msg = Preg::replace('{' . \chr(27) . '\\[[;\\d]*m}u', '', $msg); } return $msg; }; // legacy warning/info keys foreach (['warning', 'info'] as $type) { if (empty($data[$type])) { continue; } if (!empty($data[$type . '-versions'])) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($data[$type . '-versions']); $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); if (!$constraint->matches($composer)) { continue; } } $io->writeError('<' . $type . '>' . \ucfirst($type) . ' from ' . \Composer\Util\Url::sanitize($url) . ': ' . $cleanMessage($data[$type]) . ''); } // modern Composer 2.2+ format with support for multiple warning/info messages foreach (['warnings', 'infos'] as $key) { if (empty($data[$key])) { continue; } $versionParser = new VersionParser(); foreach ($data[$key] as $spec) { $type = \substr($key, 0, -1); $constraint = $versionParser->parseConstraints($spec['versions']); $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); if (!$constraint->matches($composer)) { continue; } $io->writeError('<' . $type . '>' . \ucfirst($type) . ' from ' . \Composer\Util\Url::sanitize($url) . ': ' . $cleanMessage($spec['message']) . ''); } } } /** * @internal * * @return ?string[] */ public static function getExceptionHints(\Throwable $e) : ?array { if (!$e instanceof TransportException) { return null; } if (\false !== \strpos($e->getMessage(), 'Resolving timed out') || \false !== \strpos($e->getMessage(), 'Could not resolve host')) { \Composer\Util\Silencer::suppress(); $testConnectivity = \file_get_contents('https://8.8.8.8', \false, \stream_context_create(['ssl' => ['verify_peer' => \false], 'http' => ['follow_location' => \false, 'ignore_errors' => \true]])); \Composer\Util\Silencer::restore(); if (\false !== $testConnectivity) { return ['The following exception probably indicates you have misconfigured DNS resolver(s)']; } return ['The following exception probably indicates you are offline or have misconfigured DNS resolver(s)']; } return null; } /** * @param Job $job */ private function canUseCurl(array $job) : bool { if (!$this->curl) { return \false; } if (!Preg::isMatch('{^https?://}i', $job['request']['url'])) { return \false; } if (!empty($job['request']['options']['ssl']['allow_self_signed'])) { return \false; } return \true; } /** * @internal */ public static function isCurlEnabled() : bool { return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\XdebugHandler\XdebugHandler; /** * Provides ini file location functions that work with and without a restart. * When the process has restarted it uses a tmp ini and stores the original * ini locations in an environment variable. * * @author John Stevenson */ class IniHelper { /** * Returns an array of php.ini locations with at least one entry * * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. * The loaded ini location is the first entry and may be empty. * * @return string[] */ public static function getAll() : array { return XdebugHandler::getAllIniFiles(); } /** * Describes the location of the loaded php.ini file(s) */ public static function getMessage() : string { $paths = self::getAll(); if (empty($paths[0])) { \array_shift($paths); } $ini = \array_shift($paths); if (empty($ini)) { return 'A php.ini file does not exist. You will have to create one.'; } if (!empty($paths)) { return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.'; } return 'The php.ini used by your command-line PHP is: ' . $ini; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Pcre\Preg; /** * Platform helper for uniform platform-specific tests. * * @author Niels Keurentjes */ class Platform { /** @var ?bool */ private static $isVirtualBoxGuest = null; /** @var ?bool */ private static $isWindowsSubsystemForLinux = null; /** * getcwd() equivalent which always returns a string * * @throws \RuntimeException */ public static function getCwd(bool $allowEmpty = \false) : string { $cwd = \getcwd(); // fallback to realpath('') just in case this works but odds are it would break as well if we are in a case where getcwd fails if (\false === $cwd) { $cwd = \realpath(''); } // crappy state, assume '' and hopefully relative paths allow things to continue if (\false === $cwd) { if ($allowEmpty) { return ''; } throw new \RuntimeException('Could not determine the current working directory'); } return $cwd; } /** * getenv() equivalent but reads from the runtime global variables first * * @return string|false */ public static function getEnv(string $name) { if (\array_key_exists($name, $_SERVER)) { return (string) $_SERVER[$name]; } if (\array_key_exists($name, $_ENV)) { return (string) $_ENV[$name]; } return \getenv($name); } /** * putenv() equivalent but updates the runtime global variables too */ public static function putEnv(string $name, string $value) : void { $value = (string) $value; \putenv($name . '=' . $value); $_SERVER[$name] = $_ENV[$name] = $value; } /** * putenv('X') equivalent but updates the runtime global variables too */ public static function clearEnv(string $name) : void { \putenv($name); unset($_SERVER[$name], $_ENV[$name]); } /** * Parses tildes and environment variables in paths. */ public static function expandPath(string $path) : string { if (Preg::isMatch('#^~[\\/]#', $path)) { return self::getUserDirectory() . \substr($path, 1); } return Preg::replaceCallback('#^(\\$|(?P%))(?P\\w++)(?(percent)%)(?P.*)#', static function ($matches) : string { \assert(\is_string($matches['var'])); // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons if (\Composer\Util\Platform::isWindows() && $matches['var'] === 'HOME') { return (\Composer\Util\Platform::getEnv('HOME') ?: \Composer\Util\Platform::getEnv('USERPROFILE')) . $matches['path']; } return \Composer\Util\Platform::getEnv($matches['var']) . $matches['path']; }, $path); } /** * @throws \RuntimeException If the user home could not reliably be determined * @return string The formal user home as detected from environment parameters */ public static function getUserDirectory() : string { if (\false !== ($home = self::getEnv('HOME'))) { return $home; } if (self::isWindows() && \false !== ($home = self::getEnv('USERPROFILE'))) { return $home; } if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) { $info = \posix_getpwuid(\posix_getuid()); return $info['dir']; } throw new \RuntimeException('Could not determine user directory'); } /** * @return bool Whether the host machine is running on the Windows Subsystem for Linux (WSL) */ public static function isWindowsSubsystemForLinux() : bool { if (null === self::$isWindowsSubsystemForLinux) { self::$isWindowsSubsystemForLinux = \false; // while WSL will be hosted within windows, WSL itself cannot be windows based itself. if (self::isWindows()) { return self::$isWindowsSubsystemForLinux = \false; } if (!\ini_get('open_basedir') && \is_readable('/proc/version') && \false !== \stripos((string) \Composer\Util\Silencer::call('file_get_contents', '/proc/version'), 'microsoft') && !\file_exists('/.dockerenv')) { return self::$isWindowsSubsystemForLinux = \true; } } return self::$isWindowsSubsystemForLinux; } /** * @return bool Whether the host machine is running a Windows OS */ public static function isWindows() : bool { return \defined('PHP_WINDOWS_VERSION_BUILD'); } /** * @return int return a guaranteed binary length of the string, regardless of silly mbstring configs */ public static function strlen(string $str) : int { static $useMbString = null; if (null === $useMbString) { $useMbString = \function_exists('mb_strlen') && \ini_get('mbstring.func_overload'); } if ($useMbString) { return \mb_strlen($str, '8bit'); } return \strlen($str); } /** * @param ?resource $fd Open file descriptor or null to default to STDOUT */ public static function isTty($fd = null) : bool { if ($fd === null) { $fd = \defined('STDOUT') ? \STDOUT : \fopen('php://stdout', 'w'); if ($fd === \false) { return \false; } } // detect msysgit/mingw and assume this is a tty because detection // does not work correctly, see https://github.com/composer/composer/issues/9690 if (\in_array(\strtoupper(self::getEnv('MSYSTEM') ?: ''), ['MINGW32', 'MINGW64'], \true)) { return \true; } // modern cross-platform function, includes the fstat // fallback so if it is present we trust it if (\function_exists('stream_isatty')) { return \stream_isatty($fd); } // only trusting this if it is positive, otherwise prefer fstat fallback if (\function_exists('posix_isatty') && \posix_isatty($fd)) { return \true; } $stat = @\fstat($fd); // Check if formatted mode is S_IFCHR return $stat ? 020000 === ($stat['mode'] & 0170000) : \false; } /** * @return bool Whether the current command is for bash completion */ public static function isInputCompletionProcess() : bool { return '_complete' === ($_SERVER['argv'][1] ?? null); } public static function workaroundFilesystemIssues() : void { if (self::isVirtualBoxGuest()) { \usleep(200000); } } /** * Attempts detection of VirtualBox guest VMs * * This works based on the process' user being "vagrant", the COMPOSER_RUNTIME_ENV env var being set to "virtualbox", or lsmod showing the virtualbox guest additions are loaded */ private static function isVirtualBoxGuest() : bool { if (null === self::$isVirtualBoxGuest) { self::$isVirtualBoxGuest = \false; if (self::isWindows()) { return self::$isVirtualBoxGuest; } if (\function_exists('posix_getpwuid') && \function_exists('posix_geteuid')) { $processUser = \posix_getpwuid(\posix_geteuid()); if ($processUser && $processUser['name'] === 'vagrant') { return self::$isVirtualBoxGuest = \true; } } if (self::getEnv('COMPOSER_RUNTIME_ENV') === 'virtualbox') { return self::$isVirtualBoxGuest = \true; } if (\defined('PHP_OS_FAMILY') && \PHP_OS_FAMILY === 'Linux') { $process = new \Composer\Util\ProcessExecutor(); try { if (0 === $process->execute('lsmod | grep vboxguest', $ignoredOutput)) { return self::$isVirtualBoxGuest = \true; } } catch (\Exception $e) { // noop } } } return self::$isVirtualBoxGuest; } /** * @return 'NUL'|'/dev/null' */ public static function getDevNull() : string { if (self::isWindows()) { return 'NUL'; } return '/dev/null'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Composer; use Composer\CaBundle\CaBundle; use Composer\Downloader\TransportException; use Composer\Repository\PlatformRepository; use Composer\Util\Http\ProxyManager; use _ContaoManager\Psr\Log\LoggerInterface; /** * Allows the creation of a basic context supporting http proxy * * @author Jordan Alliot * @author Markus Tacker */ final class StreamContextFactory { /** * Creates a context supporting HTTP proxies * * @param non-empty-string $url URL the context is to be used for * @phpstan-param array{http?: array{follow_location?: int, max_redirects?: int, header?: string|array}} $defaultOptions * @param mixed[] $defaultOptions Options to merge with the default * @param mixed[] $defaultParams Parameters to specify on the context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled * @return resource Default context */ public static function getContext(string $url, array $defaultOptions = [], array $defaultParams = []) { $options = ['http' => [ // specify defaults again to try and work better with curlwrappers enabled 'follow_location' => 1, 'max_redirects' => 20, ]]; $options = \array_replace_recursive($options, self::initOptions($url, $defaultOptions)); unset($defaultOptions['http']['header']); $options = \array_replace_recursive($options, $defaultOptions); if (isset($options['http']['header'])) { $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); } return \stream_context_create($options, $defaultParams); } /** * @param non-empty-string $url * @param mixed[] $options * @param bool $forCurl When true, will not add proxy values as these are handled separately * @phpstan-return array{http: array{header: string[], proxy?: string, request_fulluri: bool}, ssl?: mixed[]} * @return array formatted as a stream context array */ public static function initOptions(string $url, array $options, bool $forCurl = \false) : array { // Make sure the headers are in an array form if (!isset($options['http']['header'])) { $options['http']['header'] = []; } if (\is_string($options['http']['header'])) { $options['http']['header'] = \explode("\r\n", $options['http']['header']); } // Add stream proxy options if there is a proxy if (!$forCurl) { $proxy = ProxyManager::getInstance()->getProxyForRequest($url); if ($proxyOptions = $proxy->getContextOptions()) { $isHttpsRequest = 0 === \strpos($url, 'https://'); if ($proxy->isSecure()) { if (!\extension_loaded('openssl')) { throw new TransportException('You must enable the openssl extension to use a secure proxy.'); } if ($isHttpsRequest) { throw new TransportException('You must enable the curl extension to make https requests through a secure proxy.'); } } elseif ($isHttpsRequest && !\extension_loaded('openssl')) { throw new TransportException('You must enable the openssl extension to make https requests through a proxy.'); } // Header will be a Proxy-Authorization string or not set if (isset($proxyOptions['http']['header'])) { $options['http']['header'][] = $proxyOptions['http']['header']; unset($proxyOptions['http']['header']); } $options = \array_replace_recursive($options, $proxyOptions); } } if (\defined('_ContaoManager\\HHVM_VERSION')) { $phpVersion = 'HHVM ' . HHVM_VERSION; } else { $phpVersion = 'PHP ' . \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION . '.' . \PHP_RELEASE_VERSION; } if ($forCurl) { $curl = \curl_version(); $httpVersion = 'cURL ' . $curl['version']; } else { $httpVersion = 'streams'; } if (!isset($options['http']['header']) || \false === \stripos(\implode('', $options['http']['header']), 'user-agent')) { $platformPhpVersion = PlatformRepository::getPlatformPhpVersion(); $options['http']['header'][] = \sprintf('User-Agent: Composer/%s (%s; %s; %s; %s%s%s)', Composer::getVersion(), \function_exists('php_uname') ? \php_uname('s') : 'Unknown', \function_exists('php_uname') ? \php_uname('r') : 'Unknown', $phpVersion, $httpVersion, $platformPhpVersion ? '; Platform-PHP ' . $platformPhpVersion : '', \Composer\Util\Platform::getEnv('CI') ? '; CI' : ''); } return $options; } /** * @param mixed[] $options * * @return mixed[] */ public static function getTlsDefaults(array $options, ?LoggerInterface $logger = null) : array { $ciphers = \implode(':', ['ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-DSS-AES128-GCM-SHA256', 'kEDH+AESGCM', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA', 'ECDHE-ECDSA-AES128-SHA', 'ECDHE-RSA-AES256-SHA384', 'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA', 'DHE-RSA-AES128-SHA256', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA256', 'DHE-RSA-AES256-SHA256', 'DHE-DSS-AES256-SHA', 'DHE-RSA-AES256-SHA', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA256', 'AES256-SHA256', 'AES128-SHA', 'AES256-SHA', 'AES', 'CAMELLIA', 'DES-CBC3-SHA', '!aNULL', '!eNULL', '!EXPORT', '!DES', '!RC4', '!MD5', '!PSK', '!aECDH', '!EDH-DSS-DES-CBC3-SHA', '!EDH-RSA-DES-CBC3-SHA', '!KRB5-DES-CBC3-SHA']); /** * CN_match and SNI_server_name are only known once a URL is passed. * They will be set in the getOptionsForUrl() method which receives a URL. * * cafile or capath can be overridden by passing in those options to constructor. */ $defaults = ['ssl' => ['ciphers' => $ciphers, 'verify_peer' => \true, 'verify_depth' => 7, 'SNI_enabled' => \true, 'capture_peer_cert' => \true]]; if (isset($options['ssl'])) { $defaults['ssl'] = \array_replace_recursive($defaults['ssl'], $options['ssl']); } /** * Attempt to find a local cafile or throw an exception if none pre-set * The user may go download one if this occurs. */ if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { $result = CaBundle::getSystemCaRootBundlePath($logger); if (\is_dir($result)) { $defaults['ssl']['capath'] = $result; } else { $defaults['ssl']['cafile'] = $result; } } if (isset($defaults['ssl']['cafile']) && (!\Composer\Util\Filesystem::isReadable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) { throw new TransportException('The configured cafile was not valid or could not be read.'); } if (isset($defaults['ssl']['capath']) && (!\is_dir($defaults['ssl']['capath']) || !\Composer\Util\Filesystem::isReadable($defaults['ssl']['capath']))) { throw new TransportException('The configured capath was not valid or could not be read.'); } /** * Disable TLS compression to prevent CRIME attacks where supported. */ $defaults['ssl']['disable_compression'] = \true; return $defaults; } /** * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and * NOT at the end of the array * * This method fixes the array by moving the content-type header to the end * * @link https://bugs.php.net/bug.php?id=61548 * @param string|string[] $header * @return string[] */ private static function fixHttpHeaderField($header) : array { if (!\is_array($header)) { $header = \explode("\r\n", $header); } \uasort($header, static function ($el) : int { return \stripos($el, 'content-type') === 0 ? 1 : -1; }); return $header; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\CaBundle\CaBundle; use Composer\Pcre\Preg; /** * @author Chris Smith * @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5 compatibility, this class will be removed in Composer 3.0 */ final class TlsHelper { /** * Match hostname against a certificate. * * @param mixed $certificate X.509 certificate * @param string $hostname Hostname in the URL * @param string $cn Set to the common name of the certificate iff match found */ public static function checkCertificateHost($certificate, string $hostname, ?string &$cn = null) : bool { $names = self::getCertificateNames($certificate); if (empty($names)) { return \false; } $combinedNames = \array_merge($names['san'], [$names['cn']]); $hostname = \strtolower($hostname); foreach ($combinedNames as $certName) { $matcher = self::certNameMatcher($certName); if ($matcher && $matcher($hostname)) { $cn = $names['cn']; return \true; } } return \false; } /** * Extract DNS names out of an X.509 certificate. * * @param mixed $certificate X.509 certificate * * @return array{cn: string, san: string[]}|null */ public static function getCertificateNames($certificate) : ?array { if (\is_array($certificate)) { $info = $certificate; } elseif (CaBundle::isOpensslParseSafe()) { $info = \openssl_x509_parse($certificate, \false); } if (!isset($info['subject']['commonName'])) { return null; } $commonName = \strtolower($info['subject']['commonName']); $subjectAltNames = []; if (isset($info['extensions']['subjectAltName'])) { $subjectAltNames = Preg::split('{\\s*,\\s*}', $info['extensions']['subjectAltName']); $subjectAltNames = \array_filter(\array_map(static function ($name) : ?string { if (0 === \strpos($name, 'DNS:')) { return \strtolower(\ltrim(\substr($name, 4))); } return null; }, $subjectAltNames)); $subjectAltNames = \array_values($subjectAltNames); } return ['cn' => $commonName, 'san' => $subjectAltNames]; } /** * Get the certificate pin. * * By Kevin McArthur of StormTide Digital Studios Inc. * @KevinSMcArthur / https://github.com/StormTide * * See https://tools.ietf.org/html/draft-ietf-websec-key-pinning-02 * * This method was adapted from Sslurp. * https://github.com/EvanDotPro/Sslurp * * (c) Evan Coury * * For the full copyright and license information, please see below: * * Copyright (c) 2013, Evan Coury * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ public static function getCertificateFingerprint(string $certificate) : string { $pubkey = \openssl_get_publickey($certificate); if ($pubkey === \false) { throw new \RuntimeException('Failed to retrieve the public key from certificate'); } $pubkeydetails = \openssl_pkey_get_details($pubkey); $pubkeypem = $pubkeydetails['key']; //Convert PEM to DER before SHA1'ing $start = '-----BEGIN PUBLIC KEY-----'; $end = '-----END PUBLIC KEY-----'; $pemtrim = \substr($pubkeypem, \strpos($pubkeypem, $start) + \strlen($start), (\strlen($pubkeypem) - \strpos($pubkeypem, $end)) * -1); $der = \base64_decode($pemtrim); return \sha1($der); } /** * Test if it is safe to use the PHP function openssl_x509_parse(). * * This checks if OpenSSL extensions is vulnerable to remote code execution * via the exploit documented as CVE-2013-6420. */ public static function isOpensslParseSafe() : bool { return CaBundle::isOpensslParseSafe(); } /** * Convert certificate name into matching function. * * @param string $certName CN/SAN */ private static function certNameMatcher(string $certName) : ?callable { $wildcards = \substr_count($certName, '*'); if (0 === $wildcards) { // Literal match. return static function ($hostname) use($certName) : bool { return $hostname === $certName; }; } if (1 === $wildcards) { $components = \explode('.', $certName); if (3 > \count($components)) { // Must have 3+ components return null; } $firstComponent = $components[0]; // Wildcard must be the last character. if ('*' !== $firstComponent[\strlen($firstComponent) - 1]) { return null; } $wildcardRegex = \preg_quote($certName); $wildcardRegex = \str_replace('\\*', '[a-z0-9-]+', $wildcardRegex); $wildcardRegex = "{^{$wildcardRegex}\$}"; return static function ($hostname) use($wildcardRegex) : bool { return Preg::isMatch($wildcardRegex, $hostname); }; } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\Pcre\Preg; /** * @author Jordi Boggiano */ class Url { /** * @param non-empty-string $url * @return non-empty-string the updated URL */ public static function updateDistReference(Config $config, string $url, string $ref) : string { $host = \parse_url($url, \PHP_URL_HOST); if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') { if (Preg::isMatch('{^https?://(?:www\\.)?github\\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) { // update legacy github archives to API calls with the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/' . $match[2] . '/' . $match[3] . 'ball/' . $ref; } elseif (Preg::isMatch('{^https?://(?:www\\.)?github\\.com/([^/]+)/([^/]+)/archive/.+\\.(zip|tar)(?:\\.gz)?$}i', $url, $match)) { // update current github web archives to API calls with the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/' . $match[2] . '/' . $match[3] . 'ball/' . $ref; } elseif (Preg::isMatch('{^https?://api\\.github\\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) { // update api archives to the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/' . $match[2] . '/' . $match[3] . 'ball/' . $ref; } } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') { if (Preg::isMatch('{^https?://(?:www\\.)?bitbucket\\.org/([^/]+)/([^/]+)/get/(.+)\\.(zip|tar\\.gz|tar\\.bz2)$}i', $url, $match)) { // update Bitbucket archives to the proper reference $url = 'https://bitbucket.org/' . $match[1] . '/' . $match[2] . '/get/' . $ref . '.' . $match[4]; } } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') { if (Preg::isMatch('{^https?://(?:www\\.)?gitlab\\.com/api/v[34]/projects/([^/]+)/repository/archive\\.(zip|tar\\.gz|tar\\.bz2|tar)\\?sha=.+$}i', $url, $match)) { // update Gitlab archives to the proper reference $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref; } } elseif (\in_array($host, $config->get('github-domains'), \true)) { $url = Preg::replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/' . $ref, $url); } elseif (\in_array($host, $config->get('gitlab-domains'), \true)) { $url = Preg::replace('{(/api/v[34]/projects/[^/]+/repository/archive\\.(?:zip|tar\\.gz|tar\\.bz2|tar)\\?sha=).+$}i', '${1}' . $ref, $url); } \assert($url !== ''); return $url; } /** * @param non-empty-string $url * @return non-empty-string */ public static function getOrigin(Config $config, string $url) : string { if (0 === \strpos($url, 'file://')) { return $url; } $origin = (string) \parse_url($url, \PHP_URL_HOST); if ($port = \parse_url($url, \PHP_URL_PORT)) { $origin .= ':' . $port; } if (\strpos($origin, '.github.com') === \strlen($origin) - 11) { return 'github.com'; } if ($origin === 'repo.packagist.org') { return 'packagist.org'; } if ($origin === '') { $origin = $url; } // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl // is the host without the path, so we look for the registered gitlab-domains matching the host here if (\false === \strpos($origin, '/') && !\in_array($origin, $config->get('gitlab-domains'), \true)) { foreach ($config->get('gitlab-domains') as $gitlabDomain) { if ($gitlabDomain !== '' && \str_starts_with($gitlabDomain, $origin)) { return $gitlabDomain; } } } return $origin; } public static function sanitize(string $url) : string { // GitHub repository rename result in redirect locations containing the access_token as GET parameter // e.g. https://api.github.com/repositories/9999999999?access_token=github_token $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url); $url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\\s@]+):(?P[^@\\s/]+)@}i', static function ($m) : string { \assert(\is_string($m['user'])); // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}', $m['user'])) { return $m['prefix'] . '***:***@'; } return $m['prefix'] . $m['user'] . ':***@'; }, $url); return $url; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Process\Process; /** * @author Matt Whittom * * @phpstan-type RepoConfig array{unique_perforce_client_name?: string, depot?: string, branch?: string, p4user?: string, p4password?: string} */ class Perforce { /** @var string */ protected $path; /** @var ?string */ protected $p4Depot; /** @var ?string */ protected $p4Client; /** @var ?string */ protected $p4User; /** @var ?string */ protected $p4Password; /** @var string */ protected $p4Port; /** @var ?string */ protected $p4Stream; /** @var string */ protected $p4ClientSpec; /** @var ?string */ protected $p4DepotType; /** @var ?string */ protected $p4Branch; /** @var ProcessExecutor */ protected $process; /** @var string */ protected $uniquePerforceClientName; /** @var bool */ protected $windowsFlag; /** @var string */ protected $commandResult; /** @var IOInterface */ protected $io; /** @var ?Filesystem */ protected $filesystem; /** * @phpstan-param RepoConfig $repoConfig */ public function __construct($repoConfig, string $port, string $path, \Composer\Util\ProcessExecutor $process, bool $isWindows, IOInterface $io) { $this->windowsFlag = $isWindows; $this->p4Port = $port; $this->initializePath($path); $this->process = $process; $this->initialize($repoConfig); $this->io = $io; } /** * @phpstan-param RepoConfig $repoConfig */ public static function create($repoConfig, string $port, string $path, \Composer\Util\ProcessExecutor $process, IOInterface $io) : self { return new \Composer\Util\Perforce($repoConfig, $port, $path, $process, \Composer\Util\Platform::isWindows(), $io); } public static function checkServerExists(string $url, \Composer\Util\ProcessExecutor $processExecutor) : bool { return 0 === $processExecutor->execute('p4 -p ' . \Composer\Util\ProcessExecutor::escape($url) . ' info -s', $ignoredOutput); } /** * @phpstan-param RepoConfig $repoConfig */ public function initialize($repoConfig) : void { $this->uniquePerforceClientName = $this->generateUniquePerforceClientName(); if (!$repoConfig) { return; } if (isset($repoConfig['unique_perforce_client_name'])) { $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name']; } if (isset($repoConfig['depot'])) { $this->p4Depot = $repoConfig['depot']; } if (isset($repoConfig['branch'])) { $this->p4Branch = $repoConfig['branch']; } if (isset($repoConfig['p4user'])) { $this->p4User = $repoConfig['p4user']; } else { $this->p4User = $this->getP4variable('P4USER'); } if (isset($repoConfig['p4password'])) { $this->p4Password = $repoConfig['p4password']; } } public function initializeDepotAndBranch(?string $depot, ?string $branch) : void { if (isset($depot)) { $this->p4Depot = $depot; } if (isset($branch)) { $this->p4Branch = $branch; } } /** * @return non-empty-string */ public function generateUniquePerforceClientName() : string { return \gethostname() . "_" . \time(); } public function cleanupClientSpec() : void { $client = $this->getClient(); $task = 'client -d ' . \Composer\Util\ProcessExecutor::escape($client); $useP4Client = \false; $command = $this->generateP4Command($task, $useP4Client); $this->executeCommand($command); $clientSpec = $this->getP4ClientSpec(); $fileSystem = $this->getFilesystem(); $fileSystem->remove($clientSpec); } /** * @param non-empty-string $command */ protected function executeCommand($command) : int { $this->commandResult = ''; return $this->process->execute($command, $this->commandResult); } public function getClient() : string { if (!isset($this->p4Client)) { $cleanStreamName = \str_replace(['//', '/', '@'], ['', '_', ''], $this->getStream()); $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName; } return $this->p4Client; } protected function getPath() : string { return $this->path; } public function initializePath(string $path) : void { $this->path = $path; $fs = $this->getFilesystem(); $fs->ensureDirectoryExists($path); } protected function getPort() : string { return $this->p4Port; } public function setStream(string $stream) : void { $this->p4Stream = $stream; $index = \strrpos($stream, '/'); //Stream format is //depot/stream, while non-streaming depot is //depot if ($index > 2) { $this->p4DepotType = 'stream'; } } public function isStream() : bool { return \is_string($this->p4DepotType) && \strcmp($this->p4DepotType, 'stream') === 0; } public function getStream() : string { if (!isset($this->p4Stream)) { if ($this->isStream()) { $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch; } else { $this->p4Stream = '//' . $this->p4Depot; } } return $this->p4Stream; } public function getStreamWithoutLabel(string $stream) : string { $index = \strpos($stream, '@'); if ($index === \false) { return $stream; } return \substr($stream, 0, $index); } /** * @return non-empty-string */ public function getP4ClientSpec() : string { return $this->path . '/' . $this->getClient() . '.p4.spec'; } public function getUser() : ?string { return $this->p4User; } public function setUser(?string $user) : void { $this->p4User = $user; } public function queryP4User() : void { $this->getUser(); if (\strlen((string) $this->p4User) > 0) { return; } $this->p4User = $this->getP4variable('P4USER'); if (\strlen((string) $this->p4User) > 0) { return; } $this->p4User = $this->io->ask('Enter P4 User:'); if ($this->windowsFlag) { $command = 'p4 set P4USER=' . $this->p4User; } else { $command = 'export P4USER=' . $this->p4User; } $this->executeCommand($command); } /** * @return ?string */ protected function getP4variable(string $name) : ?string { if ($this->windowsFlag) { $command = 'p4 set'; $this->executeCommand($command); $result = \trim($this->commandResult); $resArray = \explode(\PHP_EOL, $result); foreach ($resArray as $line) { $fields = \explode('=', $line); if (\strcmp($name, $fields[0]) === 0) { $index = \strpos($fields[1], ' '); if ($index === \false) { $value = $fields[1]; } else { $value = \substr($fields[1], 0, $index); } $value = \trim($value); return $value; } } return null; } $command = 'echo $' . $name; $this->executeCommand($command); $result = \trim($this->commandResult); return $result; } public function queryP4Password() : ?string { if (isset($this->p4Password)) { return $this->p4Password; } $password = $this->getP4variable('P4PASSWD'); if (\strlen((string) $password) <= 0) { $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': '); } $this->p4Password = $password; return $password; } /** * @return non-empty-string */ public function generateP4Command(string $command, bool $useClient = \true) : string { $p4Command = 'p4 '; $p4Command .= '-u ' . $this->getUser() . ' '; if ($useClient) { $p4Command .= '-c ' . $this->getClient() . ' '; } $p4Command .= '-p ' . $this->getPort() . ' ' . $command; return $p4Command; } public function isLoggedIn() : bool { $command = $this->generateP4Command('login -s', \false); $exitCode = $this->executeCommand($command); if ($exitCode) { $errorOutput = $this->process->getErrorOutput(); $index = \strpos($errorOutput, $this->getUser()); if ($index === \false) { $index = \strpos($errorOutput, 'p4'); if ($index === \false) { return \false; } throw new \Exception('p4 command not found in path: ' . $errorOutput); } throw new \Exception('Invalid user name: ' . $this->getUser()); } return \true; } public function connectClient() : void { $p4CreateClientCommand = $this->generateP4Command('client -i < ' . \str_replace(" ", "\\ ", $this->getP4ClientSpec())); $this->executeCommand($p4CreateClientCommand); } public function syncCodeBase(?string $sourceReference) : void { $prevDir = \Composer\Util\Platform::getCwd(); \chdir($this->path); $p4SyncCommand = $this->generateP4Command('sync -f '); if (null !== $sourceReference) { $p4SyncCommand .= '@' . $sourceReference; } $this->executeCommand($p4SyncCommand); \chdir($prevDir); } /** * @param resource|false $spec */ public function writeClientSpecToFile($spec) : void { \fwrite($spec, 'Client: ' . $this->getClient() . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'Update: ' . \date('Y/m/d H:i:s') . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'Access: ' . \date('Y/m/d H:i:s') . \PHP_EOL); \fwrite($spec, 'Owner: ' . $this->getUser() . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'Description:' . \PHP_EOL); \fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'Root: ' . $this->getPath() . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'SubmitOptions: revertunchanged' . \PHP_EOL . \PHP_EOL); \fwrite($spec, 'LineEnd: local' . \PHP_EOL . \PHP_EOL); if ($this->isStream()) { \fwrite($spec, 'Stream:' . \PHP_EOL); \fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . \PHP_EOL); } else { \fwrite($spec, 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . \PHP_EOL); } } public function writeP4ClientSpec() : void { $clientSpec = $this->getP4ClientSpec(); $spec = \fopen($clientSpec, 'w'); try { $this->writeClientSpecToFile($spec); } catch (\Exception $e) { \fclose($spec); throw $e; } \fclose($spec); } /** * @param resource $pipe * @param mixed $name */ protected function read($pipe, $name) : void { if (\feof($pipe)) { return; } $line = \fgets($pipe); while ($line !== \false) { $line = \fgets($pipe); } } public function windowsLogin(?string $password) : int { $command = $this->generateP4Command(' login -a'); $process = Process::fromShellCommandline($command, null, null, $password); return $process->run(); } public function p4Login() : void { $this->queryP4User(); if (!$this->isLoggedIn()) { $password = $this->queryP4Password(); if ($this->windowsFlag) { $this->windowsLogin($password); } else { $command = 'echo ' . \Composer\Util\ProcessExecutor::escape($password) . ' | ' . $this->generateP4Command(' login -a', \false); $exitCode = $this->executeCommand($command); if ($exitCode) { throw new \Exception("Error logging in:" . $this->process->getErrorOutput()); } } } } /** * @return mixed[]|null */ public function getComposerInformation(string $identifier) : ?array { $composerFileContent = $this->getFileContent('composer.json', $identifier); if (!$composerFileContent) { return null; } return \json_decode($composerFileContent, \true); } public function getFileContent(string $file, string $identifier) : ?string { $path = $this->getFilePath($file, $identifier); $command = $this->generateP4Command(' print ' . \Composer\Util\ProcessExecutor::escape($path)); $this->executeCommand($command); $result = $this->commandResult; if (!\trim($result)) { return null; } return $result; } public function getFilePath(string $file, string $identifier) : ?string { $index = \strpos($identifier, '@'); if ($index === \false) { return $identifier . '/' . $file; } $path = \substr($identifier, 0, $index) . '/' . $file . \substr($identifier, $index); $command = $this->generateP4Command(' files ' . \Composer\Util\ProcessExecutor::escape($path), \false); $this->executeCommand($command); $result = $this->commandResult; $index2 = \strpos($result, 'no such file(s).'); if ($index2 === \false) { $index3 = \strpos($result, 'change'); if ($index3 !== \false) { $phrase = \trim(\substr($result, $index3)); $fields = \explode(' ', $phrase); return \substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1]; } } return null; } /** * @return array{master: string} */ public function getBranches() : array { $possibleBranches = []; if (!$this->isStream()) { $possibleBranches[$this->p4Branch] = $this->getStream(); } else { $command = $this->generateP4Command('streams ' . \Composer\Util\ProcessExecutor::escape('//' . $this->p4Depot . '/...')); $this->executeCommand($command); $result = $this->commandResult; $resArray = \explode(\PHP_EOL, $result); foreach ($resArray as $line) { $resBits = \explode(' ', $line); if (\count($resBits) > 4) { $branch = Preg::replace('/[^A-Za-z0-9 ]/', '', $resBits[4]); $possibleBranches[$branch] = $resBits[1]; } } } $command = $this->generateP4Command('changes ' . \Composer\Util\ProcessExecutor::escape($this->getStream() . '/...'), \false); $this->executeCommand($command); $result = $this->commandResult; $resArray = \explode(\PHP_EOL, $result); $lastCommit = $resArray[0]; $lastCommitArr = \explode(' ', $lastCommit); $lastCommitNum = $lastCommitArr[1]; return ['master' => $possibleBranches[$this->p4Branch] . '@' . $lastCommitNum]; } /** * @return array */ public function getTags() : array { $command = $this->generateP4Command('labels'); $this->executeCommand($command); $result = $this->commandResult; $resArray = \explode(\PHP_EOL, $result); $tags = []; foreach ($resArray as $line) { if (\strpos($line, 'Label') !== \false) { $fields = \explode(' ', $line); $tags[$fields[1]] = $this->getStream() . '@' . $fields[1]; } } return $tags; } public function checkStream() : bool { $command = $this->generateP4Command('depots', \false); $this->executeCommand($command); $result = $this->commandResult; $resArray = \explode(\PHP_EOL, $result); foreach ($resArray as $line) { if (\strpos($line, 'Depot') !== \false) { $fields = \explode(' ', $line); if (\strcmp($this->p4Depot, $fields[1]) === 0) { $this->p4DepotType = $fields[3]; return $this->isStream(); } } } return \false; } /** * @return mixed|null */ protected function getChangeList(string $reference) : mixed { $index = \strpos($reference, '@'); if ($index === \false) { return null; } $label = \substr($reference, $index); $command = $this->generateP4Command(' changes -m1 ' . \Composer\Util\ProcessExecutor::escape($label)); $this->executeCommand($command); $changes = $this->commandResult; if (\strpos($changes, 'Change') !== 0) { return null; } $fields = \explode(' ', $changes); return $fields[1]; } /** * @return mixed|null */ public function getCommitLogs(string $fromReference, string $toReference) : mixed { $fromChangeList = $this->getChangeList($fromReference); if ($fromChangeList === null) { return null; } $toChangeList = $this->getChangeList($toReference); if ($toChangeList === null) { return null; } $index = \strpos($fromReference, '@'); $main = \substr($fromReference, 0, $index) . '/...'; $command = $this->generateP4Command('filelog ' . \Composer\Util\ProcessExecutor::escape($main . '@' . $fromChangeList . ',' . $toChangeList)); $this->executeCommand($command); return $this->commandResult; } public function getFilesystem() : \Composer\Util\Filesystem { if (null === $this->filesystem) { $this->filesystem = new \Composer\Util\Filesystem($this->process); } return $this->filesystem; } public function setFilesystem(\Composer\Util\Filesystem $fs) : void { $this->filesystem = $fs; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; use Composer\Downloader\TransportException; use Composer\Util\NoProxyPattern; use Composer\Util\Url; /** * @internal * @author John Stevenson */ class ProxyManager { /** @var ?string */ private $error = null; /** @var array{http: ?string, https: ?string} */ private $fullProxy; /** @var array{http: ?string, https: ?string} */ private $safeProxy; /** @var array{http: array{options: mixed[]|null}, https: array{options: mixed[]|null}} */ private $streams; /** @var bool */ private $hasProxy; /** @var ?string */ private $info = null; /** @var ?NoProxyPattern */ private $noProxyHandler = null; /** @var ?ProxyManager */ private static $instance = null; private function __construct() { $this->fullProxy = $this->safeProxy = ['http' => null, 'https' => null]; $this->streams['http'] = $this->streams['https'] = ['options' => null]; $this->hasProxy = \false; $this->initProxyData(); } public static function getInstance() : \Composer\Util\Http\ProxyManager { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Clears the persistent instance */ public static function reset() : void { self::$instance = null; } /** * Returns a RequestProxy instance for the request url * * @param non-empty-string $requestUrl */ public function getProxyForRequest(string $requestUrl) : \Composer\Util\Http\RequestProxy { if ($this->error) { throw new TransportException('Unable to use a proxy: ' . $this->error); } $scheme = \parse_url($requestUrl, \PHP_URL_SCHEME) ?: 'http'; $proxyUrl = ''; $options = []; $formattedProxyUrl = ''; if ($this->hasProxy && \in_array($scheme, ['http', 'https'], \true) && $this->fullProxy[$scheme]) { if ($this->noProxy($requestUrl)) { $formattedProxyUrl = 'excluded by no_proxy'; } else { $proxyUrl = $this->fullProxy[$scheme]; $options = $this->streams[$scheme]['options']; \Composer\Util\Http\ProxyHelper::setRequestFullUri($requestUrl, $options); $formattedProxyUrl = $this->safeProxy[$scheme]; } } return new \Composer\Util\Http\RequestProxy($proxyUrl, $options, $formattedProxyUrl); } /** * Returns true if a proxy is being used * * @return bool If false any error will be in $message */ public function isProxying() : bool { return $this->hasProxy; } /** * Returns proxy configuration info which can be shown to the user * * @return string|null Safe proxy URL or an error message if setting up proxy failed or null if no proxy was configured */ public function getFormattedProxy() : ?string { return $this->hasProxy ? $this->info : $this->error; } /** * Initializes proxy values from the environment */ private function initProxyData() : void { try { [$httpProxy, $httpsProxy, $noProxy] = \Composer\Util\Http\ProxyHelper::getProxyData(); } catch (\RuntimeException $e) { $this->error = $e->getMessage(); return; } $info = []; if ($httpProxy) { $info[] = $this->setData($httpProxy, 'http'); } if ($httpsProxy) { $info[] = $this->setData($httpsProxy, 'https'); } if ($this->hasProxy) { $this->info = \implode(', ', $info); if ($noProxy) { $this->noProxyHandler = new NoProxyPattern($noProxy); } } } /** * Sets initial data * * @param non-empty-string $url Proxy url * @param 'http'|'https' $scheme Environment variable scheme * * @return non-empty-string */ private function setData($url, $scheme) : string { $safeProxy = Url::sanitize($url); $this->fullProxy[$scheme] = $url; $this->safeProxy[$scheme] = $safeProxy; $this->streams[$scheme]['options'] = \Composer\Util\Http\ProxyHelper::getContextOptions($url); $this->hasProxy = \true; return \sprintf('%s=%s', $scheme, $safeProxy); } /** * Returns true if a url matches no_proxy value */ private function noProxy(string $requestUrl) : bool { return $this->noProxyHandler && $this->noProxyHandler->test($requestUrl); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; /** * Proxy discovery and helper class * * @internal * @author John Stevenson */ class ProxyHelper { /** * Returns proxy environment values * * @return array{string|null, string|null, string|null} httpProxy, httpsProxy, noProxy values * * @throws \RuntimeException on malformed url */ public static function getProxyData() : array { $httpProxy = null; $httpsProxy = null; // Handle http_proxy/HTTP_PROXY on CLI only for security reasons if (\PHP_SAPI === 'cli' || \PHP_SAPI === 'phpdbg') { if ($env = self::getProxyEnv(['http_proxy', 'HTTP_PROXY'], $name)) { $httpProxy = self::checkProxy($env, $name); } } // Prefer CGI_HTTP_PROXY if available if ($env = self::getProxyEnv(['CGI_HTTP_PROXY'], $name)) { $httpProxy = self::checkProxy($env, $name); } // Handle https_proxy/HTTPS_PROXY if ($env = self::getProxyEnv(['https_proxy', 'HTTPS_PROXY'], $name)) { $httpsProxy = self::checkProxy($env, $name); } else { $httpsProxy = $httpProxy; } // Handle no_proxy $noProxy = self::getProxyEnv(['no_proxy', 'NO_PROXY'], $name); return [$httpProxy, $httpsProxy, $noProxy]; } /** * Returns http context options for the proxy url * * @return array{http: array{proxy: string, header?: string}} */ public static function getContextOptions(string $proxyUrl) : array { $proxy = \parse_url($proxyUrl); // Remove any authorization $proxyUrl = self::formatParsedUrl($proxy, \false); $proxyUrl = \str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $proxyUrl); $options['http']['proxy'] = $proxyUrl; // Handle any authorization if (isset($proxy['user'])) { $auth = \rawurldecode($proxy['user']); if (isset($proxy['pass'])) { $auth .= ':' . \rawurldecode($proxy['pass']); } $auth = \base64_encode($auth); // Set header as a string $options['http']['header'] = "Proxy-Authorization: Basic {$auth}"; } return $options; } /** * Sets/unsets request_fulluri value in http context options array * * @param mixed[] $options Set by method */ public static function setRequestFullUri(string $requestUrl, array &$options) : void { if ('http' === \parse_url($requestUrl, \PHP_URL_SCHEME)) { $options['http']['request_fulluri'] = \true; } else { unset($options['http']['request_fulluri']); } } /** * Searches $_SERVER for case-sensitive values * * @param string[] $names Names to search for * @param string|null $name Name of any found value * * @return string|null The found value */ private static function getProxyEnv(array $names, ?string &$name) : ?string { foreach ($names as $name) { if (!empty($_SERVER[$name])) { return $_SERVER[$name]; } } return null; } /** * Checks and formats a proxy url from the environment * * @throws \RuntimeException on malformed url * @return string The formatted proxy url */ private static function checkProxy(string $proxyUrl, string $envName) : string { $error = \sprintf('malformed %s url', $envName); $proxy = \parse_url($proxyUrl); // We need parse_url to have identified a host if (!isset($proxy['host'])) { throw new \RuntimeException($error); } $proxyUrl = self::formatParsedUrl($proxy, \true); // We need a port because streams and curl use different defaults if (!\parse_url($proxyUrl, \PHP_URL_PORT)) { throw new \RuntimeException($error); } return $proxyUrl; } /** * Formats a url from its component parts * * @param array{scheme?: string, host: string, port?: int, user?: string, pass?: string} $proxy * * @return string The formatted value */ private static function formatParsedUrl(array $proxy, bool $includeAuth) : string { $proxyUrl = isset($proxy['scheme']) ? \strtolower($proxy['scheme']) . '://' : ''; if ($includeAuth && isset($proxy['user'])) { $proxyUrl .= $proxy['user']; if (isset($proxy['pass'])) { $proxyUrl .= ':' . $proxy['pass']; } $proxyUrl .= '@'; } $proxyUrl .= $proxy['host']; if (isset($proxy['port'])) { $proxyUrl .= ':' . $proxy['port']; } elseif (\strpos($proxyUrl, 'http://') === 0) { $proxyUrl .= ':80'; } elseif (\strpos($proxyUrl, 'https://') === 0) { $proxyUrl .= ':443'; } return $proxyUrl; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; use Composer\Json\JsonFile; use Composer\Pcre\Preg; use Composer\Util\HttpDownloader; /** * @phpstan-type Request array{url: non-empty-string, options?: mixed[], copyTo?: string|null} */ class Response { /** @var Request */ private $request; /** @var int */ private $code; /** @var list */ private $headers; /** @var ?string */ private $body; /** * @param Request $request * @param list $headers */ public function __construct(array $request, ?int $code, array $headers, ?string $body) { if (!isset($request['url'])) { // @phpstan-ignore-line throw new \LogicException('url key missing from request array'); } $this->request = $request; $this->code = (int) $code; $this->headers = $headers; $this->body = $body; } public function getStatusCode() : int { return $this->code; } public function getStatusMessage() : ?string { $value = null; foreach ($this->headers as $header) { if (Preg::isMatch('{^HTTP/\\S+ \\d+}i', $header)) { // In case of redirects, headers contain the headers of all responses // so we can not return directly and need to keep iterating $value = $header; } } return $value; } /** * @return string[] */ public function getHeaders() : array { return $this->headers; } /** * @return ?string */ public function getHeader(string $name) : ?string { return self::findHeaderValue($this->headers, $name); } /** * @return ?string */ public function getBody() : ?string { return $this->body; } /** * @return mixed */ public function decodeJson() { return JsonFile::parseJson($this->body, $this->request['url']); } /** * @phpstan-impure */ public function collect() : void { /** @phpstan-ignore-next-line */ $this->request = $this->code = $this->headers = $this->body = null; } /** * @param string[] $headers array of returned headers like from getLastHeaders() * @param string $name header name (case insensitive) */ public static function findHeaderValue(array $headers, string $name) : ?string { $value = null; foreach ($headers as $header) { if (Preg::isMatch('{^' . \preg_quote($name) . ':\\s*(.+?)\\s*$}i', $header, $match)) { $value = $match[1]; } } return $value; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; use Composer\Util\Url; /** * @internal * @author John Stevenson */ class RequestProxy { /** @var mixed[] */ private $contextOptions; /** @var bool */ private $isSecure; /** @var string */ private $formattedUrl; /** @var string */ private $url; /** * @param mixed[] $contextOptions */ public function __construct(string $url, array $contextOptions, string $formattedUrl) { $this->url = $url; $this->contextOptions = $contextOptions; $this->formattedUrl = $formattedUrl; $this->isSecure = 0 === \strpos($url, 'https://'); } /** * Returns an array of context options * * @return mixed[] */ public function getContextOptions() : array { return $this->contextOptions; } /** * Returns the safe proxy url from the last request * * @param string|null $format Output format specifier * @return string Safe proxy, no proxy or empty */ public function getFormattedUrl(?string $format = '') : string { $result = ''; if ($this->formattedUrl) { $format = $format ?: '%s'; $result = \sprintf($format, $this->formattedUrl); } return $result; } /** * Returns the proxy url * * @return string Proxy url or empty */ public function getUrl() : string { return $this->url; } /** * Returns true if this is a secure-proxy * * @return bool False if not secure or there is no proxy */ public function isSecure() : bool { return $this->isSecure; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; use Composer\Config; use Composer\Downloader\MaxFileSizeExceededException; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; use Composer\Util\Platform; use Composer\Util\StreamContextFactory; use Composer\Util\AuthHelper; use Composer\Util\Url; use Composer\Util\HttpDownloader; use React\Promise\Promise; /** * @internal * @author Jordi Boggiano * @author Nicolas Grekas * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null} * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable} */ class CurlDownloader { /** @var ?resource */ private $multiHandle; /** @var ?resource */ private $shareHandle; /** @var Job[] */ private $jobs = []; /** @var IOInterface */ private $io; /** @var Config */ private $config; /** @var AuthHelper */ private $authHelper; /** @var float */ private $selectTimeout = 5.0; /** @var int */ private $maxRedirects = 20; /** @var int */ private $maxRetries = 3; /** @var ProxyManager */ private $proxyManager; /** @var bool */ private $supportsSecureProxy; /** @var array */ protected $multiErrors = [\CURLM_BAD_HANDLE => ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'], \CURLM_BAD_EASY_HANDLE => ['CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."], \CURLM_OUT_OF_MEMORY => ['CURLM_OUT_OF_MEMORY', 'You are doomed.'], \CURLM_INTERNAL_ERROR => ['CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!']]; /** @var mixed[] */ private static $options = ['http' => ['method' => \CURLOPT_CUSTOMREQUEST, 'content' => \CURLOPT_POSTFIELDS, 'header' => \CURLOPT_HTTPHEADER, 'timeout' => \CURLOPT_TIMEOUT], 'ssl' => ['cafile' => \CURLOPT_CAINFO, 'capath' => \CURLOPT_CAPATH, 'verify_peer' => \CURLOPT_SSL_VERIFYPEER, 'verify_peer_name' => \CURLOPT_SSL_VERIFYHOST, 'local_cert' => \CURLOPT_SSLCERT, 'local_pk' => \CURLOPT_SSLKEY, 'passphrase' => \CURLOPT_SSLKEYPASSWD]]; /** @var array */ private static $timeInfo = ['total_time' => \true, 'namelookup_time' => \true, 'connect_time' => \true, 'pretransfer_time' => \true, 'starttransfer_time' => \true, 'redirect_time' => \true]; /** * @param mixed[] $options */ public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = \false) { $this->io = $io; $this->config = $config; $this->multiHandle = $mh = \curl_multi_init(); if (\function_exists('curl_multi_setopt')) { \curl_multi_setopt($mh, \CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? 2 : 3); if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !\defined('_ContaoManager\\HHVM_VERSION')) { \curl_multi_setopt($mh, \CURLMOPT_MAX_HOST_CONNECTIONS, 8); } } if (\function_exists('curl_share_init')) { $this->shareHandle = $sh = \curl_share_init(); \curl_share_setopt($sh, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_COOKIE); \curl_share_setopt($sh, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS); \curl_share_setopt($sh, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION); } $this->authHelper = new AuthHelper($io, $config); $this->proxyManager = \Composer\Util\Http\ProxyManager::getInstance(); $version = \curl_version(); $features = $version['features']; $this->supportsSecureProxy = \defined('CURL_VERSION_HTTPS_PROXY') && $features & \CURL_VERSION_HTTPS_PROXY; } /** * @param mixed[] $options * @param non-empty-string $url * * @return int internal job id */ public function download(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null) : int { $attributes = []; if (isset($options['retry-auth-failure'])) { $attributes['retryAuthFailure'] = $options['retry-auth-failure']; unset($options['retry-auth-failure']); } return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes); } /** * @param mixed[] $options * * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes * @param non-empty-string $url * * @return int internal job id */ private function initDownload(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null, array $attributes = []) : int { $attributes = \array_merge(['retryAuthFailure' => \true, 'redirects' => 0, 'retries' => 0, 'storeAuth' => \false, 'ipResolve' => null], $attributes); if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') { $attributes['ipResolve'] = 4; } elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') { $attributes['ipResolve'] = 6; } $originalOptions = $options; // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 if (!Preg::isMatch('{^http://(repo\\.)?packagist\\.org/p/}', $url) || \false === \strpos($url, '$') && \false === \strpos($url, '%24')) { $this->config->prohibitUrlByConfig($url, $this->io, $options); } $curlHandle = \curl_init(); $headerHandle = \fopen('php://temp/maxmemory:32768', 'w+b'); if (\false === $headerHandle) { throw new \RuntimeException('Failed to open a temp stream to store curl headers'); } if ($copyTo !== null) { $bodyTarget = $copyTo . '~'; } else { $bodyTarget = 'php://temp/maxmemory:524288'; } $errorMessage = ''; // @phpstan-ignore-next-line \set_error_handler(static function ($code, $msg) use(&$errorMessage) : void { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^fopen\\(.*?\\): }', '', $msg); }); $bodyHandle = \fopen($bodyTarget, 'w+b'); \restore_error_handler(); if (\false === $bodyHandle) { throw new TransportException('The "' . $url . '" file could not be written to ' . ($copyTo ?? 'a temporary file') . ': ' . $errorMessage); } \curl_setopt($curlHandle, \CURLOPT_URL, $url); \curl_setopt($curlHandle, \CURLOPT_FOLLOWLOCATION, \false); \curl_setopt($curlHandle, \CURLOPT_CONNECTTIMEOUT, 10); \curl_setopt($curlHandle, \CURLOPT_TIMEOUT, \max((int) \ini_get("default_socket_timeout"), 300)); \curl_setopt($curlHandle, \CURLOPT_WRITEHEADER, $headerHandle); \curl_setopt($curlHandle, \CURLOPT_FILE, $bodyHandle); \curl_setopt($curlHandle, \CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports \curl_setopt($curlHandle, \CURLOPT_PROTOCOLS, \CURLPROTO_HTTP | \CURLPROTO_HTTPS); if ($attributes['ipResolve'] === 4) { \curl_setopt($curlHandle, \CURLOPT_IPRESOLVE, \CURL_IPRESOLVE_V4); } elseif ($attributes['ipResolve'] === 6) { \curl_setopt($curlHandle, \CURLOPT_IPRESOLVE, \CURL_IPRESOLVE_V6); } if (\function_exists('curl_share_init')) { \curl_setopt($curlHandle, \CURLOPT_SHARE, $this->shareHandle); } if (!isset($options['http']['header'])) { $options['http']['header'] = []; } $options['http']['header'] = \array_diff($options['http']['header'], ['Connection: close']); $options['http']['header'][] = 'Connection: keep-alive'; $version = \curl_version(); $features = $version['features']; if (0 === \strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && \CURL_VERSION_HTTP2 & $features) { \curl_setopt($curlHandle, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_2_0); } $options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin, $url); $options = StreamContextFactory::initOptions($url, $options, \true); foreach (self::$options as $type => $curlOptions) { foreach ($curlOptions as $name => $curlOption) { if (isset($options[$type][$name])) { if ($type === 'ssl' && $name === 'verify_peer_name') { \curl_setopt($curlHandle, $curlOption, $options[$type][$name] === \true ? 2 : $options[$type][$name]); } else { \curl_setopt($curlHandle, $curlOption, $options[$type][$name]); } } } } // Always set CURLOPT_PROXY to enable/disable proxy handling // Any proxy authorization is included in the proxy url $proxy = $this->proxyManager->getProxyForRequest($url); if ($proxy->getUrl() !== '') { \curl_setopt($curlHandle, \CURLOPT_PROXY, $proxy->getUrl()); } // Curl needs certificate locations for secure proxies. // CURLOPT_PROXY_SSL_VERIFY_PEER/HOST are enabled by default if ($proxy->isSecure()) { if (!$this->supportsSecureProxy) { throw new TransportException('Connecting to a secure proxy using curl is not supported on PHP versions below 7.3.0.'); } if (!empty($options['ssl']['cafile'])) { \curl_setopt($curlHandle, \CURLOPT_PROXY_CAINFO, $options['ssl']['cafile']); } if (!empty($options['ssl']['capath'])) { \curl_setopt($curlHandle, \CURLOPT_PROXY_CAPATH, $options['ssl']['capath']); } } $progress = \array_diff_key(\curl_getinfo($curlHandle), self::$timeInfo); $this->jobs[(int) $curlHandle] = ['url' => $url, 'origin' => $origin, 'attributes' => $attributes, 'options' => $originalOptions, 'progress' => $progress, 'curlHandle' => $curlHandle, 'filename' => $copyTo, 'headerHandle' => $headerHandle, 'bodyHandle' => $bodyHandle, 'resolve' => $resolve, 'reject' => $reject]; $usingProxy = $proxy->getFormattedUrl(' using proxy (%s)'); $ifModified = \false !== \stripos(\implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : ''; if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) { $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, \true, IOInterface::DEBUG); } $this->checkCurlResult(\curl_multi_add_handle($this->multiHandle, $curlHandle)); // TODO progress return (int) $curlHandle; } public function abortRequest(int $id) : void { if (isset($this->jobs[$id], $this->jobs[$id]['curlHandle'])) { $job = $this->jobs[$id]; \curl_multi_remove_handle($this->multiHandle, $job['curlHandle']); \curl_close($job['curlHandle']); if (\is_resource($job['headerHandle'])) { \fclose($job['headerHandle']); } if (\is_resource($job['bodyHandle'])) { \fclose($job['bodyHandle']); } if (null !== $job['filename']) { @\unlink($job['filename'] . '~'); } unset($this->jobs[$id]); } } public function tick() : void { static $timeoutWarning = \false; if (\count($this->jobs) === 0) { return; } $active = \true; $this->checkCurlResult(\curl_multi_exec($this->multiHandle, $active)); if (-1 === \curl_multi_select($this->multiHandle, $this->selectTimeout)) { // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select \usleep(150); } while ($progress = \curl_multi_info_read($this->multiHandle)) { $curlHandle = $progress['handle']; $result = $progress['result']; $i = (int) $curlHandle; if (!isset($this->jobs[$i])) { continue; } $progress = \curl_getinfo($curlHandle); if (\false === $progress) { throw new \RuntimeException('Failed getting info from curl handle ' . $i . ' (' . $this->jobs[$i]['url'] . ')'); } $job = $this->jobs[$i]; unset($this->jobs[$i]); $error = \curl_error($curlHandle); $errno = \curl_errno($curlHandle); \curl_multi_remove_handle($this->multiHandle, $curlHandle); \curl_close($curlHandle); $headers = null; $statusCode = null; $response = null; try { // TODO progress if (\CURLE_OK !== $errno || $error || $result !== \CURLE_OK) { $errno = $errno ?: $result; if (!$error && \function_exists('curl_strerror')) { $error = \curl_strerror($errno); } $progress['error_code'] = $errno; if ((!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') && (\in_array($errno, [7, 16, 92, 6], \true) || \in_array($errno, [56, 35], \true) && \str_contains((string) $error, 'Connection reset by peer')) && $job['attributes']['retries'] < $this->maxRetries) { $attributes = ['retries' => $job['attributes']['retries'] + 1]; if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected $attributes['ipResolve'] = 4; } $this->io->writeError('Retrying (' . ($job['attributes']['retries'] + 1) . ') ' . Url::sanitize($job['url']) . ' due to curl error ' . $errno, \true, IOInterface::DEBUG); $this->restartJobWithDelay($job, $job['url'], $attributes); continue; } // TODO: Remove this as soon as https://github.com/curl/curl/issues/10591 is resolved if ($errno === 55) { $this->io->writeError('Retrying (' . ($job['attributes']['retries'] + 1) . ') ' . Url::sanitize($job['url']) . ' due to curl error ' . $errno, \true, IOInterface::DEBUG); $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); continue; } if ($errno === 28 && \PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) { $timeoutWarning = \true; $this->io->writeError('A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.'); } throw new TransportException('curl error ' . $errno . ' while downloading ' . Url::sanitize($progress['url']) . ': ' . $error); } $statusCode = $progress['http_code']; \rewind($job['headerHandle']); $headers = \explode("\r\n", \rtrim(\stream_get_contents($job['headerHandle']))); \fclose($job['headerHandle']); if ($statusCode === 0) { throw new \LogicException('Received unexpected http status code 0 without error for ' . Url::sanitize($progress['url']) . ': headers ' . \var_export($headers, \true) . ' curl info ' . \var_export($progress, \true)); } // prepare response object if (null !== $job['filename']) { $contents = $job['filename'] . '~'; if ($statusCode >= 300) { \rewind($job['bodyHandle']); $contents = \stream_get_contents($job['bodyHandle']); } $response = new \Composer\Util\Http\CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); $this->io->writeError('[' . $statusCode . '] ' . Url::sanitize($job['url']), \true, IOInterface::DEBUG); } else { $maxFileSize = $job['options']['max_file_size'] ?? null; \rewind($job['bodyHandle']); if ($maxFileSize !== null) { $contents = \stream_get_contents($job['bodyHandle'], $maxFileSize); // Gzipped responses with missing Content-Length header cannot be detected during the file download // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size if ($contents !== \false && Platform::strlen($contents) >= $maxFileSize) { throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' . $maxFileSize . ' bytes'); } } else { $contents = \stream_get_contents($job['bodyHandle']); } $response = new \Composer\Util\Http\CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); $this->io->writeError('[' . $statusCode . '] ' . Url::sanitize($job['url']), \true, IOInterface::DEBUG); } \fclose($job['bodyHandle']); if ($response->getStatusCode() >= 400 && $response->getHeader('content-type') === 'application/json') { HttpDownloader::outputWarnings($this->io, $job['origin'], \json_decode($response->getBody(), \true)); } $result = $this->isAuthenticatedRetryNeeded($job, $response); if ($result['retry']) { $this->restartJob($job, $job['url'], ['storeAuth' => $result['storeAuth'], 'retries' => $job['attributes']['retries'] + 1]); continue; } // handle 3xx redirects, 304 Not Modified is excluded if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { $location = $this->handleRedirect($job, $response); if ($location) { $this->restartJob($job, $location, ['redirects' => $job['attributes']['redirects'] + 1]); continue; } } // fail 4xx and 5xx responses and capture the response if ($statusCode >= 400 && $statusCode <= 599) { if ((!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') && \in_array($statusCode, [423, 425, 500, 502, 503, 504, 507, 510], \true) && $job['attributes']['retries'] < $this->maxRetries) { $this->io->writeError('Retrying (' . ($job['attributes']['retries'] + 1) . ') ' . Url::sanitize($job['url']) . ' due to status code ' . $statusCode, \true, IOInterface::DEBUG); $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); continue; } throw $this->failResponse($job, $response, $response->getStatusMessage()); } if ($job['attributes']['storeAuth'] !== \false) { $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); } // resolve promise if (null !== $job['filename']) { \rename($job['filename'] . '~', $job['filename']); $job['resolve']($response); } else { $job['resolve']($response); } } catch (\Exception $e) { if ($e instanceof TransportException) { if (null !== $headers) { $e->setHeaders($headers); $e->setStatusCode($statusCode); } if (null !== $response) { $e->setResponse($response->getBody()); } $e->setResponseInfo($progress); } $this->rejectJob($job, $e); } } foreach ($this->jobs as $i => $curlHandle) { $curlHandle = $this->jobs[$i]['curlHandle']; $progress = \array_diff_key(\curl_getinfo($curlHandle), self::$timeInfo); if ($this->jobs[$i]['progress'] !== $progress) { $this->jobs[$i]['progress'] = $progress; if (isset($this->jobs[$i]['options']['max_file_size'])) { // Compare max_file_size with the content-length header this value will be -1 until the header is parsed if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) { $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); } // Compare max_file_size with the download size in bytes if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) { $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); } } // TODO progress } } } /** * @param Job $job */ private function handleRedirect(array $job, \Composer\Util\Http\Response $response) : string { if ($locationHeader = $response->getHeader('location')) { if (\parse_url($locationHeader, \PHP_URL_SCHEME)) { // Absolute URL; e.g. https://example.com/composer $targetUrl = $locationHeader; } elseif (\parse_url($locationHeader, \PHP_URL_HOST)) { // Scheme relative; e.g. //example.com/foo $targetUrl = \parse_url($job['url'], \PHP_URL_SCHEME) . ':' . $locationHeader; } elseif ('/' === $locationHeader[0]) { // Absolute path; e.g. /foo $urlHost = \parse_url($job['url'], \PHP_URL_HOST); // Replace path using hostname as an anchor. $targetUrl = Preg::replace('{^(.+(?://|@)' . \preg_quote($urlHost) . '(?::\\d+)?)(?:[/\\?].*)?$}', '\\1' . $locationHeader, $job['url']); } else { // Relative path; e.g. foo // This actually differs from PHP which seems to add duplicate slashes. $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\\?.*)?$}', '\\1' . $locationHeader, $job['url']); } } if (!empty($targetUrl)) { $this->io->writeError(\sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), \true, IOInterface::DEBUG); return $targetUrl; } throw new TransportException('The "' . $job['url'] . '" file could not be downloaded, got redirect without Location (' . $response->getStatusMessage() . ')'); } /** * @param Job $job * @return array{retry: bool, storeAuth: 'prompt'|bool} */ private function isAuthenticatedRetryNeeded(array $job, \Composer\Util\Http\Response $response) : array { if (\in_array($response->getStatusCode(), [401, 403]) && $job['attributes']['retryAuthFailure']) { $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries']); if ($result['retry']) { return $result; } } $locationHeader = $response->getHeader('location'); $needsAuthRetry = \false; // check for bitbucket login page asking to authenticate if ($job['origin'] === 'bitbucket.org' && !$this->authHelper->isPublicBitBucketDownload($job['url']) && \substr($job['url'], -4) === '.zip' && (!$locationHeader || \substr($locationHeader, -4) !== '.zip') && Preg::isMatch('{^text/html\\b}i', $response->getHeader('content-type'))) { $needsAuthRetry = 'Bitbucket requires authentication and it was not provided'; } // check for gitlab 404 when downloading archives if ($response->getStatusCode() === 404 && \in_array($job['origin'], $this->config->get('gitlab-domains'), \true) && \false !== \strpos($job['url'], 'archive.zip')) { $needsAuthRetry = 'GitLab requires authentication and it was not provided'; } if ($needsAuthRetry) { if ($job['attributes']['retryAuthFailure']) { $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, [], $job['attributes']['retries']); if ($result['retry']) { return $result; } } throw $this->failResponse($job, $response, $needsAuthRetry); } return ['retry' => \false, 'storeAuth' => \false]; } /** * @param Job $job * @param non-empty-string $url * * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes */ private function restartJob(array $job, string $url, array $attributes = []) : void { if (null !== $job['filename']) { @\unlink($job['filename'] . '~'); } $attributes = \array_merge($job['attributes'], $attributes); $origin = Url::getOrigin($this->config, $url); $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes); } /** * @param Job $job * @param non-empty-string $url * * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes */ private function restartJobWithDelay(array $job, string $url, array $attributes) : void { if ($attributes['retries'] >= 3) { \usleep(500000); // half a second delay for 3rd retry and beyond } elseif ($attributes['retries'] >= 2) { \usleep(100000); // 100ms delay for 2nd retry } // no sleep for the first retry $this->restartJob($job, $url, $attributes); } /** * @param Job $job */ private function failResponse(array $job, \Composer\Util\Http\Response $response, string $errorMessage) : TransportException { if (null !== $job['filename']) { @\unlink($job['filename'] . '~'); } $details = ''; if (\in_array(\strtolower((string) $response->getHeader('content-type')), ['application/json', 'application/json; charset=utf-8'], \true)) { $details = ':' . \PHP_EOL . \substr($response->getBody(), 0, 200) . (\strlen($response->getBody()) > 200 ? '...' : ''); } return new TransportException('The "' . $job['url'] . '" file could not be downloaded (' . $errorMessage . ')' . $details, $response->getStatusCode()); } /** * @param Job $job */ private function rejectJob(array $job, \Exception $e) : void { if (\is_resource($job['headerHandle'])) { \fclose($job['headerHandle']); } if (\is_resource($job['bodyHandle'])) { \fclose($job['bodyHandle']); } if (null !== $job['filename']) { @\unlink($job['filename'] . '~'); } $job['reject']($e); } private function checkCurlResult(int $code) : void { if ($code !== \CURLM_OK && $code !== \CURLM_CALL_MULTI_PERFORM) { throw new \RuntimeException(isset($this->multiErrors[$code]) ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" : 'Unexpected cURL error: ' . $code); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util\Http; /** * @phpstan-type CurlInfo array{url: mixed, content_type: mixed, http_code: mixed, header_size: mixed, request_size: mixed, filetime: mixed, ssl_verify_result: mixed, redirect_count: mixed, total_time: mixed, namelookup_time: mixed, connect_time: mixed, pretransfer_time: mixed, size_upload: mixed, size_download: mixed, speed_download: mixed, speed_upload: mixed, download_content_length: mixed, upload_content_length: mixed, starttransfer_time: mixed, redirect_time: mixed, certinfo: mixed, primary_ip: mixed, primary_port: mixed, local_ip: mixed, local_port: mixed, redirect_url: mixed} */ class CurlResponse extends \Composer\Util\Http\Response { /** * @see https://www.php.net/curl_getinfo * @var array * @phpstan-var CurlInfo */ private $curlInfo; /** * @phpstan-param CurlInfo $curlInfo */ public function __construct(array $request, ?int $code, array $headers, ?string $body, array $curlInfo) { parent::__construct($request, $code, $headers, $body); $this->curlInfo = $curlInfo; } /** * @phpstan-return CurlInfo */ public function getCurlInfo() : array { return $this->curlInfo; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; @\trigger_error('Composer\\Util\\MetadataMinifier is deprecated, use Composer\\MetadataMinifier\\MetadataMinifier from composer/metadata-minifier instead.', \E_USER_DEPRECATED); /** * @deprecated Use Composer\MetadataMinifier\MetadataMinifier instead */ class MetadataMinifier extends \Composer\MetadataMinifier\MetadataMinifier { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use _ContaoManager\Seld\Signal\SignalHandler; use _ContaoManager\Symfony\Component\Process\Exception\ProcessSignaledException; use _ContaoManager\Symfony\Component\Process\Process; use _ContaoManager\Symfony\Component\Process\Exception\RuntimeException; use React\Promise\Promise; use React\Promise\PromiseInterface; /** * @author Robert Schönthal * @author Jordi Boggiano */ class ProcessExecutor { private const STATUS_QUEUED = 1; private const STATUS_STARTED = 2; private const STATUS_COMPLETED = 3; private const STATUS_FAILED = 4; private const STATUS_ABORTED = 5; /** @var int */ protected static $timeout = 300; /** @var bool */ protected $captureOutput = \false; /** @var string */ protected $errorOutput = ''; /** @var ?IOInterface */ protected $io; /** * @phpstan-var array> */ private $jobs = []; /** @var int */ private $runningJobs = 0; /** @var int */ private $maxJobs = 10; /** @var int */ private $idGen = 0; /** @var bool */ private $allowAsync = \false; public function __construct(?IOInterface $io = null) { $this->io = $io; } /** * runs a process on the commandline * * @param string|list $command the command to execute * @param mixed $output the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler * @param null|string $cwd the working directory * @return int statuscode */ public function execute($command, &$output = null, ?string $cwd = null) : int { if (\func_num_args() > 1) { return $this->doExecute($command, $cwd, \false, $output); } return $this->doExecute($command, $cwd, \false); } /** * runs a process on the commandline in TTY mode * * @param string|list $command the command to execute * @param null|string $cwd the working directory * @return int statuscode */ public function executeTty($command, ?string $cwd = null) : int { if (\Composer\Util\Platform::isTty()) { return $this->doExecute($command, $cwd, \true); } return $this->doExecute($command, $cwd, \false); } /** * @param string|list $command * @param mixed $output */ private function doExecute($command, ?string $cwd, bool $tty, &$output = null) : int { $this->outputCommandRun($command, $cwd, \false); $this->captureOutput = \func_num_args() > 3; $this->errorOutput = ''; if (\is_string($command)) { $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); } else { $process = new Process($command, $cwd, null, null, static::getTimeout()); } if (!\Composer\Util\Platform::isWindows() && $tty) { try { $process->setTty(\true); } catch (RuntimeException $e) { // ignore TTY enabling errors } } $callback = \is_callable($output) ? $output : function (string $type, string $buffer) : void { $this->outputHandler($type, $buffer); }; $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal) { if ($this->io !== null) { $this->io->writeError('Received ' . $signal . ', aborting when child process is done', \true, IOInterface::DEBUG); } }); try { $process->run($callback); if ($this->captureOutput && !\is_callable($output)) { $output = $process->getOutput(); } $this->errorOutput = $process->getErrorOutput(); } catch (ProcessSignaledException $e) { if ($signalHandler->isTriggered()) { // exiting as we were signaled and the child process exited too due to the signal $signalHandler->exitWithLastSignal(); } } finally { $signalHandler->unregister(); } return $process->getExitCode(); } /** * starts a process on the commandline in async mode * * @param string|list $command the command to execute * @param string $cwd the working directory * @phpstan-return PromiseInterface */ public function executeAsync($command, ?string $cwd = null) : PromiseInterface { if (!$this->allowAsync) { throw new \LogicException('You must use the ProcessExecutor instance which is part of a Composer\\Loop instance to be able to run async processes'); } $job = ['id' => $this->idGen++, 'status' => self::STATUS_QUEUED, 'command' => $command, 'cwd' => $cwd]; $resolver = static function ($resolve, $reject) use(&$job) : void { $job['status'] = \Composer\Util\ProcessExecutor::STATUS_QUEUED; $job['resolve'] = $resolve; $job['reject'] = $reject; }; $canceler = static function () use(&$job) : void { if ($job['status'] === \Composer\Util\ProcessExecutor::STATUS_QUEUED) { $job['status'] = \Composer\Util\ProcessExecutor::STATUS_ABORTED; } if ($job['status'] !== \Composer\Util\ProcessExecutor::STATUS_STARTED) { return; } $job['status'] = \Composer\Util\ProcessExecutor::STATUS_ABORTED; try { if (\defined('SIGINT')) { $job['process']->signal(\SIGINT); } } catch (\Exception $e) { // signal can throw in various conditions, but we don't care if it fails } $job['process']->stop(1); throw new \RuntimeException('Aborted process'); }; $promise = new Promise($resolver, $canceler); $promise = $promise->then(function () use(&$job) { if ($job['process']->isSuccessful()) { $job['status'] = \Composer\Util\ProcessExecutor::STATUS_COMPLETED; } else { $job['status'] = \Composer\Util\ProcessExecutor::STATUS_FAILED; } $this->markJobDone(); return $job['process']; }, function ($e) use(&$job) : void { $job['status'] = \Composer\Util\ProcessExecutor::STATUS_FAILED; $this->markJobDone(); throw $e; }); $this->jobs[$job['id']] =& $job; if ($this->runningJobs < $this->maxJobs) { $this->startJob($job['id']); } return $promise; } protected function outputHandler(string $type, string $buffer) : void { if ($this->captureOutput) { return; } if (null === $this->io) { echo $buffer; return; } if (Process::ERR === $type) { $this->io->writeErrorRaw($buffer, \false); } else { $this->io->writeRaw($buffer, \false); } } private function startJob(int $id) : void { $job =& $this->jobs[$id]; if ($job['status'] !== self::STATUS_QUEUED) { return; } // start job $job['status'] = self::STATUS_STARTED; $this->runningJobs++; $command = $job['command']; $cwd = $job['cwd']; $this->outputCommandRun($command, $cwd, \true); try { if (\is_string($command)) { $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); } else { $process = new Process($command, $cwd, null, null, static::getTimeout()); } } catch (\Throwable $e) { $job['reject']($e); return; } $job['process'] = $process; try { $process->start(); } catch (\Throwable $e) { $job['reject']($e); return; } } public function setMaxJobs(int $maxJobs) : void { $this->maxJobs = $maxJobs; } public function resetMaxJobs() : void { $this->maxJobs = 10; } /** * @param ?int $index job id */ public function wait($index = null) : void { while (\true) { if (0 === $this->countActiveJobs($index)) { return; } \usleep(1000); } } /** * @internal */ public function enableAsync() : void { $this->allowAsync = \true; } /** * @internal * * @param ?int $index job id * @return int number of active (queued or started) jobs */ public function countActiveJobs($index = null) : int { // tick foreach ($this->jobs as $job) { if ($job['status'] === self::STATUS_STARTED) { if (!$job['process']->isRunning()) { \call_user_func($job['resolve'], $job['process']); } $job['process']->checkTimeout(); } if ($this->runningJobs < $this->maxJobs) { if ($job['status'] === self::STATUS_QUEUED) { $this->startJob($job['id']); } } } if (null !== $index) { return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; } $active = 0; foreach ($this->jobs as $job) { if ($job['status'] < self::STATUS_COMPLETED) { $active++; } else { unset($this->jobs[$job['id']]); } } return $active; } private function markJobDone() : void { $this->runningJobs--; } /** * @return string[] */ public function splitLines(?string $output) : array { $output = \trim((string) $output); return $output === '' ? [] : Preg::split('{\\r?\\n}', $output); } /** * Get any error output from the last command */ public function getErrorOutput() : string { return $this->errorOutput; } /** * @return int the timeout in seconds */ public static function getTimeout() : int { return static::$timeout; } /** * @param int $timeout the timeout in seconds */ public static function setTimeout(int $timeout) : void { static::$timeout = $timeout; } /** * Escapes a string to be used as a shell argument. * * @param string|false|null $argument The argument that will be escaped * * @return string The escaped argument */ public static function escape($argument) : string { return self::escapeArgument($argument); } /** * @param string|list $command */ private function outputCommandRun($command, ?string $cwd, bool $async) : void { if (null === $this->io || !$this->io->isDebug()) { return; } $commandString = \is_string($command) ? $command : \implode(' ', \array_map(self::class . '::escape', $command)); $safeCommand = Preg::replaceCallback('{://(?P[^:/\\s]+):(?P[^@\\s/]+)@}i', static function ($m) : string { \assert(\is_string($m['user'])); // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) { return '://***:***@'; } if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) { return '://***:***@'; } return '://' . $m['user'] . ':***@'; }, $commandString); $safeCommand = Preg::replace("{--password (.*[^\\\\]\\') }", '--password \'***\' ', $safeCommand); $this->io->writeError('Executing' . ($async ? ' async' : '') . ' command (' . ($cwd ?: 'CWD') . '): ' . $safeCommand); } /** * Escapes a string to be used as a shell argument for Symfony Process. * * This method expects cmd.exe to be started with the /V:ON option, which * enables delayed environment variable expansion using ! as the delimiter. * If this is not the case, any escaped ^^!var^^! will be transformed to * ^!var^! and introduce two unintended carets. * * Modified from https://github.com/johnstevenson/winbox-args * MIT Licensed (c) John Stevenson * * @param string|false|null $argument */ private static function escapeArgument($argument) : string { if ('' === ($argument = (string) $argument)) { return \escapeshellarg($argument); } if (!\Composer\Util\Platform::isWindows()) { return "'" . \str_replace("'", "'\\''", $argument) . "'"; } // New lines break cmd.exe command parsing $argument = \strtr($argument, "\n", ' '); // In addition to whitespace, commas need quoting to preserve paths $quote = \strpbrk($argument, " \t,") !== \false; $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes); $meta = $dquotes > 0 || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument); if (!$meta && !$quote) { $quote = \strpbrk($argument, '^&|<>()') !== \false; } if ($quote) { $argument = '"' . Preg::replace('/(\\\\*)$/', '$1$1', $argument) . '"'; } if ($meta) { $argument = Preg::replace('/(["^&|<>()%])/', '^$1', $argument); $argument = Preg::replace('/(!)/', '^^$1', $argument); } return $argument; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; use Composer\Pcre\Preg; /** * @author Jonas Renaudot */ class Hg { /** @var string|false|null */ private static $version = \false; /** * @var \Composer\IO\IOInterface */ private $io; /** * @var \Composer\Config */ private $config; /** * @var \Composer\Util\ProcessExecutor */ private $process; public function __construct(IOInterface $io, Config $config, \Composer\Util\ProcessExecutor $process) { $this->io = $io; $this->config = $config; $this->process = $process; } public function runCommand(callable $commandCallable, string $url, ?string $cwd) : void { $this->config->prohibitUrlByConfig($url, $this->io); // Try as is $command = $commandCallable($url); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } // Try with the authentication information available if (Preg::isMatch('{^(https?)://((.+)(?:\\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication((string) $match[5])) { $auth = $this->io->getAuthentication((string) $match[5]); $authenticatedUrl = $match[1] . '://' . \rawurlencode($auth['username']) . ':' . \rawurlencode($auth['password']) . '@' . $match[5] . $match[6]; $command = $commandCallable($authenticatedUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } $error = $this->process->getErrorOutput(); } else { $error = 'The given URL (' . $url . ') does not match the required format (http(s)://(username:password@)example.com/path-to-repository)'; } $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url); } /** * @param non-empty-string $message * * @return never */ private function throwException($message, string $url) : void { if (null === self::getVersion($this->process)) { throw new \RuntimeException(\Composer\Util\Url::sanitize('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); } throw new \RuntimeException(\Composer\Util\Url::sanitize($message)); } /** * Retrieves the current hg version. * * @return string|null The hg version number, if present. */ public static function getVersion(\Composer\Util\ProcessExecutor $process) : ?string { if (\false === self::$version) { self::$version = null; if (0 === $process->execute('hg --version', $output) && Preg::isMatch('/^.+? (\\d+(?:\\.\\d+)+)(?:\\+.*?)?\\)?\\r?\\n/', $output, $matches)) { self::$version = $matches[1]; } } return self::$version; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Config; use Composer\Downloader\MaxFileSizeExceededException; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; use Composer\Util\Http\Response; use Composer\Util\Http\ProxyManager; /** * @internal * @author François Pluchino * @author Jordi Boggiano * @author Nils Adermann */ class RemoteFilesystem { /** @var IOInterface */ private $io; /** @var Config */ private $config; /** @var string */ private $scheme; /** @var int */ private $bytesMax; /** @var string */ private $originUrl; /** @var non-empty-string */ private $fileUrl; /** @var ?string */ private $fileName; /** @var bool */ private $retry = \false; /** @var bool */ private $progress; /** @var ?int */ private $lastProgress; /** @var mixed[] */ private $options = []; /** @var bool */ private $disableTls = \false; /** @var list */ private $lastHeaders; /** @var bool */ private $storeAuth = \false; /** @var AuthHelper */ private $authHelper; /** @var bool */ private $degradedMode = \false; /** @var int */ private $redirects; /** @var int */ private $maxRedirects = 20; /** @var ProxyManager */ private $proxyManager; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The config * @param mixed[] $options The options * @param AuthHelper $authHelper */ public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = \false, ?\Composer\Util\AuthHelper $authHelper = null) { $this->io = $io; // Setup TLS options // The cafile option can be set via config.json if ($disableTls === \false) { $this->options = \Composer\Util\StreamContextFactory::getTlsDefaults($options, $io); } else { $this->disableTls = \true; } // handle the other externally set options normally. $this->options = \array_replace_recursive($this->options, $options); $this->config = $config; $this->authHelper = $authHelper ?? new \Composer\Util\AuthHelper($io, $config); $this->proxyManager = ProxyManager::getInstance(); } /** * Copy the remote file in local. * * @param string $originUrl The origin URL * @param non-empty-string $fileUrl The file URL * @param string $fileName the local filename * @param bool $progress Display the progression * @param mixed[] $options Additional context options * * @return bool true */ public function copy(string $originUrl, string $fileUrl, string $fileName, bool $progress = \true, array $options = []) { return $this->get($originUrl, $fileUrl, $options, $fileName, $progress); } /** * Get the content. * * @param string $originUrl The origin URL * @param non-empty-string $fileUrl The file URL * @param bool $progress Display the progression * @param mixed[] $options Additional context options * * @return bool|string The content */ public function getContents(string $originUrl, string $fileUrl, bool $progress = \true, array $options = []) { return $this->get($originUrl, $fileUrl, $options, null, $progress); } /** * Retrieve the options set in the constructor * * @return mixed[] Options */ public function getOptions() { return $this->options; } /** * Merges new options * * @param mixed[] $options * @return void */ public function setOptions(array $options) { $this->options = \array_replace_recursive($this->options, $options); } /** * Check is disable TLS. * * @return bool */ public function isTlsDisabled() { return $this->disableTls === \true; } /** * Returns the headers of the last request * * @return list */ public function getLastHeaders() { return $this->lastHeaders; } /** * @param string[] $headers array of returned headers like from getLastHeaders() * @return int|null */ public static function findStatusCode(array $headers) { $value = null; foreach ($headers as $header) { if (Preg::isMatch('{^HTTP/\\S+ (\\d+)}i', $header, $match)) { // In case of redirects, http_response_headers contains the headers of all responses // so we can not return directly and need to keep iterating $value = (int) $match[1]; } } return $value; } /** * @param string[] $headers array of returned headers like from getLastHeaders() * @return string|null */ public function findStatusMessage(array $headers) { $value = null; foreach ($headers as $header) { if (Preg::isMatch('{^HTTP/\\S+ \\d+}i', $header)) { // In case of redirects, http_response_headers contains the headers of all responses // so we can not return directly and need to keep iterating $value = $header; } } return $value; } /** * Get file content or copy action. * * @param string $originUrl The origin URL * @param non-empty-string $fileUrl The file URL * @param mixed[] $additionalOptions context options * @param string $fileName the local filename * @param bool $progress Display the progression * * @throws TransportException|\Exception * @throws TransportException When the file could not be downloaded * * @return bool|string */ protected function get(string $originUrl, string $fileUrl, array $additionalOptions = [], ?string $fileName = null, bool $progress = \true) { $this->scheme = \parse_url(\strtr($fileUrl, '\\', '/'), \PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; $this->fileName = $fileName; $this->progress = $progress; $this->lastProgress = null; $retryAuthFailure = \true; $this->lastHeaders = []; $this->redirects = 1; // The first request counts. $tempAdditionalOptions = $additionalOptions; if (isset($tempAdditionalOptions['retry-auth-failure'])) { $retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; unset($tempAdditionalOptions['retry-auth-failure']); } $isRedirect = \false; if (isset($tempAdditionalOptions['redirects'])) { $this->redirects = $tempAdditionalOptions['redirects']; $isRedirect = \true; unset($tempAdditionalOptions['redirects']); } $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions); unset($tempAdditionalOptions); $origFileUrl = $fileUrl; if (isset($options['gitlab-token'])) { $fileUrl .= (\false === \strpos($fileUrl, '?') ? '?' : '&') . 'access_token=' . $options['gitlab-token']; unset($options['gitlab-token']); } if (isset($options['http'])) { $options['http']['ignore_errors'] = \true; } if ($this->degradedMode && \strpos($fileUrl, 'http://repo.packagist.org/') === 0) { // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol $fileUrl = 'http://' . \gethostbyname('repo.packagist.org') . \substr($fileUrl, 20); $degradedPackagist = \true; } $maxFileSize = null; if (isset($options['max_file_size'])) { $maxFileSize = $options['max_file_size']; unset($options['max_file_size']); } $ctx = \Composer\Util\StreamContextFactory::getContext($fileUrl, $options, ['notification' => [$this, 'callbackGet']]); $proxy = $this->proxyManager->getProxyForRequest($fileUrl); $usingProxy = $proxy->getFormattedUrl(' using proxy (%s)'); $this->io->writeError((\strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . \Composer\Util\Url::sanitize($origFileUrl) . $usingProxy, \true, IOInterface::DEBUG); unset($origFileUrl, $proxy, $usingProxy); // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 if ((!Preg::isMatch('{^http://(repo\\.)?packagist\\.org/p/}', $fileUrl) || \false === \strpos($fileUrl, '$') && \false === \strpos($fileUrl, '%24')) && empty($degradedPackagist)) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } if ($this->progress && !$isRedirect) { $this->io->writeError("Downloading (connecting...)", \false); } $errorMessage = ''; $errorCode = 0; $result = \false; \set_error_handler(static function ($code, $msg) use(&$errorMessage) : bool { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^file_get_contents\\(.*?\\): }', '', $msg); return \true; }); $http_response_header = []; try { $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize); if (!empty($http_response_header[0])) { $statusCode = self::findStatusCode($http_response_header); if ($statusCode >= 400 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') { \Composer\Util\HttpDownloader::outputWarnings($this->io, $originUrl, \json_decode($result, \true)); } if (\in_array($statusCode, [401, 403]) && $retryAuthFailure) { $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header); } } $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && \Composer\Util\Platform::strlen($result) < $contentLength) { // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP $e = new TransportException('Content-Length mismatch, received ' . \Composer\Util\Platform::strlen($result) . ' bytes out of the expected ' . $contentLength); $e->setHeaders($http_response_header); $e->setStatusCode(self::findStatusCode($http_response_header)); try { $e->setResponse($this->decodeResult($result, $http_response_header)); } catch (\Exception $discarded) { $e->setResponse($this->normalizeResult($result)); } $this->io->writeError('Content-Length mismatch, received ' . \Composer\Util\Platform::strlen($result) . ' out of ' . $contentLength . ' bytes: (' . \base64_encode($result) . ')', \true, IOInterface::DEBUG); throw $e; } } catch (\Exception $e) { if ($e instanceof TransportException && !empty($http_response_header[0])) { $e->setHeaders($http_response_header); $e->setStatusCode(self::findStatusCode($http_response_header)); } if ($e instanceof TransportException && $result !== \false) { $e->setResponse($this->decodeResult($result, $http_response_header)); } $result = \false; } if ($errorMessage && !\filter_var(\ini_get('allow_url_fopen'), \FILTER_VALIDATE_BOOLEAN)) { $errorMessage = 'allow_url_fopen must be enabled in php.ini (' . $errorMessage . ')'; } \restore_error_handler(); if (isset($e) && !$this->retry) { if (!$this->degradedMode && \false !== \strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = \true; $this->io->writeError(''); $this->io->writeError(['' . $e->getMessage() . '', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info']); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } $statusCode = null; $contentType = null; $locationHeader = null; if (!empty($http_response_header[0])) { $statusCode = self::findStatusCode($http_response_header); $contentType = Response::findHeaderValue($http_response_header, 'content-type'); $locationHeader = Response::findHeaderValue($http_response_header, 'location'); } // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' && !$this->authHelper->isPublicBitBucketDownload($fileUrl) && \substr($fileUrl, -4) === '.zip' && (!$locationHeader || \substr(\parse_url($locationHeader, \PHP_URL_PATH), -4) !== '.zip') && $contentType && Preg::isMatch('{^text/html\\b}i', $contentType)) { $result = \false; if ($retryAuthFailure) { $this->promptAuthAndRetry(401); } } // check for gitlab 404 when downloading archives if ($statusCode === 404 && \in_array($originUrl, $this->config->get('gitlab-domains'), \true) && \false !== \strpos($fileUrl, 'archive.zip')) { $result = \false; if ($retryAuthFailure) { $this->promptAuthAndRetry(401); } } // handle 3xx redirects, 304 Not Modified is excluded $hasFollowedRedirect = \false; if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) { $hasFollowedRedirect = \true; $result = $this->handleRedirect($http_response_header, $additionalOptions, $result); } // fail 4xx and 5xx responses and capture the response if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { if (!$this->retry) { if ($this->progress && !$isRedirect) { $this->io->overwriteError("Downloading (failed)", \false); } $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded (' . $http_response_header[0] . ')', $statusCode); $e->setHeaders($http_response_header); $e->setResponse($this->decodeResult($result, $http_response_header)); $e->setStatusCode($statusCode); throw $e; } $result = \false; } if ($this->progress && !$this->retry && !$isRedirect) { $this->io->overwriteError("Downloading (" . ($result === \false ? 'failed' : '100%') . ")", \false); } // decode gzip if ($result && \extension_loaded('zlib') && \strpos($fileUrl, 'http') === 0 && !$hasFollowedRedirect) { try { $result = $this->decodeResult($result, $http_response_header); } catch (\Exception $e) { if ($this->degradedMode) { throw $e; } $this->degradedMode = \true; $this->io->writeError(['', 'Failed to decode response: ' . $e->getMessage() . '', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info']); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } } // handle copy command if download was successful if (\false !== $result && null !== $fileName && !$isRedirect) { if ('' === $result) { throw new TransportException('"' . $this->fileUrl . '" appears broken, and returned an empty 200 response'); } $errorMessage = ''; \set_error_handler(static function ($code, $msg) use(&$errorMessage) : bool { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^file_put_contents\\(.*?\\): }', '', $msg); return \true; }); $result = (bool) \file_put_contents($fileName, $result); \restore_error_handler(); if (\false === $result) { throw new TransportException('The "' . $this->fileUrl . '" file could not be written to ' . $fileName . ': ' . $errorMessage); } } if ($this->retry) { $this->retry = \false; $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); if ($this->storeAuth) { $this->authHelper->storeAuth($this->originUrl, $this->storeAuth); $this->storeAuth = \false; } return $result; } if (\false === $result) { $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded: ' . $errorMessage, $errorCode); if (!empty($http_response_header[0])) { $e->setHeaders($http_response_header); } if (!$this->degradedMode && \false !== \strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = \true; $this->io->writeError(''); $this->io->writeError(['' . $e->getMessage() . '', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info']); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } if (!empty($http_response_header[0])) { $this->lastHeaders = $http_response_header; } return $result; } /** * Get contents of remote URL. * * @param string $originUrl The origin URL * @param string $fileUrl The file URL * @param resource $context The stream context * @param string[] $responseHeaders * @param int $maxFileSize The maximum allowed file size * * @return string|false The response contents or false on failure */ protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null) { $result = \false; try { $e = null; if ($maxFileSize !== null) { $result = \file_get_contents($fileUrl, \false, $context, 0, $maxFileSize); } else { // passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes $result = \file_get_contents($fileUrl, \false, $context); } } catch (\Throwable $e) { } if ($result !== \false && $maxFileSize !== null && \Composer\Util\Platform::strlen($result) >= $maxFileSize) { throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . \Composer\Util\Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes'); } // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php $responseHeaders = $http_response_header ?? []; if (null !== $e) { throw $e; } return $result; } /** * Get notification action. * * @param int $notificationCode The notification code * @param int $severity The severity level * @param string $message The message * @param int $messageCode The message code * @param int $bytesTransferred The loaded size * @param int $bytesMax The total size * * @return void * * @throws TransportException */ protected function callbackGet(int $notificationCode, int $severity, ?string $message, int $messageCode, int $bytesTransferred, int $bytesMax) { switch ($notificationCode) { case \STREAM_NOTIFY_FAILURE: if (400 === $messageCode) { // This might happen if your host is secured by ssl client certificate authentication // but you do not send an appropriate certificate throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode); } break; case \STREAM_NOTIFY_FILE_SIZE_IS: $this->bytesMax = $bytesMax; break; case \STREAM_NOTIFY_PROGRESS: if ($this->bytesMax > 0 && $this->progress) { $progression = \min(100, (int) \round($bytesTransferred / $this->bytesMax * 100)); if (0 === $progression % 5 && 100 !== $progression && $progression !== $this->lastProgress) { $this->lastProgress = $progression; $this->io->overwriteError("Downloading ({$progression}%)", \false); } } break; default: break; } } /** * @param positive-int $httpStatus * @param string[] $headers * * @return void */ protected function promptAuthAndRetry($httpStatus, ?string $reason = null, array $headers = []) { $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1); $this->storeAuth = $result['storeAuth']; $this->retry = $result['retry']; if ($this->retry) { throw new TransportException('RETRY'); } } /** * @param mixed[] $additionalOptions * * @return mixed[] */ protected function getOptionsForUrl(string $originUrl, array $additionalOptions) { $tlsOptions = []; $headers = []; if (\extension_loaded('zlib')) { $headers[] = 'Accept-Encoding: gzip'; } $options = \array_replace_recursive($this->options, $tlsOptions, $additionalOptions); if (!$this->degradedMode) { // degraded mode disables HTTP/1.1 which causes issues with some bad // proxies/software due to the use of chunked encoding $options['http']['protocol_version'] = 1.1; $headers[] = 'Connection: close'; } $headers = $this->authHelper->addAuthenticationHeader($headers, $originUrl, $this->fileUrl); $options['http']['follow_location'] = 0; if (isset($options['http']['header']) && !\is_array($options['http']['header'])) { $options['http']['header'] = \explode("\r\n", \trim($options['http']['header'], "\r\n")); } foreach ($headers as $header) { $options['http']['header'][] = $header; } return $options; } /** * @param string[] $http_response_header * @param mixed[] $additionalOptions * @param string|false $result * * @return bool|string */ private function handleRedirect(array $http_response_header, array $additionalOptions, $result) { if ($locationHeader = Response::findHeaderValue($http_response_header, 'location')) { if (\parse_url($locationHeader, \PHP_URL_SCHEME)) { // Absolute URL; e.g. https://example.com/composer $targetUrl = $locationHeader; } elseif (\parse_url($locationHeader, \PHP_URL_HOST)) { // Scheme relative; e.g. //example.com/foo $targetUrl = $this->scheme . ':' . $locationHeader; } elseif ('/' === $locationHeader[0]) { // Absolute path; e.g. /foo $urlHost = \parse_url($this->fileUrl, \PHP_URL_HOST); // Replace path using hostname as an anchor. $targetUrl = Preg::replace('{^(.+(?://|@)' . \preg_quote($urlHost) . '(?::\\d+)?)(?:[/\\?].*)?$}', '\\1' . $locationHeader, $this->fileUrl); } else { // Relative path; e.g. foo // This actually differs from PHP which seems to add duplicate slashes. $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\\?.*)?$}', '\\1' . $locationHeader, $this->fileUrl); } } if (!empty($targetUrl)) { $this->redirects++; $this->io->writeError('', \true, IOInterface::DEBUG); $this->io->writeError(\sprintf('Following redirect (%u) %s', $this->redirects, \Composer\Util\Url::sanitize($targetUrl)), \true, IOInterface::DEBUG); $additionalOptions['redirects'] = $this->redirects; return $this->get(\parse_url($targetUrl, \PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress); } if (!$this->retry) { $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded, got redirect without Location (' . $http_response_header[0] . ')'); $e->setHeaders($http_response_header); $e->setResponse($this->decodeResult($result, $http_response_header)); throw $e; } return \false; } /** * @param string|false $result * @param string[] $http_response_header */ private function decodeResult($result, array $http_response_header) : ?string { // decode gzip if ($result && \extension_loaded('zlib')) { $contentEncoding = Response::findHeaderValue($http_response_header, 'content-encoding'); $decode = $contentEncoding && 'gzip' === \strtolower($contentEncoding); if ($decode) { $result = \zlib_decode($result); if ($result === \false) { throw new TransportException('Failed to decode zlib stream'); } } } return $this->normalizeResult($result); } /** * @param string|false $result */ private function normalizeResult($result) : ?string { if ($result === \false) { return null; } return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\IO\IOInterface; /** * Convert PHP errors into exceptions * * @author Artem Lopata */ class ErrorHandler { /** @var ?IOInterface */ private static $io; /** * Error handler * * @param int $level Level of the error raised * @param string $message Error message * @param string $file Filename that the error was raised in * @param int $line Line number the error was raised at * * @static * @throws \ErrorException */ public static function handle(int $level, string $message, string $file, int $line) : bool { $isDeprecationNotice = $level === \E_DEPRECATED || $level === \E_USER_DEPRECATED; // error code is not included in error_reporting if (!$isDeprecationNotice && !(\error_reporting() & $level)) { return \true; } if (\filter_var(\ini_get('xdebug.scream'), \FILTER_VALIDATE_BOOLEAN)) { $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be" . "\na legitimately suppressed error that you were not supposed to see."; } if (!$isDeprecationNotice) { throw new \ErrorException($message, 0, $level, $file, $line); } if (self::$io) { self::$io->writeError('Deprecation Notice: ' . $message . ' in ' . $file . ':' . $line . ''); if (self::$io->isVerbose()) { self::$io->writeError('Stack trace:'); self::$io->writeError(\array_filter(\array_map(static function ($a) : ?string { if (isset($a['line'], $a['file'])) { return ' ' . $a['file'] . ':' . $a['line'] . ''; } return null; }, \array_slice(\debug_backtrace(), 2)))); } } return \true; } /** * Register error handler. */ public static function register(?IOInterface $io = null) : void { \set_error_handler([__CLASS__, 'handle']); \error_reporting(\E_ALL | \E_STRICT); self::$io = $io; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use Composer\Downloader\DownloaderInterface; use Composer\Downloader\DownloadManager; use Composer\Package\PackageInterface; use React\Promise\PromiseInterface; class SyncHelper { /** * Helps you download + install a single package in a synchronous way * * This executes all the required steps and waits for promises to complete * * @param Loop $loop Loop instance which you can get from $composer->getLoop() * @param DownloaderInterface|DownloadManager $downloader DownloadManager instance or Downloader instance you can get from $composer->getDownloadManager()->getDownloader('zip') for example * @param string $path The installation path for the package * @param PackageInterface $package The package to install * @param PackageInterface|null $prevPackage The previous package if this is an update and not an initial installation */ public static function downloadAndInstallPackageSync(\Composer\Util\Loop $loop, $downloader, string $path, PackageInterface $package, ?PackageInterface $prevPackage = null) : void { \assert($downloader instanceof DownloaderInterface || $downloader instanceof DownloadManager); $type = $prevPackage !== null ? 'update' : 'install'; try { self::await($loop, $downloader->download($package, $path, $prevPackage)); self::await($loop, $downloader->prepare($type, $package, $path, $prevPackage)); if ($type === 'update' && $prevPackage !== null) { self::await($loop, $downloader->update($package, $prevPackage, $path)); } else { self::await($loop, $downloader->install($package, $path)); } } catch (\Exception $e) { self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); throw $e; } self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); } /** * Waits for a promise to resolve * * @param Loop $loop Loop instance which you can get from $composer->getLoop() * @phpstan-param PromiseInterface|null $promise */ public static function await(\Composer\Util\Loop $loop, ?PromiseInterface $promise = null) : void { if ($promise !== null) { $loop->wait([$promise]); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Util; use React\Promise\CancellablePromiseInterface; use _ContaoManager\Symfony\Component\Console\Helper\ProgressBar; use React\Promise\PromiseInterface; /** * @author Jordi Boggiano */ class Loop { /** @var HttpDownloader */ private $httpDownloader; /** @var ProcessExecutor|null */ private $processExecutor; /** @var array>> */ private $currentPromises = []; /** @var int */ private $waitIndex = 0; public function __construct(\Composer\Util\HttpDownloader $httpDownloader, ?\Composer\Util\ProcessExecutor $processExecutor = null) { $this->httpDownloader = $httpDownloader; $this->httpDownloader->enableAsync(); $this->processExecutor = $processExecutor; if ($this->processExecutor) { $this->processExecutor->enableAsync(); } } public function getHttpDownloader() : \Composer\Util\HttpDownloader { return $this->httpDownloader; } public function getProcessExecutor() : ?\Composer\Util\ProcessExecutor { return $this->processExecutor; } /** * @param array> $promises * @param ProgressBar|null $progress */ public function wait(array $promises, ?ProgressBar $progress = null) : void { $uncaught = null; \React\Promise\all($promises)->then(static function () : void { }, static function (\Throwable $e) use(&$uncaught) : void { $uncaught = $e; }); // keep track of every group of promises that is waited on, so abortJobs can // cancel them all, even if wait() was called within a wait() $waitIndex = $this->waitIndex++; $this->currentPromises[$waitIndex] = $promises; if ($progress) { $totalJobs = 0; $totalJobs += $this->httpDownloader->countActiveJobs(); if ($this->processExecutor) { $totalJobs += $this->processExecutor->countActiveJobs(); } $progress->start($totalJobs); } $lastUpdate = 0; while (\true) { $activeJobs = 0; $activeJobs += $this->httpDownloader->countActiveJobs(); if ($this->processExecutor) { $activeJobs += $this->processExecutor->countActiveJobs(); } if ($progress && \microtime(\true) - $lastUpdate > 0.1) { $lastUpdate = \microtime(\true); $progress->setProgress($progress->getMaxSteps() - $activeJobs); } if (!$activeJobs) { break; } } // as we skip progress updates if they are too quick, make sure we do one last one here at 100% if ($progress) { $progress->finish(); } unset($this->currentPromises[$waitIndex]); if (null !== $uncaught) { throw $uncaught; } } public function abortJobs() : void { foreach ($this->currentPromises as $promiseGroup) { foreach ($promiseGroup as $promise) { // to support react/promise 2.x we wrap the promise in a resolve() call for safety \React\Promise\resolve($promise)->cancel(); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Config; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Json\JsonValidationException; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Silencer; /** * JSON Configuration Source * * @author Jordi Boggiano * @author Beau Simensen */ class JsonConfigSource implements \Composer\Config\ConfigSourceInterface { /** * @var JsonFile */ private $file; /** * @var bool */ private $authConfig; /** * Constructor */ public function __construct(JsonFile $file, bool $authConfig = \false) { $this->file = $file; $this->authConfig = $authConfig; } /** * @inheritDoc */ public function getName() : string { return $this->file->getPath(); } /** * @inheritDoc */ public function addRepository(string $name, $config, bool $append = \true) : void { $this->manipulateJson('addRepository', static function (&$config, $repo, $repoConfig) use($append) : void { // if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have // to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation if (isset($config['repositories'])) { foreach ($config['repositories'] as $index => $val) { if ($index === $repo) { continue; } if (\is_numeric($index) && ($val === ['packagist' => \false] || $val === ['packagist.org' => \false])) { unset($config['repositories'][$index]); $config['repositories']['packagist.org'] = \false; break; } } } if ($append) { $config['repositories'][$repo] = $repoConfig; } else { $config['repositories'] = [$repo => $repoConfig] + $config['repositories']; } }, $name, $config, $append); } /** * @inheritDoc */ public function removeRepository(string $name) : void { $this->manipulateJson('removeRepository', static function (&$config, $repo) : void { unset($config['repositories'][$repo]); }, $name); } /** * @inheritDoc */ public function addConfigSetting(string $name, $value) : void { $authConfig = $this->authConfig; $this->manipulateJson('addConfigSetting', static function (&$config, $key, $val) use($authConfig) : void { if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\\.}', $key)) { [$key, $host] = \explode('.', $key, 2); if ($authConfig) { $config[$key][$host] = $val; } else { $config['config'][$key][$host] = $val; } } else { $config['config'][$key] = $val; } }, $name, $value); } /** * @inheritDoc */ public function removeConfigSetting(string $name) : void { $authConfig = $this->authConfig; $this->manipulateJson('removeConfigSetting', static function (&$config, $key) use($authConfig) : void { if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\\.}', $key)) { [$key, $host] = \explode('.', $key, 2); if ($authConfig) { unset($config[$key][$host]); } else { unset($config['config'][$key][$host]); } } else { unset($config['config'][$key]); } }, $name); } /** * @inheritDoc */ public function addProperty(string $name, $value) : void { $this->manipulateJson('addProperty', static function (&$config, $key, $val) : void { if (\strpos($key, 'extra.') === 0 || \strpos($key, 'scripts.') === 0) { $bits = \explode('.', $key); $last = \array_pop($bits); $arr =& $config[\reset($bits)]; foreach ($bits as $bit) { if (!isset($arr[$bit])) { $arr[$bit] = []; } $arr =& $arr[$bit]; } $arr[$last] = $val; } else { $config[$key] = $val; } }, $name, $value); } /** * @inheritDoc */ public function removeProperty(string $name) : void { $this->manipulateJson('removeProperty', static function (&$config, $key) : void { if (\strpos($key, 'extra.') === 0 || \strpos($key, 'scripts.') === 0) { $bits = \explode('.', $key); $last = \array_pop($bits); $arr =& $config[\reset($bits)]; foreach ($bits as $bit) { if (!isset($arr[$bit])) { return; } $arr =& $arr[$bit]; } unset($arr[$last]); } else { unset($config[$key]); } }, $name); } /** * @inheritDoc */ public function addLink(string $type, string $name, string $value) : void { $this->manipulateJson('addLink', static function (&$config, $type, $name, $value) : void { $config[$type][$name] = $value; }, $type, $name, $value); } /** * @inheritDoc */ public function removeLink(string $type, string $name) : void { $this->manipulateJson('removeSubNode', static function (&$config, $type, $name) : void { unset($config[$type][$name]); }, $type, $name); $this->manipulateJson('removeMainKeyIfEmpty', static function (&$config, $type) : void { if (0 === \count($config[$type])) { unset($config[$type]); } }, $type); } /** * @param mixed ...$args */ private function manipulateJson(string $method, callable $fallback, ...$args) : void { if ($this->file->exists()) { if (!\is_writable($this->file->getPath())) { throw new \RuntimeException(\sprintf('The file "%s" is not writable.', $this->file->getPath())); } if (!Filesystem::isReadable($this->file->getPath())) { throw new \RuntimeException(\sprintf('The file "%s" is not readable.', $this->file->getPath())); } $contents = \file_get_contents($this->file->getPath()); } elseif ($this->authConfig) { $contents = "{\n}\n"; } else { $contents = "{\n \"config\": {\n }\n}\n"; } $manipulator = new JsonManipulator($contents); $newFile = !$this->file->exists(); // override manipulator method for auth config files if ($this->authConfig && $method === 'addConfigSetting') { $method = 'addSubNode'; [$mainNode, $name] = \explode('.', $args[0], 2); $args = [$mainNode, $name, $args[1]]; } elseif ($this->authConfig && $method === 'removeConfigSetting') { $method = 'removeSubNode'; [$mainNode, $name] = \explode('.', $args[0], 2); $args = [$mainNode, $name]; } // try to update cleanly if (\call_user_func_array([$manipulator, $method], $args)) { \file_put_contents($this->file->getPath(), $manipulator->getContents()); } else { // on failed clean update, call the fallback and rewrite the whole file $config = $this->file->read(); $this->arrayUnshiftRef($args, $config); $fallback(...$args); // avoid ending up with arrays for keys that should be objects foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) { if (isset($config[$prop]) && $config[$prop] === []) { $config[$prop] = new \stdClass(); } } foreach (['psr-0', 'psr-4'] as $prop) { if (isset($config['autoload'][$prop]) && $config['autoload'][$prop] === []) { $config['autoload'][$prop] = new \stdClass(); } if (isset($config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === []) { $config['autoload-dev'][$prop] = new \stdClass(); } } foreach (['platform', 'http-basic', 'bearer', 'gitlab-token', 'gitlab-oauth', 'github-oauth', 'preferred-install'] as $prop) { if (isset($config['config'][$prop]) && $config['config'][$prop] === []) { $config['config'][$prop] = new \stdClass(); } } $this->file->write($config); } try { $this->file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { // restore contents to the original state \file_put_contents($this->file->getPath(), $contents); throw new \RuntimeException('Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). ' . \PHP_EOL . \implode(\PHP_EOL, $e->getErrors()), 0, $e); } if ($newFile) { Silencer::call('chmod', $this->file->getPath(), 0600); } } /** * Prepend a reference to an element to the beginning of an array. * * @param mixed[] $array * @param mixed $value */ private function arrayUnshiftRef(array &$array, &$value) : int { $return = \array_unshift($array, ''); $array[0] =& $value; return $return; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Config; /** * Configuration Source Interface * * @author Jordi Boggiano * @author Beau Simensen */ interface ConfigSourceInterface { /** * Add a repository * * @param string $name Name * @param mixed[]|false $config Configuration * @param bool $append Whether the repo should be appended (true) or prepended (false) */ public function addRepository(string $name, $config, bool $append = \true) : void; /** * Remove a repository */ public function removeRepository(string $name) : void; /** * Add a config setting * * @param string $name Name * @param mixed $value Value */ public function addConfigSetting(string $name, $value) : void; /** * Remove a config setting */ public function removeConfigSetting(string $name) : void; /** * Add a property * * @param string $name Name * @param string|string[] $value Value */ public function addProperty(string $name, $value) : void; /** * Remove a property */ public function removeProperty(string $name) : void; /** * Add a package link * * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) * @param string $name Name * @param string $value Value */ public function addLink(string $type, string $name, string $value) : void; /** * Remove a package link * * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) * @param string $name Name */ public function removeLink(string $type, string $name) : void; /** * Gives a user-friendly name to this source (file path or so) */ public function getName() : string; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\SelfUpdate; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Util\HttpDownloader; use Composer\Config; /** * @author Jordi Boggiano */ class Versions { /** * @var string[] * @deprecated use Versions::CHANNELS */ public static $channels = self::CHANNELS; public const CHANNELS = ['stable', 'preview', 'snapshot', '1', '2', '2.2']; /** @var HttpDownloader */ private $httpDownloader; /** @var Config */ private $config; /** @var string */ private $channel; /** @var array>|null */ private $versionsData = null; public function __construct(Config $config, HttpDownloader $httpDownloader) { $this->httpDownloader = $httpDownloader; $this->config = $config; } public function getChannel() : string { if ($this->channel) { return $this->channel; } $channelFile = $this->config->get('home') . '/update-channel'; if (\file_exists($channelFile)) { $channel = \trim(\file_get_contents($channelFile)); if (\in_array($channel, ['stable', 'preview', 'snapshot', '2.2'], \true)) { return $this->channel = $channel; } } return $this->channel = 'stable'; } public function setChannel(string $channel, ?IOInterface $io = null) : void { if (!\in_array($channel, self::CHANNELS, \true)) { throw new \InvalidArgumentException('Invalid channel ' . $channel . ', must be one of: ' . \implode(', ', self::CHANNELS)); } $channelFile = $this->config->get('home') . '/update-channel'; $this->channel = $channel; // rewrite '2' and '1' channels to stable for future self-updates, but LTS ones like '2.2' remain pinned $storedChannel = Preg::isMatch('{^\\d+$}D', $channel) ? 'stable' : $channel; $previouslyStored = \file_exists($channelFile) ? \trim((string) \file_get_contents($channelFile)) : null; \file_put_contents($channelFile, $storedChannel . \PHP_EOL); if ($io !== null && $previouslyStored !== $storedChannel) { $io->writeError('Storing "' . $storedChannel . '" as default update channel for the next self-update run.'); } } /** * @return array{path: string, version: string, min-php: int, eol?: true} */ public function getLatest(?string $channel = null) : array { $versions = $this->getVersionsData(); foreach ($versions[$channel ?: $this->getChannel()] as $version) { if ($version['min-php'] <= \PHP_VERSION_ID) { return $version; } } throw new \UnexpectedValueException('There is no version of Composer available for your PHP version (' . \PHP_VERSION . ')'); } /** * @return array> */ private function getVersionsData() : array { if (null === $this->versionsData) { if ($this->config->get('disable-tls') === \true) { $protocol = 'http'; } else { $protocol = 'https'; } $this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson(); } return $this->versionsData; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\SelfUpdate; use Composer\Pcre\Preg; /** * @author Jordi Boggiano */ class Keys { public static function fingerprint(string $path) : string { $hash = \strtoupper(\hash('sha256', Preg::replace('{\\s}', '', \file_get_contents($path)))); return \implode(' ', [ \substr($hash, 0, 8), \substr($hash, 8, 8), \substr($hash, 16, 8), \substr($hash, 24, 8), '', // Extra space \substr($hash, 32, 8), \substr($hash, 40, 8), \substr($hash, 48, 8), \substr($hash, 56, 8), ]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\Silencer; use _ContaoManager\Symfony\Component\Finder\Finder; /** * Reads/writes to a filesystem cache * * @author Jordi Boggiano */ class Cache { /** @var bool|null */ private static $cacheCollected = null; /** @var IOInterface */ private $io; /** @var string */ private $root; /** @var ?bool */ private $enabled = null; /** @var string */ private $allowlist; /** @var Filesystem */ private $filesystem; /** @var bool */ private $readOnly; /** * @param string $cacheDir location of the cache * @param string $allowlist List of characters that are allowed in path names (used in a regex character class) * @param Filesystem $filesystem optional filesystem instance * @param bool $readOnly whether the cache is in readOnly mode */ public function __construct(IOInterface $io, string $cacheDir, string $allowlist = 'a-z0-9._', ?Filesystem $filesystem = null, bool $readOnly = \false) { $this->io = $io; $this->root = \rtrim($cacheDir, '/\\') . '/'; $this->allowlist = $allowlist; $this->filesystem = $filesystem ?: new Filesystem(); $this->readOnly = (bool) $readOnly; if (!self::isUsable($cacheDir)) { $this->enabled = \false; } } /** * @return void */ public function setReadOnly(bool $readOnly) { $this->readOnly = (bool) $readOnly; } /** * @return bool */ public function isReadOnly() { return $this->readOnly; } /** * @return bool */ public static function isUsable(string $path) { return !Preg::isMatch('{(^|[\\\\/])(\\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); } /** * @return bool */ public function isEnabled() { if ($this->enabled === null) { $this->enabled = \true; if (!$this->readOnly && (!\is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, \true) || !\is_writable($this->root))) { $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.'); $this->enabled = \false; } } return $this->enabled; } /** * @return string */ public function getRoot() { return $this->root; } /** * @return string|false */ public function read(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); if (\file_exists($this->root . $file)) { $this->io->writeError('Reading ' . $this->root . $file . ' from cache', \true, IOInterface::DEBUG); return \file_get_contents($this->root . $file); } } return \false; } /** * @return bool */ public function write(string $file, string $contents) { $wasEnabled = $this->enabled === \true; if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); $this->io->writeError('Writing ' . $this->root . $file . ' into cache', \true, IOInterface::DEBUG); $tempFileName = $this->root . $file . \uniqid('.', \true) . '.tmp'; try { return \file_put_contents($tempFileName, $contents) !== \false && \rename($tempFileName, $this->root . $file); } catch (\ErrorException $e) { // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076 if ($wasEnabled) { \clearstatcache(); $this->enabled = null; return $this->write($file, $contents); } $this->io->writeError('Failed to write into cache: ' . $e->getMessage() . '', \true, IOInterface::DEBUG); if (Preg::isMatch('{^file_put_contents\\(\\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { // Remove partial file. \unlink($tempFileName); $message = \sprintf('Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available', $tempFileName, $m[1], $m[2], \function_exists('disk_free_space') ? @\disk_free_space(\dirname($tempFileName)) : 'unknown'); $this->io->writeError($message); return \false; } throw $e; } } return \false; } /** * Copy a file into the cache * * @return bool */ public function copyFrom(string $file, string $source) { if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); $this->filesystem->ensureDirectoryExists(\dirname($this->root . $file)); if (!\file_exists($source)) { $this->io->writeError('' . $source . ' does not exist, can not write into cache'); } elseif ($this->io->isDebug()) { $this->io->writeError('Writing ' . $this->root . $file . ' into cache from ' . $source); } return \copy($source, $this->root . $file); } return \false; } /** * Copy a file out of the cache * * @return bool */ public function copyTo(string $file, string $target) { if ($this->isEnabled()) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); if (\file_exists($this->root . $file)) { try { \touch($this->root . $file, (int) \filemtime($this->root . $file), \time()); } catch (\ErrorException $e) { // fallback in case the above failed due to incorrect ownership // see https://github.com/composer/composer/issues/4070 Silencer::call('touch', $this->root . $file); } $this->io->writeError('Reading ' . $this->root . $file . ' from cache', \true, IOInterface::DEBUG); return \copy($this->root . $file, $target); } } return \false; } /** * @return bool */ public function gcIsNecessary() { if (self::$cacheCollected) { return \false; } self::$cacheCollected = \true; if (Platform::getEnv('COMPOSER_TEST_SUITE')) { return \false; } if (Platform::isInputCompletionProcess()) { return \false; } return !\random_int(0, 50); } /** * @return bool */ public function remove(string $file) { if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); if (\file_exists($this->root . $file)) { return $this->filesystem->unlink($this->root . $file); } } return \false; } /** * @return bool */ public function clear() { if ($this->isEnabled() && !$this->readOnly) { $this->filesystem->emptyDirectory($this->root); return \true; } return \false; } /** * @return int|false * @phpstan-return int<0, max>|false */ public function getAge(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); if (\file_exists($this->root . $file) && ($mtime = \filemtime($this->root . $file)) !== \false) { return \abs(\time() - $mtime); } } return \false; } /** * @return bool */ public function gc(int $ttl, int $maxSize) { if ($this->isEnabled() && !$this->readOnly) { $expire = new \DateTime(); $expire->modify('-' . $ttl . ' seconds'); $finder = $this->getFinder()->date('until ' . $expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->unlink($file->getPathname()); } $totalSize = $this->filesystem->size($this->root); if ($totalSize > $maxSize) { $iterator = $this->getFinder()->sortByAccessedTime()->getIterator(); while ($totalSize > $maxSize && $iterator->valid()) { $filepath = $iterator->current()->getPathname(); $totalSize -= $this->filesystem->size($filepath); $this->filesystem->unlink($filepath); $iterator->next(); } } self::$cacheCollected = \true; return \true; } return \false; } public function gcVcsCache(int $ttl) : bool { if ($this->isEnabled()) { $expire = new \DateTime(); $expire->modify('-' . $ttl . ' seconds'); $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until ' . $expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->removeDirectory($file->getPathname()); } self::$cacheCollected = \true; return \true; } return \false; } /** * @return string|false */ public function sha1(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); if (\file_exists($this->root . $file)) { return \sha1_file($this->root . $file); } } return \false; } /** * @return string|false */ public function sha256(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^' . $this->allowlist . ']}i', '-', $file); if (\file_exists($this->root . $file)) { return \hash_file('sha256', $this->root . $file); } } return \false; } /** * @return Finder */ protected function getFinder() { return Finder::create()->in($this->root)->files(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; /** * The base event class * * @author Nils Adermann */ class Event { /** * @var string This event's name */ protected $name; /** * @var string[] Arguments passed by the user, these will be forwarded to CLI script handlers */ protected $args; /** * @var mixed[] Flags usable in PHP script handlers */ protected $flags; /** * @var bool Whether the event should not be passed to more listeners */ private $propagationStopped = \false; /** * Constructor. * * @param string $name The event name * @param string[] $args Arguments passed by the user * @param mixed[] $flags Optional flags to pass data not as argument */ public function __construct(string $name, array $args = [], array $flags = []) { $this->name = $name; $this->args = $args; $this->flags = $flags; } /** * Returns the event's name. * * @return string The event name */ public function getName() : string { return $this->name; } /** * Returns the event's arguments. * * @return string[] The event arguments */ public function getArguments() : array { return $this->args; } /** * Returns the event's flags. * * @return mixed[] The event flags */ public function getFlags() : array { return $this->flags; } /** * Checks if stopPropagation has been called * * @return bool Whether propagation has been stopped */ public function isPropagationStopped() : bool { return $this->propagationStopped; } /** * Prevents the event from being passed to further listeners */ public function stopPropagation() : void { $this->propagationStopped = \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; /** * Thrown when a script running an external process exits with a non-0 status code * * @author Jordi Boggiano */ class ScriptExecutionException extends \RuntimeException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; use Composer\DependencyResolver\Transaction; use Composer\Installer\InstallerEvent; use Composer\IO\BufferIO; use Composer\IO\ConsoleIO; use Composer\IO\IOInterface; use Composer\Composer; use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Plugin\CommandEvent; use Composer\Plugin\PreCommandRunEvent; use Composer\Util\Platform; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Repository\RepositoryInterface; use Composer\Script; use Composer\Installer\PackageEvent; use Composer\Installer\BinaryInstaller; use Composer\Util\ProcessExecutor; use Composer\Script\Event as ScriptEvent; use Composer\Autoload\ClassLoader; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\StringInput; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutput; use _ContaoManager\Symfony\Component\Process\PhpExecutableFinder; use _ContaoManager\Symfony\Component\Process\ExecutableFinder; /** * The Event Dispatcher. * * Example in command: * $dispatcher = new EventDispatcher($this->requireComposer(), $this->getApplication()->getIO()); * // ... * $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD); * // ... * * @author François Pluchino * @author Jordi Boggiano * @author Nils Adermann */ class EventDispatcher { /** @var PartialComposer */ protected $composer; /** @var IOInterface */ protected $io; /** @var ?ClassLoader */ protected $loader; /** @var ProcessExecutor */ protected $process; /** @var array>> */ protected $listeners = []; /** @var bool */ protected $runScripts = \true; /** @var list */ private $eventStack; /** * Constructor. * * @param PartialComposer $composer The composer instance * @param IOInterface $io The IOInterface instance * @param ProcessExecutor $process */ public function __construct(PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null) { $this->composer = $composer; $this->io = $io; $this->process = $process ?? new ProcessExecutor($io); $this->eventStack = []; } /** * Set whether script handlers are active or not * * @return $this */ public function setRunScripts(bool $runScripts = \true) : self { $this->runScripts = (bool) $runScripts; return $this; } /** * Dispatch an event * * @param string|null $eventName The event name, required if no $event is provided * @param Event $event An event instance, required if no $eventName is provided * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatch(?string $eventName, ?\Composer\EventDispatcher\Event $event = null) : int { if (null === $event) { if (null === $eventName) { throw new \InvalidArgumentException('If no $event is passed in to ' . __METHOD__ . ' you have to pass in an $eventName, got null.'); } $event = new \Composer\EventDispatcher\Event($eventName); } return $this->doDispatch($event); } /** * Dispatch a script event. * * @param string $eventName The constant in ScriptEvents * @param array $additionalArgs Arguments passed by the user * @param array $flags Optional flags to pass data not as argument * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatchScript(string $eventName, bool $devMode = \false, array $additionalArgs = [], array $flags = []) : int { \assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); } /** * Dispatch a package event. * * @param string $eventName The constant in PackageEvents * @param bool $devMode Whether or not we are in dev mode * @param RepositoryInterface $localRepo The installed repository * @param OperationInterface[] $operations The list of operations * @param OperationInterface $operation The package being installed/updated/removed * * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatchPackageEvent(string $eventName, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation) : int { \assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation)); } /** * Dispatch a installer event. * * @param string $eventName The constant in InstallerEvents * @param bool $devMode Whether or not we are in dev mode * @param bool $executeOperations True if operations will be executed, false in --dry-run * @param Transaction $transaction The transaction contains the list of operations * * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ public function dispatchInstallerEvent(string $eventName, bool $devMode, bool $executeOperations, Transaction $transaction) : int { \assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction)); } /** * Triggers the listeners of an event. * * @param Event $event The event object to pass to the event handlers/listeners. * @throws \RuntimeException|\Exception * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ protected function doDispatch(\Composer\EventDispatcher\Event $event) { if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) { $details = null; if ($event instanceof PackageEvent) { $details = (string) $event->getOperation(); } elseif ($event instanceof CommandEvent) { $details = $event->getCommandName(); } elseif ($event instanceof PreCommandRunEvent) { $details = $event->getCommand(); } $this->io->writeError('Dispatching ' . $event->getName() . '' . ($details ? ' (' . $details . ')' : '') . ' event'); } $listeners = $this->getListeners($event); $this->pushEvent($event); try { $returnMax = 0; foreach ($listeners as $callable) { $return = 0; $this->ensureBinDirIsInPath(); $formattedEventNameWithArgs = $event->getName() . ($event->getArguments() !== [] ? ' (' . \implode(', ', $event->getArguments()) . ')' : ''); if (!\is_string($callable)) { if (!\is_callable($callable)) { $className = \is_object($callable[0]) ? \get_class($callable[0]) : $callable[0]; throw new \RuntimeException('Subscriber ' . $className . '::' . $callable[1] . ' for event ' . $event->getName() . ' is not callable, make sure the function is defined and public'); } if (\is_array($callable) && (\is_string($callable[0]) || \is_object($callable[0])) && \is_string($callable[1])) { $this->io->writeError(\sprintf('> %s: %s', $formattedEventNameWithArgs, (\is_object($callable[0]) ? \get_class($callable[0]) : $callable[0]) . '->' . $callable[1]), \true, IOInterface::VERBOSE); } $return = \false === $callable($event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { $this->io->writeError(\sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), \true, IOInterface::VERBOSE); $script = \explode(' ', \substr($callable, 1)); $scriptName = $script[0]; unset($script[0]); $args = \array_merge($script, $event->getArguments()); $flags = $event->getFlags(); if (isset($flags['script-alias-input'])) { $argsString = \implode(' ', \array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script)); $flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input']; unset($argsString); } if (\strpos($callable, '@composer ') === 0) { $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . \implode(' ', $args); if (0 !== ($exitCode = $this->executeTty($exec))) { $this->io->writeError(\sprintf('Script %s handling the %s event returned with error code ' . $exitCode . '', $callable, $event->getName()), \true, IOInterface::QUIET); throw new \Composer\EventDispatcher\ScriptExecutionException('Error Output: ' . $this->process->getErrorOutput(), $exitCode); } } else { if (!$this->getListeners(new \Composer\EventDispatcher\Event($scriptName))) { $this->io->writeError(\sprintf('You made a reference to a non-existent script %s', $callable), \true, IOInterface::QUIET); } try { /** @var InstallerEvent $event */ $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); $scriptEvent->setOriginatingEvent($event); $return = $this->dispatch($scriptName, $scriptEvent); } catch (\Composer\EventDispatcher\ScriptExecutionException $e) { $this->io->writeError(\sprintf('Script %s was called via %s', $callable, $event->getName()), \true, IOInterface::QUIET); throw $e; } } } elseif ($this->isPhpScript($callable)) { $className = \substr($callable, 0, \strpos($callable, '::')); $methodName = \substr($callable, \strpos($callable, '::') + 2); if (!\class_exists($className)) { $this->io->writeError('Class ' . $className . ' is not autoloadable, can not call ' . $event->getName() . ' script', \true, IOInterface::QUIET); continue; } if (!\is_callable($callable)) { $this->io->writeError('Method ' . $callable . ' is not callable, can not call ' . $event->getName() . ' script', \true, IOInterface::QUIET); continue; } try { $return = \false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; } catch (\Exception $e) { $message = "Script %s handling the %s event terminated with an exception"; $this->io->writeError('' . \sprintf($message, $callable, $event->getName()) . '', \true, IOInterface::QUIET); throw $e; } } elseif ($this->isCommandClass($callable)) { $className = $callable; if (!\class_exists($className)) { $this->io->writeError('Class ' . $className . ' is not autoloadable, can not call ' . $event->getName() . ' script', \true, IOInterface::QUIET); continue; } if (!\is_a($className, Command::class, \true)) { $this->io->writeError('Class ' . $className . ' does not extend ' . Command::class . ', can not call ' . $event->getName() . ' script', \true, IOInterface::QUIET); continue; } if (\defined('Composer\\Script\\ScriptEvents::' . \str_replace('-', '_', \strtoupper($event->getName())))) { $this->io->writeError('You cannot bind ' . $event->getName() . ' to a Command class, use a non-reserved name', \true, IOInterface::QUIET); continue; } $app = new Application(); $app->setCatchExceptions(\false); if (\method_exists($app, 'setCatchErrors')) { $app->setCatchErrors(\false); } $app->setAutoExit(\false); $cmd = new $className($event->getName()); $app->add($cmd); $app->setDefaultCommand((string) $cmd->getName(), \true); try { $args = \implode(' ', \array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $event->getArguments())); // reusing the output from $this->io is mostly needed for tests, but generally speaking // it does not hurt to keep the same stream as the current Application if ($this->io instanceof ConsoleIO) { $reflProp = new \ReflectionProperty($this->io, 'output'); if (\PHP_VERSION_ID < 80100) { $reflProp->setAccessible(\true); } $output = $reflProp->getValue($this->io); } else { $output = new ConsoleOutput(); } $return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output); } catch (\Exception $e) { $message = "Script %s handling the %s event terminated with an exception"; $this->io->writeError('' . \sprintf($message, $callable, $event->getName()) . '', \true, IOInterface::QUIET); throw $e; } } else { $args = \implode(' ', \array_map(['Composer\\Util\\ProcessExecutor', 'escape'], $event->getArguments())); // @putenv does not receive arguments if (\strpos($callable, '@putenv ') === 0) { $exec = $callable; } else { $exec = $callable . ($args === '' ? '' : ' ' . $args); } if ($this->io->isVerbose()) { $this->io->writeError(\sprintf('> %s: %s', $event->getName(), $exec)); } elseif ($event->getName() !== '__exec_command') { // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it $this->io->writeError(\sprintf('> %s', $exec)); } $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); if ($possibleLocalBinaries) { foreach ($possibleLocalBinaries as $localExec) { if (Preg::isMatch('{\\b' . \preg_quote($callable) . '$}', $localExec)) { $caller = BinaryInstaller::determineBinaryCaller($localExec); $exec = Preg::replace('{^' . \preg_quote($callable) . '}', $caller . ' ' . $localExec, $exec); break; } } } if (\strpos($exec, '@putenv ') === 0) { if (\false === \strpos($exec, '=')) { Platform::clearEnv(\substr($exec, 8)); } else { [$var, $value] = \explode('=', \substr($exec, 8), 2); Platform::putEnv($var, $value); } continue; } if (\strpos($exec, '@php ') === 0) { $pathAndArgs = \substr($exec, 5); if (Platform::isWindows()) { $pathAndArgs = Preg::replaceCallback('{^\\S+}', static function ($path) { return \str_replace('/', '\\', (string) $path[0]); }, $pathAndArgs); } // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path $matched = Preg::isMatchStrictGroups('{^[^\'"\\s/\\\\]+}', $pathAndArgs, $match); if ($matched && !\file_exists($match[0])) { $finder = new ExecutableFinder(); if ($pathToExec = $finder->find($match[0])) { if (Platform::isWindows()) { $execWithoutExt = Preg::replace('{\\.(exe|bat|cmd|com)$}i', '', $pathToExec); // prefer non-extension file if it exists when executing with PHP if (\file_exists($execWithoutExt)) { $pathToExec = $execWithoutExt; } unset($execWithoutExt); } $pathAndArgs = $pathToExec . \substr($pathAndArgs, \strlen($match[0])); } } $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs; } else { $finder = new PhpExecutableFinder(); $phpPath = $finder->find(\false); if ($phpPath) { Platform::putEnv('PHP_BINARY', $phpPath); } if (Platform::isWindows()) { $exec = Preg::replaceCallback('{^\\S+}', static function ($path) { \assert(\is_string($path[0])); return \str_replace('/', '\\', $path[0]); }, $exec); } } // if composer is being executed, make sure it runs the expected composer from current path // resolution, even if bin-dir contains composer too because the project requires composer/composer // see https://github.com/composer/composer/issues/8748 if (\strpos($exec, 'composer ') === 0) { $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . \substr($exec, 8); } if (0 !== ($exitCode = $this->executeTty($exec))) { $this->io->writeError(\sprintf('Script %s handling the %s event returned with error code ' . $exitCode . '', $callable, $event->getName()), \true, IOInterface::QUIET); throw new \Composer\EventDispatcher\ScriptExecutionException('Error Output: ' . $this->process->getErrorOutput(), $exitCode); } } $returnMax = \max($returnMax, $return); if ($event->isPropagationStopped()) { break; } } } finally { $this->popEvent(); } return $returnMax; } protected function executeTty(string $exec) : int { if ($this->io->isInteractive()) { return $this->process->executeTty($exec); } return $this->process->execute($exec); } protected function getPhpExecCommand() : string { $finder = new PhpExecutableFinder(); $phpPath = $finder->find(\false); if (!$phpPath) { throw new \RuntimeException('Failed to locate PHP binary to execute ' . $phpPath); } $phpArgs = $finder->findArguments(); $phpArgs = $phpArgs ? ' ' . \implode(' ', $phpArgs) : ''; $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(\ini_get('allow_url_fopen')); $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(\ini_get('disable_functions')); $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(\ini_get('memory_limit')); return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; } /** * @param Event $event Event invoking the PHP callable * * @return mixed */ protected function executeEventPhpScript(string $className, string $methodName, \Composer\EventDispatcher\Event $event) { if ($this->io->isVerbose()) { $this->io->writeError(\sprintf('> %s: %s::%s', $event->getName(), $className, $methodName)); } else { $this->io->writeError(\sprintf('> %s::%s', $className, $methodName)); } return $className::$methodName($event); } /** * Add a listener for a particular event * * @param string $eventName The event name - typically a constant * @param callable|string $listener A callable expecting an event argument, or a command string to be executed (same as a composer.json "scripts" entry) * @param int $priority A higher value represents a higher priority */ public function addListener(string $eventName, $listener, int $priority = 0) : void { $this->listeners[$eventName][$priority][] = $listener; } /** * @param callable|object $listener A callable or an object instance for which all listeners should be removed */ public function removeListener($listener) : void { foreach ($this->listeners as $eventName => $priorities) { foreach ($priorities as $priority => $listeners) { foreach ($listeners as $index => $candidate) { if ($listener === $candidate || \is_array($candidate) && \is_object($listener) && $candidate[0] === $listener) { unset($this->listeners[$eventName][$priority][$index]); } } } } } /** * Adds object methods as listeners for the events in getSubscribedEvents * * @see EventSubscriberInterface */ public function addSubscriber(\Composer\EventDispatcher\EventSubscriberInterface $subscriber) : void { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (\is_string($params)) { $this->addListener($eventName, [$subscriber, $params]); } elseif (\is_string($params[0])) { $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); } else { foreach ($params as $listener) { $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); } } } } /** * Retrieves all listeners for a given event * * @return array All listeners: callables and scripts */ protected function getListeners(\Composer\EventDispatcher\Event $event) : array { $scriptListeners = $this->runScripts ? $this->getScriptListeners($event) : []; if (!isset($this->listeners[$event->getName()][0])) { $this->listeners[$event->getName()][0] = []; } \krsort($this->listeners[$event->getName()]); $listeners = $this->listeners; $listeners[$event->getName()][0] = \array_merge($listeners[$event->getName()][0], $scriptListeners); return \array_merge(...$listeners[$event->getName()]); } /** * Checks if an event has listeners registered */ public function hasEventListeners(\Composer\EventDispatcher\Event $event) : bool { $listeners = $this->getListeners($event); return \count($listeners) > 0; } /** * Finds all listeners defined as scripts in the package * * @param Event $event Event object * @return string[] Listeners */ protected function getScriptListeners(\Composer\EventDispatcher\Event $event) : array { $package = $this->composer->getPackage(); $scripts = $package->getScripts(); if (empty($scripts[$event->getName()])) { return []; } \assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); if ($this->loader) { $this->loader->unregister(); } $generator = $this->composer->getAutoloadGenerator(); if ($event instanceof ScriptEvent) { $generator->setDevMode($event->isDevMode()); } $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages); $map = $generator->parseAutoloads($packageMap, $package); $this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); $this->loader->register(\false); return $scripts[$event->getName()]; } /** * Checks if string given references a class path and method */ protected function isPhpScript(string $callable) : bool { return \false === \strpos($callable, ' ') && \false !== \strpos($callable, '::'); } /** * Checks if string given references a command class */ protected function isCommandClass(string $callable) : bool { return \str_contains($callable, '\\') && !\str_contains($callable, ' ') && \str_ends_with($callable, 'Command'); } /** * Checks if string given references a composer run-script */ protected function isComposerScript(string $callable) : bool { return \strpos($callable, '@') === 0 && \strpos($callable, '@php ') !== 0 && \strpos($callable, '@putenv ') !== 0; } /** * Push an event to the stack of active event * * @throws \RuntimeException */ protected function pushEvent(\Composer\EventDispatcher\Event $event) : int { $eventName = $event->getName(); if (\in_array($eventName, $this->eventStack)) { throw new \RuntimeException(\sprintf("Circular call to script handler '%s' detected", $eventName)); } return \array_push($this->eventStack, $eventName); } /** * Pops the active event from the stack */ protected function popEvent() : ?string { return \array_pop($this->eventStack); } private function ensureBinDirIsInPath() : void { $pathEnv = 'PATH'; // checking if only Path and not PATH is set then we probably need to update the Path env // on Windows getenv is case-insensitive so we cannot check it via Platform::getEnv and // we need to check in $_SERVER directly if (!isset($_SERVER[$pathEnv]) && isset($_SERVER['Path'])) { $pathEnv = 'Path'; } // add the bin dir to the PATH to make local binaries of deps usable in scripts $binDir = $this->composer->getConfig()->get('bin-dir'); if (\is_dir($binDir)) { $binDir = \realpath($binDir); $pathValue = (string) Platform::getEnv($pathEnv); if (!Preg::isMatch('{(^|' . \PATH_SEPARATOR . ')' . \preg_quote($binDir) . '($|' . \PATH_SEPARATOR . ')}', $pathValue)) { Platform::putEnv($pathEnv, $binDir . \PATH_SEPARATOR . $pathValue); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\EventDispatcher; /** * An EventSubscriber knows which events it is interested in. * * If an EventSubscriber is added to an EventDispatcher, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. * * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek */ interface EventSubscriberInterface { /** * Returns an array of event names this subscriber wants to listen to. * * The array keys are event names and the value can be: * * * The method name to call (priority defaults to 0) * * An array composed of the method name to call and the priority * * An array of arrays composed of the method names to call and respective * priorities, or 0 if unset * * For instance: * * * array('eventName' => 'methodName') * * array('eventName' => array('methodName', $priority)) * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) * * @return array> The event names to listen to */ public static function getSubscribedEvents(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Package\RootPackageInterface; use Composer\Util\Loop; use Composer\Repository\RepositoryManager; use Composer\Installer\InstallationManager; use Composer\EventDispatcher\EventDispatcher; /** * @author Jordi Boggiano */ class PartialComposer { /** * @var RootPackageInterface */ private $package; /** * @var Loop */ private $loop; /** * @var Repository\RepositoryManager */ private $repositoryManager; /** * @var Installer\InstallationManager */ private $installationManager; /** * @var Config */ private $config; /** * @var EventDispatcher */ private $eventDispatcher; public function setPackage(RootPackageInterface $package) : void { $this->package = $package; } public function getPackage() : RootPackageInterface { return $this->package; } public function setConfig(\Composer\Config $config) : void { $this->config = $config; } public function getConfig() : \Composer\Config { return $this->config; } public function setLoop(Loop $loop) : void { $this->loop = $loop; } public function getLoop() : Loop { return $this->loop; } public function setRepositoryManager(RepositoryManager $manager) : void { $this->repositoryManager = $manager; } public function getRepositoryManager() : RepositoryManager { return $this->repositoryManager; } public function setInstallationManager(InstallationManager $manager) : void { $this->installationManager = $manager; } public function getInstallationManager() : InstallationManager { return $this->installationManager; } public function setEventDispatcher(EventDispatcher $eventDispatcher) : void { $this->eventDispatcher = $eventDispatcher; } public function getEventDispatcher() : EventDispatcher { return $this->eventDispatcher; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Platform; class Runtime { /** * @param class-string $class */ public function hasConstant(string $constant, ?string $class = null) : bool { return \defined(\ltrim($class . '::' . $constant, ':')); } /** * @param class-string $class * * @return mixed */ public function getConstant(string $constant, ?string $class = null) { return \constant(\ltrim($class . '::' . $constant, ':')); } public function hasFunction(string $fn) : bool { return \function_exists($fn); } /** * @param mixed[] $arguments * * @return mixed */ public function invoke(callable $callable, array $arguments = []) { return $callable(...$arguments); } /** * @param class-string $class */ public function hasClass(string $class) : bool { return \class_exists($class, \false); } /** * @param class-string $class * @param mixed[] $arguments * * @throws \ReflectionException */ public function construct(string $class, array $arguments = []) : object { if (empty($arguments)) { return new $class(); } $refl = new \ReflectionClass($class); return $refl->newInstanceArgs($arguments); } /** @return string[] */ public function getExtensions() : array { return \get_loaded_extensions(); } public function getExtensionVersion(string $extension) : string { $version = \phpversion($extension); if ($version === \false) { $version = '0'; } return $version; } /** * @throws \ReflectionException */ public function getExtensionInfo(string $extension) : string { $reflector = new \ReflectionExtension($extension); \ob_start(); $reflector->info(); return \ob_get_clean(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Platform; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use _ContaoManager\Symfony\Component\Process\ExecutableFinder; class HhvmDetector { /** @var string|false|null */ private static $hhvmVersion = null; /** @var ?ExecutableFinder */ private $executableFinder; /** @var ?ProcessExecutor */ private $processExecutor; public function __construct(?ExecutableFinder $executableFinder = null, ?ProcessExecutor $processExecutor = null) { $this->executableFinder = $executableFinder; $this->processExecutor = $processExecutor; } public function reset() : void { self::$hhvmVersion = null; } public function getVersion() : ?string { if (null !== self::$hhvmVersion) { return self::$hhvmVersion ?: null; } self::$hhvmVersion = \defined('_ContaoManager\\HHVM_VERSION') ? HHVM_VERSION : null; if (self::$hhvmVersion === null && !Platform::isWindows()) { self::$hhvmVersion = \false; $this->executableFinder = $this->executableFinder ?: new ExecutableFinder(); $hhvmPath = $this->executableFinder->find('hhvm'); if ($hhvmPath !== null) { $this->processExecutor = $this->processExecutor ?? new ProcessExecutor(); $exitCode = $this->processExecutor->execute(ProcessExecutor::escape($hhvmPath) . ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', self::$hhvmVersion); if ($exitCode !== 0) { self::$hhvmVersion = \false; } } } return self::$hhvmVersion ?: null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Platform; use Composer\Pcre\Preg; /** * @author Lars Strojny */ class Version { /** * @param bool $isFips Set by the method */ public static function parseOpenssl(string $opensslVersion, ?bool &$isFips) : ?string { $isFips = \false; if (!Preg::isMatchStrictGroups('/^(?[0-9.]+)(?[a-z]{0,2})(?(?:-?(?:dev|pre|alpha|beta|rc|fips)[\\d]*)*)(?:-\\w+)?(?: \\(.+?\\))?$/', $opensslVersion, $matches)) { return null; } // OpenSSL 1 used 1.2.3a style versioning, 3+ uses semver $patch = ''; if (\version_compare($matches['version'], '3.0.0', '<')) { $patch = '.' . self::convertAlphaVersionToIntVersion($matches['patch']); } $isFips = \strpos($matches['suffix'], 'fips') !== \false; $suffix = \strtr('-' . \ltrim($matches['suffix'], '-'), ['-fips' => '', '-pre' => '-alpha']); return \rtrim($matches['version'] . $patch . $suffix, '-'); } public static function parseLibjpeg(string $libjpegVersion) : ?string { if (!Preg::isMatchStrictGroups('/^(?\\d+)(?[a-z]*)$/', $libjpegVersion, $matches)) { return null; } return $matches['major'] . '.' . self::convertAlphaVersionToIntVersion($matches['minor']); } public static function parseZoneinfoVersion(string $zoneinfoVersion) : ?string { if (!Preg::isMatchStrictGroups('/^(?\\d{4})(?[a-z]*)$/', $zoneinfoVersion, $matches)) { return null; } return $matches['year'] . '.' . self::convertAlphaVersionToIntVersion($matches['revision']); } /** * "" => 0, "a" => 1, "zg" => 33 */ private static function convertAlphaVersionToIntVersion(string $alpha) : int { return \strlen($alpha) * (-\ord('a') + 1) + \array_sum(\array_map('ord', \str_split($alpha))); } public static function convertLibxpmVersionId(int $versionId) : string { return self::convertVersionId($versionId, 100); } public static function convertOpenldapVersionId(int $versionId) : string { return self::convertVersionId($versionId, 100); } private static function convertVersionId(int $versionId, int $base) : string { return \sprintf('%d.%d.%d', $versionId / ($base * $base), (int) ($versionId / $base) % $base, $versionId % $base); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Console\Helper\QuestionHelper; use _ContaoManager\Symfony\Component\Console\Output\StreamOutput; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; use _ContaoManager\Symfony\Component\Console\Input\StreamableInputInterface; use _ContaoManager\Symfony\Component\Console\Input\StringInput; use _ContaoManager\Symfony\Component\Console\Helper\HelperSet; /** * @author Jordi Boggiano */ class BufferIO extends \Composer\IO\ConsoleIO { /** @var StringInput */ protected $input; /** @var StreamOutput */ protected $output; public function __construct(string $input = '', int $verbosity = StreamOutput::VERBOSITY_NORMAL, ?OutputFormatterInterface $formatter = null) { $input = new StringInput($input); $input->setInteractive(\false); $output = new StreamOutput(\fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : \false, $formatter); parent::__construct($input, $output, new HelperSet([new QuestionHelper()])); } /** * @return string output */ public function getOutput() : string { \fseek($this->output->getStream(), 0); $output = \stream_get_contents($this->output->getStream()); $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches) : string { \assert(\is_string($matches[1])); \assert(\is_string($matches[2])); $pre = \strip_tags($matches[1]); if (\strlen($pre) === \strlen($matches[2])) { return ''; } // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs return \rtrim($matches[1]) . "\n"; }, $output); return $output; } /** * @param string[] $inputs * * @see createStream */ public function setUserInputs(array $inputs) : void { if (!$this->input instanceof StreamableInputInterface) { throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); } $this->input->setStream($this->createStream($inputs)); $this->input->setInteractive(\true); } /** * @param string[] $inputs * * @return false|resource stream */ private function createStream(array $inputs) { $stream = \fopen('php://memory', 'r+'); foreach ($inputs as $input) { \fwrite($stream, $input . \PHP_EOL); } \rewind($stream); return $stream; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Composer\Question\StrictConfirmationQuestion; use _ContaoManager\Symfony\Component\Console\Helper\HelperSet; use _ContaoManager\Symfony\Component\Console\Helper\ProgressBar; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Question\ChoiceQuestion; use _ContaoManager\Symfony\Component\Console\Question\Question; /** * The Input/Output helper. * * @author François Pluchino * @author Jordi Boggiano */ class ConsoleIO extends \Composer\IO\BaseIO { /** @var InputInterface */ protected $input; /** @var OutputInterface */ protected $output; /** @var HelperSet */ protected $helperSet; /** @var string */ protected $lastMessage = ''; /** @var string */ protected $lastMessageErr = ''; /** @var float */ private $startTime; /** @var array */ private $verbosityMap; /** * Constructor. * * @param InputInterface $input The input instance * @param OutputInterface $output The output instance * @param HelperSet $helperSet The helperSet instance */ public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) { $this->input = $input; $this->output = $output; $this->helperSet = $helperSet; $this->verbosityMap = [self::QUIET => OutputInterface::VERBOSITY_QUIET, self::NORMAL => OutputInterface::VERBOSITY_NORMAL, self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE, self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE, self::DEBUG => OutputInterface::VERBOSITY_DEBUG]; } /** * @return void */ public function enableDebugging(float $startTime) { $this->startTime = $startTime; } /** * @inheritDoc */ public function isInteractive() { return $this->input->isInteractive(); } /** * @inheritDoc */ public function isDecorated() { return $this->output->isDecorated(); } /** * @inheritDoc */ public function isVerbose() { return $this->output->isVerbose(); } /** * @inheritDoc */ public function isVeryVerbose() { return $this->output->isVeryVerbose(); } /** * @inheritDoc */ public function isDebug() { return $this->output->isDebug(); } /** * @inheritDoc */ public function write($messages, bool $newline = \true, int $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, \false, $verbosity); } /** * @inheritDoc */ public function writeError($messages, bool $newline = \true, int $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, \true, $verbosity); } /** * @inheritDoc */ public function writeRaw($messages, bool $newline = \true, int $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, \false, $verbosity, \true); } /** * @inheritDoc */ public function writeErrorRaw($messages, bool $newline = \true, int $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, \true, $verbosity, \true); } /** * @param string[]|string $messages */ private function doWrite($messages, bool $newline, bool $stderr, int $verbosity, bool $raw = \false) : void { $sfVerbosity = $this->verbosityMap[$verbosity]; if ($sfVerbosity > $this->output->getVerbosity()) { return; } if ($raw) { $sfVerbosity |= OutputInterface::OUTPUT_RAW; } if (null !== $this->startTime) { $memoryUsage = \memory_get_usage() / 1024 / 1024; $timeSpent = \microtime(\true) - $this->startTime; $messages = \array_map(static function ($message) use($memoryUsage, $timeSpent) : string { return \sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } if (\true === $stderr && $this->output instanceof ConsoleOutputInterface) { $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity); $this->lastMessageErr = \implode($newline ? "\n" : '', (array) $messages); return; } $this->output->write($messages, $newline, $sfVerbosity); $this->lastMessage = \implode($newline ? "\n" : '', (array) $messages); } /** * @inheritDoc */ public function overwrite($messages, bool $newline = \true, ?int $size = null, int $verbosity = self::NORMAL) { $this->doOverwrite($messages, $newline, $size, \false, $verbosity); } /** * @inheritDoc */ public function overwriteError($messages, bool $newline = \true, ?int $size = null, int $verbosity = self::NORMAL) { $this->doOverwrite($messages, $newline, $size, \true, $verbosity); } /** * @param string[]|string $messages */ private function doOverwrite($messages, bool $newline, ?int $size, bool $stderr, int $verbosity) : void { // messages can be an array, let's convert it to string anyway $messages = \implode($newline ? "\n" : '', (array) $messages); // since overwrite is supposed to overwrite last message... if (!isset($size)) { // removing possible formatting of lastMessage with strip_tags $size = \strlen(\strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); } // ...let's fill its length with backspaces $this->doWrite(\str_repeat("\x08", $size), \false, $stderr, $verbosity); // write the new message $this->doWrite($messages, \false, $stderr, $verbosity); // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to // track the length of previous output and fill it with spaces to make sure the line is cleared. // See https://github.com/composer/composer/pull/5836 for more details $fill = $size - \strlen(\strip_tags($messages)); if ($fill > 0) { // whitespace whatever has left $this->doWrite(\str_repeat(' ', $fill), \false, $stderr, $verbosity); // move the cursor back $this->doWrite(\str_repeat("\x08", $fill), \false, $stderr, $verbosity); } if ($newline) { $this->doWrite('', \true, $stderr, $verbosity); } if ($stderr) { $this->lastMessageErr = $messages; } else { $this->lastMessage = $messages; } } /** * @return ProgressBar */ public function getProgressBar(int $max = 0) { return new ProgressBar($this->getErrorOutput(), $max); } /** * @inheritDoc */ public function ask($question, $default = null) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new Question($question, $default); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * @inheritDoc */ public function askConfirmation($question, $default = \true) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new StrictConfirmationQuestion($question, $default); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * @inheritDoc */ public function askAndValidate($question, $validator, $attempts = null, $default = null) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new Question($question, $default); $question->setValidator($validator); $question->setMaxAttempts($attempts); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * @inheritDoc */ public function askAndHideAnswer($question) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new Question($question); $question->setHidden(\true); return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** * @inheritDoc */ public function select($question, $choices, $default, $attempts = \false, $errorMessage = 'Value "%s" is invalid', $multiselect = \false) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ $helper = $this->helperSet->get('question'); $question = new ChoiceQuestion($question, $choices, $default); $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int $question->setErrorMessage($errorMessage); $question->setMultiselect($multiselect); $result = $helper->ask($this->input, $this->getErrorOutput(), $question); $isAssoc = (bool) \count(\array_filter(\array_keys($choices), 'is_string')); if ($isAssoc) { return $result; } if (!\is_array($result)) { return (string) \array_search($result, $choices, \true); } $results = []; foreach ($choices as $index => $choice) { if (\in_array($choice, $result, \true)) { $results[] = (string) $index; } } return $results; } public function getTable() : Table { return new Table($this->output); } private function getErrorOutput() : OutputInterface { if ($this->output instanceof ConsoleOutputInterface) { return $this->output->getErrorOutput(); } return $this->output; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Composer\Config; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; use _ContaoManager\Psr\Log\LogLevel; abstract class BaseIO implements \Composer\IO\IOInterface { /** @var array */ protected $authentications = []; /** * @inheritDoc */ public function getAuthentications() { return $this->authentications; } /** * @return void */ public function resetAuthentications() { $this->authentications = []; } /** * @inheritDoc */ public function hasAuthentication($repositoryName) { return isset($this->authentications[$repositoryName]); } /** * @inheritDoc */ public function getAuthentication($repositoryName) { if (isset($this->authentications[$repositoryName])) { return $this->authentications[$repositoryName]; } return ['username' => null, 'password' => null]; } /** * @inheritDoc */ public function setAuthentication($repositoryName, $username, $password = null) { $this->authentications[$repositoryName] = ['username' => $username, 'password' => $password]; } /** * @inheritDoc */ public function writeRaw($messages, bool $newline = \true, int $verbosity = self::NORMAL) { $this->write($messages, $newline, $verbosity); } /** * @inheritDoc */ public function writeErrorRaw($messages, bool $newline = \true, int $verbosity = self::NORMAL) { $this->writeError($messages, $newline, $verbosity); } /** * Check for overwrite and set the authentication information for the repository. * * @param string $repositoryName The unique name of repository * @param string $username The username * @param string $password The password * * @return void */ protected function checkAndSetAuthentication(string $repositoryName, string $username, ?string $password = null) { if ($this->hasAuthentication($repositoryName)) { $auth = $this->getAuthentication($repositoryName); if ($auth['username'] === $username && $auth['password'] === $password) { return; } $this->writeError(\sprintf("Warning: You should avoid overwriting already defined auth settings for %s.", $repositoryName)); } $this->setAuthentication($repositoryName, $username, $password); } /** * @inheritDoc */ public function loadConfiguration(Config $config) { $bitbucketOauth = $config->get('bitbucket-oauth'); $githubOauth = $config->get('github-oauth'); $gitlabOauth = $config->get('gitlab-oauth'); $gitlabToken = $config->get('gitlab-token'); $httpBasic = $config->get('http-basic'); $bearerToken = $config->get('bearer'); // reload oauth tokens from config if available foreach ($bitbucketOauth as $domain => $cred) { $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']); } foreach ($githubOauth as $domain => $token) { if ($domain !== 'github.com' && !\in_array($domain, $config->get('github-domains'), \true)) { $this->debug($domain . ' is not in the configured github-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['github-domains' => \array_merge($config->get('github-domains'), [$domain])]], 'implicit-due-to-auth'); } // allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/ // plus dots which were at some point used for GH app integration tokens if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) { throw new \UnexpectedValueException('Your github oauth token for ' . $domain . ' contains invalid characters: "' . $token . '"'); } $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); } foreach ($gitlabOauth as $domain => $token) { if ($domain !== 'gitlab.com' && !\in_array($domain, $config->get('gitlab-domains'), \true)) { $this->debug($domain . ' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['gitlab-domains' => \array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); } $token = \is_array($token) ? $token["token"] : $token; $this->checkAndSetAuthentication($domain, $token, 'oauth2'); } foreach ($gitlabToken as $domain => $token) { if ($domain !== 'gitlab.com' && !\in_array($domain, $config->get('gitlab-domains'), \true)) { $this->debug($domain . ' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['gitlab-domains' => \array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); } $username = \is_array($token) ? $token["username"] : $token; $password = \is_array($token) ? $token["token"] : 'private-token'; $this->checkAndSetAuthentication($domain, $username, $password); } // reload http basic credentials from config if available foreach ($httpBasic as $domain => $cred) { $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']); } foreach ($bearerToken as $domain => $token) { $this->checkAndSetAuthentication($domain, $token, 'bearer'); } // setup process timeout ProcessExecutor::setTimeout($config->get('process-timeout')); } public function emergency($message, array $context = []) : void { $this->log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = []) : void { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = []) : void { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = []) : void { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = []) : void { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = []) : void { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = []) : void { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = []) : void { $this->log(LogLevel::DEBUG, $message, $context); } public function log($level, $message, array $context = []) : void { $message = (string) $message; if (\in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) { $this->writeError('' . $message . ''); } elseif ($level === LogLevel::WARNING) { $this->writeError('' . $message . ''); } elseif ($level === LogLevel::NOTICE) { $this->writeError('' . $message . '', \true, self::VERBOSE); } elseif ($level === LogLevel::INFO) { $this->writeError('' . $message . '', \true, self::VERY_VERBOSE); } else { $this->writeError($message, \true, self::DEBUG); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; /** * IOInterface that is not interactive and never writes the output * * @author Christophe Coevoet */ class NullIO extends \Composer\IO\BaseIO { /** * @inheritDoc */ public function isInteractive() : bool { return \false; } /** * @inheritDoc */ public function isVerbose() : bool { return \false; } /** * @inheritDoc */ public function isVeryVerbose() : bool { return \false; } /** * @inheritDoc */ public function isDebug() : bool { return \false; } /** * @inheritDoc */ public function isDecorated() : bool { return \false; } /** * @inheritDoc */ public function write($messages, bool $newline = \true, int $verbosity = self::NORMAL) : void { } /** * @inheritDoc */ public function writeError($messages, bool $newline = \true, int $verbosity = self::NORMAL) : void { } /** * @inheritDoc */ public function overwrite($messages, bool $newline = \true, ?int $size = null, int $verbosity = self::NORMAL) : void { } /** * @inheritDoc */ public function overwriteError($messages, bool $newline = \true, ?int $size = null, int $verbosity = self::NORMAL) : void { } /** * @inheritDoc */ public function ask($question, $default = null) { return $default; } /** * @inheritDoc */ public function askConfirmation($question, $default = \true) : bool { return $default; } /** * @inheritDoc */ public function askAndValidate($question, $validator, $attempts = null, $default = null) { return $default; } /** * @inheritDoc */ public function askAndHideAnswer($question) : ?string { return null; } /** * @inheritDoc */ public function select($question, $choices, $default, $attempts = \false, $errorMessage = 'Value "%s" is invalid', $multiselect = \false) { return $default; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\IO; use Composer\Config; use _ContaoManager\Psr\Log\LoggerInterface; /** * The Input/Output helper interface. * * @author François Pluchino */ interface IOInterface extends LoggerInterface { public const QUIET = 1; public const NORMAL = 2; public const VERBOSE = 4; public const VERY_VERBOSE = 8; public const DEBUG = 16; /** * Is this input means interactive? * * @return bool */ public function isInteractive(); /** * Is this output verbose? * * @return bool */ public function isVerbose(); /** * Is the output very verbose? * * @return bool */ public function isVeryVerbose(); /** * Is the output in debug verbosity? * * @return bool */ public function isDebug(); /** * Is this output decorated? * * @return bool */ public function isDecorated(); /** * Writes a message to the output. * * @param string|string[] $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $verbosity Verbosity level from the VERBOSITY_* constants * * @return void */ public function write($messages, bool $newline = \true, int $verbosity = self::NORMAL); /** * Writes a message to the error output. * * @param string|string[] $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $verbosity Verbosity level from the VERBOSITY_* constants * * @return void */ public function writeError($messages, bool $newline = \true, int $verbosity = self::NORMAL); /** * Writes a message to the output, without formatting it. * * @param string|string[] $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $verbosity Verbosity level from the VERBOSITY_* constants * * @return void */ public function writeRaw($messages, bool $newline = \true, int $verbosity = self::NORMAL); /** * Writes a message to the error output, without formatting it. * * @param string|string[] $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $verbosity Verbosity level from the VERBOSITY_* constants * * @return void */ public function writeErrorRaw($messages, bool $newline = \true, int $verbosity = self::NORMAL); /** * Overwrites a previous message to the output. * * @param string|string[] $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $size The size of line * @param int $verbosity Verbosity level from the VERBOSITY_* constants * * @return void */ public function overwrite($messages, bool $newline = \true, ?int $size = null, int $verbosity = self::NORMAL); /** * Overwrites a previous message to the error output. * * @param string|string[] $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline or not * @param int $size The size of line * @param int $verbosity Verbosity level from the VERBOSITY_* constants * * @return void */ public function overwriteError($messages, bool $newline = \true, ?int $size = null, int $verbosity = self::NORMAL); /** * Asks a question to the user. * * @param string $question The question to ask * @param string|bool|int|float|null $default The default answer if none is given by the user * * @throws \RuntimeException If there is no data to read in the input stream * @return mixed The user answer */ public function ask(string $question, $default = null); /** * Asks a confirmation to the user. * * The question will be asked until the user answers by nothing, yes, or no. * * @param string $question The question to ask * @param bool $default The default answer if the user enters nothing * * @return bool true if the user has confirmed, false otherwise */ public function askConfirmation(string $question, bool $default = \true); /** * Asks for a value and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param string $question The question to ask * @param callable $validator A PHP callback * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) * @param mixed $default The default answer if none is given by the user * * @throws \Exception When any of the validators return an error * @return mixed */ public function askAndValidate(string $question, callable $validator, ?int $attempts = null, $default = null); /** * Asks a question to the user and hide the answer. * * @param string $question The question to ask * * @return string|null The answer */ public function askAndHideAnswer(string $question); /** * Asks the user to select a value. * * @param string $question The question to ask * @param string[] $choices List of choices to pick from * @param bool|string $default The default answer if the user enters nothing * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked * @param bool $multiselect Select more than one value separated by comma * * @throws \InvalidArgumentException * * @return int|string|list|bool The selected value or values (the key of the choices array) * @phpstan-return ($multiselect is true ? list : string|int|bool) */ public function select(string $question, array $choices, $default, $attempts = \false, string $errorMessage = 'Value "%s" is invalid', bool $multiselect = \false); /** * Get all authentication information entered. * * @return array The map of authentication data */ public function getAuthentications(); /** * Verify if the repository has a authentication information. * * @param string $repositoryName The unique name of repository * * @return bool */ public function hasAuthentication(string $repositoryName); /** * Get the username and password of repository. * * @param string $repositoryName The unique name of repository * * @return array{username: string|null, password: string|null} */ public function getAuthentication(string $repositoryName); /** * Set the authentication information for the repository. * * @param string $repositoryName The unique name of repository * @param string $username The username * @param null|string $password The password * * @return void */ public function setAuthentication(string $repositoryName, string $username, ?string $password = null); /** * Loads authentications from a config instance * * @return void */ public function loadConfiguration(Config $config); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; /** * The Plugin Events. * * @author Nils Adermann */ class PluginEvents { /** * The INIT event occurs after a Composer instance is done being initialized * * The event listener method receives a * Composer\EventDispatcher\Event instance. * * @var string */ public const INIT = 'init'; /** * The COMMAND event occurs as a command begins * * The event listener method receives a * Composer\Plugin\CommandEvent instance. * * @var string */ public const COMMAND = 'command'; /** * The PRE_FILE_DOWNLOAD event occurs before downloading a file * * The event listener method receives a * Composer\Plugin\PreFileDownloadEvent instance. * * @var string */ public const PRE_FILE_DOWNLOAD = 'pre-file-download'; /** * The POST_FILE_DOWNLOAD event occurs after downloading a package dist file * * The event listener method receives a * Composer\Plugin\PostFileDownloadEvent instance. * * @var string */ public const POST_FILE_DOWNLOAD = 'post-file-download'; /** * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options * * The event listener method receives a * Composer\Plugin\PreCommandRunEvent instance. * * @var string */ public const PRE_COMMAND_RUN = 'pre-command-run'; /** * The PRE_POOL_CREATE event occurs before the Pool of packages is created, and lets * you filter the list of packages which is going to enter the Solver * * The event listener method receives a * Composer\Plugin\PrePoolCreateEvent instance. * * @var string */ public const PRE_POOL_CREATE = 'pre-pool-create'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; /** * The pre command run event. * * @author Jordi Boggiano */ class PreCommandRunEvent extends Event { /** * @var InputInterface */ private $input; /** * @var string */ private $command; /** * Constructor. * * @param string $name The event name * @param string $command The command about to be executed */ public function __construct(string $name, InputInterface $input, string $command) { parent::__construct($name); $this->input = $input; $this->command = $command; } /** * Returns the console input */ public function getInput() : InputInterface { return $this->input; } /** * Returns the command about to be executed */ public function getCommand() : string { return $this->command; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin\Capability; /** * Commands Provider Interface * * This capability will receive an array with 'composer' and 'io' keys as * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface * instances. It also contains a 'plugin' key containing the plugin instance that * created the capability. * * @author Jérémy Derussé */ interface CommandProvider extends \Composer\Plugin\Capability\Capability { /** * Retrieves an array of commands * * @return \Composer\Command\BaseCommand[] */ public function getCommands(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin\Capability; /** * Marker interface for Plugin capabilities. * Every new Capability which is added to the Plugin API must implement this interface. * * @api */ interface Capability { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use UnexpectedValueException; class PluginBlockedException extends UnexpectedValueException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * An event for all commands. * * @author Nils Adermann */ class CommandEvent extends Event { /** * @var string */ private $commandName; /** * @var InputInterface */ private $input; /** * @var OutputInterface */ private $output; /** * Constructor. * * @param string $name The event name * @param string $commandName The command name * @param mixed[] $args Arguments passed by the user * @param mixed[] $flags Optional flags to pass data not as argument */ public function __construct(string $name, string $commandName, InputInterface $input, OutputInterface $output, array $args = [], array $flags = []) { parent::__construct($name, $args, $flags); $this->commandName = $commandName; $this->input = $input; $this->output = $output; } /** * Returns the command input interface */ public function getInput() : InputInterface { return $this->input; } /** * Retrieves the command output interface */ public function getOutput() : OutputInterface { return $this->output; } /** * Retrieves the name of the command being run */ public function getCommandName() : string { return $this->commandName; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\Composer; use Composer\IO\IOInterface; /** * Plugin interface * * @author Nils Adermann */ interface PluginInterface { /** * Version number of the internal composer-plugin-api package * * This is used to denote the API version of Plugin specific * features, but is also bumped to a new major if Composer * includes a major break in internal APIs which are susceptible * to be used by plugins. * * @var string */ public const PLUGIN_API_VERSION = '2.6.0'; /** * Apply plugin modifications to Composer * * @return void */ public function activate(Composer $composer, IOInterface $io); /** * Remove any hooks from Composer * * This will be called when a plugin is deactivated before being * uninstalled, but also before it gets upgraded to a new version * so the old one can be deactivated and the new one activated. * * @return void */ public function deactivate(Composer $composer, IOInterface $io); /** * Prepare the plugin to be uninstalled * * This will be called after deactivate. * * @return void */ public function uninstall(Composer $composer, IOInterface $io); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer\InstallerInterface; use Composer\IO\IOInterface; use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\Locker; use Composer\Package\Package; use Composer\Package\Version\VersionParser; use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepository; use Composer\Repository\RootPackageRepository; use Composer\Package\PackageInterface; use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; use Composer\Plugin\Capability\Capability; use Composer\Util\PackageSorter; /** * Plugin manager * * @author Nils Adermann * @author Jordi Boggiano */ class PluginManager { /** @var Composer */ protected $composer; /** @var IOInterface */ protected $io; /** @var PartialComposer|null */ protected $globalComposer; /** @var VersionParser */ protected $versionParser; /** @var bool|'local'|'global' */ protected $disablePlugins = \false; /** @var array */ protected $plugins = []; /** @var array */ protected $registeredPlugins = []; /** * @var array|null */ private $allowPluginRules; /** * @var array|null */ private $allowGlobalPluginRules; /** @var bool */ private $runningInGlobalDir = \false; /** @var int */ private static $classCounter = 0; /** * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins */ public function __construct(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = \false) { $this->io = $io; $this->composer = $composer; $this->globalComposer = $globalComposer; $this->versionParser = new VersionParser(); $this->disablePlugins = $disablePlugins; $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker()); $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : \false); } public function setRunningInGlobalDir(bool $runningInGlobalDir) : void { $this->runningInGlobalDir = $runningInGlobalDir; } /** * Loads all plugins from currently installed plugin packages */ public function loadInstalledPlugins() : void { if (!$this->arePluginsDisabled('local')) { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); $this->loadRepository($repo, \false); } if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), \true); } } /** * Deactivate all plugins from currently installed plugin packages */ public function deactivateInstalledPlugins() : void { if (!$this->arePluginsDisabled('local')) { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); $this->deactivateRepository($repo, \false); } if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), \true); } } /** * Gets all currently active plugin instances * * @return array plugins */ public function getPlugins() : array { return $this->plugins; } /** * Gets global composer or null when main composer is not fully loaded */ public function getGlobalComposer() : ?PartialComposer { return $this->globalComposer; } /** * Register a plugin package, activate it etc. * * If it's of type composer-installer it is registered as an installer * instead for BC * * @param bool $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception * @param bool $isGlobalPlugin Set to true to denote plugins which are installed in the global Composer directory * * @throws \UnexpectedValueException */ public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = \false, bool $isGlobalPlugin = \false) : void { if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { $this->io->writeError('The "' . $package->getName() . '" plugin was not loaded as plugins are disabled.'); return; } if ($package->getType() === 'composer-plugin') { $requiresComposer = null; foreach ($package->getRequires() as $link) { /** @var Link $link */ if ('composer-plugin-api' === $link->getTarget()) { $requiresComposer = $link->getConstraint(); break; } } if (!$requiresComposer) { throw new \RuntimeException("Plugin " . $package->getName() . " is missing a require statement for a version of the composer-plugin-api package."); } $currentPluginApiVersion = $this->getPluginApiVersion(); $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) { $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api ' . $this->getPluginApiVersion() . ', this *WILL* break in the future and it should be fixed ASAP (require ^' . $this->getPluginApiVersion() . ' instead for example).'); } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) { $this->io->writeError('The "' . $package->getName() . '" plugin ' . ($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '') . 'was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); return; } if ($package->getName() === 'symfony/flex' && Preg::isMatch('{^[0-9.]+$}', $package->getVersion()) && \version_compare($package->getVersion(), '1.9.8', '<')) { $this->io->writeError('The "' . $package->getName() . '" plugin ' . ($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '') . 'was skipped because it is not compatible with Composer 2+. Make sure to update it to version 1.9.8 or greater.'); return; } } if (!$this->isPluginAllowed($package->getName(), $isGlobalPlugin, \true === ($package->getExtra()['plugin-optional'] ?? \false))) { $this->io->writeError('Skipped loading "' . $package->getName() . '" ' . ($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '') . 'as it is not in config.allow-plugins', \true, IOInterface::DEBUG); return; } $oldInstallerPlugin = $package->getType() === 'composer-installer'; if (isset($this->registeredPlugins[$package->getName()])) { return; } $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing ' . $package->getPrettyName() . ', composer-plugin packages should have a class defined in their extra key to be usable.'); } $classes = \is_array($extra['class']) ? $extra['class'] : [$extra['class']]; $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; $rootPackage = clone $this->composer->getPackage(); // clear files autoload rules from the root package as the root dependencies are not // necessarily all present yet when booting this runtime autoloader $rootPackageAutoloads = $rootPackage->getAutoload(); $rootPackageAutoloads['files'] = []; $rootPackage->setAutoload($rootPackageAutoloads); $rootPackageAutoloads = $rootPackage->getDevAutoload(); $rootPackageAutoloads['files'] = []; $rootPackage->setDevAutoload($rootPackageAutoloads); unset($rootPackageAutoloads); $rootPackageRepo = new RootPackageRepository($rootPackage); $installedRepo = new InstalledRepository([$localRepo, $rootPackageRepo]); if ($globalRepo) { $installedRepo->addRepository($globalRepo); } $autoloadPackages = [$package->getName() => $package]; $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package); $generator = $this->composer->getAutoloadGenerator(); $autoloads = [[$rootPackage, '']]; foreach ($autoloadPackages as $autoloadPackage) { if ($autoloadPackage === $rootPackage) { continue; } $installPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage)); if ($installPath === null) { continue; } $autoloads[] = [$autoloadPackage, $installPath]; } $map = $generator->parseAutoloads($autoloads, $rootPackage); $classLoader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); $classLoader->register(\false); foreach ($map['files'] as $fileIdentifier => $file) { // exclude laminas/laminas-zendframework-bridge:src/autoload.php as it breaks Composer in some conditions // see https://github.com/composer/composer/issues/10349 and https://github.com/composer/composer/issues/10401 // this hack can be removed once this deprecated package stop being installed if ($fileIdentifier === '7e9bd612cc444b3eed788ebbe46263a0') { continue; } \Composer\Autoload\composerRequire($fileIdentifier, $file); } foreach ($classes as $class) { if (\class_exists($class, \false)) { $class = \trim($class, '\\'); $path = $classLoader->findFile($class); $code = \file_get_contents($path); $separatorPos = \strrpos($class, '\\'); $className = $class; if ($separatorPos) { $className = \substr($class, $separatorPos + 1); } $code = Preg::replace('{^((?:(?:final|readonly)\\s+)*(?:\\s*))class\\s+(' . \preg_quote($className) . ')}mi', '$1class $2_composer_tmp' . self::$classCounter, $code, 1); $code = \strtr($code, ['__FILE__' => \var_export($path, \true), '__DIR__' => \var_export(\dirname($path), \true), '__CLASS__' => \var_export($class, \true)]); $code = Preg::replace('/^\\s*<\\?(php)?/i', '', $code, 1); eval($code); $class .= '_composer_tmp' . self::$classCounter; self::$classCounter++; } if ($oldInstallerPlugin) { if (!\is_a($class, 'Composer\\Installer\\InstallerInterface', \true)) { throw new \RuntimeException('Could not activate plugin "' . $package->getName() . '" as "' . $class . '" does not implement Composer\\Installer\\InstallerInterface'); } $this->io->writeError('Loading "' . $package->getName() . '" ' . ($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '') . 'which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.'); $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); $this->registeredPlugins[$package->getName()] = $installer; } elseif (\class_exists($class)) { if (!\is_a($class, 'Composer\\Plugin\\PluginInterface', \true)) { throw new \RuntimeException('Could not activate plugin "' . $package->getName() . '" as "' . $class . '" does not implement Composer\\Plugin\\PluginInterface'); } $plugin = new $class(); $this->addPlugin($plugin, $isGlobalPlugin, $package); $this->registeredPlugins[$package->getName()] = $plugin; } elseif ($failOnMissingClasses) { throw new \UnexpectedValueException('Plugin ' . $package->getName() . ' could not be initialized, class not found: ' . $class); } } } /** * Deactivates a plugin package * * If it's of type composer-installer it is unregistered from the installers * instead for BC * * @throws \UnexpectedValueException */ public function deactivatePackage(PackageInterface $package) : void { if (!isset($this->registeredPlugins[$package->getName()])) { return; } $plugin = $this->registeredPlugins[$package->getName()]; unset($this->registeredPlugins[$package->getName()]); if ($plugin instanceof InstallerInterface) { $this->composer->getInstallationManager()->removeInstaller($plugin); } else { $this->removePlugin($plugin); } } /** * Uninstall a plugin package * * If it's of type composer-installer it is unregistered from the installers * instead for BC * * @throws \UnexpectedValueException */ public function uninstallPackage(PackageInterface $package) : void { if (!isset($this->registeredPlugins[$package->getName()])) { return; } $plugin = $this->registeredPlugins[$package->getName()]; if ($plugin instanceof InstallerInterface) { $this->deactivatePackage($package); } else { unset($this->registeredPlugins[$package->getName()]); $this->removePlugin($plugin); $this->uninstallPlugin($plugin); } } /** * Returns the version of the internal composer-plugin-api package. */ protected function getPluginApiVersion() : string { return \Composer\Plugin\PluginInterface::PLUGIN_API_VERSION; } /** * Adds a plugin, activates it and registers it with the event dispatcher * * Ideally plugin packages should be registered via registerPackage, but if you use Composer * programmatically and want to register a plugin class directly this is a valid way * to do it. * * @param PluginInterface $plugin plugin instance * @param ?PackageInterface $sourcePackage Package from which the plugin comes from */ public function addPlugin(\Composer\Plugin\PluginInterface $plugin, bool $isGlobalPlugin = \false, ?PackageInterface $sourcePackage = null) : void { if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { return; } if ($sourcePackage === null) { \trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', \E_USER_DEPRECATED); } elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin, \true === ($sourcePackage->getExtra()['plugin-optional'] ?? \false))) { $this->io->writeError('Skipped loading "' . \get_class($plugin) . ' from ' . $sourcePackage->getName() . '" ' . ($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '') . ' as it is not in config.allow-plugins', \true, IOInterface::DEBUG); return; } $details = []; if ($sourcePackage) { $details[] = 'from ' . $sourcePackage->getName(); } if ($isGlobalPlugin || $this->runningInGlobalDir) { $details[] = 'installed globally'; } $this->io->writeError('Loading plugin ' . \get_class($plugin) . ($details ? ' (' . \implode(', ', $details) . ')' : ''), \true, IOInterface::DEBUG); $this->plugins[] = $plugin; $plugin->activate($this->composer, $this->io); if ($plugin instanceof EventSubscriberInterface) { $this->composer->getEventDispatcher()->addSubscriber($plugin); } } /** * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance * * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer * programmatically and want to deregister a plugin class directly this is a valid way * to do it. * * @param PluginInterface $plugin plugin instance */ public function removePlugin(\Composer\Plugin\PluginInterface $plugin) : void { $index = \array_search($plugin, $this->plugins, \true); if ($index === \false) { return; } $this->io->writeError('Unloading plugin ' . \get_class($plugin), \true, IOInterface::DEBUG); unset($this->plugins[$index]); $plugin->deactivate($this->composer, $this->io); $this->composer->getEventDispatcher()->removeListener($plugin); } /** * Notifies a plugin it is being uninstalled and should clean up * * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer * programmatically and want to deregister a plugin class directly this is a valid way * to do it. * * @param PluginInterface $plugin plugin instance */ public function uninstallPlugin(\Composer\Plugin\PluginInterface $plugin) : void { $this->io->writeError('Uninstalling plugin ' . \get_class($plugin), \true, IOInterface::DEBUG); $plugin->uninstall($this->composer, $this->io); } /** * Load all plugins and installers from a repository * * If a plugin requires another plugin, the required one will be loaded first * * Note that plugins in the specified repository that rely on events that * have fired prior to loading will be missed. This means you likely want to * call this method as early as possible. * * @param RepositoryInterface $repo Repository to scan for plugins to install * * @throws \RuntimeException */ private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo) : void { $packages = $repo->getPackages(); $weights = []; foreach ($packages as $package) { if ($package->getType() === 'composer-plugin') { $extra = $package->getExtra(); if ($package->getName() === 'composer/installers' || \true === ($extra['plugin-modifies-install-path'] ?? \false)) { $weights[$package->getName()] = -10000; } } } $sortedPackages = PackageSorter::sortPackages($packages, $weights); foreach ($sortedPackages as $package) { if (!$package instanceof CompletePackage) { continue; } if ('composer-plugin' === $package->getType()) { $this->registerPackage($package, \false, $isGlobalRepo); // Backward compatibility } elseif ('composer-installer' === $package->getType()) { $this->registerPackage($package, \false, $isGlobalRepo); } } } /** * Deactivate all plugins and installers from a repository * * If a plugin requires another plugin, the required one will be deactivated last * * @param RepositoryInterface $repo Repository to scan for plugins to install */ private function deactivateRepository(RepositoryInterface $repo, bool $isGlobalRepo) : void { $packages = $repo->getPackages(); $sortedPackages = \array_reverse(PackageSorter::sortPackages($packages)); foreach ($sortedPackages as $package) { if (!$package instanceof CompletePackage) { continue; } if ('composer-plugin' === $package->getType()) { $this->deactivatePackage($package); // Backward compatibility } elseif ('composer-installer' === $package->getType()) { $this->deactivatePackage($package); } } } /** * Recursively generates a map of package names to packages for all deps * * @param InstalledRepository $installedRepo Set of local repos * @param array $collected Current state of the map for recursion * @param PackageInterface $package The package to analyze * * @return array Map of package names to packages */ private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package) : array { foreach ($package->getRequires() as $requireLink) { foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) { if (!isset($collected[$requiredPackage->getName()])) { $collected[$requiredPackage->getName()] = $requiredPackage; $collected = $this->collectDependencies($installedRepo, $collected, $requiredPackage); } } } return $collected; } /** * Retrieves the path a package is installed to. * * @param bool $global Whether this is a global package * * @return string|null Install path */ private function getInstallPath(PackageInterface $package, bool $global = \false) : ?string { if (!$global) { return $this->composer->getInstallationManager()->getInstallPath($package); } \assert(null !== $this->globalComposer); return $this->globalComposer->getInstallationManager()->getInstallPath($package); } /** * @throws \RuntimeException On empty or non-string implementation class name value * @return null|string The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it */ protected function getCapabilityImplementationClassName(\Composer\Plugin\PluginInterface $plugin, string $capability) : ?string { if (!$plugin instanceof \Composer\Plugin\Capable) { return null; } $capabilities = (array) $plugin->getCapabilities(); if (!empty($capabilities[$capability]) && \is_string($capabilities[$capability]) && \trim($capabilities[$capability])) { return \trim($capabilities[$capability]); } if (\array_key_exists($capability, $capabilities) && (empty($capabilities[$capability]) || !\is_string($capabilities[$capability]) || !\trim($capabilities[$capability]))) { throw new \UnexpectedValueException('Plugin ' . \get_class($plugin) . ' provided invalid capability class name(s), got ' . \var_export($capabilities[$capability], \true)); } return null; } /** * @template CapabilityClass of Capability * @param class-string $capabilityClassName The fully qualified name of the API interface which the plugin may provide * an implementation of. * @param array $ctorArgs Arguments passed to Capability's constructor. * Keeping it an array will allow future values to be passed w\o changing the signature. * @phpstan-param class-string $capabilityClassName * @phpstan-return null|CapabilityClass */ public function getPluginCapability(\Composer\Plugin\PluginInterface $plugin, $capabilityClassName, array $ctorArgs = []) : ?Capability { if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) { if (!\class_exists($capabilityClass)) { throw new \RuntimeException("Cannot instantiate Capability, as class {$capabilityClass} from plugin " . \get_class($plugin) . " does not exist."); } $ctorArgs['plugin'] = $plugin; $capabilityObj = new $capabilityClass($ctorArgs); // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9 if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) { throw new \RuntimeException('Class ' . $capabilityClass . ' must implement both Composer\\Plugin\\Capability\\Capability and ' . $capabilityClassName . '.'); } return $capabilityObj; } return null; } /** * @template CapabilityClass of Capability * @param class-string $capabilityClassName The fully qualified name of the API interface which the plugin may provide * an implementation of. * @param array $ctorArgs Arguments passed to Capability's constructor. * Keeping it an array will allow future values to be passed w\o changing the signature. * @return CapabilityClass[] */ public function getPluginCapabilities($capabilityClassName, array $ctorArgs = []) : array { $capabilities = []; foreach ($this->getPlugins() as $plugin) { $capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs); if (null !== $capability) { $capabilities[] = $capability; } } return $capabilities; } /** * @param array|bool $allowPluginsConfig * @return array|null */ private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null) : ?array { if ([] === $allowPluginsConfig && $locker !== null && $locker->isLocked() && \version_compare($locker->getPluginApi(), '2.2.0', '<')) { return null; } if (\true === $allowPluginsConfig) { return ['{}' => \true]; } if (\false === $allowPluginsConfig) { return ['{}' => \false]; } $rules = []; foreach ($allowPluginsConfig as $pattern => $allow) { $rules[BasePackage::packageNameToRegexp($pattern)] = $allow; } return $rules; } /** * @internal * * @param 'local'|'global' $type * @return bool */ public function arePluginsDisabled($type) { return $this->disablePlugins === \true || $this->disablePlugins === $type; } /** * @internal */ public function disablePlugins() : void { $this->disablePlugins = \true; } /** * @internal */ public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = \false) : bool { if ($isGlobalPlugin) { $rules =& $this->allowGlobalPluginRules; } else { $rules =& $this->allowPluginRules; } // This is a BC mode for lock files created pre-Composer-2.2 where the expectation of // an allow-plugins config being present cannot be made. if ($rules === null) { if (!$this->io->isInteractive()) { $this->io->writeError('For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins'); $this->io->writeError('This warning will become an exception once you run composer update!'); $rules = ['{}' => \true]; // if no config is defined we allow all plugins for BC return \true; } // keep going and prompt the user $rules = []; } foreach ($rules as $pattern => $allow) { if (Preg::isMatch($pattern, $package)) { return $allow === \true; } } if ($package === 'composer/package-versions-deprecated') { return \false; } if ($this->io->isInteractive()) { $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; $this->io->writeError('' . $package . ($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '') . ' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins'); $attempts = 0; while (\true) { // do not allow more than 5 prints of the help message, at some point assume the // input is not interactive and bail defaulting to a disabled plugin $default = '?'; if ($attempts > 5) { $this->io->writeError('Too many failed prompts, aborting.'); break; } switch ($answer = $this->io->ask('Do you trust "' . $package . '" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] ', $default)) { case 'y': case 'n': case 'd': $allow = $answer === 'y'; // persist answer in current rules to avoid prompting again if the package gets reloaded $rules[BasePackage::packageNameToRegexp($package)] = $allow; // persist answer in composer.json if it wasn't simply discarded if ($answer === 'y' || $answer === 'n') { $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins.' . $package, $allow); } return $allow; case '?': default: $attempts++; $this->io->writeError(['y - add package to allow-plugins in composer.json and let it run immediately', 'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts', 'd - discard this, do not change composer.json and do not allow the plugin to run', '? - print help']); break; } } } elseif ($optional) { return \false; } throw new \Composer\Plugin\PluginBlockedException($package . ($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '') . ' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.' . \PHP_EOL . 'You can run "composer ' . ($isGlobalPlugin || $this->runningInGlobalDir ? 'global ' : '') . 'config --no-plugins allow-plugins.' . $package . ' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)' . \PHP_EOL . 'See https://getcomposer.org/allow-plugins'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use Composer\Util\HttpDownloader; /** * The pre file download event. * * @author Nils Adermann */ class PreFileDownloadEvent extends Event { /** * @var HttpDownloader */ private $httpDownloader; /** * @var non-empty-string */ private $processedUrl; /** * @var string|null */ private $customCacheKey; /** * @var string */ private $type; /** * @var mixed */ private $context; /** * @var mixed[] */ private $transportOptions = []; /** * Constructor. * * @param string $name The event name * @param mixed $context * @param non-empty-string $processedUrl */ public function __construct(string $name, HttpDownloader $httpDownloader, string $processedUrl, string $type, $context = null) { parent::__construct($name); $this->httpDownloader = $httpDownloader; $this->processedUrl = $processedUrl; $this->type = $type; $this->context = $context; } public function getHttpDownloader() : HttpDownloader { return $this->httpDownloader; } /** * Retrieves the processed URL that will be downloaded. * * @return non-empty-string */ public function getProcessedUrl() : string { return $this->processedUrl; } /** * Sets the processed URL that will be downloaded. * * @param non-empty-string $processedUrl New processed URL */ public function setProcessedUrl(string $processedUrl) : void { $this->processedUrl = $processedUrl; } /** * Retrieves a custom package cache key for this download. */ public function getCustomCacheKey() : ?string { return $this->customCacheKey; } /** * Sets a custom package cache key for this download. * * @param string|null $customCacheKey New cache key */ public function setCustomCacheKey(?string $customCacheKey) : void { $this->customCacheKey = $customCacheKey; } /** * Returns the type of this download (package, metadata). */ public function getType() : string { return $this->type; } /** * Returns the context of this download, if any. * * If this download is of type package, the package object is returned. * If the type is metadata, an array{repository: RepositoryInterface} is returned. * * @return mixed */ public function getContext() { return $this->context; } /** * Returns transport options for the download. * * Only available for events with type metadata, for packages set the transport options on the package itself. * * @return mixed[] */ public function getTransportOptions() : array { return $this->transportOptions; } /** * Sets transport options for the download. * * Only available for events with type metadata, for packages set the transport options on the package itself. * * @param mixed[] $options */ public function setTransportOptions(array $options) : void { $this->transportOptions = $options; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use Composer\Package\PackageInterface; /** * The post file download event. * * @author Nils Adermann */ class PostFileDownloadEvent extends Event { /** * @var string */ private $fileName; /** * @var string|null */ private $checksum; /** * @var string */ private $url; /** * @var mixed */ private $context; /** * @var string */ private $type; /** * Constructor. * * @param string $name The event name * @param string|null $fileName The file name * @param string|null $checksum The checksum * @param string $url The processed url * @param string $type The type (package or metadata). * @param mixed $context Additional context for the download. */ public function __construct(string $name, ?string $fileName, ?string $checksum, string $url, string $type, $context = null) { /** @phpstan-ignore-next-line */ if ($context === null && $type instanceof PackageInterface) { $context = $type; $type = 'package'; \trigger_error('PostFileDownloadEvent::__construct should receive a $type=package and the package object in $context since Composer 2.1.', \E_USER_DEPRECATED); } parent::__construct($name); $this->fileName = $fileName; $this->checksum = $checksum; $this->url = $url; $this->context = $context; $this->type = $type; } /** * Retrieves the target file name location. * * If this download is of type metadata, null is returned. */ public function getFileName() : ?string { return $this->fileName; } /** * Gets the checksum. */ public function getChecksum() : ?string { return $this->checksum; } /** * Gets the processed URL. */ public function getUrl() : string { return $this->url; } /** * Returns the context of this download, if any. * * If this download is of type package, the package object is returned. If * this download is of type metadata, an array{response: Response, repository: RepositoryInterface} is returned. * * @return mixed */ public function getContext() { return $this->context; } /** * Get the package. * * If this download is of type metadata, null is returned. * * @return \Composer\Package\PackageInterface|null The package. * @deprecated Use getContext instead */ public function getPackage() : ?PackageInterface { \trigger_error('PostFileDownloadEvent::getPackage is deprecated since Composer 2.1, use getContext instead.', \E_USER_DEPRECATED); $context = $this->getContext(); return $context instanceof PackageInterface ? $context : null; } /** * Returns the type of this download (package, metadata). */ public function getType() : string { return $this->type; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; /** * Plugins which need to expose various implementations * of the Composer Plugin Capabilities must have their * declared Plugin class implementing this interface. * * @api */ interface Capable { /** * Method by which a Plugin announces its API implementations, through an array * with a special structure. * * The key must be a string, representing a fully qualified class/interface name * which Composer Plugin API exposes. * The value must be a string as well, representing the fully qualified class name * of the implementing class. * * @tutorial * * return array( * 'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider', * 'Composer\Plugin\Capability\Validator' => 'My\Validator', * ); * * @return string[] */ public function getCapabilities(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Plugin; use Composer\EventDispatcher\Event; use Composer\Repository\RepositoryInterface; use Composer\DependencyResolver\Request; use Composer\Package\BasePackage; /** * The pre command run event. * * @author Jordi Boggiano */ class PrePoolCreateEvent extends Event { /** * @var RepositoryInterface[] */ private $repositories; /** * @var Request */ private $request; /** * @var int[] array of stability => BasePackage::STABILITY_* value * @phpstan-var array */ private $acceptableStabilities; /** * @var int[] array of package name => BasePackage::STABILITY_* value * @phpstan-var array */ private $stabilityFlags; /** * @var array[] of package => version => [alias, alias_normalized] * @phpstan-var array> */ private $rootAliases; /** * @var string[] * @phpstan-var array */ private $rootReferences; /** * @var BasePackage[] */ private $packages; /** * @var BasePackage[] */ private $unacceptableFixedPackages; /** * @param string $name The event name * @param RepositoryInterface[] $repositories * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value * @param int[] $stabilityFlags array of package name => BasePackage::STABILITY_* value * @param array[] $rootAliases array of package => version => [alias, alias_normalized] * @param string[] $rootReferences * @param BasePackage[] $packages * @param BasePackage[] $unacceptableFixedPackages * * @phpstan-param array $acceptableStabilities * @phpstan-param array $stabilityFlags * @phpstan-param array> $rootAliases * @phpstan-param array $rootReferences */ public function __construct(string $name, array $repositories, Request $request, array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $packages, array $unacceptableFixedPackages) { parent::__construct($name); $this->repositories = $repositories; $this->request = $request; $this->acceptableStabilities = $acceptableStabilities; $this->stabilityFlags = $stabilityFlags; $this->rootAliases = $rootAliases; $this->rootReferences = $rootReferences; $this->packages = $packages; $this->unacceptableFixedPackages = $unacceptableFixedPackages; } /** * @return RepositoryInterface[] */ public function getRepositories() : array { return $this->repositories; } public function getRequest() : Request { return $this->request; } /** * @return int[] array of stability => BasePackage::STABILITY_* value * @phpstan-return array */ public function getAcceptableStabilities() : array { return $this->acceptableStabilities; } /** * @return int[] array of package name => BasePackage::STABILITY_* value * @phpstan-return array */ public function getStabilityFlags() : array { return $this->stabilityFlags; } /** * @return array[] of package => version => [alias, alias_normalized] * @phpstan-return array> */ public function getRootAliases() : array { return $this->rootAliases; } /** * @return string[] * @phpstan-return array */ public function getRootReferences() : array { return $this->rootReferences; } /** * @return BasePackage[] */ public function getPackages() : array { return $this->packages; } /** * @return BasePackage[] */ public function getUnacceptableFixedPackages() : array { return $this->unacceptableFixedPackages; } /** * @param BasePackage[] $packages */ public function setPackages(array $packages) : void { $this->packages = $packages; } /** * @param BasePackage[] $packages */ public function setUnacceptableFixedPackages(array $packages) : void { $this->unacceptableFixedPackages = $packages; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Package\PackageInterface; use RarArchive; /** * RAR archive downloader. * * Based on previous work by Jordi Boggiano ({@see ZipDownloader}). * * @author Derrick Nelson */ class RarDownloader extends \Composer\Downloader\ArchiveDownloader { protected function extract(PackageInterface $package, string $file, string $path) : PromiseInterface { $processError = null; // Try to use unrar on *nix if (!Platform::isWindows()) { $command = 'unrar x -- ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' . ProcessExecutor::escape($path); if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); } if (!\class_exists('RarArchive')) { // php.ini path is added to the error message to help users find the correct file $iniMessage = IniHelper::getMessage(); $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" . $iniMessage . "\n" . $processError; if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; } throw new \RuntimeException($error); } $rarArchive = RarArchive::open($file); if (\false === $rarArchive) { throw new \UnexpectedValueException('Could not open RAR archive: ' . $file); } $entries = $rarArchive->getEntries(); if (\false === $entries) { throw new \RuntimeException('Could not retrieve RAR archive entries'); } foreach ($entries as $entry) { if (\false === $entry->extract($path)) { throw new \RuntimeException('Could not extract entry'); } } $rarArchive->close(); return \React\Promise\resolve(null); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * VCS Capable Downloader interface. * * @author Steve Buzonas */ interface VcsCapableDownloaderInterface { /** * Gets the VCS Reference for the package at path * * @param PackageInterface $package package instance * @param string $path package directory * @return string|null reference or null */ public function getVcsReference(PackageInterface $package, string $path) : ?string; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; use React\Promise\PromiseInterface; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; /** * @author Jordi Boggiano */ abstract class VcsDownloader implements \Composer\Downloader\DownloaderInterface, \Composer\Downloader\ChangeReportInterface, \Composer\Downloader\VcsCapableDownloaderInterface { /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var ProcessExecutor */ protected $process; /** @var Filesystem */ protected $filesystem; /** @var array */ protected $hasCleanedChanges = []; public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null) { $this->io = $io; $this->config = $config; $this->process = $process ?? new ProcessExecutor($io); $this->filesystem = $fs ?? new Filesystem($this->process); } /** * @inheritDoc */ public function getInstallationSource() : string { return 'source'; } /** * @inheritDoc */ public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { if (!$package->getSourceReference()) { throw new \InvalidArgumentException('Package ' . $package->getPrettyName() . ' is missing reference information'); } $urls = $this->prepareUrls($package->getSourceUrls()); while ($url = \array_shift($urls)) { try { return $this->doDownload($package, $path, $url, $prevPackage); } catch (\Exception $e) { // rethrow phpunit exceptions to avoid hard to debug bug failures if ($e instanceof \_ContaoManager\PHPUnit\Framework\Exception) { throw $e; } if ($this->io->isDebug()) { $this->io->writeError('Failed: [' . \get_class($e) . '] ' . $e->getMessage()); } elseif (\count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } if (!\count($urls)) { throw $e; } } } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { if ($type === 'update') { $this->cleanChanges($prevPackage, $path, \true); $this->hasCleanedChanges[$prevPackage->getUniqueName()] = \true; } elseif ($type === 'install') { $this->filesystem->emptyDirectory($path); } elseif ($type === 'uninstall') { $this->cleanChanges($package, $path, \false); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { if ($type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) { $this->reapplyChanges($path); unset($this->hasCleanedChanges[$prevPackage->getUniqueName()]); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function install(PackageInterface $package, string $path) : PromiseInterface { if (!$package->getSourceReference()) { throw new \InvalidArgumentException('Package ' . $package->getPrettyName() . ' is missing reference information'); } $this->io->writeError(" - " . InstallOperation::format($package) . ': ', \false); $urls = $this->prepareUrls($package->getSourceUrls()); while ($url = \array_shift($urls)) { try { $this->doInstall($package, $path, $url); break; } catch (\Exception $e) { // rethrow phpunit exceptions to avoid hard to debug bug failures if ($e instanceof \_ContaoManager\PHPUnit\Framework\Exception) { throw $e; } if ($this->io->isDebug()) { $this->io->writeError('Failed: [' . \get_class($e) . '] ' . $e->getMessage()); } elseif (\count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } if (!\count($urls)) { throw $e; } } } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function update(PackageInterface $initial, PackageInterface $target, string $path) : PromiseInterface { if (!$target->getSourceReference()) { throw new \InvalidArgumentException('Package ' . $target->getPrettyName() . ' is missing reference information'); } $this->io->writeError(" - " . UpdateOperation::format($initial, $target) . ': ', \false); $urls = $this->prepareUrls($target->getSourceUrls()); $exception = null; while ($url = \array_shift($urls)) { try { $this->doUpdate($initial, $target, $path, $url); $exception = null; break; } catch (\Exception $exception) { // rethrow phpunit exceptions to avoid hard to debug bug failures if ($exception instanceof \_ContaoManager\PHPUnit\Framework\Exception) { throw $exception; } if ($this->io->isDebug()) { $this->io->writeError('Failed: [' . \get_class($exception) . '] ' . $exception->getMessage()); } elseif (\count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } } } // print the commit logs if in verbose mode and VCS metadata is present // because in case of missing metadata code would trigger another exception if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) { $message = 'Pulling in changes:'; $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path); if ('' === \trim($logs)) { $message = 'Rolling back changes:'; $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path); } if ('' !== \trim($logs)) { $logs = \implode("\n", \array_map(static function ($line) : string { return ' ' . $line; }, \explode("\n", $logs))); // escape angle brackets for proper output in the console $logs = \str_replace('<', '\\<', $logs); $this->io->writeError(' ' . $message); $this->io->writeError($logs); } } if (!$urls && $exception) { throw $exception; } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function remove(PackageInterface $package, string $path) : PromiseInterface { $this->io->writeError(" - " . UninstallOperation::format($package)); $promise = $this->filesystem->removeDirectoryAsync($path); return $promise->then(static function (bool $result) use($path) { if (!$result) { throw new \RuntimeException('Could not completely delete ' . $path . ', aborting.'); } }); } /** * @inheritDoc */ public function getVcsReference(PackageInterface $package, string $path) : ?string { $parser = new VersionParser(); $guesser = new VersionGuesser($this->config, $this->process, $parser); $dumper = new ArrayDumper(); $packageConfig = $dumper->dump($package); if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { return $packageVersion['commit']; } return null; } /** * Prompt the user to check if changes should be stashed/removed or the operation aborted * * @param bool $update if true (update) the changes can be stashed and reapplied after an update, * if false (remove) the changes should be assumed to be lost if the operation is not aborted * * @throws \RuntimeException in case the operation must be aborted * @phpstan-return PromiseInterface */ protected function cleanChanges(PackageInterface $package, string $path, bool $update) : PromiseInterface { // the default implementation just fails if there are any changes, override in child classes to provide stash-ability if (null !== $this->getLocalChanges($package, $path)) { throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.'); } return \React\Promise\resolve(null); } /** * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not) * * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly */ protected function reapplyChanges(string $path) : void { } /** * Downloads data needed to run an install/update later * * @param PackageInterface $package package instance * @param string $path download path * @param string $url package url * @param PackageInterface|null $prevPackage previous package (in case of an update) * @phpstan-return PromiseInterface */ protected abstract function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null) : PromiseInterface; /** * Downloads specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path * @param string $url package url * @phpstan-return PromiseInterface */ protected abstract function doInstall(PackageInterface $package, string $path, string $url) : PromiseInterface; /** * Updates specific package in specific folder from initial to target version. * * @param PackageInterface $initial initial package * @param PackageInterface $target updated package * @param string $path download path * @param string $url package url * @phpstan-return PromiseInterface */ protected abstract function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url) : PromiseInterface; /** * Fetches the commit logs between two commits * * @param string $fromReference the source reference * @param string $toReference the target reference * @param string $path the package path */ protected abstract function getCommitLogs(string $fromReference, string $toReference, string $path) : string; /** * Checks if VCS metadata repository has been initialized * repository example: .git|.svn|.hg */ protected abstract function hasMetadataRepository(string $path) : bool; /** * @param string[] $urls * * @return string[] */ private function prepareUrls(array $urls) : array { foreach ($urls as $index => $url) { if (Filesystem::isLocalPath($url)) { // realpath() below will not understand // url that starts with "file://" $fileProtocol = 'file://'; $isFileProtocol = \false; if (0 === \strpos($url, $fileProtocol)) { $url = \substr($url, \strlen($fileProtocol)); $isFileProtocol = \true; } // realpath() below will not understand %20 spaces etc. if (\false !== \strpos($url, '%')) { $url = \rawurldecode($url); } $urls[$index] = \realpath($url); if ($isFileProtocol) { $urls[$index] = $fileProtocol . $urls[$index]; } } } return $urls; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; /** * Exception thrown when issues exist on local filesystem * * @author Javier Spagnoletti */ class FilesystemException extends \Exception { public function __construct(string $message = '', int $code = 0, ?\Exception $previous = null) { parent::__construct("Filesystem exception: \n" . $message, $code, $previous); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Exception\IrrecoverableDownloadException; use React\Promise\PromiseInterface; /** * Downloaders manager. * * @author Konstantin Kudryashov */ class DownloadManager { /** @var IOInterface */ private $io; /** @var bool */ private $preferDist = \false; /** @var bool */ private $preferSource; /** @var array */ private $packagePreferences = []; /** @var Filesystem */ private $filesystem; /** @var array */ private $downloaders = []; /** * Initializes download manager. * * @param IOInterface $io The Input Output Interface * @param bool $preferSource prefer downloading from source * @param Filesystem|null $filesystem custom Filesystem object */ public function __construct(IOInterface $io, bool $preferSource = \false, ?Filesystem $filesystem = null) { $this->io = $io; $this->preferSource = $preferSource; $this->filesystem = $filesystem ?: new Filesystem(); } /** * Makes downloader prefer source installation over the dist. * * @param bool $preferSource prefer downloading from source * @return DownloadManager */ public function setPreferSource(bool $preferSource) : self { $this->preferSource = $preferSource; return $this; } /** * Makes downloader prefer dist installation over the source. * * @param bool $preferDist prefer downloading from dist * @return DownloadManager */ public function setPreferDist(bool $preferDist) : self { $this->preferDist = $preferDist; return $this; } /** * Sets fine tuned preference settings for package level source/dist selection. * * @param array $preferences array of preferences by package patterns * * @return DownloadManager */ public function setPreferences(array $preferences) : self { $this->packagePreferences = $preferences; return $this; } /** * Sets installer downloader for a specific installation type. * * @param string $type installation type * @param DownloaderInterface $downloader downloader instance * @return DownloadManager */ public function setDownloader(string $type, \Composer\Downloader\DownloaderInterface $downloader) : self { $type = \strtolower($type); $this->downloaders[$type] = $downloader; return $this; } /** * Returns downloader for a specific installation type. * * @param string $type installation type * @throws \InvalidArgumentException if downloader for provided type is not registered */ public function getDownloader(string $type) : \Composer\Downloader\DownloaderInterface { $type = \strtolower($type); if (!isset($this->downloaders[$type])) { throw new \InvalidArgumentException(\sprintf('Unknown downloader type: %s. Available types: %s.', $type, \implode(', ', \array_keys($this->downloaders)))); } return $this->downloaders[$type]; } /** * Returns downloader for already installed package. * * @param PackageInterface $package package instance * @throws \InvalidArgumentException if package has no installation source specified * @throws \LogicException if specific downloader used to load package with * wrong type */ public function getDownloaderForPackage(PackageInterface $package) : ?\Composer\Downloader\DownloaderInterface { $installationSource = $package->getInstallationSource(); if ('metapackage' === $package->getType()) { return null; } if ('dist' === $installationSource) { $downloader = $this->getDownloader($package->getDistType()); } elseif ('source' === $installationSource) { $downloader = $this->getDownloader($package->getSourceType()); } else { throw new \InvalidArgumentException('Package ' . $package . ' does not have an installation source set'); } if ($installationSource !== $downloader->getInstallationSource()) { throw new \LogicException(\sprintf('Downloader "%s" is a %s type downloader and can not be used to download %s for package %s', \get_class($downloader), $downloader->getInstallationSource(), $installationSource, $package)); } return $downloader; } public function getDownloaderType(\Composer\Downloader\DownloaderInterface $downloader) : string { return \array_search($downloader, $this->downloaders); } /** * Downloads package into target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir * @param PackageInterface|null $prevPackage previous package instance in case of updates * @phpstan-return PromiseInterface * * @throws \InvalidArgumentException if package have no urls to download from * @throws \RuntimeException */ public function download(PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null) : PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $this->filesystem->ensureDirectoryExists(\dirname($targetDir)); $sources = $this->getAvailableSources($package, $prevPackage); $io = $this->io; $download = function ($retry = \false) use(&$sources, $io, $package, $targetDir, &$download, $prevPackage) { $source = \array_shift($sources); if ($retry) { $io->writeError(' Now trying to download from ' . $source . ''); } $package->setInstallationSource($source); $downloader = $this->getDownloaderForPackage($package); if (!$downloader) { return \React\Promise\resolve(null); } $handleError = static function ($e) use($sources, $source, $package, $io, $download) { if ($e instanceof \RuntimeException && !$e instanceof IrrecoverableDownloadException) { if (!$sources) { throw $e; } $io->writeError(' Failed to download ' . $package->getPrettyName() . ' from ' . $source . ': ' . $e->getMessage() . ''); return $download(\true); } throw $e; }; try { $result = $downloader->download($package, $targetDir, $prevPackage); } catch (\Exception $e) { return $handleError($e); } $res = $result->then(static function ($res) { return $res; }, $handleError); return $res; }; return $download(); } /** * Prepares an operation execution * * @param string $type one of install/update/uninstall * @param PackageInterface $package package instance * @param string $targetDir target dir * @param PackageInterface|null $prevPackage previous package instance in case of updates * @phpstan-return PromiseInterface */ public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null) : PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->prepare($type, $package, $targetDir, $prevPackage); } return \React\Promise\resolve(null); } /** * Installs package into target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir * @phpstan-return PromiseInterface * * @throws \InvalidArgumentException if package have no urls to download from * @throws \RuntimeException */ public function install(PackageInterface $package, string $targetDir) : PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->install($package, $targetDir); } return \React\Promise\resolve(null); } /** * Updates package from initial to target version. * * @param PackageInterface $initial initial package version * @param PackageInterface $target target package version * @param string $targetDir target dir * @phpstan-return PromiseInterface * * @throws \InvalidArgumentException if initial package is not installed */ public function update(PackageInterface $initial, PackageInterface $target, string $targetDir) : PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($target); $initialDownloader = $this->getDownloaderForPackage($initial); // no downloaders present means update from metapackage to metapackage, nothing to do if (!$initialDownloader && !$downloader) { return \React\Promise\resolve(null); } // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed if (!$downloader) { return $initialDownloader->remove($initial, $targetDir); } $initialType = $this->getDownloaderType($initialDownloader); $targetType = $this->getDownloaderType($downloader); if ($initialType === $targetType) { try { return $downloader->update($initial, $target, $targetDir); } catch (\RuntimeException $e) { if (!$this->io->isInteractive()) { throw $e; } $this->io->writeError(' Update failed (' . $e->getMessage() . ')'); if (!$this->io->askConfirmation(' Would you like to try reinstalling the package instead [yes]? ')) { throw $e; } } } // if downloader type changed, or update failed and user asks for reinstall, // we wipe the dir and do a new install instead of updating it $promise = $initialDownloader->remove($initial, $targetDir); return $promise->then(function ($res) use($target, $targetDir) : PromiseInterface { return $this->install($target, $targetDir); }); } /** * Removes package from target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir * @phpstan-return PromiseInterface */ public function remove(PackageInterface $package, string $targetDir) : PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->remove($package, $targetDir); } return \React\Promise\resolve(null); } /** * Cleans up a failed operation * * @param string $type one of install/update/uninstall * @param PackageInterface $package package instance * @param string $targetDir target dir * @param PackageInterface|null $prevPackage previous package instance in case of updates * @phpstan-return PromiseInterface */ public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null) : PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->cleanup($type, $package, $targetDir, $prevPackage); } return \React\Promise\resolve(null); } /** * Determines the install preference of a package * * @param PackageInterface $package package instance */ protected function resolvePackageInstallPreference(PackageInterface $package) : string { foreach ($this->packagePreferences as $pattern => $preference) { $pattern = '{^' . \str_replace('\\*', '.*', \preg_quote($pattern)) . '$}i'; if (Preg::isMatch($pattern, $package->getName())) { if ('dist' === $preference || !$package->isDev() && 'auto' === $preference) { return 'dist'; } return 'source'; } } return $package->isDev() ? 'source' : 'dist'; } /** * @return string[] * @phpstan-return array<'dist'|'source'>&non-empty-array */ private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null) : array { $sourceType = $package->getSourceType(); $distType = $package->getDistType(); // add source before dist by default $sources = []; if ($sourceType) { $sources[] = 'source'; } if ($distType) { $sources[] = 'dist'; } if (empty($sources)) { throw new \InvalidArgumentException('Package ' . $package . ' must have a source or dist specified'); } if ($prevPackage && \in_array($prevPackage->getInstallationSource(), $sources, \true) && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())) { $prevSource = $prevPackage->getInstallationSource(); \usort($sources, static function ($a, $b) use($prevSource) : int { return $a === $prevSource ? -1 : 1; }); return $sources; } // reverse sources in case dist is the preferred source for this package if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { $sources = \array_reverse($sources); } return $sources; } /** * Downloaders expect a /path/to/dir without trailing slash * * If any Installer provides a path with a trailing slash, this can cause bugs so make sure we remove them */ private function normalizeTargetDir(string $dir) : string { if ($dir === '\\' || $dir === '/') { return $dir; } return \rtrim($dir, '\\/'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use React\Promise\PromiseInterface; /** * Downloader for tar files: tar, tar.gz or tar.bz2 * * @author Kirill chEbba Chebunin */ class TarDownloader extends \Composer\Downloader\ArchiveDownloader { /** * @inheritDoc */ protected function extract(PackageInterface $package, string $file, string $path) : PromiseInterface { // Can throw an UnexpectedValueException $archive = new \PharData($file); $archive->extractTo($path, null, \true); return \React\Promise\resolve(null); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Util\ProcessExecutor; /** * @author BohwaZ */ class FossilDownloader extends \Composer\Downloader\VcsDownloader { /** * @inheritDoc */ protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null) : PromiseInterface { return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doInstall(PackageInterface $package, string $path, string $url) : PromiseInterface { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($package->getSourceReference()); $repoFile = $path . '.fossil'; $this->io->writeError("Cloning " . $package->getSourceReference()); $command = \sprintf('fossil clone -- %s %s', $url, ProcessExecutor::escape($repoFile)); if (0 !== $this->process->execute($command, $ignoredOutput)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $command = \sprintf('fossil open --nested -- %s', ProcessExecutor::escape($repoFile)); if (0 !== $this->process->execute($command, $ignoredOutput, \realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $command = \sprintf('fossil update -- %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, \realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url) : PromiseInterface { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); $ref = ProcessExecutor::escape($target->getSourceReference()); $this->io->writeError(" Updating to " . $target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .fslckout file is missing from ' . $path . ', see https://getcomposer.org/commit-deps for more information'); } $command = \sprintf('fossil pull && fossil up %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, \realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function getLocalChanges(PackageInterface $package, string $path) : ?string { if (!$this->hasMetadataRepository($path)) { return null; } $this->process->execute('fossil changes', $output, \realpath($path)); $output = \trim($output); return \strlen($output) > 0 ? $output : null; } /** * @inheritDoc */ protected function getCommitLogs(string $fromReference, string $toReference, string $path) : string { $command = \sprintf('fossil timeline -t ci -W 0 -n 0 before %s', ProcessExecutor::escape($toReference)); if (0 !== $this->process->execute($command, $output, \realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $log = ''; $match = '/\\d\\d:\\d\\d:\\d\\d\\s+\\[' . $toReference . '\\]/'; foreach ($this->process->splitLines($output) as $line) { if (Preg::isMatch($match, $line)) { break; } $log .= $line; } return $log; } /** * @inheritDoc */ protected function hasMetadataRepository(string $path) : bool { return \is_file($path . '/.fslckout') || \is_file($path . '/_FOSSIL_'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; class MaxFileSizeExceededException extends \Composer\Downloader\TransportException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use React\Promise\PromiseInterface; /** * Downloader interface. * * @author Konstantin Kudryashov * @author Jordi Boggiano */ interface DownloaderInterface { /** * Returns installation source (either source or dist). * * @return string "source" or "dist" */ public function getInstallationSource() : string; /** * This should do any network-related tasks to prepare for an upcoming install/update * * @param string $path download path * @phpstan-return PromiseInterface */ public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface; /** * Do anything that needs to be done between all downloads have been completed and the actual operation is executed * * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can * be undone as much as possible. * * @param string $type one of install/update/uninstall * @param PackageInterface $package package instance * @param string $path download path * @param PackageInterface $prevPackage previous package instance in case of an update * @phpstan-return PromiseInterface */ public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface; /** * Installs specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path * @phpstan-return PromiseInterface */ public function install(PackageInterface $package, string $path) : PromiseInterface; /** * Updates specific package in specific folder from initial to target version. * * @param PackageInterface $initial initial package * @param PackageInterface $target updated package * @param string $path download path * @phpstan-return PromiseInterface */ public function update(PackageInterface $initial, PackageInterface $target, string $path) : PromiseInterface; /** * Removes specific package from specific folder. * * @param PackageInterface $package package instance * @param string $path download path * @phpstan-return PromiseInterface */ public function remove(PackageInterface $package, string $path) : PromiseInterface; /** * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps * * Note that cleanup will be called for all packages, either after install/update/uninstall is complete, * or if any package failed any operation. This is to give all installers a change to cleanup things * they did previously, so you need to keep track of changes applied in the installer/downloader themselves. * * @param string $type one of install/update/uninstall * @param PackageInterface $package package instance * @param string $path download path * @param PackageInterface $prevPackage previous package instance in case of an update * @phpstan-return PromiseInterface */ public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; /** * Xz archive downloader. * * @author Pavel Puchkin * @author Pierre Rudloff */ class XzDownloader extends \Composer\Downloader\ArchiveDownloader { protected function extract(PackageInterface $package, string $file, string $path) : PromiseInterface { $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path); if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; use Composer\Util\Hg as HgUtils; /** * @author Per Bernhardt */ class HgDownloader extends \Composer\Downloader\VcsDownloader { /** * @inheritDoc */ protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null) : PromiseInterface { if (null === HgUtils::getVersion($this->process)) { throw new \RuntimeException('hg was not found in your PATH, skipping source download'); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doInstall(PackageInterface $package, string $path, string $url) : PromiseInterface { $hgUtils = new HgUtils($this->io, $this->config, $this->process); $cloneCommand = static function (string $url) use($path) : string { return \sprintf('hg clone -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path)); }; $hgUtils->runCommand($cloneCommand, $url, $path); $ref = ProcessExecutor::escape($package->getSourceReference()); $command = \sprintf('hg up -- %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, \realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url) : PromiseInterface { $hgUtils = new HgUtils($this->io, $this->config, $this->process); $ref = $target->getSourceReference(); $this->io->writeError(" Updating to " . $target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .hg directory is missing from ' . $path . ', see https://getcomposer.org/commit-deps for more information'); } $command = static function ($url) use($ref) : string { return \sprintf('hg pull -- %s && hg up -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref)); }; $hgUtils->runCommand($command, $url, $path); return \React\Promise\resolve(null); } /** * @inheritDoc */ public function getLocalChanges(PackageInterface $package, string $path) : ?string { if (!\is_dir($path . '/.hg')) { return null; } $this->process->execute('hg st', $output, \realpath($path)); $output = \trim($output); return \strlen($output) > 0 ? $output : null; } /** * @inheritDoc */ protected function getCommitLogs(string $fromReference, string $toReference, string $path) : string { $command = \sprintf('hg log -r %s:%s --style compact', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference)); if (0 !== $this->process->execute($command, $output, \realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return $output; } /** * @inheritDoc */ protected function hasMetadataRepository(string $path) : bool { return \is_dir($path . '/.hg'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Util\Svn as SvnUtil; use Composer\Repository\VcsRepository; use Composer\Util\ProcessExecutor; use React\Promise\PromiseInterface; /** * @author Ben Bieker * @author Till Klampaeckel */ class SvnDownloader extends \Composer\Downloader\VcsDownloader { /** @var bool */ protected $cacheCredentials = \true; /** * @inheritDoc */ protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null) : PromiseInterface { SvnUtil::cleanEnv(); $util = new SvnUtil($url, $this->io, $this->config, $this->process); if (null === $util->binaryVersion()) { throw new \RuntimeException('svn was not found in your PATH, skipping source download'); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doInstall(PackageInterface $package, string $path, string $url) : PromiseInterface { SvnUtil::cleanEnv(); $ref = $package->getSourceReference(); $repo = $package->getRepository(); if ($repo instanceof VcsRepository) { $repoConfig = $repo->getRepoConfig(); if (\array_key_exists('svn-cache-credentials', $repoConfig)) { $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials']; } } $this->io->writeError(" Checking out " . $package->getSourceReference()); $this->execute($package, $url, "svn co", \sprintf("%s/%s", $url, $ref), null, $path); return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url) : PromiseInterface { SvnUtil::cleanEnv(); $ref = $target->getSourceReference(); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .svn directory is missing from ' . $path . ', see https://getcomposer.org/commit-deps for more information'); } $util = new SvnUtil($url, $this->io, $this->config, $this->process); $flags = ""; if (\version_compare($util->binaryVersion(), '1.7.0', '>=')) { $flags .= ' --ignore-ancestry'; } $this->io->writeError(" Checking out " . $ref); $this->execute($target, $url, "svn switch" . $flags, \sprintf("%s/%s", $url, $ref), $path); return \React\Promise\resolve(null); } /** * @inheritDoc */ public function getLocalChanges(PackageInterface $package, string $path) : ?string { if (!$this->hasMetadataRepository($path)) { return null; } $this->process->execute('svn status --ignore-externals', $output, $path); return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null; } /** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $baseUrl Base URL of the repository * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @throws \RuntimeException */ protected function execute(PackageInterface $package, string $baseUrl, string $command, string $url, ?string $cwd = null, ?string $path = null) : string { $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); try { return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); } catch (\RuntimeException $e) { throw new \RuntimeException($package->getPrettyName() . ' could not be downloaded, ' . $e->getMessage()); } } /** * @inheritDoc */ protected function cleanChanges(PackageInterface $package, string $path, bool $update) : PromiseInterface { if (null === ($changes = $this->getLocalChanges($package, $path))) { return \React\Promise\resolve(null); } if (!$this->io->isInteractive()) { if (\true === $this->config->get('discard-changes')) { return $this->discardChanges($path); } return parent::cleanChanges($package, $path, $update); } $changes = \array_map(static function ($elem) : string { return ' ' . $elem; }, Preg::split('{\\s*\\r?\\n\\s*}', $changes)); $countChanges = \count($changes); $this->io->writeError(\sprintf(' ' . $package->getPrettyName() . ' has modified file%s:', $countChanges === 1 ? '' : 's')); $this->io->writeError(\array_slice($changes, 0, 10)); if ($countChanges > 10) { $remainingChanges = $countChanges - 10; $this->io->writeError(\sprintf(' ' . $remainingChanges . ' more file%s modified, choose "v" to view the full list', $remainingChanges === 1 ? '' : 's')); } while (\true) { switch ($this->io->ask(' Discard changes [y,n,v,?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; case 'n': throw new \RuntimeException('Update aborted'); case 'v': $this->io->writeError($changes); break; case '?': default: $this->io->writeError([' y - discard changes and apply the ' . ($update ? 'update' : 'uninstall'), ' n - abort the ' . ($update ? 'update' : 'uninstall') . ' and let you manually clean things up', ' v - view modified files', ' ? - print help']); break; } } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function getCommitLogs(string $fromReference, string $toReference, string $path) : string { if (Preg::isMatch('{@(\\d+)$}', $fromReference) && Preg::isMatch('{@(\\d+)$}', $toReference)) { // retrieve the svn base url from the checkout folder $command = \sprintf('svn info --non-interactive --xml -- %s', ProcessExecutor::escape($path)); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $urlPattern = '#(.*)#'; if (Preg::isMatchStrictGroups($urlPattern, $output, $matches)) { $baseUrl = $matches[1]; } else { throw new \RuntimeException('Unable to determine svn url for path ' . $path); } // strip paths from references and only keep the actual revision $fromRevision = Preg::replace('{.*@(\\d+)$}', '$1', $fromReference); $toRevision = Preg::replace('{.*@(\\d+)$}', '$1', $toReference); $command = \sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision)); $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); try { return $util->executeLocal($command, $path, null, $this->io->isVerbose()); } catch (\RuntimeException $e) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $e->getMessage()); } } return "Could not retrieve changes between {$fromReference} and {$toReference} due to missing revision information"; } /** * @phpstan-return PromiseInterface */ protected function discardChanges(string $path) : PromiseInterface { if (0 !== $this->process->execute('svn revert -R .', $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:" . $this->process->getErrorOutput()); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function hasMetadataRepository(string $path) : bool { return \is_dir($path . '/.svn'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\Cache; use Composer\IO\IOInterface; use Composer\IO\NullIO; use Composer\Exception\IrrecoverableDownloadException; use Composer\Package\Comparer\Comparer; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\Package\PackageInterface; use Composer\Plugin\PluginEvents; use Composer\Plugin\PostFileDownloadEvent; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\Silencer; use Composer\Util\HttpDownloader; use Composer\Util\Url as UrlUtil; use Composer\Util\ProcessExecutor; use React\Promise\PromiseInterface; /** * Base downloader for files * * @author Kirill chEbba Chebunin * @author Jordi Boggiano * @author François Pluchino * @author Nils Adermann */ class FileDownloader implements \Composer\Downloader\DownloaderInterface, \Composer\Downloader\ChangeReportInterface { /** @var IOInterface */ protected $io; /** @var Config */ protected $config; /** @var HttpDownloader */ protected $httpDownloader; /** @var Filesystem */ protected $filesystem; /** @var ?Cache */ protected $cache; /** @var ?EventDispatcher */ protected $eventDispatcher; /** @var ProcessExecutor */ protected $process; /** * @var array * @private * @internal */ public static $downloadMetadata = []; /** * Collects response headers when running on GH Actions * * @see https://github.com/composer/composer/issues/11148 * @var array> * @private * @internal */ public static $responseHeaders = []; /** * @var array Map of package name to cache key */ private $lastCacheWrites = []; /** @var array Map of package name to list of paths */ private $additionalCleanupPaths = []; /** * Constructor. * * @param IOInterface $io The IO instance * @param Config $config The config * @param HttpDownloader $httpDownloader The remote filesystem * @param EventDispatcher $eventDispatcher The event dispatcher * @param Cache $cache Cache instance * @param Filesystem $filesystem The filesystem */ public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?Cache $cache = null, ?Filesystem $filesystem = null, ?ProcessExecutor $process = null) { $this->io = $io; $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->httpDownloader = $httpDownloader; $this->cache = $cache; $this->process = $process ?? new ProcessExecutor($io); $this->filesystem = $filesystem ?: new Filesystem($this->process); if ($this->cache && $this->cache->gcIsNecessary()) { $this->io->writeError('Running cache garbage collection', \true, IOInterface::VERY_VERBOSE); $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); } } /** * @inheritDoc */ public function getInstallationSource() : string { return 'dist'; } /** * @inheritDoc */ public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = \true) : PromiseInterface { if (!$package->getDistUrl()) { throw new \InvalidArgumentException('The given package is missing url information'); } $cacheKeyGenerator = static function (PackageInterface $package, $key) : string { $cacheKey = \sha1($key); return $package->getName() . '/' . $cacheKey . '.' . $package->getDistType(); }; $retries = 3; $distUrls = $package->getDistUrls(); /** @var array $urls */ $urls = []; foreach ($distUrls as $index => $url) { $processedUrl = $this->processUrl($package, $url); $urls[$index] = [ 'base' => $url, 'processed' => $processedUrl, // we use the complete download url here to avoid conflicting entries // from different packages, which would potentially allow a given package // in a third party repo to pre-populate the cache for the same package in // packagist for example. 'cacheKey' => $cacheKeyGenerator($package, $processedUrl), ]; } \assert(\count($urls) > 0); $fileName = $this->getFileName($package, $path); $this->filesystem->ensureDirectoryExists($path); $this->filesystem->ensureDirectoryExists(\dirname($fileName)); $io = $this->io; $cache = $this->cache; $httpDownloader = $this->httpDownloader; $eventDispatcher = $this->eventDispatcher; $filesystem = $this->filesystem; $accept = null; $reject = null; $download = function () use($io, $output, $httpDownloader, $cache, $cacheKeyGenerator, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject) { $url = \reset($urls); $index = \key($urls); if ($eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed'], 'package', $package); $eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); if ($preFileDownloadEvent->getCustomCacheKey() !== null) { $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey()); } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) { $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getProcessedUrl()); } $url['processed'] = $preFileDownloadEvent->getProcessedUrl(); } $urls[$index] = $url; $checksum = $package->getDistSha1Checksum(); $cacheKey = $url['cacheKey']; // use from cache if it is present and has a valid checksum or we have no checksum to check against if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) { if ($output) { $io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache", \true, IOInterface::VERY_VERBOSE); } // mark the file as having been written in cache even though it is only read from cache, so that if // the cache is corrupt the archive will be deleted and the next attempt will re-download it // see https://github.com/composer/composer/issues/10028 if (!$cache->isReadOnly()) { $this->lastCacheWrites[$package->getName()] = $cacheKey; } $result = \React\Promise\resolve($fileName); } else { if ($output) { $io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } $result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())->then($accept, $reject); } return $result->then(static function ($result) use($fileName, $checksum, $url, $package, $eventDispatcher) : string { // in case of retry, the first call's Promise chain finally calls this twice at the end, // once with $result being the returned $fileName from $accept, and then once for every // failed request with a null result, which can be skipped. if (null === $result) { return $fileName; } if (!\file_exists($fileName)) { throw new \UnexpectedValueException($url['base'] . ' could not be saved to ' . $fileName . ', make sure the' . ' directory is writable and you have internet connectivity'); } if ($checksum && \hash_file('sha1', $fileName) !== $checksum) { throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from ' . $url['base'] . ')'); } if ($eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package); $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } return $fileName; }); }; $accept = function ($response) use($cache, $package, $fileName, &$urls) : string { $url = \reset($urls); $cacheKey = $url['cacheKey']; \Composer\Downloader\FileDownloader::$downloadMetadata[$package->getName()] = (@\filesize($fileName) ?: $response->getHeader('Content-Length')) ?: '?'; if (Platform::getEnv('GITHUB_ACTIONS') !== \false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === \false) { \Composer\Downloader\FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders(); } if ($cache && !$cache->isReadOnly()) { $this->lastCacheWrites[$package->getName()] = $cacheKey; $cache->copyFrom($cacheKey, $fileName); } $response->collect(); return $fileName; }; $reject = function ($e) use($io, &$urls, $download, $fileName, $package, &$retries, $filesystem) { // clean up if (\file_exists($fileName)) { $filesystem->unlink($fileName); } $this->clearLastCacheWrite($package); if ($e instanceof IrrecoverableDownloadException) { throw $e; } if ($e instanceof \Composer\Downloader\MaxFileSizeExceededException) { throw $e; } if ($e instanceof \Composer\Downloader\TransportException) { // if we got an http response with a proper code, then requesting again will probably not help, abort if (0 !== $e->getCode() && !\in_array($e->getCode(), [500, 502, 503, 504]) || !$retries) { $retries = 0; } } // special error code returned when network is being artificially disabled if ($e instanceof \Composer\Downloader\TransportException && $e->getStatusCode() === 499) { $retries = 0; $urls = []; } if ($retries) { \usleep(500000); $retries--; return $download(); } \array_shift($urls); if ($urls) { if ($io->isDebug()) { $io->writeError(' Failed downloading ' . $package->getName() . ': [' . \get_class($e) . '] ' . $e->getCode() . ': ' . $e->getMessage()); $io->writeError(' Trying the next URL for ' . $package->getName()); } else { $io->writeError(' Failed downloading ' . $package->getName() . ', trying the next URL (' . $e->getCode() . ': ' . $e->getMessage() . ')'); } $retries = 3; \usleep(100000); return $download(); } throw $e; }; return $download(); } /** * @inheritDoc */ public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { return \React\Promise\resolve(null); } /** * @inheritDoc */ public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { $fileName = $this->getFileName($package, $path); if (\file_exists($fileName)) { $this->filesystem->unlink($fileName); } $dirsToCleanUp = [$path, $this->config->get('vendor-dir') . '/' . \explode('/', $package->getPrettyName())[0], $this->config->get('vendor-dir') . '/composer/', $this->config->get('vendor-dir')]; if (isset($this->additionalCleanupPaths[$package->getName()])) { foreach ($this->additionalCleanupPaths[$package->getName()] as $path) { $this->filesystem->remove($path); } } foreach ($dirsToCleanUp as $dir) { if (\is_dir($dir) && $this->filesystem->isDirEmpty($dir) && \realpath($dir) !== Platform::getCwd()) { $this->filesystem->removeDirectoryPhp($dir); } } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function install(PackageInterface $package, string $path, bool $output = \true) : PromiseInterface { if ($output) { $this->io->writeError(" - " . InstallOperation::format($package)); } $this->filesystem->emptyDirectory($path); $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, \PATHINFO_BASENAME)); if ($package->getBinaries()) { // Single files can not have a mode set like files in archives // so we make sure if the file is a binary that it is executable foreach ($package->getBinaries() as $bin) { if (\file_exists($path . '/' . $bin) && !\is_executable($path . '/' . $bin)) { Silencer::call('chmod', $path . '/' . $bin, 0777 & ~\umask()); } } } return \React\Promise\resolve(null); } protected function getDistPath(PackageInterface $package, int $component) : string { return \pathinfo((string) \parse_url(\strtr((string) $package->getDistUrl(), '\\', '/'), \PHP_URL_PATH), $component); } protected function clearLastCacheWrite(PackageInterface $package) : void { if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { $this->cache->remove($this->lastCacheWrites[$package->getName()]); unset($this->lastCacheWrites[$package->getName()]); } } protected function addCleanupPath(PackageInterface $package, string $path) : void { $this->additionalCleanupPaths[$package->getName()][] = $path; } protected function removeCleanupPath(PackageInterface $package, string $path) : void { if (isset($this->additionalCleanupPaths[$package->getName()])) { $idx = \array_search($path, $this->additionalCleanupPaths[$package->getName()]); if (\false !== $idx) { unset($this->additionalCleanupPaths[$package->getName()][$idx]); } } } /** * @inheritDoc */ public function update(PackageInterface $initial, PackageInterface $target, string $path) : PromiseInterface { $this->io->writeError(" - " . UpdateOperation::format($initial, $target) . $this->getInstallOperationAppendix($target, $path)); $promise = $this->remove($initial, $path, \false); return $promise->then(function () use($target, $path) : PromiseInterface { return $this->install($target, $path, \false); }); } /** * @inheritDoc */ public function remove(PackageInterface $package, string $path, bool $output = \true) : PromiseInterface { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package)); } $promise = $this->filesystem->removeDirectoryAsync($path); return $promise->then(static function ($result) use($path) : void { if (!$result) { throw new \RuntimeException('Could not completely delete ' . $path . ', aborting.'); } }); } /** * Gets file name for specific package * * @param PackageInterface $package package instance * @param string $path download path * @return string file name */ protected function getFileName(PackageInterface $package, string $path) : string { $extension = $this->getDistPath($package, \PATHINFO_EXTENSION); if ($extension === '') { $extension = $package->getDistType(); } return \rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . \md5($package . \spl_object_hash($package)) . '.' . $extension, '.'); } /** * Gets appendix message to add to the "- Upgrading x" string being output on update * * @param PackageInterface $package package instance * @param string $path download path */ protected function getInstallOperationAppendix(PackageInterface $package, string $path) : string { return ''; } /** * Process the download url * * @param PackageInterface $package package instance * @param non-empty-string $url download url * @throws \RuntimeException If any problem with the url * @return non-empty-string url */ protected function processUrl(PackageInterface $package, string $url) : string { if (!\extension_loaded('openssl') && 0 === \strpos($url, 'https:')) { throw new \RuntimeException('You must enable the openssl extension to download files via https'); } if ($package->getDistReference()) { $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference()); } return $url; } /** * @inheritDoc * @throws \RuntimeException */ public function getLocalChanges(PackageInterface $package, string $path) : ?string { $prevIO = $this->io; $this->io = new NullIO(); $this->io->loadConfiguration($this->config); $e = null; $output = ''; $targetDir = Filesystem::trimTrailingSlash($path); try { if (\is_dir($targetDir . '_compare')) { $this->filesystem->removeDirectory($targetDir . '_compare'); } $this->download($package, $targetDir . '_compare', null, \false); $this->httpDownloader->wait(); $this->install($package, $targetDir . '_compare', \false); $this->process->wait(); $comparer = new Comparer(); $comparer->setSource($targetDir . '_compare'); $comparer->setUpdate($targetDir); $comparer->doCompare(); $output = $comparer->getChangedAsString(\true); $this->filesystem->removeDirectory($targetDir . '_compare'); } catch (\Exception $e) { } $this->io = $prevIO; if ($e) { throw $e; } $output = \trim($output); return \strlen($output) > 0 ? $output : null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * ChangeReport interface. * * @author Sascha Egerer */ interface ChangeReportInterface { /** * Checks for changes to the local copy * * @param PackageInterface $package package instance * @param string $path package directory * @return string|null changes or null */ public function getLocalChanges(PackageInterface $package, string $path) : ?string; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; /** * @author Jordi Boggiano */ class TransportException extends \RuntimeException { /** @var ?array */ protected $headers; /** @var ?string */ protected $response; /** @var ?int */ protected $statusCode; /** @var array */ protected $responseInfo = []; /** * @param array $headers */ public function setHeaders(array $headers) : void { $this->headers = $headers; } /** * @return ?array */ public function getHeaders() : ?array { return $this->headers; } public function setResponse(?string $response) : void { $this->response = $response; } /** * @return ?string */ public function getResponse() : ?string { return $this->response; } /** * @param ?int $statusCode */ public function setStatusCode($statusCode) : void { $this->statusCode = $statusCode; } /** * @return ?int */ public function getStatusCode() : ?int { return $this->statusCode; } /** * @return array */ public function getResponseInfo() : array { return $this->responseInfo; } /** * @param array $responseInfo */ public function setResponseInfo(array $responseInfo) : void { $this->responseInfo = $responseInfo; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\Platform; use _ContaoManager\Symfony\Component\Finder\Finder; use React\Promise\PromiseInterface; use Composer\DependencyResolver\Operation\InstallOperation; /** * Base downloader for archives * * @author Kirill chEbba Chebunin * @author Jordi Boggiano * @author François Pluchino */ abstract class ArchiveDownloader extends \Composer\Downloader\FileDownloader { /** * @var array */ protected $cleanupExecuted = []; public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { unset($this->cleanupExecuted[$package->getName()]); return parent::prepare($type, $package, $path, $prevPackage); } public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null) : PromiseInterface { $this->cleanupExecuted[$package->getName()] = \true; return parent::cleanup($type, $package, $path, $prevPackage); } /** * @inheritDoc * * @throws \RuntimeException * @throws \UnexpectedValueException */ public function install(PackageInterface $package, string $path, bool $output = \true) : PromiseInterface { if ($output) { $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); } $vendorDir = $this->config->get('vendor-dir'); // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains // the archive to be extracted. This is the case when installing with create-project in the current directory // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. if (\false === \strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path . \DIRECTORY_SEPARATOR))) { $this->filesystem->emptyDirectory($path); } do { $temporaryDir = $vendorDir . '/composer/' . \substr(\md5(\uniqid('', \true)), 0, 8); } while (\is_dir($temporaryDir)); $this->addCleanupPath($package, $temporaryDir); // avoid cleaning up $path if installing in "." for eg create-project as we can not // delete the directory we are currently in on windows if (!\is_dir($path) || \realpath($path) !== Platform::getCwd()) { $this->addCleanupPath($package, $path); } $this->filesystem->ensureDirectoryExists($temporaryDir); $fileName = $this->getFileName($package, $path); $filesystem = $this->filesystem; $cleanup = function () use($path, $filesystem, $temporaryDir, $package) { // remove cache if the file was corrupted $this->clearLastCacheWrite($package); // clean up $filesystem->removeDirectory($temporaryDir); if (\is_dir($path) && \realpath($path) !== Platform::getCwd()) { $filesystem->removeDirectory($path); } $this->removeCleanupPath($package, $temporaryDir); $realpath = \realpath($path); if ($realpath !== \false) { $this->removeCleanupPath($package, $realpath); } }; try { $promise = $this->extract($package, $fileName, $temporaryDir); } catch (\Exception $e) { $cleanup(); throw $e; } return $promise->then(function () use($package, $filesystem, $fileName, $temporaryDir, $path) : \React\Promise\PromiseInterface { if (\file_exists($fileName)) { $filesystem->unlink($fileName); } /** * Returns the folder content, excluding .DS_Store * * @param string $dir Directory * @return \SplFileInfo[] */ $getFolderContent = static function ($dir) : array { $finder = Finder::create()->ignoreVCS(\false)->ignoreDotFiles(\false)->notName('.DS_Store')->depth(0)->in($dir); return \iterator_to_array($finder); }; $renameRecursively = null; /** * Renames (and recursively merges if needed) a folder into another one * * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/ * * @param string $from Directory * @param string $to Directory * @return void */ $renameRecursively = static function ($from, $to) use($filesystem, $getFolderContent, $package, &$renameRecursively) { $contentDir = $getFolderContent($from); // move files back out of the temp dir foreach ($contentDir as $file) { $file = (string) $file; if (\is_dir($to . '/' . \basename($file))) { if (!\is_dir($file)) { throw new \RuntimeException('Installing ' . $package . ' would lead to overwriting the ' . $to . '/' . \basename($file) . ' directory with a file from the package, invalid operation.'); } $renameRecursively($file, $to . '/' . \basename($file)); } else { $filesystem->rename($file, $to . '/' . \basename($file)); } } }; $renameAsOne = \false; if (!\file_exists($path)) { $renameAsOne = \true; } elseif ($filesystem->isDirEmpty($path)) { try { if ($filesystem->removeDirectoryPhp($path)) { $renameAsOne = \true; } } catch (\RuntimeException $e) { // ignore error, and simply do not renameAsOne } } $contentDir = $getFolderContent($temporaryDir); $singleDirAtTopLevel = 1 === \count($contentDir) && \is_dir((string) \reset($contentDir)); if ($renameAsOne) { // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents if ($singleDirAtTopLevel) { $extractedDir = (string) \reset($contentDir); } else { $extractedDir = $temporaryDir; } $filesystem->rename($extractedDir, $path); } else { // only one dir in the archive, extract its contents out of it $from = $temporaryDir; if ($singleDirAtTopLevel) { $from = (string) \reset($contentDir); } $renameRecursively($from, $path); } $promise = $filesystem->removeDirectoryAsync($temporaryDir); return $promise->then(function () use($package, $path, $temporaryDir) { $this->removeCleanupPath($package, $temporaryDir); $this->removeCleanupPath($package, $path); }); }, static function ($e) use($cleanup) { $cleanup(); throw $e; }); } /** * @inheritDoc */ protected function getInstallOperationAppendix(PackageInterface $package, string $path) : string { return ': Extracting archive'; } /** * Extract file to directory * * @param string $file Extracted file * @param string $path Directory * @phpstan-return PromiseInterface * * @throws \UnexpectedValueException If can not extract downloaded file to path */ protected abstract function extract(PackageInterface $package, string $file, string $path) : PromiseInterface; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; /** * DVCS Downloader interface. * * @author James Titcumb */ interface DvcsDownloaderInterface { /** * Checks for unpushed changes to a current branch * * @param PackageInterface $package package instance * @param string $path package directory * @return string|null changes or null */ public function getUnpushedChanges(PackageInterface $package, string $path) : ?string; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; /** * Downloader for phar files * * @author Kirill chEbba Chebunin */ class PharDownloader extends \Composer\Downloader\ArchiveDownloader { /** * @inheritDoc */ protected function extract(PackageInterface $package, string $file, string $path) : PromiseInterface { // Can throw an UnexpectedValueException $archive = new \Phar($file); $archive->extractTo($path, null, \true); /* TODO: handle openssl signed phars * https://github.com/composer/composer/pull/33#issuecomment-2250768 * https://github.com/koto/phar-util * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html */ return \React\Promise\resolve(null); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\Archiver\ArchivableFilesFinder; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Util\Platform; use Composer\Util\Filesystem; use _ContaoManager\Symfony\Component\Filesystem\Exception\IOException; use _ContaoManager\Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; /** * Download a package from a local path. * * @author Samuel Roze * @author Johann Reinke */ class PathDownloader extends \Composer\Downloader\FileDownloader implements \Composer\Downloader\VcsCapableDownloaderInterface { private const STRATEGY_SYMLINK = 10; private const STRATEGY_MIRROR = 20; /** * @inheritDoc */ public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = \true) : PromiseInterface { $path = Filesystem::trimTrailingSlash($path); $url = $package->getDistUrl(); $realUrl = \realpath($url); if (\false === $realUrl || !\file_exists($realUrl) || !\is_dir($realUrl)) { throw new \RuntimeException(\sprintf('Source path "%s" is not found for package %s', $url, $package->getName())); } if (\realpath($path) === $realUrl) { return \React\Promise\resolve(null); } if (\strpos(\realpath($path) . \DIRECTORY_SEPARATOR, $realUrl . \DIRECTORY_SEPARATOR) === 0) { // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours. // // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174 // for previous attempts that were shut down because they did not work well enough or introduced too many risks. throw new \RuntimeException(\sprintf('Package %s cannot install to "%s" inside its source at "%s"', $package->getName(), \realpath($path), $realUrl)); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function install(PackageInterface $package, string $path, bool $output = \true) : PromiseInterface { $path = Filesystem::trimTrailingSlash($path); $url = $package->getDistUrl(); $realUrl = \realpath($url); if (\realpath($path) === $realUrl) { if ($output) { $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); } return \React\Promise\resolve(null); } // Get the transport options with default values $transportOptions = $package->getTransportOptions() + ['relative' => \true]; [$currentStrategy, $allowedStrategies] = $this->computeAllowedStrategies($transportOptions); $symfonyFilesystem = new SymfonyFilesystem(); $this->filesystem->removeDirectory($path); if ($output) { $this->io->writeError(" - " . InstallOperation::format($package) . ': ', \false); } $isFallback = \false; if (self::STRATEGY_SYMLINK === $currentStrategy) { try { if (Platform::isWindows()) { // Implement symlinks as NTFS junctions on Windows if ($output) { $this->io->writeError(\sprintf('Junctioning from %s', $url), \false); } $this->filesystem->junction($realUrl, $path); } else { $absolutePath = $path; if (!$this->filesystem->isAbsolutePath($absolutePath)) { $absolutePath = Platform::getCwd() . \DIRECTORY_SEPARATOR . $path; } $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl); $path = \rtrim($path, "/"); if ($output) { $this->io->writeError(\sprintf('Symlinking from %s', $url), \false); } if ($transportOptions['relative']) { $symfonyFilesystem->symlink($shortestPath . '/', $path); } else { $symfonyFilesystem->symlink($realUrl . '/', $path); } } } catch (IOException $e) { if (\in_array(self::STRATEGY_MIRROR, $allowedStrategies, \true)) { if ($output) { $this->io->writeError(''); $this->io->writeError(' Symlink failed, fallback to use mirroring!'); } $currentStrategy = self::STRATEGY_MIRROR; $isFallback = \true; } else { throw new \RuntimeException(\sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path)); } } } // Fallback if symlink failed or if symlink is not allowed for the package if (self::STRATEGY_MIRROR === $currentStrategy) { $realUrl = $this->filesystem->normalizePath($realUrl); if ($output) { $this->io->writeError(\sprintf('%sMirroring from %s', $isFallback ? ' ' : '', $url), \false); } $iterator = new ArchivableFilesFinder($realUrl, []); $symfonyFilesystem->mirror($realUrl, $path, $iterator); } if ($output) { $this->io->writeError(''); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function remove(PackageInterface $package, string $path, bool $output = \true) : PromiseInterface { $path = Filesystem::trimTrailingSlash($path); /** * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first * to prevent a false positive when checking if the dist and install paths are the same. * See https://bugs.php.net/bug.php?id=77639 * * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which * is disastrous within a junction. So in that case we have no other real choice but to fail hard. */ if (Platform::isWindows() && $this->filesystem->isJunction($path)) { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package) . ", source is still present in {$path}"); } if (!$this->filesystem->removeJunction($path)) { $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); } return \React\Promise\resolve(null); } // ensure that the source path (dist url) is not the same as the install path, which // can happen when using custom installers, see https://github.com/composer/composer/pull/9116 // not using realpath here as we do not want to resolve the symlink to the original dist url // it points to $fs = new Filesystem(); $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path; $absDistUrl = $fs->isAbsolutePath($package->getDistUrl()) ? $package->getDistUrl() : Platform::getCwd() . '/' . $package->getDistUrl(); if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package) . ", source is still present in {$path}"); } return \React\Promise\resolve(null); } return parent::remove($package, $path, $output); } /** * @inheritDoc */ public function getVcsReference(PackageInterface $package, string $path) : ?string { $path = Filesystem::trimTrailingSlash($path); $parser = new VersionParser(); $guesser = new VersionGuesser($this->config, $this->process, $parser); $dumper = new ArrayDumper(); $packageConfig = $dumper->dump($package); if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { return $packageVersion['commit']; } return null; } /** * @inheritDoc */ protected function getInstallOperationAppendix(PackageInterface $package, string $path) : string { $realUrl = \realpath($package->getDistUrl()); if (\realpath($path) === $realUrl) { return ': Source already present'; } [$currentStrategy] = $this->computeAllowedStrategies($package->getTransportOptions()); if ($currentStrategy === self::STRATEGY_SYMLINK) { if (Platform::isWindows()) { return ': Junctioning from ' . $package->getDistUrl(); } return ': Symlinking from ' . $package->getDistUrl(); } return ': Mirroring from ' . $package->getDistUrl(); } /** * @param mixed[] $transportOptions * * @phpstan-return array{self::STRATEGY_*, non-empty-list} */ private function computeAllowedStrategies(array $transportOptions) : array { // When symlink transport option is null, both symlink and mirror are allowed $currentStrategy = self::STRATEGY_SYMLINK; $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR]; $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS'); if ($mirrorPathRepos) { $currentStrategy = self::STRATEGY_MIRROR; } $symlinkOption = $transportOptions['symlink'] ?? null; if (\true === $symlinkOption) { $currentStrategy = self::STRATEGY_SYMLINK; $allowedStrategies = [self::STRATEGY_SYMLINK]; } elseif (\false === $symlinkOption) { $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = [self::STRATEGY_MIRROR]; } // Check we can use junctions safely if we are on Windows if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) { if (!\in_array(self::STRATEGY_MIRROR, $allowedStrategies, \true)) { throw new \RuntimeException('You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed'); } $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = [self::STRATEGY_MIRROR]; } // Check we can use symlink() otherwise if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !\function_exists('symlink')) { if (!\in_array(self::STRATEGY_MIRROR, $allowedStrategies, \true)) { throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed'); } $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = [self::STRATEGY_MIRROR]; } return [$currentStrategy, $allowedStrategies]; } /** * Returns true if junctions can be created and safely used on Windows * * A PHP bug makes junction detection fragile, leading to possible data loss * when removing a package. See https://bugs.php.net/bug.php?id=77552 * * For safety we require a minimum version of Windows 7, so we can call the * system rmdir which will preserve target content if given a junction. * * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7). */ private function safeJunctions() : bool { // We need to call mklink, and rmdir on Windows 7 (version 6.1) return \function_exists('proc_open') && (\PHP_WINDOWS_VERSION_MAJOR > 6 || \PHP_WINDOWS_VERSION_MAJOR === 6 && \PHP_WINDOWS_VERSION_MINOR >= 1); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Repository\VcsRepository; use Composer\Util\Perforce; /** * @author Matt Whittom */ class PerforceDownloader extends \Composer\Downloader\VcsDownloader { /** @var Perforce|null */ protected $perforce; /** * @inheritDoc */ protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null) : PromiseInterface { return \React\Promise\resolve(null); } /** * @inheritDoc */ public function doInstall(PackageInterface $package, string $path, string $url) : PromiseInterface { $ref = $package->getSourceReference(); $label = $this->getLabelFromSourceReference((string) $ref); $this->io->writeError('Cloning ' . $ref); $this->initPerforce($package, $path, $url); $this->perforce->setStream($ref); $this->perforce->p4Login(); $this->perforce->writeP4ClientSpec(); $this->perforce->connectClient(); $this->perforce->syncCodeBase($label); $this->perforce->cleanupClientSpec(); return \React\Promise\resolve(null); } private function getLabelFromSourceReference(string $ref) : ?string { $pos = \strpos($ref, '@'); if (\false !== $pos) { return \substr($ref, $pos + 1); } return null; } public function initPerforce(PackageInterface $package, string $path, string $url) : void { if (!empty($this->perforce)) { $this->perforce->initializePath($path); return; } $repository = $package->getRepository(); $repoConfig = null; if ($repository instanceof VcsRepository) { $repoConfig = $this->getRepoConfig($repository); } $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io); } /** * @return array */ private function getRepoConfig(VcsRepository $repository) : array { return $repository->getRepoConfig(); } /** * @inheritDoc */ protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url) : PromiseInterface { return $this->doInstall($target, $path, $url); } /** * @inheritDoc */ public function getLocalChanges(PackageInterface $package, string $path) : ?string { $this->io->writeError('Perforce driver does not check for local changes before overriding'); return null; } /** * @inheritDoc */ protected function getCommitLogs(string $fromReference, string $toReference, string $path) : string { return $this->perforce->getCommitLogs($fromReference, $toReference); } public function setPerforce(Perforce $perforce) : void { $this->perforce = $perforce; } /** * @inheritDoc */ protected function hasMetadataRepository(string $path) : bool { return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Config; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Git as GitUtil; use Composer\Util\Url; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Cache; use React\Promise\PromiseInterface; /** * @author Jordi Boggiano */ class GitDownloader extends \Composer\Downloader\VcsDownloader implements \Composer\Downloader\DvcsDownloaderInterface { /** * @var bool[] * @phpstan-var array */ private $hasStashedChanges = []; /** * @var bool[] * @phpstan-var array */ private $hasDiscardedChanges = []; /** * @var GitUtil */ private $gitUtil; /** * @var array * @phpstan-var array> */ private $cachedPackages = []; public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null) { parent::__construct($io, $config, $process, $fs); $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem); } /** * @inheritDoc */ protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null) : PromiseInterface { // Do not create an extra local cache when repository is already local if (Filesystem::isLocalPath($url)) { return \React\Promise\resolve(null); } GitUtil::cleanEnv(); $cachePath = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', $url) . '/'; $gitVersion = GitUtil::getVersion($this->process); // --dissociate option is only available since git 2.3.0-rc0 if ($gitVersion && \version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { $this->io->writeError(" - Syncing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") into cache"); $this->io->writeError(\sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), \true, IOInterface::DEBUG); $ref = $package->getSourceReference(); if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && \is_dir($cachePath)) { $this->cachedPackages[$package->getId()][$ref] = \true; } } elseif (null === $gitVersion) { throw new \RuntimeException('git was not found in your PATH, skipping source download'); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doInstall(PackageInterface $package, string $path, string $url) : PromiseInterface { GitUtil::cleanEnv(); $path = $this->normalizePath($path); $cachePath = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', $url) . '/'; $ref = $package->getSourceReference(); $flag = Platform::isWindows() ? '/D ' : ''; if (!empty($this->cachedPackages[$package->getId()][$ref])) { $msg = "Cloning " . $this->getShortHash($ref) . ' from cache'; $cloneFlags = '--dissociate --reference %cachePath% '; $transportOptions = $package->getTransportOptions(); if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) { $cloneFlags = ''; } $command = 'git clone --no-checkout %cachePath% %path% ' . $cloneFlags . '&& cd ' . $flag . '%path% ' . '&& git remote set-url origin -- %sanitizedUrl% && git remote add composer -- %sanitizedUrl%'; } else { $msg = "Cloning " . $this->getShortHash($ref); $command = 'git clone --no-checkout -- %url% %path% && cd ' . $flag . '%path% && git remote add composer -- %url% && git fetch composer && git remote set-url origin -- %sanitizedUrl% && git remote set-url composer -- %sanitizedUrl%'; if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for ' . $package->getName() . ' is not in cache and network is disabled, aborting'); } } $this->io->writeError($msg); $commandCallable = static function (string $url) use($path, $command, $cachePath) : string { return \str_replace(['%url%', '%path%', '%cachePath%', '%sanitizedUrl%'], [ProcessExecutor::escape($url), ProcessExecutor::escape($path), ProcessExecutor::escape($cachePath), ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}', '://', $url))], $command); }; $this->gitUtil->runCommand($commandCallable, $url, $path, \true); $sourceUrl = $package->getSourceUrl(); if ($url !== $sourceUrl && $sourceUrl !== null) { $this->updateOriginUrl($path, $sourceUrl); } else { $this->setPushUrl($path, $url); } if ($newRef = $this->updateToCommit($package, $path, (string) $ref, $package->getPrettyVersion())) { if ($package->getDistReference() === $package->getSourceReference()) { $package->setDistReference($newRef); } $package->setSourceReference($newRef); } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url) : PromiseInterface { GitUtil::cleanEnv(); $path = $this->normalizePath($path); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .git directory is missing from ' . $path . ', see https://getcomposer.org/commit-deps for more information'); } $cachePath = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', $url) . '/'; $ref = $target->getSourceReference(); if (!empty($this->cachedPackages[$target->getId()][$ref])) { $msg = "Checking out " . $this->getShortHash($ref) . ' from cache'; $command = '(git rev-parse --quiet --verify %ref% || (git remote set-url composer -- %cachePath% && git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%'; } else { $msg = "Checking out " . $this->getShortHash($ref); $command = '(git remote set-url composer -- %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%'; if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for ' . $target->getName() . ' is not in cache and network is disabled, aborting'); } } $this->io->writeError($msg); $commandCallable = static function ($url) use($ref, $command, $cachePath) : string { return \str_replace(['%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'], [ProcessExecutor::escape($url), ProcessExecutor::escape($ref . '^{commit}'), ProcessExecutor::escape($cachePath), ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}', '://', $url))], $command); }; $this->gitUtil->runCommand($commandCallable, $url, $path); if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) { if ($target->getDistReference() === $target->getSourceReference()) { $target->setDistReference($newRef); } $target->setSourceReference($newRef); } $updateOriginUrl = \false; if (0 === $this->process->execute('git remote -v', $output, $path) && Preg::isMatch('{^origin\\s+(?P\\S+)}m', $output, $originMatch) && Preg::isMatch('{^composer\\s+(?P\\S+)}m', $output, $composerMatch)) { if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) { $updateOriginUrl = \true; } } if ($updateOriginUrl && $target->getSourceUrl() !== null) { $this->updateOriginUrl($path, $target->getSourceUrl()); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function getLocalChanges(PackageInterface $package, string $path) : ?string { GitUtil::cleanEnv(); if (!$this->hasMetadataRepository($path)) { return null; } $command = 'git status --porcelain --untracked-files=no'; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $output = \trim($output); return \strlen($output) > 0 ? $output : null; } public function getUnpushedChanges(PackageInterface $package, string $path) : ?string { GitUtil::cleanEnv(); $path = $this->normalizePath($path); if (!$this->hasMetadataRepository($path)) { return null; } $command = 'git show-ref --head -d'; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $refs = \trim($output); if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) { // could not match the HEAD for some reason return null; } $headRef = $match[1]; if (!Preg::isMatchAllStrictGroups('{^' . $headRef . ' refs/heads/(.+)$}mi', $refs, $matches)) { // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this return null; } $candidateBranches = $matches[1]; // use the first match as branch name for now $branch = $candidateBranches[0]; $unpushedChanges = null; $branchNotFoundError = \false; // do two passes, as if we find anything we want to fetch and then re-try for ($i = 0; $i <= 1; $i++) { $remoteBranches = []; // try to find matching branch names in remote repos foreach ($candidateBranches as $candidate) { if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/' . \preg_quote($candidate) . ')$}mi', $refs, $matches)) { foreach ($matches[1] as $match) { $branch = $candidate; $remoteBranches[] = $match; } break; } } // if it doesn't exist, then we assume it is an unpushed branch // this is bad as we have no reference point to do a diff so we just bail listing // the branch as being unpushed if (\count($remoteBranches) === 0) { $unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed'; $branchNotFoundError = \true; } else { // if first iteration found no remote branch but it has now found some, reset $unpushedChanges // so we get the real diff output no matter its length if ($branchNotFoundError) { $unpushedChanges = null; } foreach ($remoteBranches as $remoteBranch) { $command = \sprintf('git diff --name-status %s...%s --', $remoteBranch, $branch); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $output = \trim($output); // keep the shortest diff from all remote branches we compare against if ($unpushedChanges === null || \strlen($output) < \strlen($unpushedChanges)) { $unpushedChanges = $output; } } } // first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date // remotes and then try again as outdated remotes can sometimes cause false-positives if ($unpushedChanges && $i === 0) { $this->process->execute('git fetch --all', $output, $path); // update list of refs after fetching $command = 'git show-ref --head -d'; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } $refs = \trim($output); } // abort after first pass if we didn't find anything if (!$unpushedChanges) { break; } } return $unpushedChanges; } /** * @inheritDoc */ protected function cleanChanges(PackageInterface $package, string $path, bool $update) : PromiseInterface { GitUtil::cleanEnv(); $path = $this->normalizePath($path); $unpushed = $this->getUnpushedChanges($package, $path); if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== \true)) { throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: ' . "\n" . $unpushed); } if (null === ($changes = $this->getLocalChanges($package, $path))) { return \React\Promise\resolve(null); } if (!$this->io->isInteractive()) { $discardChanges = $this->config->get('discard-changes'); if (\true === $discardChanges) { return $this->discardChanges($path); } if ('stash' === $discardChanges) { if (!$update) { return parent::cleanChanges($package, $path, $update); } return $this->stashChanges($path); } return parent::cleanChanges($package, $path, $update); } $changes = \array_map(static function ($elem) : string { return ' ' . $elem; }, Preg::split('{\\s*\\r?\\n\\s*}', $changes)); $this->io->writeError(' ' . $package->getPrettyName() . ' has modified files:'); $this->io->writeError(\array_slice($changes, 0, 10)); if (\count($changes) > 10) { $this->io->writeError(' ' . (\count($changes) - 10) . ' more files modified, choose "v" to view the full list'); } while (\true) { switch ($this->io->ask(' Discard changes [y,n,v,d,' . ($update ? 's,' : '') . '?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; case 's': if (!$update) { goto help; } $this->stashChanges($path); break 2; case 'n': throw new \RuntimeException('Update aborted'); case 'v': $this->io->writeError($changes); break; case 'd': $this->viewDiff($path); break; case '?': default: help: $this->io->writeError([' y - discard changes and apply the ' . ($update ? 'update' : 'uninstall'), ' n - abort the ' . ($update ? 'update' : 'uninstall') . ' and let you manually clean things up', ' v - view modified files', ' d - view local modifications (diff)']); if ($update) { $this->io->writeError(' s - stash changes and try to reapply them after the update'); } $this->io->writeError(' ? - print help'); break; } } return \React\Promise\resolve(null); } /** * @inheritDoc */ protected function reapplyChanges(string $path) : void { $path = $this->normalizePath($path); if (!empty($this->hasStashedChanges[$path])) { unset($this->hasStashedChanges[$path]); $this->io->writeError(' Re-applying stashed changes'); if (0 !== $this->process->execute('git stash pop', $output, $path)) { throw new \RuntimeException("Failed to apply stashed changes:\n\n" . $this->process->getErrorOutput()); } } unset($this->hasDiscardedChanges[$path]); } /** * Updates the given path to the given commit ref * * @throws \RuntimeException * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found */ protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion) : ?string { $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? '-f ' : ''; // This uses the "--" sequence to separate branch from file parameters. // // Otherwise git tries the branch name as well as file name. // If the non-existent branch is actually the name of a file, the file // is checked out. $template = 'git checkout ' . $force . '%s -- && git reset --hard %1$s --'; $branch = Preg::replace('{(?:^dev-|(?:\\.x)?-dev$)}i', '', $prettyVersion); $branches = null; if (0 === $this->process->execute('git branch -r', $output, $path)) { $branches = $output; } // check whether non-commitish are branches or tags, and fetch branches with the remote name $gitRef = $reference; if (!Preg::isMatch('{^[a-f0-9]{40}$}', $reference) && null !== $branches && Preg::isMatch('{^\\s+composer/' . \preg_quote($reference) . '$}m', $branches)) { $command = \sprintf('git checkout ' . $force . '-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/' . $reference)); if (0 === $this->process->execute($command, $output, $path)) { return null; } } // try to checkout branch by name and then reset it so it's on the proper branch name if (Preg::isMatch('{^[a-f0-9]{40}$}', $reference)) { // add 'v' in front of the branch if it was stripped when generating the pretty name if (null !== $branches && !Preg::isMatch('{^\\s+composer/' . \preg_quote($branch) . '$}m', $branches) && Preg::isMatch('{^\\s+composer/v' . \preg_quote($branch) . '$}m', $branches)) { $branch = 'v' . $branch; } $command = \sprintf('git checkout %s --', ProcessExecutor::escape($branch)); $fallbackCommand = \sprintf('git checkout ' . $force . '-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/' . $branch)); $resetCommand = \sprintf('git reset --hard %s --', ProcessExecutor::escape($reference)); if (0 === $this->process->execute("({$command} || {$fallbackCommand}) && {$resetCommand}", $output, $path)) { return null; } } $command = \sprintf($template, ProcessExecutor::escape($gitRef)); if (0 === $this->process->execute($command, $output, $path)) { return null; } $exceptionExtra = ''; // reference was not found (prints "fatal: reference is not a tree: $ref") if (\false !== \strpos($this->process->getErrorOutput(), $reference)) { $this->io->writeError(' ' . $reference . ' is gone (history was rewritten?)'); $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe " . ($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated') . '? Run "composer update ' . $package->getPrettyName() . '" to resolve this.'; } throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra)); } protected function updateOriginUrl(string $path, string $url) : void { $this->process->execute(\sprintf('git remote set-url origin -- %s', ProcessExecutor::escape($url)), $output, $path); $this->setPushUrl($path, $url); } protected function setPushUrl(string $path, string $url) : void { // set push url for github projects if (Preg::isMatch('{^(?:https?|git)://' . GitUtil::getGitHubDomainsRegex($this->config) . '/([^/]+)/([^/]+?)(?:\\.git)?$}', $url, $match)) { $protocols = $this->config->get('github-protocols'); $pushUrl = 'git@' . $match[1] . ':' . $match[2] . '/' . $match[3] . '.git'; if (!\in_array('ssh', $protocols, \true)) { $pushUrl = 'https://' . $match[1] . '/' . $match[2] . '/' . $match[3] . '.git'; } $cmd = \sprintf('git remote set-url --push origin -- %s', ProcessExecutor::escape($pushUrl)); $this->process->execute($cmd, $ignoredOutput, $path); } } /** * @inheritDoc */ protected function getCommitLogs(string $fromReference, string $toReference, string $path) : string { $path = $this->normalizePath($path); $command = \sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"' . GitUtil::getNoShowSignatureFlag($this->process), ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference)); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } return $output; } /** * @phpstan-return PromiseInterface * @throws \RuntimeException */ protected function discardChanges(string $path) : PromiseInterface { $path = $this->normalizePath($path); if (0 !== $this->process->execute('git clean -df && git reset --hard', $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:" . $output); } $this->hasDiscardedChanges[$path] = \true; return \React\Promise\resolve(null); } /** * @phpstan-return PromiseInterface * @throws \RuntimeException */ protected function stashChanges(string $path) : PromiseInterface { $path = $this->normalizePath($path); if (0 !== $this->process->execute('git stash --include-untracked', $output, $path)) { throw new \RuntimeException("Could not stash changes\n\n:" . $output); } $this->hasStashedChanges[$path] = \true; return \React\Promise\resolve(null); } /** * @throws \RuntimeException */ protected function viewDiff(string $path) : void { $path = $this->normalizePath($path); if (0 !== $this->process->execute('git diff HEAD', $output, $path)) { throw new \RuntimeException("Could not view diff\n\n:" . $output); } $this->io->writeError($output); } protected function normalizePath(string $path) : string { if (Platform::isWindows() && \strlen($path) > 0) { $basePath = $path; $removed = []; while (!\is_dir($basePath) && $basePath !== '\\') { \array_unshift($removed, \basename($basePath)); $basePath = \dirname($basePath); } if ($basePath === '\\') { return $path; } $path = \rtrim(\realpath($basePath) . '/' . \implode('/', $removed), '/'); } return $path; } /** * @inheritDoc */ protected function hasMetadataRepository(string $path) : bool { $path = $this->normalizePath($path); return \is_dir($path . '/.git'); } protected function getShortHash(string $reference) : string { if (!$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}', $reference)) { return \substr($reference, 0, 10); } return $reference; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use _ContaoManager\Symfony\Component\Process\ExecutableFinder; use _ContaoManager\Symfony\Component\Process\Process; use React\Promise\PromiseInterface; use ZipArchive; /** * @author Jordi Boggiano */ class ZipDownloader extends \Composer\Downloader\ArchiveDownloader { /** @var array */ private static $unzipCommands; /** @var bool */ private static $hasZipArchive; /** @var bool */ private static $isWindows; /** @var ZipArchive|null */ private $zipArchiveObject; // @phpstan-ignore-line helper property that is set via reflection for testing purposes /** * @inheritDoc */ public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = \true) : PromiseInterface { if (null === self::$unzipCommands) { self::$unzipCommands = []; $finder = new ExecutableFinder(); if (Platform::isWindows() && ($cmd = $finder->find('7z', null, ['C:\\Program Files\\7-Zip']))) { self::$unzipCommands[] = ['7z', ProcessExecutor::escape($cmd) . ' x -bb0 -y %s -o%s']; } if ($cmd = $finder->find('unzip')) { self::$unzipCommands[] = ['unzip', ProcessExecutor::escape($cmd) . ' -qq %s -d %s']; } if (!Platform::isWindows() && ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present self::$unzipCommands[] = ['7z', ProcessExecutor::escape($cmd) . ' x -bb0 -y %s -o%s']; } if (!Platform::isWindows() && ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present self::$unzipCommands[] = ['7zz', ProcessExecutor::escape($cmd) . ' x -bb0 -y %s -o%s']; } } $procOpenMissing = \false; if (!\function_exists('proc_open')) { self::$unzipCommands = []; $procOpenMissing = \true; } if (null === self::$hasZipArchive) { self::$hasZipArchive = \class_exists('ZipArchive'); } if (!self::$hasZipArchive && !self::$unzipCommands) { // php.ini path is added to the error message to help users find the correct file $iniMessage = IniHelper::getMessage(); if ($procOpenMissing) { $error = "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n" . $iniMessage; } else { $error = "The zip extension and unzip/7z commands are both missing, skipping.\n" . $iniMessage; } throw new \RuntimeException($error); } if (null === self::$isWindows) { self::$isWindows = Platform::isWindows(); if (!self::$isWindows && !self::$unzipCommands) { if ($procOpenMissing) { $this->io->writeError("proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension."); $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); $this->io->writeError("Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them."); } else { $this->io->writeError("As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension."); $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); $this->io->writeError("Installing 'unzip' or '7z' (21.01+) may remediate them."); } } } return parent::download($package, $path, $prevPackage, $output); } /** * extract $file to $path with "unzip" command * * @param string $file File to extract * @param string $path Path where to extract file * @phpstan-return PromiseInterface */ private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path) : PromiseInterface { static $warned7ZipLinux = \false; // Force Exception throwing if the other alternative extraction method is not available $isLastChance = !self::$hasZipArchive; if (!self::$unzipCommands) { // This was call as the favorite extract way, but is not available // We switch to the alternative return $this->extractWithZipArchive($package, $file, $path); } $commandSpec = \reset(self::$unzipCommands); $command = \sprintf($commandSpec[1], ProcessExecutor::escape($file), ProcessExecutor::escape($path)); // normalize separators to backslashes to avoid problems with 7-zip on windows // see https://github.com/composer/composer/issues/10058 if (Platform::isWindows()) { $command = \sprintf($commandSpec[1], ProcessExecutor::escape(\strtr($file, '/', '\\')), ProcessExecutor::escape(\strtr($path, '/', '\\'))); } $executable = $commandSpec[0]; if (!$warned7ZipLinux && !Platform::isWindows() && \in_array($executable, ['7z', '7zz'], \true)) { $warned7ZipLinux = \true; if (0 === $this->process->execute($executable, $output)) { if (Preg::isMatchStrictGroups('{^\\s*7-Zip(?: \\[64\\])? ([0-9.]+)}', $output, $match) && \version_compare($match[1], '21.01', '<')) { $this->io->writeError(' Unzipping using ' . $executable . ' ' . $match[1] . ' may result in incorrect file permissions. Install ' . $executable . ' 21.01+ or unzip to ensure you get correct permissions.'); } } } $io = $this->io; $tryFallback = function (\Throwable $processError) use($isLastChance, $io, $file, $path, $package, $executable) : \React\Promise\PromiseInterface { if ($isLastChance) { throw $processError; } if (!\is_file($file)) { $io->writeError(' ' . $processError->getMessage() . ''); $io->writeError(' This most likely is due to a custom installer plugin not handling the returned Promise from the downloader'); $io->writeError(' See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix'); } else { $io->writeError(' ' . $processError->getMessage() . ''); $io->writeError(' The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)'); $io->writeError(' Unzip with ' . $executable . ' command failed, falling back to ZipArchive class'); // additional debug data to try to figure out GH actions issues https://github.com/composer/composer/issues/11148 if (Platform::getEnv('GITHUB_ACTIONS') !== \false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === \false) { $io->writeError(' Additional debug info, please report to https://github.com/composer/composer/issues/11148 if you see this:'); $io->writeError('File size: ' . @\filesize($file)); $io->writeError('File SHA1: ' . \hash_file('sha1', $file)); $io->writeError('First 100 bytes (hex): ' . \bin2hex(\substr((string) \file_get_contents($file), 0, 100))); $io->writeError('Last 100 bytes (hex): ' . \bin2hex(\substr((string) \file_get_contents($file), -100))); if (\strlen((string) $package->getDistUrl()) > 0) { $io->writeError('Origin URL: ' . $this->processUrl($package, (string) $package->getDistUrl())); $io->writeError('Response Headers: ' . \json_encode(\Composer\Downloader\FileDownloader::$responseHeaders[$package->getName()] ?? [])); } } } return $this->extractWithZipArchive($package, $file, $path); }; try { $promise = $this->process->executeAsync($command); return $promise->then(function (Process $process) use($tryFallback, $command, $package, $file) { if (!$process->isSuccessful()) { if (isset($this->cleanupExecuted[$package->getName()])) { throw new \RuntimeException('Failed to extract ' . $package->getName() . ' as the installation was aborted by another package operation.'); } $output = $process->getErrorOutput(); $output = \str_replace(', ' . $file . '.zip or ' . $file . '.ZIP', '', $output); return $tryFallback(new \RuntimeException('Failed to extract ' . $package->getName() . ': (' . $process->getExitCode() . ') ' . $command . "\n\n" . $output)); } }); } catch (\Throwable $e) { return $tryFallback($e); } } /** * extract $file to $path with ZipArchive * * @param string $file File to extract * @param string $path Path where to extract file * @phpstan-return PromiseInterface */ private function extractWithZipArchive(PackageInterface $package, string $file, string $path) : PromiseInterface { $processError = null; $zipArchive = $this->zipArchiveObject ?: new ZipArchive(); try { if (!\file_exists($file) || ($filesize = \filesize($file)) === \false || $filesize === 0) { $retval = -1; } else { $retval = $zipArchive->open($file); } if (\true === $retval) { $extractResult = $zipArchive->extractTo($path); if (\true === $extractResult) { $zipArchive->close(); return \React\Promise\resolve(null); } $processError = new \RuntimeException(\rtrim("There was an error extracting the ZIP file, it is either corrupted or using an invalid format.\n")); } else { $processError = new \UnexpectedValueException(\rtrim($this->getErrorMessage($retval, $file) . "\n"), $retval); } } catch (\ErrorException $e) { $processError = new \RuntimeException('The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems): ' . $e->getMessage(), 0, $e); } catch (\Throwable $e) { $processError = $e; } throw $processError; } /** * extract $file to $path * * @param string $file File to extract * @param string $path Path where to extract file */ protected function extract(PackageInterface $package, string $file, string $path) : PromiseInterface { return $this->extractWithSystemUnzip($package, $file, $path); } /** * Give a meaningful error message to the user. */ protected function getErrorMessage(int $retval, string $file) : string { switch ($retval) { case ZipArchive::ER_EXISTS: return \sprintf("File '%s' already exists.", $file); case ZipArchive::ER_INCONS: return \sprintf("Zip archive '%s' is inconsistent.", $file); case ZipArchive::ER_INVAL: return \sprintf("Invalid argument (%s)", $file); case ZipArchive::ER_MEMORY: return \sprintf("Malloc failure (%s)", $file); case ZipArchive::ER_NOENT: return \sprintf("No such zip file: '%s'", $file); case ZipArchive::ER_NOZIP: return \sprintf("'%s' is not a zip archive.", $file); case ZipArchive::ER_OPEN: return \sprintf("Can't open zip file: %s", $file); case ZipArchive::ER_READ: return \sprintf("Zip read error (%s)", $file); case ZipArchive::ER_SEEK: return \sprintf("Zip seek error (%s)", $file); case -1: return \sprintf("'%s' is a corrupted zip archive (0 bytes), try again.", $file); default: return \sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Downloader; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** * GZip archive downloader. * * @author Pavel Puchkin */ class GzipDownloader extends \Composer\Downloader\ArchiveDownloader { protected function extract(PackageInterface $package, string $file, string $path) : PromiseInterface { $filename = \pathinfo(\parse_url(\strtr((string) $package->getDistUrl(), '\\', '/'), \PHP_URL_PATH), \PATHINFO_FILENAME); $targetFilepath = $path . \DIRECTORY_SEPARATOR . $filename; // Try to use gunzip on *nix if (!Platform::isWindows()) { $command = 'gzip -cd -- ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath); if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } if (\extension_loaded('zlib')) { // Fallback to using the PHP extension. $this->extractUsingExt($file, $targetFilepath); return \React\Promise\resolve(null); } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } // Windows version of PHP has built-in support of gzip functions $this->extractUsingExt($file, $targetFilepath); return \React\Promise\resolve(null); } private function extractUsingExt(string $file, string $targetFilepath) : void { $archiveFile = \gzopen($file, 'rb'); $targetFile = \fopen($targetFilepath, 'wb'); while ($string = \gzread($archiveFile, 4096)) { \fwrite($targetFile, $string, Platform::strlen($string)); } \gzclose($archiveFile); \fclose($targetFile); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Advisory\Auditor; use Composer\Config\ConfigSourceInterface; use Composer\Downloader\TransportException; use Composer\IO\IOInterface; use Composer\Pcre\Preg; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** * @author Jordi Boggiano */ class Config { public const SOURCE_DEFAULT = 'default'; public const SOURCE_COMMAND = 'command'; public const SOURCE_UNKNOWN = 'unknown'; public const RELATIVE_PATHS = 1; /** @var array */ public static $defaultConfig = [ 'process-timeout' => 300, 'use-include-path' => \false, 'allow-plugins' => [], 'use-parent-dir' => 'prompt', 'preferred-install' => 'dist', 'audit' => ['ignore' => [], 'abandoned' => Auditor::ABANDONED_FAIL], 'notify-on-install' => \true, 'github-protocols' => ['https', 'ssh', 'git'], 'gitlab-protocol' => null, 'vendor-dir' => 'vendor', 'bin-dir' => '{$vendor-dir}/bin', 'cache-dir' => '{$home}/cache', 'data-dir' => '{$home}', 'cache-files-dir' => '{$cache-dir}/files', 'cache-repo-dir' => '{$cache-dir}/repo', 'cache-vcs-dir' => '{$cache-dir}/vcs', 'cache-ttl' => 15552000, // 6 months 'cache-files-ttl' => null, // fallback to cache-ttl 'cache-files-maxsize' => '300MiB', 'cache-read-only' => \false, 'bin-compat' => 'auto', 'discard-changes' => \false, 'autoloader-suffix' => null, 'sort-packages' => \false, 'optimize-autoloader' => \false, 'classmap-authoritative' => \false, 'apcu-autoloader' => \false, 'prepend-autoloader' => \true, 'github-domains' => ['github.com'], 'bitbucket-expose-hostname' => \true, 'disable-tls' => \false, 'secure-http' => \true, 'secure-svn-domains' => [], 'cafile' => null, 'capath' => null, 'github-expose-hostname' => \true, 'gitlab-domains' => ['gitlab.com'], 'store-auths' => 'prompt', 'platform' => [], 'archive-format' => 'tar', 'archive-dir' => '.', 'htaccess-protect' => \true, 'use-github-api' => \true, 'lock' => \true, 'platform-check' => 'php-only', 'bitbucket-oauth' => [], 'github-oauth' => [], 'gitlab-oauth' => [], 'gitlab-token' => [], 'http-basic' => [], 'bearer' => [], ]; /** @var array */ public static $defaultRepositories = ['packagist.org' => ['type' => 'composer', 'url' => 'https://repo.packagist.org']]; /** @var array */ private $config; /** @var ?string */ private $baseDir; /** @var array */ private $repositories; /** @var ConfigSourceInterface */ private $configSource; /** @var ConfigSourceInterface */ private $authConfigSource; /** @var ConfigSourceInterface|null */ private $localAuthConfigSource = null; /** @var bool */ private $useEnvironment; /** @var array */ private $warnedHosts = []; /** @var array */ private $sslVerifyWarnedHosts = []; /** @var array */ private $sourceOfConfigValue = []; /** * @param bool $useEnvironment Use COMPOSER_ environment variables to replace config settings * @param ?string $baseDir Optional base directory of the config */ public function __construct(bool $useEnvironment = \true, ?string $baseDir = null) { // load defaults $this->config = static::$defaultConfig; $this->repositories = static::$defaultRepositories; $this->useEnvironment = (bool) $useEnvironment; $this->baseDir = \is_string($baseDir) && '' !== $baseDir ? $baseDir : null; foreach ($this->config as $configKey => $configValue) { $this->setSourceOfConfigValue($configValue, $configKey, self::SOURCE_DEFAULT); } foreach ($this->repositories as $configKey => $configValue) { $this->setSourceOfConfigValue($configValue, 'repositories.' . $configKey, self::SOURCE_DEFAULT); } } public function setConfigSource(ConfigSourceInterface $source) : void { $this->configSource = $source; } public function getConfigSource() : ConfigSourceInterface { return $this->configSource; } public function setAuthConfigSource(ConfigSourceInterface $source) : void { $this->authConfigSource = $source; } public function getAuthConfigSource() : ConfigSourceInterface { return $this->authConfigSource; } public function setLocalAuthConfigSource(ConfigSourceInterface $source) : void { $this->localAuthConfigSource = $source; } public function getLocalAuthConfigSource() : ?ConfigSourceInterface { return $this->localAuthConfigSource; } /** * Merges new config values with the existing ones (overriding) * * @param array{config?: array, repositories?: array} $config */ public function merge(array $config, string $source = self::SOURCE_UNKNOWN) : void { // override defaults with given config if (!empty($config['config']) && \is_array($config['config'])) { foreach ($config['config'] as $key => $val) { if (\in_array($key, ['bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer'], \true) && isset($this->config[$key])) { $this->config[$key] = \array_merge($this->config[$key], $val); $this->setSourceOfConfigValue($val, $key, $source); } elseif (\in_array($key, ['allow-plugins'], \true) && isset($this->config[$key]) && \is_array($this->config[$key]) && \is_array($val)) { // merging $val first to get the local config on top of the global one, then appending the global config, // then merging local one again to make sure the values from local win over global ones for keys present in both $this->config[$key] = \array_merge($val, $this->config[$key], $val); $this->setSourceOfConfigValue($val, $key, $source); } elseif (\in_array($key, ['gitlab-domains', 'github-domains'], \true) && isset($this->config[$key])) { $this->config[$key] = \array_unique(\array_merge($this->config[$key], $val)); $this->setSourceOfConfigValue($val, $key, $source); } elseif ('preferred-install' === $key && isset($this->config[$key])) { if (\is_array($val) || \is_array($this->config[$key])) { if (\is_string($val)) { $val = ['*' => $val]; } if (\is_string($this->config[$key])) { $this->config[$key] = ['*' => $this->config[$key]]; $this->sourceOfConfigValue[$key . '*'] = $source; } $this->config[$key] = \array_merge($this->config[$key], $val); $this->setSourceOfConfigValue($val, $key, $source); // the full match pattern needs to be last if (isset($this->config[$key]['*'])) { $wildcard = $this->config[$key]['*']; unset($this->config[$key]['*']); $this->config[$key]['*'] = $wildcard; } } else { $this->config[$key] = $val; $this->setSourceOfConfigValue($val, $key, $source); } } elseif ('audit' === $key) { $currentIgnores = $this->config['audit']['ignore']; $this->config[$key] = \array_merge($this->config['audit'], $val); $this->setSourceOfConfigValue($val, $key, $source); $this->config['audit']['ignore'] = \array_merge($currentIgnores, $val['ignore'] ?? []); } else { $this->config[$key] = $val; $this->setSourceOfConfigValue($val, $key, $source); } } } if (!empty($config['repositories']) && \is_array($config['repositories'])) { $this->repositories = \array_reverse($this->repositories, \true); $newRepos = \array_reverse($config['repositories'], \true); foreach ($newRepos as $name => $repository) { // disable a repository by name if (\false === $repository) { $this->disableRepoByName((string) $name); continue; } // disable a repository with an anonymous {"name": false} repo if (\is_array($repository) && 1 === \count($repository) && \false === \current($repository)) { $this->disableRepoByName((string) \key($repository)); continue; } // auto-deactivate the default packagist.org repo if it gets redefined if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && Preg::isMatch('{^https?://(?:[a-z0-9-.]+\\.)?packagist.org(/|$)}', $repository['url'])) { $this->disableRepoByName('packagist.org'); } // store repo if (\is_int($name)) { $this->repositories[] = $repository; $this->setSourceOfConfigValue($repository, 'repositories.' . \array_search($repository, $this->repositories, \true), $source); } else { if ($name === 'packagist') { // BC support for default "packagist" named repo $this->repositories[$name . '.org'] = $repository; $this->setSourceOfConfigValue($repository, 'repositories.' . $name . '.org', $source); } else { $this->repositories[$name] = $repository; $this->setSourceOfConfigValue($repository, 'repositories.' . $name, $source); } } } $this->repositories = \array_reverse($this->repositories, \true); } } /** * @return array */ public function getRepositories() : array { return $this->repositories; } /** * Returns a setting * * @param int $flags Options (see class constants) * @throws \RuntimeException * * @return mixed */ public function get(string $key, int $flags = 0) { switch ($key) { // strings/paths with env var and {$refs} support case 'vendor-dir': case 'bin-dir': case 'process-timeout': case 'data-dir': case 'cache-dir': case 'cache-files-dir': case 'cache-repo-dir': case 'cache-vcs-dir': case 'cafile': case 'capath': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . \strtoupper(\strtr($key, '-', '_')); $val = $this->getComposerEnv($env); if ($val !== \false) { $this->setSourceOfConfigValue($val, $key, $env); } if ($key === 'process-timeout') { return \max(0, \false !== $val ? (int) $val : $this->config[$key]); } $val = \rtrim((string) $this->process(\false !== $val ? $val : $this->config[$key], $flags), '/\\'); $val = Platform::expandPath($val); if (\substr($key, -4) !== '-dir') { return $val; } return ($flags & self::RELATIVE_PATHS) === self::RELATIVE_PATHS ? $val : $this->realpath($val); // booleans with env var support case 'cache-read-only': case 'htaccess-protect': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . \strtoupper(\strtr($key, '-', '_')); $val = $this->getComposerEnv($env); if (\false === $val) { $val = $this->config[$key]; } else { $this->setSourceOfConfigValue($val, $key, $env); } return $val !== 'false' && (bool) $val; // booleans without env var support case 'disable-tls': case 'secure-http': case 'use-github-api': case 'lock': // special case for secure-http if ($key === 'secure-http' && $this->get('disable-tls') === \true) { return \false; } return $this->config[$key] !== 'false' && (bool) $this->config[$key]; // ints without env var support case 'cache-ttl': return \max(0, (int) $this->config[$key]); // numbers with kb/mb/gb support, without env var support case 'cache-files-maxsize': if (!Preg::isMatch('/^\\s*([0-9.]+)\\s*(?:([kmg])(?:i?b)?)?\\s*$/i', (string) $this->config[$key], $matches)) { throw new \RuntimeException("Could not parse the value of '{$key}': {$this->config[$key]}"); } $size = (float) $matches[1]; if (isset($matches[2])) { switch (\strtolower($matches[2])) { case 'g': $size *= 1024; // intentional fallthrough // no break case 'm': $size *= 1024; // intentional fallthrough // no break case 'k': $size *= 1024; break; } } return \max(0, (int) $size); // special cases below case 'cache-files-ttl': if (isset($this->config[$key])) { return \max(0, (int) $this->config[$key]); } return $this->get('cache-ttl'); case 'home': return \rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\'); case 'bin-compat': $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; if (!\in_array($value, ['auto', 'full', 'proxy', 'symlink'])) { throw new \RuntimeException("Invalid value for 'bin-compat': {$value}. Expected auto, full or proxy"); } if ($value === 'symlink') { \trigger_error('config.bin-compat "symlink" is deprecated since Composer 2.2, use auto, full (for Windows compatibility) or proxy instead.', \E_USER_DEPRECATED); } return $value; case 'discard-changes': $env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES'); if ($env !== \false) { if (!\in_array($env, ['stash', 'true', 'false', '1', '0'], \true)) { throw new \RuntimeException("Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"); } if ('stash' === $env) { return 'stash'; } // convert string value to bool return $env !== 'false' && (bool) $env; } if (!\in_array($this->config[$key], [\true, \false, 'stash'], \true)) { throw new \RuntimeException("Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash"); } return $this->config[$key]; case 'github-protocols': $protos = $this->config['github-protocols']; if ($this->config['secure-http'] && \false !== ($index = \array_search('git', $protos))) { unset($protos[$index]); } if (\reset($protos) === 'http') { throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"'); } return $protos; case 'autoloader-suffix': if ($this->config[$key] === '') { // we need to guarantee null or non-empty-string return null; } return $this->process($this->config[$key], $flags); case 'audit': $result = $this->config[$key]; $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED'); if (\false !== $abandonedEnv) { if (!\in_array($abandonedEnv, $validChoices = [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], \true)) { throw new \RuntimeException("Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected " . Auditor::ABANDONED_IGNORE . ", " . Auditor::ABANDONED_REPORT . " or " . Auditor::ABANDONED_FAIL); } $result['abandoned'] = $abandonedEnv; } return $result; default: if (!isset($this->config[$key])) { return null; } return $this->process($this->config[$key], $flags); } } /** * @return array */ public function all(int $flags = 0) : array { $all = ['repositories' => $this->getRepositories()]; foreach (\array_keys($this->config) as $key) { $all['config'][$key] = $this->get($key, $flags); } return $all; } public function getSourceOfValue(string $key) : string { $this->get($key); return $this->sourceOfConfigValue[$key] ?? self::SOURCE_UNKNOWN; } /** * @param mixed $configValue */ private function setSourceOfConfigValue($configValue, string $path, string $source) : void { $this->sourceOfConfigValue[$path] = $source; if (\is_array($configValue)) { foreach ($configValue as $key => $value) { $this->setSourceOfConfigValue($value, $path . '.' . $key, $source); } } } /** * @return array */ public function raw() : array { return ['repositories' => $this->getRepositories(), 'config' => $this->config]; } /** * Checks whether a setting exists */ public function has(string $key) : bool { return \array_key_exists($key, $this->config); } /** * Replaces {$refs} inside a config string * * @param string|mixed $value a config string that can contain {$refs-to-other-config} * @param int $flags Options (see class constants) * * @return string|mixed */ private function process($value, int $flags) { if (!\is_string($value)) { return $value; } return Preg::replaceCallback('#\\{\\$(.+)\\}#', function ($match) use($flags) { \assert(\is_string($match[1])); return $this->get($match[1], $flags); }, $value); } /** * Turns relative paths in absolute paths without realpath() * * Since the dirs might not exist yet we can not call realpath or it will fail. */ private function realpath(string $path) : string { if (Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://|\\\\\\\\)}i', $path)) { return $path; } return $this->baseDir ? $this->baseDir . '/' . $path : $path; } /** * Reads the value of a Composer environment variable * * This should be used to read COMPOSER_ environment variables * that overload config values. * * @return string|false */ private function getComposerEnv(string $var) { if ($this->useEnvironment) { return Platform::getEnv($var); } return \false; } private function disableRepoByName(string $name) : void { if (isset($this->repositories[$name])) { unset($this->repositories[$name]); } elseif ($name === 'packagist') { // BC support for default "packagist" named repo unset($this->repositories['packagist.org']); } } /** * Validates that the passed URL is allowed to be used by current config, or throws an exception. * * @param IOInterface $io * @param mixed[] $repoOptions */ public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []) : void { // Return right away if the URL is malformed or custom (see issue #5173) if (\false === \filter_var($url, \FILTER_VALIDATE_URL)) { return; } // Extract scheme and throw exception on known insecure protocols $scheme = \parse_url($url, \PHP_URL_SCHEME); $hostname = \parse_url($url, \PHP_URL_HOST); if (\in_array($scheme, ['http', 'git', 'ftp', 'svn'])) { if ($this->get('secure-http')) { if ($scheme === 'svn') { if (\in_array($hostname, $this->get('secure-svn-domains'), \true)) { return; } throw new TransportException("Your configuration does not allow connections to {$url}. See https://getcomposer.org/doc/06-config.md#secure-svn-domains for details."); } throw new TransportException("Your configuration does not allow connections to {$url}. See https://getcomposer.org/doc/06-config.md#secure-http for details."); } if ($io !== null) { if (\is_string($hostname)) { if (!isset($this->warnedHosts[$hostname])) { $io->writeError("Warning: Accessing {$hostname} over {$scheme} which is an insecure protocol."); } $this->warnedHosts[$hostname] = \true; } } } if ($io !== null && \is_string($hostname) && !isset($this->sslVerifyWarnedHosts[$hostname])) { $warning = null; if (isset($repoOptions['ssl']['verify_peer']) && !(bool) $repoOptions['ssl']['verify_peer']) { $warning = 'verify_peer'; } if (isset($repoOptions['ssl']['verify_peer_name']) && !(bool) $repoOptions['ssl']['verify_peer_name']) { $warning = $warning === null ? 'verify_peer_name' : $warning . ' and verify_peer_name'; } if ($warning !== null) { $io->writeError("Warning: Accessing {$hostname} with {$warning} disabled."); $this->sslVerifyWarnedHosts[$hostname] = \true; } } } /** * Used by long-running custom scripts in composer.json * * "scripts": { * "watch": [ * "Composer\\Config::disableProcessTimeout", * "vendor/bin/long-running-script --watch" * ] * } */ public static function disableProcessTimeout() : void { // Override global timeout set earlier by environment or config ProcessExecutor::setTimeout(0); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Script; use Composer\Composer; use Composer\IO\IOInterface; use Composer\EventDispatcher\Event as BaseEvent; /** * The script event class * * @author François Pluchino * @author Nils Adermann */ class Event extends BaseEvent { /** * @var Composer The composer instance */ private $composer; /** * @var IOInterface The IO instance */ private $io; /** * @var bool Dev mode flag */ private $devMode; /** * @var BaseEvent|null */ private $originatingEvent; /** * Constructor. * * @param string $name The event name * @param Composer $composer The composer object * @param IOInterface $io The IOInterface object * @param bool $devMode Whether or not we are in dev mode * @param array $args Arguments passed by the user * @param mixed[] $flags Optional flags to pass data not as argument */ public function __construct(string $name, Composer $composer, IOInterface $io, bool $devMode = \false, array $args = [], array $flags = []) { parent::__construct($name, $args, $flags); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; } /** * Returns the composer instance. */ public function getComposer() : Composer { return $this->composer; } /** * Returns the IO instance. */ public function getIO() : IOInterface { return $this->io; } /** * Return the dev mode flag */ public function isDevMode() : bool { return $this->devMode; } /** * Set the originating event. * * @return ?BaseEvent */ public function getOriginatingEvent() : ?BaseEvent { return $this->originatingEvent; } /** * Set the originating event. * * @return $this */ public function setOriginatingEvent(BaseEvent $event) : self { $this->originatingEvent = $this->calculateOriginatingEvent($event); return $this; } /** * Returns the upper-most event in chain. */ private function calculateOriginatingEvent(BaseEvent $event) : BaseEvent { if ($event instanceof \Composer\Script\Event && $event->getOriginatingEvent()) { return $this->calculateOriginatingEvent($event->getOriginatingEvent()); } return $event; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Script; /** * The Script Events. * * @author François Pluchino * @author Jordi Boggiano */ class ScriptEvents { /** * The PRE_INSTALL_CMD event occurs before the install command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const PRE_INSTALL_CMD = 'pre-install-cmd'; /** * The POST_INSTALL_CMD event occurs after the install command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_INSTALL_CMD = 'post-install-cmd'; /** * The PRE_UPDATE_CMD event occurs before the update command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const PRE_UPDATE_CMD = 'pre-update-cmd'; /** * The POST_UPDATE_CMD event occurs after the update command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_UPDATE_CMD = 'post-update-cmd'; /** * The PRE_STATUS_CMD event occurs before the status command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const PRE_STATUS_CMD = 'pre-status-cmd'; /** * The POST_STATUS_CMD event occurs after the status command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_STATUS_CMD = 'post-status-cmd'; /** * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump'; /** * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_AUTOLOAD_DUMP = 'post-autoload-dump'; /** * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install'; /** * The POST_CREATE_PROJECT event occurs after the create-project command has been executed. * Note: Event occurs after POST_INSTALL_CMD * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd'; /** * The PRE_ARCHIVE_CMD event occurs before the update command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const PRE_ARCHIVE_CMD = 'pre-archive-cmd'; /** * The POST_ARCHIVE_CMD event occurs after the status command is executed. * * The event listener method receives a Composer\Script\Event instance. * * @var string */ public const POST_ARCHIVE_CMD = 'post-archive-cmd'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Question; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Question\Question; /** * Represents a yes/no question * Enforces strict responses rather than non-standard answers counting as default * Based on Symfony\Component\Console\Question\ConfirmationQuestion * * @author Theo Tonge */ class StrictConfirmationQuestion extends Question { /** @var non-empty-string */ private $trueAnswerRegex; /** @var non-empty-string */ private $falseAnswerRegex; /** * Constructor.s * * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param non-empty-string $trueAnswerRegex A regex to match the "yes" answer * @param non-empty-string $falseAnswerRegex A regex to match the "no" answer */ public function __construct(string $question, bool $default = \true, string $trueAnswerRegex = '/^y(?:es)?$/i', string $falseAnswerRegex = '/^no?$/i') { parent::__construct($question, (bool) $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->falseAnswerRegex = $falseAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); $this->setValidator($this->getDefaultValidator()); } /** * Returns the default answer normalizer. */ private function getDefaultNormalizer() : callable { $default = $this->getDefault(); $trueRegex = $this->trueAnswerRegex; $falseRegex = $this->falseAnswerRegex; return static function ($answer) use($default, $trueRegex, $falseRegex) { if (\is_bool($answer)) { return $answer; } if (empty($answer) && !empty($default)) { return $default; } if (Preg::isMatch($trueRegex, $answer)) { return \true; } if (Preg::isMatch($falseRegex, $answer)) { return \false; } return null; }; } /** * Returns the default answer validator. */ private function getDefaultValidator() : callable { return static function ($answer) : bool { if (!\is_bool($answer)) { throw new InvalidArgumentException('Please answer yes, y, no, or n.'); } return $answer; }; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Package\Locker; use Composer\Pcre\Preg; use Composer\Plugin\PluginManager; use Composer\Downloader\DownloadManager; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Archiver\ArchiveManager; /** * @author Jordi Boggiano * @author Konstantin Kudryashiv * @author Nils Adermann */ class Composer extends \Composer\PartialComposer { /* * Examples of the following constants in the various configurations they can be in * * You are probably better off using Composer::getVersion() though as that will always return something usable * * releases (phar): * const VERSION = '1.8.2'; * const BRANCH_ALIAS_VERSION = ''; * const RELEASE_DATE = '2019-01-29 15:00:53'; * const SOURCE_VERSION = ''; * * snapshot builds (phar): * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7'; * const BRANCH_ALIAS_VERSION = '1.9-dev'; * const RELEASE_DATE = '2019-02-20 07:43:56'; * const SOURCE_VERSION = ''; * * source (git clone): * const VERSION = '@package_version@'; * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; * const RELEASE_DATE = '@release_date@'; * const SOURCE_VERSION = '1.8-dev+source'; * * @see getVersion() */ public const VERSION = '2.7.1'; public const BRANCH_ALIAS_VERSION = ''; public const RELEASE_DATE = '2024-02-09 15:26:28'; public const SOURCE_VERSION = ''; /** * Version number of the internal composer-runtime-api package * * This is used to version features available to projects at runtime * like the platform-check file, the Composer\InstalledVersions class * and possibly others in the future. * * @var string */ public const RUNTIME_API_VERSION = '2.2.2'; public static function getVersion() : string { // no replacement done, this must be a source checkout if (self::VERSION === '@package_version' . '@') { return self::SOURCE_VERSION; } // we have a branch alias and version is a commit id, this must be a snapshot build if (self::BRANCH_ALIAS_VERSION !== '' && Preg::isMatch('{^[a-f0-9]{40}$}', self::VERSION)) { return self::BRANCH_ALIAS_VERSION . '+' . self::VERSION; } return self::VERSION; } /** * @var Locker */ private $locker; /** * @var Downloader\DownloadManager */ private $downloadManager; /** * @var Plugin\PluginManager */ private $pluginManager; /** * @var Autoload\AutoloadGenerator */ private $autoloadGenerator; /** * @var ArchiveManager */ private $archiveManager; public function setLocker(Locker $locker) : void { $this->locker = $locker; } public function getLocker() : Locker { return $this->locker; } public function setDownloadManager(DownloadManager $manager) : void { $this->downloadManager = $manager; } public function getDownloadManager() : DownloadManager { return $this->downloadManager; } public function setArchiveManager(ArchiveManager $manager) : void { $this->archiveManager = $manager; } public function getArchiveManager() : ArchiveManager { return $this->archiveManager; } public function setPluginManager(PluginManager $manager) : void { $this->pluginManager = $manager; } public function getPluginManager() : PluginManager { return $this->pluginManager; } public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator) : void { $this->autoloadGenerator = $autoloadGenerator; } public function getAutoloadGenerator() : AutoloadGenerator { return $this->autoloadGenerator; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use Exception; /** * @author Jordi Boggiano */ class JsonValidationException extends Exception { /** * @var string[] */ protected $errors; /** * @param string[] $errors */ public function __construct(string $message, array $errors = [], ?Exception $previous = null) { $this->errors = $errors; parent::__construct((string) $message, 0, $previous); } /** * @return string[] */ public function getErrors() : array { return $this->errors; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use Composer\Pcre\Preg; /** * Formats json strings used for php < 5.4 because the json_encode doesn't * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE * in these versions * * @author Konstantin Kudryashiv * @author Jordi Boggiano * * @deprecated Use json_encode or JsonFile::encode() with modern JSON_* flags to configure formatting - this class will be removed in 3.0 */ class JsonFormatter { /** * This code is based on the function found at: * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ * * Originally licensed under MIT by Dave Perrett * * @param bool $unescapeUnicode Un escape unicode * @param bool $unescapeSlashes Un escape slashes */ public static function format(string $json, bool $unescapeUnicode, bool $unescapeSlashes) : string { $result = ''; $pos = 0; $strLen = \strlen($json); $indentStr = ' '; $newLine = "\n"; $outOfQuotes = \true; $buffer = ''; $noescape = \true; for ($i = 0; $i < $strLen; $i++) { // Grab the next character in the string $char = \substr($json, $i, 1); // Are we inside a quoted string? if ('"' === $char && $noescape) { $outOfQuotes = !$outOfQuotes; } if (!$outOfQuotes) { $buffer .= $char; $noescape = '\\' === $char ? !$noescape : \true; continue; } if ('' !== $buffer) { if ($unescapeSlashes) { $buffer = \str_replace('\\/', '/', $buffer); } if ($unescapeUnicode && \function_exists('mb_convert_encoding')) { // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match) { \assert(\is_string($match[1])); \assert(\is_string($match[2])); $l = \strlen($match[1]); if ($l % 2) { $code = \hexdec($match[2]); // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped // see https://github.com/composer/composer/issues/7510 if (0xd800 <= $code && 0xdfff >= $code) { return $match[0]; } return \str_repeat('\\', $l - 1) . \mb_convert_encoding(\pack('H*', $match[2]), 'UTF-8', 'UCS-2BE'); } return $match[0]; }, $buffer); } $result .= $buffer . $char; $buffer = ''; continue; } if (':' === $char) { // Add a space after the : character $char .= ' '; } elseif ('}' === $char || ']' === $char) { $pos--; $prevChar = \substr($json, $i - 1, 1); if ('{' !== $prevChar && '[' !== $prevChar) { // If this character is the end of an element, // output a new line and indent the next line $result .= $newLine; $result .= \str_repeat($indentStr, $pos); } else { // Collapse empty {} and [] $result = \rtrim($result); } } $result .= $char; // If the last character was the beginning of an element, // output a new line and indent the next line if (',' === $char || '{' === $char || '[' === $char) { $result .= $newLine; if ('{' === $char || '[' === $char) { $pos++; } $result .= \str_repeat($indentStr, $pos); } } return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use _ContaoManager\JsonSchema\Validator; use _ContaoManager\Seld\JsonLint\JsonParser; use _ContaoManager\Seld\JsonLint\ParsingException; use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; /** * Reads/writes json files. * * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class JsonFile { public const LAX_SCHEMA = 1; public const STRICT_SCHEMA = 2; public const AUTH_SCHEMA = 3; /** @deprecated Use \JSON_UNESCAPED_SLASHES */ public const JSON_UNESCAPED_SLASHES = 64; /** @deprecated Use \JSON_PRETTY_PRINT */ public const JSON_PRETTY_PRINT = 128; /** @deprecated Use \JSON_UNESCAPED_UNICODE */ public const JSON_UNESCAPED_UNICODE = 256; public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; public const INDENT_DEFAULT = ' '; /** @var string */ private $path; /** @var ?HttpDownloader */ private $httpDownloader; /** @var ?IOInterface */ private $io; /** @var string */ private $indent = self::INDENT_DEFAULT; /** * Initializes json file reader/parser. * * @param string $path path to a lockfile * @param ?HttpDownloader $httpDownloader required for loading http/https json files * @param ?IOInterface $io * @throws \InvalidArgumentException */ public function __construct(string $path, ?HttpDownloader $httpDownloader = null, ?IOInterface $io = null) { $this->path = $path; if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) { throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed'); } $this->httpDownloader = $httpDownloader; $this->io = $io; } public function getPath() : string { return $this->path; } /** * Checks whether json file exists. */ public function exists() : bool { return \is_file($this->path); } /** * Reads json file. * * @throws ParsingException * @throws \RuntimeException * @return mixed */ public function read() { try { if ($this->httpDownloader) { $json = $this->httpDownloader->get($this->path)->getBody(); } else { if (!Filesystem::isReadable($this->path)) { throw new \RuntimeException('The file "' . $this->path . '" is not readable.'); } if ($this->io && $this->io->isDebug()) { $realpathInfo = ''; $realpath = \realpath($this->path); if (\false !== $realpath && $realpath !== $this->path) { $realpathInfo = ' (' . $realpath . ')'; } $this->io->writeError('Reading ' . $this->path . $realpathInfo); } $json = \file_get_contents($this->path); } } catch (TransportException $e) { throw new \RuntimeException($e->getMessage(), 0, $e); } catch (\Exception $e) { throw new \RuntimeException('Could not read ' . $this->path . "\n\n" . $e->getMessage()); } if ($json === \false) { throw new \RuntimeException('Could not read ' . $this->path); } $this->indent = self::detectIndenting($json); return static::parseJson($json, $this->path); } /** * Writes json file. * * @param mixed[] $hash writes hash into json file * @param int $options json_encode options * @throws \UnexpectedValueException|\Exception * @return void */ public function write(array $hash, int $options = \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE) { if ($this->path === 'php://memory') { \file_put_contents($this->path, static::encode($hash, $options, $this->indent)); return; } $dir = \dirname($this->path); if (!\is_dir($dir)) { if (\file_exists($dir)) { throw new \UnexpectedValueException(\realpath($dir) . ' exists and is not a directory.'); } if (!@\mkdir($dir, 0777, \true)) { throw new \UnexpectedValueException($dir . ' does not exist and could not be created.'); } } $retries = 3; while ($retries--) { try { $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent) . ($options & \JSON_PRETTY_PRINT ? "\n" : '')); break; } catch (\Exception $e) { if ($retries > 0) { \usleep(500000); continue; } throw $e; } } } /** * Modify file properties only if content modified * * @return int|false */ private function filePutContentsIfModified(string $path, string $content) { $currentContent = @\file_get_contents($path); if (\false === $currentContent || $currentContent !== $content) { return \file_put_contents($path, $content); } return 0; } /** * Validates the schema of the current json file according to composer-schema.json rules * * @param int $schema a JsonFile::*_SCHEMA constant * @param string|null $schemaFile a path to the schema file * @throws JsonValidationException * @throws ParsingException * @return true true on success * * @phpstan-param self::*_SCHEMA $schema */ public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null) : bool { if (!Filesystem::isReadable($this->path)) { throw new \RuntimeException('The file "' . $this->path . '" is not readable.'); } $content = \file_get_contents($this->path); $data = \json_decode($content); if (null === $data && 'null' !== $content) { self::validateSyntax($content, $this->path); } return self::validateJsonSchema($this->path, $data, $schema, $schemaFile); } /** * Validates the schema of the current json file according to composer-schema.json rules * * @param mixed $data Decoded JSON data to validate * @param int $schema a JsonFile::*_SCHEMA constant * @param string|null $schemaFile a path to the schema file * @throws JsonValidationException * @return true true on success * * @phpstan-param self::*_SCHEMA $schema */ public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null) : bool { $isComposerSchemaFile = \false; if (null === $schemaFile) { $isComposerSchemaFile = \true; $schemaFile = self::COMPOSER_SCHEMA_PATH; } // Prepend with file:// only when not using a special schema already (e.g. in the phar) if (\false === \strpos($schemaFile, '://')) { $schemaFile = 'file://' . $schemaFile; } $schemaData = (object) ['$ref' => $schemaFile]; if ($schema === self::LAX_SCHEMA) { $schemaData->additionalProperties = \true; $schemaData->required = []; } elseif ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) { $schemaData->additionalProperties = \false; $schemaData->required = ['name', 'description']; } elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) { $schemaData = (object) ['$ref' => $schemaFile . '#/properties/config', '$schema' => "https://json-schema.org/draft-04/schema#"]; } $validator = new Validator(); $validator->check($data, $schemaData); if (!$validator->isValid()) { $errors = []; foreach ((array) $validator->getErrors() as $error) { $errors[] = ($error['property'] ? $error['property'] . ' : ' : '') . $error['message']; } throw new \Composer\Json\JsonValidationException('"' . $source . '" does not match the expected JSON schema', $errors); } return \true; } /** * Encodes an array into (optionally pretty-printed) JSON * * @param mixed $data Data to encode into a formatted JSON string * @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) * @param string $indent Indentation string * @return string Encoded json */ public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT) : string { $json = \json_encode($data, $options); if (\false === $json) { self::throwEncodeError(\json_last_error()); } if (($options & \JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT) { // Pretty printing and not using default indentation return Preg::replaceCallback('#^ {4,}#m', static function ($match) use($indent) : string { return \str_repeat($indent, (int) (\strlen($match[0] ?? '') / 4)); }, $json); } return $json; } /** * Throws an exception according to a given code with a customized message * * @param int $code return code of json_last_error function * @throws \RuntimeException * @return never */ private static function throwEncodeError(int $code) : void { switch ($code) { case \JSON_ERROR_DEPTH: $msg = 'Maximum stack depth exceeded'; break; case \JSON_ERROR_STATE_MISMATCH: $msg = 'Underflow or the modes mismatch'; break; case \JSON_ERROR_CTRL_CHAR: $msg = 'Unexpected control character found'; break; case \JSON_ERROR_UTF8: $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; break; default: $msg = 'Unknown error'; } throw new \RuntimeException('JSON encoding failed: ' . $msg); } /** * Parses json string and returns hash. * * @param null|string $json json string * @param string $file the json file * * @throws ParsingException * @return mixed */ public static function parseJson(?string $json, ?string $file = null) { if (null === $json) { return null; } $data = \json_decode($json, \true); if (null === $data && \JSON_ERROR_NONE !== \json_last_error()) { self::validateSyntax($json, $file); } return $data; } /** * Validates the syntax of a JSON string * * @param string $file * @throws \UnexpectedValueException * @throws ParsingException * @return bool true on success */ protected static function validateSyntax(string $json, ?string $file = null) : bool { $parser = new JsonParser(); $result = $parser->lint($json); if (null === $result) { if (\defined('JSON_ERROR_UTF8') && \JSON_ERROR_UTF8 === \json_last_error()) { if ($file === null) { throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON'); } else { throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON'); } } return \true; } if ($file === null) { throw new ParsingException('The input does not contain valid JSON' . "\n" . $result->getMessage(), $result->getDetails()); } else { throw new ParsingException('"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(), $result->getDetails()); } } public static function detectIndenting(?string $json) : string { if (Preg::isMatchStrictGroups('#^([ \\t]+)"#m', $json ?? '', $match)) { return $match[1]; } return self::INDENT_DEFAULT; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Json; use Composer\Pcre\Preg; use Composer\Repository\PlatformRepository; /** * @author Jordi Boggiano */ class JsonManipulator { /** @var string */ private const DEFINES = '(?(DEFINE) (? -? (?= [1-9]|0(?!\\d) ) \\d++ (?:\\.\\d++)? (?:[eE] [+-]?+ \\d++)? ) (? true | false | null ) (? " (?:[^"\\\\]*+ | \\\\ ["\\\\bfnrt\\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) (? \\[ (?: (?&json) \\s*+ (?: , (?&json) \\s*+ )*+ )?+ \\s*+ \\] ) (? \\s*+ (?&string) \\s*+ : (?&json) \\s*+ ) (? \\{ (?: (?&pair) (?: , (?&pair) )*+ )?+ \\s*+ \\} ) (? \\s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) ) )'; /** @var string */ private $contents; /** @var string */ private $newline; /** @var string */ private $indent; public function __construct(string $contents) { $contents = \trim($contents); if ($contents === '') { $contents = '{}'; } if (!Preg::isMatch('#^\\{(.*)\\}$#s', $contents)) { throw new \InvalidArgumentException('The json file must be an object ({})'); } $this->newline = \false !== \strpos($contents, "\r\n") ? "\r\n" : "\n"; $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents; $this->detectIndenting(); } public function getContents() : string { return $this->contents . $this->newline; } public function addLink(string $type, string $package, string $constraint, bool $sortPackages = \false) : bool { $decoded = \Composer\Json\JsonFile::parseJson($this->contents); // no link of that type yet if (!isset($decoded[$type])) { return $this->addMainKey($type, [$package => $constraint]); } $regex = '{' . self::DEFINES . '^(?P\\s*\\{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?)' . '(?P' . \preg_quote(\Composer\Json\JsonFile::encode($type)) . '\\s*:\\s*)(?P(?&json))(?P.*)}sx'; if (!Preg::isMatch($regex, $this->contents, $matches)) { return \false; } \assert(\is_string($matches['start'])); \assert(\is_string($matches['value'])); \assert(\is_string($matches['end'])); $links = $matches['value']; // try to find existing link $packageRegex = \str_replace('/', '\\\\?/', \preg_quote($package)); $regex = '{' . self::DEFINES . '"(?P' . $packageRegex . ')"(\\s*:\\s*)(?&string)}ix'; if (Preg::isMatch($regex, $links, $packageMatches)) { \assert(\is_string($packageMatches['package'])); // update existing link $existingPackage = $packageMatches['package']; $packageRegex = \str_replace('/', '\\\\?/', \preg_quote($existingPackage)); $links = Preg::replaceCallback('{' . self::DEFINES . '"' . $packageRegex . '"(?P\\s*:\\s*)(?&string)}ix', static function ($m) use($existingPackage, $constraint) : string { return \Composer\Json\JsonFile::encode(\str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"'; }, $links); } else { if (Preg::isMatchStrictGroups('#^\\s*\\{\\s*\\S+.*?(\\s*\\}\\s*)$#s', $links, $match)) { // link missing but non empty links $links = Preg::replace( '{' . \preg_quote($match[1]) . '$}', // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588 \addcslashes(',' . $this->newline . $this->indent . $this->indent . \Composer\Json\JsonFile::encode($package) . ': ' . \Composer\Json\JsonFile::encode($constraint) . $match[1], '\\$'), $links ); } else { // links empty $links = '{' . $this->newline . $this->indent . $this->indent . \Composer\Json\JsonFile::encode($package) . ': ' . \Composer\Json\JsonFile::encode($constraint) . $this->newline . $this->indent . '}'; } } if (\true === $sortPackages) { $requirements = \json_decode($links, \true); $this->sortPackages($requirements); $links = $this->format($requirements); } $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end']; return \true; } /** * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically. * * @link https://getcomposer.org/doc/02-libraries.md#platform-packages * * @param array $packages */ private function sortPackages(array &$packages = []) : void { $prefix = static function ($requirement) : string { if (PlatformRepository::isPlatformPackage($requirement)) { return Preg::replace(['/^php/', '/^hhvm/', '/^ext/', '/^lib/', '/^\\D/'], ['0-$0', '1-$0', '2-$0', '3-$0', '4-$0'], $requirement); } return '5-' . $requirement; }; \uksort($packages, static function ($a, $b) use($prefix) : int { return \strnatcmp($prefix($a), $prefix($b)); }); } /** * @param array|false $config */ public function addRepository(string $name, $config, bool $append = \true) : bool { return $this->addSubNode('repositories', $name, $config, $append); } public function removeRepository(string $name) : bool { return $this->removeSubNode('repositories', $name); } /** * @param mixed $value */ public function addConfigSetting(string $name, $value) : bool { return $this->addSubNode('config', $name, $value); } public function removeConfigSetting(string $name) : bool { return $this->removeSubNode('config', $name); } /** * @param mixed $value */ public function addProperty(string $name, $value) : bool { if (\strpos($name, 'suggest.') === 0) { return $this->addSubNode('suggest', \substr($name, 8), $value); } if (\strpos($name, 'extra.') === 0) { return $this->addSubNode('extra', \substr($name, 6), $value); } if (\strpos($name, 'scripts.') === 0) { return $this->addSubNode('scripts', \substr($name, 8), $value); } return $this->addMainKey($name, $value); } public function removeProperty(string $name) : bool { if (\strpos($name, 'suggest.') === 0) { return $this->removeSubNode('suggest', \substr($name, 8)); } if (\strpos($name, 'extra.') === 0) { return $this->removeSubNode('extra', \substr($name, 6)); } if (\strpos($name, 'scripts.') === 0) { return $this->removeSubNode('scripts', \substr($name, 8)); } return $this->removeMainKey($name); } /** * @param mixed $value */ public function addSubNode(string $mainNode, string $name, $value, bool $append = \true) : bool { $decoded = \Composer\Json\JsonFile::parseJson($this->contents); $subName = null; if (\in_array($mainNode, ['config', 'extra', 'scripts']) && \false !== \strpos($name, '.')) { [$name, $subName] = \explode('.', $name, 2); } // no main node yet if (!isset($decoded[$mainNode])) { if ($subName !== null) { $this->addMainKey($mainNode, [$name => [$subName => $value]]); } else { $this->addMainKey($mainNode, [$name => $value]); } return \true; } // main node content not match-able $nodeRegex = '{' . self::DEFINES . '^(?P \\s* \\{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?' . \preg_quote(\Composer\Json\JsonFile::encode($mainNode)) . '\\s*:\\s*)(?P(?&object))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return \false; } } catch (\RuntimeException $e) { if ($e->getCode() === \PREG_BACKTRACK_LIMIT_ERROR) { return \false; } throw $e; } \assert(\is_string($match['start'])); \assert(\is_string($match['content'])); \assert(\is_string($match['end'])); $children = $match['content']; // invalid match due to un-regexable content, abort if (!@\json_decode($children)) { return \false; } // child exists $childRegex = '{' . self::DEFINES . '(?P"' . \preg_quote($name) . '"\\s*:\\s*)(?P(?&json))(?P,?)}x'; if (Preg::isMatch($childRegex, $children, $matches)) { $children = Preg::replaceCallback($childRegex, function ($matches) use($subName, $value) : string { if ($subName !== null && \is_string($matches['content'])) { $curVal = \json_decode($matches['content'], \true); if (!\is_array($curVal)) { $curVal = []; } $curVal[$subName] = $value; $value = $curVal; } return $matches['start'] . $this->format($value, 1) . $matches['end']; }, $children); } else { Preg::match('#^{ (?P\\s*?) (?P\\S+.*?)? (?P\\s*) }$#sx', $children, $match); $whitespace = ''; if (!empty($match['trailingspace'])) { $whitespace = $match['trailingspace']; } if (!empty($match['content'])) { if ($subName !== null) { $value = [$subName => $value]; } // child missing but non empty children if ($append) { $children = Preg::replace('#' . $whitespace . '}$#', \addcslashes(',' . $this->newline . $this->indent . $this->indent . \Composer\Json\JsonFile::encode($name) . ': ' . $this->format($value, 1) . $whitespace . '}', '\\$'), $children); } else { $whitespace = ''; if (!empty($match['leadingspace'])) { $whitespace = $match['leadingspace']; } $children = Preg::replace('#^{' . $whitespace . '#', \addcslashes('{' . $whitespace . \Composer\Json\JsonFile::encode($name) . ': ' . $this->format($value, 1) . ',' . $this->newline . $this->indent . $this->indent, '\\$'), $children); } } else { if ($subName !== null) { $value = [$subName => $value]; } // children present but empty $children = '{' . $this->newline . $this->indent . $this->indent . \Composer\Json\JsonFile::encode($name) . ': ' . $this->format($value, 1) . $whitespace . '}'; } } $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use($children) : string { return $m['start'] . $children . $m['end']; }, $this->contents); return \true; } public function removeSubNode(string $mainNode, string $name) : bool { $decoded = \Composer\Json\JsonFile::parseJson($this->contents); // no node or empty node if (empty($decoded[$mainNode])) { return \true; } // no node content match-able $nodeRegex = '{' . self::DEFINES . '^(?P \\s* \\{ \\s* (?: (?&string) \\s* : (?&json) \\s* , \\s* )*?' . \preg_quote(\Composer\Json\JsonFile::encode($mainNode)) . '\\s*:\\s*)(?P(?&object))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return \false; } } catch (\RuntimeException $e) { if ($e->getCode() === \PREG_BACKTRACK_LIMIT_ERROR) { return \false; } throw $e; } \assert(\is_string($match['start'])); \assert(\is_string($match['content'])); \assert(\is_string($match['end'])); $children = $match['content']; // invalid match due to un-regexable content, abort if (!@\json_decode($children, \true)) { return \false; } $subName = null; if (\in_array($mainNode, ['config', 'extra', 'scripts']) && \false !== \strpos($name, '.')) { [$name, $subName] = \explode('.', $name, 2); } // no node to remove if (!isset($decoded[$mainNode][$name]) || $subName && !isset($decoded[$mainNode][$name][$subName])) { return \true; } // try and find a match for the subkey $keyRegex = \str_replace('/', '\\\\?/', \preg_quote($name)); if (Preg::isMatch('{"' . $keyRegex . '"\\s*:}i', $children)) { // find best match for the value of "name" if (Preg::isMatchAll('{' . self::DEFINES . '"' . $keyRegex . '"\\s*:\\s*(?:(?&json))}x', $children, $matches)) { $bestMatch = ''; foreach ($matches[0] as $match) { \assert(\is_string($match)); if (\strlen($bestMatch) < \strlen($match)) { $bestMatch = $match; } } $childrenClean = Preg::replace('{,\\s*' . \preg_quote($bestMatch) . '}i', '', $children, -1, $count); if (1 !== $count) { $childrenClean = Preg::replace('{' . \preg_quote($bestMatch) . '\\s*,?\\s*}i', '', $childrenClean, -1, $count); if (1 !== $count) { return \false; } } } } else { $childrenClean = $children; } if (!isset($childrenClean)) { throw new \InvalidArgumentException("JsonManipulator: \$childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new."); } // no child data left, $name was the only key in unset($match); Preg::match('#^{ \\s*? (?P\\S+.*?)? (?P\\s*) }$#sx', $childrenClean, $match); if (empty($match['content'])) { $newline = $this->newline; $indent = $this->indent; $this->contents = Preg::replaceCallback($nodeRegex, static function ($matches) use($indent, $newline) : string { return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end']; }, $this->contents); // we have a subname, so we restore the rest of $name if ($subName !== null) { $curVal = \json_decode($children, \true); unset($curVal[$name][$subName]); $this->addSubNode($mainNode, $name, $curVal[$name]); } return \true; } $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use($name, $subName, $childrenClean) : string { \assert(\is_string($matches['content'])); if ($subName !== null) { $curVal = \json_decode($matches['content'], \true); unset($curVal[$name][$subName]); $childrenClean = $this->format($curVal); } return $matches['start'] . $childrenClean . $matches['end']; }, $this->contents); return \true; } /** * @param mixed $content */ public function addMainKey(string $key, $content) : bool { $decoded = \Composer\Json\JsonFile::parseJson($this->contents); $content = $this->format($content); // key exists already $regex = '{' . self::DEFINES . '^(?P\\s*\\{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?)' . '(?P' . \preg_quote(\Composer\Json\JsonFile::encode($key)) . '\\s*:\\s*(?&json))(?P.*)}sx'; if (isset($decoded[$key]) && Preg::isMatch($regex, $this->contents, $matches)) { // invalid match due to un-regexable content, abort if (!@\json_decode('{' . $matches['key'] . '}')) { return \false; } $this->contents = $matches['start'] . \Composer\Json\JsonFile::encode($key) . ': ' . $content . $matches['end']; return \true; } // append at the end of the file and keep whitespace if (Preg::isMatch('#[^{\\s](\\s*)\\}$#', $this->contents, $match)) { $this->contents = Preg::replace('#' . $match[1] . '\\}$#', \addcslashes(',' . $this->newline . $this->indent . \Composer\Json\JsonFile::encode($key) . ': ' . $content . $this->newline . '}', '\\$'), $this->contents); return \true; } // append at the end of the file $this->contents = Preg::replace('#\\}$#', \addcslashes($this->indent . \Composer\Json\JsonFile::encode($key) . ': ' . $content . $this->newline . '}', '\\$'), $this->contents); return \true; } public function removeMainKey(string $key) : bool { $decoded = \Composer\Json\JsonFile::parseJson($this->contents); if (!\array_key_exists($key, $decoded)) { return \true; } // key exists already $regex = '{' . self::DEFINES . '^(?P\\s*\\{\\s*(?:(?&string)\\s*:\\s*(?&json)\\s*,\\s*)*?)' . '(?P' . \preg_quote(\Composer\Json\JsonFile::encode($key)) . '\\s*:\\s*(?&json))\\s*,?\\s*(?P.*)}sx'; if (Preg::isMatch($regex, $this->contents, $matches)) { \assert(\is_string($matches['start'])); \assert(\is_string($matches['removal'])); \assert(\is_string($matches['end'])); // invalid match due to un-regexable content, abort if (!@\json_decode('{' . $matches['removal'] . '}')) { return \false; } // check that we are not leaving a dangling comma on the previous line if the last line was removed if (Preg::isMatchStrictGroups('#,\\s*$#', $matches['start']) && Preg::isMatch('#^\\}$#', $matches['end'])) { $matches['start'] = \rtrim(Preg::replace('#,(\\s*)$#', '$1', $matches['start']), $this->indent); } $this->contents = $matches['start'] . $matches['end']; if (Preg::isMatch('#^\\{\\s*\\}\\s*$#', $this->contents)) { $this->contents = "{\n}"; } return \true; } return \false; } public function removeMainKeyIfEmpty(string $key) : bool { $decoded = \Composer\Json\JsonFile::parseJson($this->contents); if (!\array_key_exists($key, $decoded)) { return \true; } if (\is_array($decoded[$key]) && \count($decoded[$key]) === 0) { return $this->removeMainKey($key); } return \true; } /** * @param mixed $data */ public function format($data, int $depth = 0) : string { if (\is_array($data)) { \reset($data); if (\is_numeric(\key($data))) { foreach ($data as $key => $val) { $data[$key] = $this->format($val, $depth + 1); } return '[' . \implode(', ', $data) . ']'; } $out = '{' . $this->newline; $elems = []; foreach ($data as $key => $val) { $elems[] = \str_repeat($this->indent, $depth + 2) . \Composer\Json\JsonFile::encode($key) . ': ' . $this->format($val, $depth + 1); } return $out . \implode(',' . $this->newline, $elems) . $this->newline . \str_repeat($this->indent, $depth + 1) . '}'; } return \Composer\Json\JsonFile::encode($data); } protected function detectIndenting() : void { $this->indent = \Composer\Json\JsonFile::detectIndenting($this->contents); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; /** * This class is copied in every Composer installed project and available to all * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` * * @final */ class InstalledVersions { /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; /** * @var bool|null */ private static $canGetVendors; /** * @var array[] * @psalm-var array}> */ private static $installedByVendor = array(); /** * Returns a list of all package names which are present, either by being installed, replaced or provided * * @return string[] * @psalm-return list */ public static function getInstalledPackages() { $packages = array(); foreach (self::getInstalled() as $installed) { $packages[] = \array_keys($installed['versions']); } if (1 === \count($packages)) { return $packages[0]; } return \array_keys(\array_flip(\call_user_func_array('array_merge', $packages))); } /** * Returns a list of all package names with a specific type e.g. 'library' * * @param string $type * @return string[] * @psalm-return list */ public static function getInstalledPackagesByType($type) { $packagesByType = array(); foreach (self::getInstalled() as $installed) { foreach ($installed['versions'] as $name => $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } /** * Checks whether the given package is installed * * This also returns true if the package name is provided or replaced by another package * * @param string $packageName * @param bool $includeDevRequirements * @return bool */ public static function isInstalled($packageName, $includeDevRequirements = \true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === \false; } } return \false; } /** * Checks whether the given package satisfies a version constraint * * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: * * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') * * @param VersionParser $parser Install composer/semver to have access to this class and functionality * @param string $packageName * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package * @return bool */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } /** * Returns a version constraint representing all the range(s) which are installed for a given package * * It is easier to use this via isInstalled() with the $constraint argument if you need to check * whether a given version of a package is installed, and not just whether it exists * * @param string $packageName * @return string Version constraint usable with composer/semver */ public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (\array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = \array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (\array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = \array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (\array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = \array_merge($ranges, $installed['versions'][$packageName]['provided']); } return \implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference */ public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. */ public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @return array * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } /** * Returns the raw installed.php data for custom implementations * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @\trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', \E_USER_DEPRECATED); if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (\substr(__DIR__, -8, 1) !== 'C') { self::$installed = (include __DIR__ . '/installed.php'); } else { self::$installed = array(); } } return self::$installed; } /** * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] * @psalm-return list}> */ public static function getAllRawData() { return self::getInstalled(); } /** * Lets you reload the static array from another file * * This is only useful for complex integrations in which a project needs to use * this class but then also needs to execute another project's autoloader in process, * and wants to ensure both projects have access to their version of installed.php. * * A typical case would be PHPUnit, where it would need to make sure it reads all * the data it needs from this class, then call reload() with * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure * the project in which it runs can then also use this class safely, without * interference between PHPUnit's dependencies and the project's dependencies. * * @param array[] $data A vendor/composer/installed.php data set * @return void * * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } /** * @return array[] * @psalm-return list}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = \method_exists('Composer\\Autoload\\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (\is_file($vendorDir . '/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = (require $vendorDir . '/composer/installed.php'); $installed[] = self::$installedByVendor[$vendorDir] = $required; if (null === self::$installed && \strtr($vendorDir . '/composer', '\\', '/') === \strtr(__DIR__, '\\', '/')) { self::$installed = $installed[\count($installed) - 1]; } } } } if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (\substr(__DIR__, -8, 1) !== 'C') { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = (require __DIR__ . '/installed.php'); self::$installed = $required; } else { self::$installed = array(); } } if (self::$installed !== array()) { $installed[] = self::$installed; } return $installed; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; /** * Solver install operation. * * @author Konstantin Kudryashov */ class InstallOperation extends \Composer\DependencyResolver\Operation\SolverOperation implements \Composer\DependencyResolver\Operation\OperationInterface { protected const TYPE = 'install'; /** * @var PackageInterface */ protected $package; public function __construct(PackageInterface $package) { $this->package = $package; } /** * Returns package instance. */ public function getPackage() : PackageInterface { return $this->package; } /** * @inheritDoc */ public function show($lock) : string { return self::format($this->package, $lock); } public static function format(PackageInterface $package, bool $lock = \false) : string { return ($lock ? 'Locking ' : 'Installing ') . '' . $package->getPrettyName() . ' (' . $package->getFullPrettyVersion() . ')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\AliasPackage; /** * Solver install operation. * * @author Nils Adermann */ class MarkAliasInstalledOperation extends \Composer\DependencyResolver\Operation\SolverOperation implements \Composer\DependencyResolver\Operation\OperationInterface { protected const TYPE = 'markAliasInstalled'; /** * @var AliasPackage */ protected $package; public function __construct(AliasPackage $package) { $this->package = $package; } /** * Returns package instance. */ public function getPackage() : AliasPackage { return $this->package; } /** * @inheritDoc */ public function show($lock) : string { return 'Marking ' . $this->package->getPrettyName() . ' (' . $this->package->getFullPrettyVersion() . ') as installed, alias of ' . $this->package->getAliasOf()->getPrettyName() . ' (' . $this->package->getAliasOf()->getFullPrettyVersion() . ')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; /** * Solver operation interface. * * @author Konstantin Kudryashov */ interface OperationInterface { /** * Returns operation type. * * @return string */ public function getOperationType(); /** * Serializes the operation in a human readable format * * @param bool $lock Whether this is an operation on the lock file * @return string */ public function show(bool $lock); /** * Serializes the operation in a human readable format * * @return string */ public function __toString(); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; /** * Solver update operation. * * @author Konstantin Kudryashov */ class UpdateOperation extends \Composer\DependencyResolver\Operation\SolverOperation implements \Composer\DependencyResolver\Operation\OperationInterface { protected const TYPE = 'update'; /** * @var PackageInterface */ protected $initialPackage; /** * @var PackageInterface */ protected $targetPackage; /** * @param PackageInterface $initial initial package * @param PackageInterface $target target package (updated) */ public function __construct(PackageInterface $initial, PackageInterface $target) { $this->initialPackage = $initial; $this->targetPackage = $target; } /** * Returns initial package. */ public function getInitialPackage() : PackageInterface { return $this->initialPackage; } /** * Returns target package. */ public function getTargetPackage() : PackageInterface { return $this->targetPackage; } /** * @inheritDoc */ public function show($lock) : string { return self::format($this->initialPackage, $this->targetPackage, $lock); } public static function format(PackageInterface $initialPackage, PackageInterface $targetPackage, bool $lock = \false) : string { $fromVersion = $initialPackage->getFullPrettyVersion(); $toVersion = $targetPackage->getFullPrettyVersion(); if ($fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) { $fromVersion = $initialPackage->getFullPrettyVersion(\true, PackageInterface::DISPLAY_SOURCE_REF); $toVersion = $targetPackage->getFullPrettyVersion(\true, PackageInterface::DISPLAY_SOURCE_REF); } elseif ($fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) { $fromVersion = $initialPackage->getFullPrettyVersion(\true, PackageInterface::DISPLAY_DIST_REF); $toVersion = $targetPackage->getFullPrettyVersion(\true, PackageInterface::DISPLAY_DIST_REF); } $actionName = VersionParser::isUpgrade($initialPackage->getVersion(), $targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading'; return $actionName . ' ' . $initialPackage->getPrettyName() . ' (' . $fromVersion . ' => ' . $toVersion . ')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; /** * Abstract operation class. * * @author Aleksandr Bezpiatov */ abstract class SolverOperation implements \Composer\DependencyResolver\Operation\OperationInterface { /** * @abstract must be redefined by extending classes */ protected const TYPE = ''; /** * Returns operation type. */ public function getOperationType() : string { return static::TYPE; } /** * @inheritDoc */ public function __toString() { return $this->show(\false); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\PackageInterface; /** * Solver uninstall operation. * * @author Konstantin Kudryashov */ class UninstallOperation extends \Composer\DependencyResolver\Operation\SolverOperation implements \Composer\DependencyResolver\Operation\OperationInterface { protected const TYPE = 'uninstall'; /** * @var PackageInterface */ protected $package; public function __construct(PackageInterface $package) { $this->package = $package; } /** * Returns package instance. */ public function getPackage() : PackageInterface { return $this->package; } /** * @inheritDoc */ public function show($lock) : string { return self::format($this->package, $lock); } public static function format(PackageInterface $package, bool $lock = \false) : string { return 'Removing ' . $package->getPrettyName() . ' (' . $package->getFullPrettyVersion() . ')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver\Operation; use Composer\Package\AliasPackage; /** * Solver install operation. * * @author Nils Adermann */ class MarkAliasUninstalledOperation extends \Composer\DependencyResolver\Operation\SolverOperation implements \Composer\DependencyResolver\Operation\OperationInterface { protected const TYPE = 'markAliasUninstalled'; /** * @var AliasPackage */ protected $package; public function __construct(AliasPackage $package) { $this->package = $package; } /** * Returns package instance. */ public function getPackage() : AliasPackage { return $this->package; } /** * @inheritDoc */ public function show($lock) : string { return 'Marking ' . $this->package->getPrettyName() . ' (' . $this->package->getFullPrettyVersion() . ') as uninstalled, alias of ' . $this->package->getAliasOf()->getPrettyName() . ' (' . $this->package->getAliasOf()->getFullPrettyVersion() . ')'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\PlatformRepository; use Composer\DependencyResolver\Operation\OperationInterface; /** * @author Nils Adermann * @internal */ class Transaction { /** * @var OperationInterface[] */ protected $operations; /** * Packages present at the beginning of the transaction * @var PackageInterface[] */ protected $presentPackages; /** * Package set resulting from this transaction * @var array */ protected $resultPackageMap; /** * @var array */ protected $resultPackagesByName = []; /** * @param PackageInterface[] $presentPackages * @param PackageInterface[] $resultPackages */ public function __construct(array $presentPackages, array $resultPackages) { $this->presentPackages = $presentPackages; $this->setResultPackageMaps($resultPackages); $this->operations = $this->calculateOperations(); } /** * @return OperationInterface[] */ public function getOperations() : array { return $this->operations; } /** * @param PackageInterface[] $resultPackages */ private function setResultPackageMaps(array $resultPackages) : void { $packageSort = static function (PackageInterface $a, PackageInterface $b) : int { // sort alias packages by the same name behind their non alias version if ($a->getName() === $b->getName()) { if ($a instanceof AliasPackage !== $b instanceof AliasPackage) { return $a instanceof AliasPackage ? -1 : 1; } // if names are the same, compare version, e.g. to sort aliases reliably, actual order does not matter return \strcmp($b->getVersion(), $a->getVersion()); } return \strcmp($b->getName(), $a->getName()); }; $this->resultPackageMap = []; foreach ($resultPackages as $package) { $this->resultPackageMap[\spl_object_hash($package)] = $package; foreach ($package->getNames() as $name) { $this->resultPackagesByName[$name][] = $package; } } \uasort($this->resultPackageMap, $packageSort); foreach ($this->resultPackagesByName as $name => $packages) { \uasort($this->resultPackagesByName[$name], $packageSort); } } /** * @return OperationInterface[] */ protected function calculateOperations() : array { $operations = []; $presentPackageMap = []; $removeMap = []; $presentAliasMap = []; $removeAliasMap = []; foreach ($this->presentPackages as $package) { if ($package instanceof AliasPackage) { $presentAliasMap[$package->getName() . '::' . $package->getVersion()] = $package; $removeAliasMap[$package->getName() . '::' . $package->getVersion()] = $package; } else { $presentPackageMap[$package->getName()] = $package; $removeMap[$package->getName()] = $package; } } $stack = $this->getRootPackages(); $visited = []; $processed = []; while (!empty($stack)) { $package = \array_pop($stack); if (isset($processed[\spl_object_hash($package)])) { continue; } if (!isset($visited[\spl_object_hash($package)])) { $visited[\spl_object_hash($package)] = \true; $stack[] = $package; if ($package instanceof AliasPackage) { $stack[] = $package->getAliasOf(); } else { foreach ($package->getRequires() as $link) { $possibleRequires = $this->getProvidersInResult($link); foreach ($possibleRequires as $require) { $stack[] = $require; } } } } elseif (!isset($processed[\spl_object_hash($package)])) { $processed[\spl_object_hash($package)] = \true; if ($package instanceof AliasPackage) { $aliasKey = $package->getName() . '::' . $package->getVersion(); if (isset($presentAliasMap[$aliasKey])) { unset($removeAliasMap[$aliasKey]); } else { $operations[] = new \Composer\DependencyResolver\Operation\MarkAliasInstalledOperation($package); } } else { if (isset($presentPackageMap[$package->getName()])) { $source = $presentPackageMap[$package->getName()]; // do we need to update? // TODO different for lock? if ($package->getVersion() !== $presentPackageMap[$package->getName()]->getVersion() || $package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() || $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference()) { $operations[] = new \Composer\DependencyResolver\Operation\UpdateOperation($source, $package); } unset($removeMap[$package->getName()]); } else { $operations[] = new \Composer\DependencyResolver\Operation\InstallOperation($package); unset($removeMap[$package->getName()]); } } } } foreach ($removeMap as $name => $package) { \array_unshift($operations, new \Composer\DependencyResolver\Operation\UninstallOperation($package)); } foreach ($removeAliasMap as $nameVersion => $package) { $operations[] = new \Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation($package); } $operations = $this->movePluginsToFront($operations); // TODO fix this: // we have to do this again here even though the above stack code did it because moving plugins moves them before uninstalls $operations = $this->moveUninstallsToFront($operations); // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place? /* if ('update' === $opType) { $targetPackage = $operation->getTargetPackage(); if ($targetPackage->isDev()) { $initialPackage = $operation->getInitialPackage(); if ($targetPackage->getVersion() === $initialPackage->getVersion() && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference()) && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference()) ) { $this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG); $this->io->writeError('', true, IOInterface::DEBUG); continue; } } }*/ return $this->operations = $operations; } /** * Determine which packages in the result are not required by any other packages in it. * * These serve as a starting point to enumerate packages in a topological order despite potential cycles. * If there are packages with a cycle on the top level the package with the lowest name gets picked * * @return array */ protected function getRootPackages() : array { $roots = $this->resultPackageMap; foreach ($this->resultPackageMap as $packageHash => $package) { if (!isset($roots[$packageHash])) { continue; } foreach ($package->getRequires() as $link) { $possibleRequires = $this->getProvidersInResult($link); foreach ($possibleRequires as $require) { if ($require !== $package) { unset($roots[\spl_object_hash($require)]); } } } } return $roots; } /** * @return PackageInterface[] */ protected function getProvidersInResult(Link $link) : array { if (!isset($this->resultPackagesByName[$link->getTarget()])) { return []; } return $this->resultPackagesByName[$link->getTarget()]; } /** * Workaround: if your packages depend on plugins, we must be sure * that those are installed / updated first; else it would lead to packages * being installed multiple times in different folders, when running Composer * twice. * * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147, * it at least fixes the symptoms and makes usage of composer possible (again) * in such scenarios. * * @param OperationInterface[] $operations * @return OperationInterface[] reordered operation list */ private function movePluginsToFront(array $operations) : array { $dlModifyingPluginsNoDeps = []; $dlModifyingPluginsWithDeps = []; $dlModifyingPluginRequires = []; $pluginsNoDeps = []; $pluginsWithDeps = []; $pluginRequires = []; foreach (\array_reverse($operations, \true) as $idx => $op) { if ($op instanceof \Composer\DependencyResolver\Operation\InstallOperation) { $package = $op->getPackage(); } elseif ($op instanceof \Composer\DependencyResolver\Operation\UpdateOperation) { $package = $op->getTargetPackage(); } else { continue; } $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && ($extra = $package->getExtra()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === \true; // is this a downloads modifying plugin or a dependency of one? if ($isDownloadsModifyingPlugin || \count(\array_intersect($package->getNames(), $dlModifyingPluginRequires))) { // get the package's requires, but filter out any platform requirements $requires = \array_filter(\array_keys($package->getRequires()), static function ($req) : bool { return !PlatformRepository::isPlatformPackage($req); }); // is this a plugin with no meaningful dependencies? if ($isDownloadsModifyingPlugin && !\count($requires)) { // plugins with no dependencies go to the very front \array_unshift($dlModifyingPluginsNoDeps, $op); } else { // capture the requirements for this package so those packages will be moved up as well $dlModifyingPluginRequires = \array_merge($dlModifyingPluginRequires, $requires); // move the operation to the front \array_unshift($dlModifyingPluginsWithDeps, $op); } unset($operations[$idx]); continue; } // is this package a plugin? $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; // is this a plugin or a dependency of a plugin? if ($isPlugin || \count(\array_intersect($package->getNames(), $pluginRequires))) { // get the package's requires, but filter out any platform requirements $requires = \array_filter(\array_keys($package->getRequires()), static function ($req) : bool { return !PlatformRepository::isPlatformPackage($req); }); // is this a plugin with no meaningful dependencies? if ($isPlugin && !\count($requires)) { // plugins with no dependencies go to the very front \array_unshift($pluginsNoDeps, $op); } else { // capture the requirements for this package so those packages will be moved up as well $pluginRequires = \array_merge($pluginRequires, $requires); // move the operation to the front \array_unshift($pluginsWithDeps, $op); } unset($operations[$idx]); } } return \array_merge($dlModifyingPluginsNoDeps, $dlModifyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations); } /** * Removals of packages should be executed before installations in * case two packages resolve to the same path (due to custom installers) * * @param OperationInterface[] $operations * @return OperationInterface[] reordered operation list */ private function moveUninstallsToFront(array $operations) : array { $uninstOps = []; foreach ($operations as $idx => $op) { if ($op instanceof \Composer\DependencyResolver\Operation\UninstallOperation || $op instanceof \Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation) { $uninstOps[] = $op; unset($operations[$idx]); } } return \array_merge($uninstOps, $operations); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Semver\Constraint\Constraint; /** * @author Nils Adermann */ interface PolicyInterface { /** * @phpstan-param Constraint::STR_OP_* $operator */ public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator) : bool; /** * @param int[] $literals * @return int[] */ public function selectPreferredPackages(\Composer\DependencyResolver\Pool $pool, array $literals, ?string $requiredPackage = null) : array; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\RepositoryInterface; /** * @author Nils Adermann * @internal */ class LocalRepoTransaction extends \Composer\DependencyResolver\Transaction { public function __construct(RepositoryInterface $lockedRepository, InstalledRepositoryInterface $localRepository) { parent::__construct($localRepository->getPackages(), $lockedRepository->getPackages()); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * The RuleWatchGraph efficiently propagates decisions to other rules * * All rules generated for solving a SAT problem should be inserted into the * graph. When a decision on a literal is made, the graph can be used to * propagate the decision to all other rules involving the literal, leading to * other trivial decisions resulting from unit clauses. * * @author Nils Adermann */ class RuleWatchGraph { /** @var array */ protected $watchChains = []; /** * Inserts a rule node into the appropriate chains within the graph * * The node is prepended to the watch chains for each of the two literals it * watches. * * Assertions are skipped because they only depend on a single package and * have no alternative literal that could be true, so there is no need to * watch changes in any literals. * * @param RuleWatchNode $node The rule node to be inserted into the graph */ public function insert(\Composer\DependencyResolver\RuleWatchNode $node) : void { if ($node->getRule()->isAssertion()) { return; } if (!$node->getRule() instanceof \Composer\DependencyResolver\MultiConflictRule) { foreach ([$node->watch1, $node->watch2] as $literal) { if (!isset($this->watchChains[$literal])) { $this->watchChains[$literal] = new \Composer\DependencyResolver\RuleWatchChain(); } $this->watchChains[$literal]->unshift($node); } } else { foreach ($node->getRule()->getLiterals() as $literal) { if (!isset($this->watchChains[$literal])) { $this->watchChains[$literal] = new \Composer\DependencyResolver\RuleWatchChain(); } $this->watchChains[$literal]->unshift($node); } } } /** * Propagates a decision on a literal to all rules watching the literal * * If a decision, e.g. +A has been made, then all rules containing -A, e.g. * (-A|+B|+C) now need to satisfy at least one of the other literals, so * that the rule as a whole becomes true, since with +A applied the rule * is now (false|+B|+C) so essentially (+B|+C). * * This means that all rules watching the literal -A need to be updated to * watch 2 other literals which can still be satisfied instead. So literals * that conflict with previously made decisions are not an option. * * Alternatively it can occur that a unit clause results: e.g. if in the * above example the rule was (-A|+B), then A turning true means that * B must now be decided true as well. * * @param int $decidedLiteral The literal which was decided (A in our example) * @param int $level The level at which the decision took place and at which * all resulting decisions should be made. * @param Decisions $decisions Used to check previous decisions and to * register decisions resulting from propagation * @return Rule|null If a conflict is found the conflicting rule is returned */ public function propagateLiteral(int $decidedLiteral, int $level, \Composer\DependencyResolver\Decisions $decisions) : ?\Composer\DependencyResolver\Rule { // we invert the decided literal here, example: // A was decided => (-A|B) now requires B to be true, so we look for // rules which are fulfilled by -A, rather than A. $literal = -$decidedLiteral; if (!isset($this->watchChains[$literal])) { return null; } $chain = $this->watchChains[$literal]; $chain->rewind(); while ($chain->valid()) { $node = $chain->current(); if (!$node->getRule() instanceof \Composer\DependencyResolver\MultiConflictRule) { $otherWatch = $node->getOtherWatch($literal); if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) { $ruleLiterals = $node->getRule()->getLiterals(); $alternativeLiterals = \array_filter($ruleLiterals, static function ($ruleLiteral) use($literal, $otherWatch, $decisions) : bool { return $literal !== $ruleLiteral && $otherWatch !== $ruleLiteral && !$decisions->conflict($ruleLiteral); }); if (\count($alternativeLiterals) > 0) { \reset($alternativeLiterals); $this->moveWatch($literal, \current($alternativeLiterals), $node); continue; } if ($decisions->conflict($otherWatch)) { return $node->getRule(); } $decisions->decide($otherWatch, $level, $node->getRule()); } } else { foreach ($node->getRule()->getLiterals() as $otherLiteral) { if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) { if ($decisions->conflict($otherLiteral)) { return $node->getRule(); } $decisions->decide($otherLiteral, $level, $node->getRule()); } } } $chain->next(); } return null; } /** * Moves a rule node from one watch chain to another * * The rule node's watched literals are updated accordingly. * * @param int $fromLiteral A literal the node used to watch * @param int $toLiteral A literal the node should watch now * @param RuleWatchNode $node The rule node to be moved */ protected function moveWatch(int $fromLiteral, int $toLiteral, \Composer\DependencyResolver\RuleWatchNode $node) : void { if (!isset($this->watchChains[$toLiteral])) { $this->watchChains[$toLiteral] = new \Composer\DependencyResolver\RuleWatchChain(); } $node->moveWatch($fromLiteral, $toLiteral); $this->watchChains[$fromLiteral]->remove(); $this->watchChains[$toLiteral]->unshift($node); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann */ class GenericRule extends \Composer\DependencyResolver\Rule { /** @var list */ protected $literals; /** * @param list $literals */ public function __construct(array $literals, $reason, $reasonData) { parent::__construct($reason, $reasonData); // sort all packages ascending by id \sort($literals); $this->literals = $literals; } /** * @return list */ public function getLiterals() : array { return $this->literals; } /** * @inheritDoc */ public function getHash() { $data = \unpack('ihash', \md5(\implode(',', $this->literals), \true)); return $data['hash']; } /** * Checks if this rule is equal to another one * * Ignores whether either of the rules is disabled. * * @param Rule $rule The rule to check against * @return bool Whether the rules are equal */ public function equals(\Composer\DependencyResolver\Rule $rule) : bool { return $this->literals === $rule->getLiterals(); } public function isAssertion() : bool { return 1 === \count($this->literals); } /** * Formats a rule as a string of the format (Literal1|Literal2|...) */ public function __toString() : string { $result = $this->isDisabled() ? 'disabled(' : '('; foreach ($this->literals as $i => $literal) { if ($i !== 0) { $result .= '|'; } $result .= $literal; } $result .= ')'; return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Package; use Composer\Pcre\Preg; /** * @author Nils Adermann * @internal */ class LockTransaction extends \Composer\DependencyResolver\Transaction { /** * packages in current lock file, platform repo or otherwise present * * Indexed by spl_object_hash * * @var array */ protected $presentMap; /** * Packages which cannot be mapped, platform repo, root package, other fixed repos * * Indexed by package id * * @var array */ protected $unlockableMap; /** * @var array{dev: BasePackage[], non-dev: BasePackage[], all: BasePackage[]} */ protected $resultPackages; /** * @param array $presentMap * @param array $unlockableMap */ public function __construct(\Composer\DependencyResolver\Pool $pool, array $presentMap, array $unlockableMap, \Composer\DependencyResolver\Decisions $decisions) { $this->presentMap = $presentMap; $this->unlockableMap = $unlockableMap; $this->setResultPackages($pool, $decisions); parent::__construct($this->presentMap, $this->resultPackages['all']); } // TODO make this a bit prettier instead of the two text indexes? public function setResultPackages(\Composer\DependencyResolver\Pool $pool, \Composer\DependencyResolver\Decisions $decisions) : void { $this->resultPackages = ['all' => [], 'non-dev' => [], 'dev' => []]; foreach ($decisions as $i => $decision) { $literal = $decision[\Composer\DependencyResolver\Decisions::DECISION_LITERAL]; if ($literal > 0) { $package = $pool->literalToPackage($literal); $this->resultPackages['all'][] = $package; if (!isset($this->unlockableMap[$package->id])) { $this->resultPackages['non-dev'][] = $package; } } } } public function setNonDevPackages(\Composer\DependencyResolver\LockTransaction $extractionResult) : void { $packages = $extractionResult->getNewLockPackages(\false); $this->resultPackages['dev'] = $this->resultPackages['non-dev']; $this->resultPackages['non-dev'] = []; foreach ($packages as $package) { foreach ($this->resultPackages['dev'] as $i => $resultPackage) { // TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible? if ($package->getName() === $resultPackage->getName()) { $this->resultPackages['non-dev'][] = $resultPackage; unset($this->resultPackages['dev'][$i]); } } } } // TODO additionalFixedRepository needs to be looked at here as well? /** * @return BasePackage[] */ public function getNewLockPackages(bool $devMode, bool $updateMirrors = \false) : array { $packages = []; foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) { if (!$package instanceof AliasPackage) { // if we're just updating mirrors we need to reset references to the same as currently "present" packages' references to keep the lock file as-is // we do not reset references if the currently present package didn't have any, or if the type of VCS has changed if ($updateMirrors && !isset($this->presentMap[\spl_object_hash($package)])) { foreach ($this->presentMap as $presentPackage) { if ($package->getName() === $presentPackage->getName() && $package->getVersion() === $presentPackage->getVersion()) { if ($presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) { $package->setSourceDistReferences($presentPackage->getSourceReference()); // if the dist url is not one of those handled gracefully by setSourceDistReferences then we should overwrite it with the old one if ($package->getDistUrl() !== null && !Preg::isMatch('{^https?://(?:(?:www\\.)?bitbucket\\.org|(api\\.)?github\\.com|(?:www\\.)?gitlab\\.com)/}i', $package->getDistUrl())) { $package->setDistUrl($presentPackage->getDistUrl()); } $package->setDistType($presentPackage->getDistType()); if ($package instanceof Package) { $package->setDistSha1Checksum($presentPackage->getDistSha1Checksum()); } } if ($presentPackage->getReleaseDate() !== null && $package instanceof Package) { $package->setReleaseDate($presentPackage->getReleaseDate()); } } } } $packages[] = $package; } } return $packages; } /** * Checks which of the given aliases from composer.json are actually in use for the lock file * @param list $aliases * @return list */ public function getAliases(array $aliases) : array { $usedAliases = []; foreach ($this->resultPackages['all'] as $package) { if ($package instanceof AliasPackage) { foreach ($aliases as $index => $alias) { if ($alias['package'] === $package->getName()) { $usedAliases[] = $alias; unset($aliases[$index]); } } } } \usort($usedAliases, static function ($a, $b) : int { return \strcmp($a['package'], $b['package']); }); return $usedAliases; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\IO\IOInterface; use Composer\Package\BasePackage; /** * @author Nils Adermann */ class Solver { private const BRANCH_LITERALS = 0; private const BRANCH_LEVEL = 1; /** @var PolicyInterface */ protected $policy; /** @var Pool */ protected $pool; /** @var RuleSet */ protected $rules; /** @var RuleWatchGraph */ protected $watchGraph; /** @var Decisions */ protected $decisions; /** @var BasePackage[] */ protected $fixedMap; /** @var int */ protected $propagateIndex; /** @var mixed[] */ protected $branches = []; /** @var Problem[] */ protected $problems = []; /** @var array */ protected $learnedPool = []; /** @var array */ protected $learnedWhy = []; /** @var bool */ public $testFlagLearnedPositiveLiteral = \false; /** @var IOInterface */ protected $io; public function __construct(\Composer\DependencyResolver\PolicyInterface $policy, \Composer\DependencyResolver\Pool $pool, IOInterface $io) { $this->io = $io; $this->policy = $policy; $this->pool = $pool; } public function getRuleSetSize() : int { return \count($this->rules); } public function getPool() : \Composer\DependencyResolver\Pool { return $this->pool; } // aka solver_makeruledecisions private function makeAssertionRuleDecisions() : void { $decisionStart = \count($this->decisions) - 1; $rulesCount = \count($this->rules); for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { $rule = $this->rules->ruleById[$ruleIndex]; if (!$rule->isAssertion() || $rule->isDisabled()) { continue; } $literals = $rule->getLiterals(); $literal = $literals[0]; if (!$this->decisions->decided($literal)) { $this->decisions->decide($literal, 1, $rule); continue; } if ($this->decisions->satisfy($literal)) { continue; } // found a conflict if (\Composer\DependencyResolver\RuleSet::TYPE_LEARNED === $rule->getType()) { $rule->disable(); continue; } $conflict = $this->decisions->decisionRule($literal); if ($conflict && \Composer\DependencyResolver\RuleSet::TYPE_PACKAGE === $conflict->getType()) { $problem = new \Composer\DependencyResolver\Problem(); $problem->addRule($rule); $problem->addRule($conflict); $rule->disable(); $this->problems[] = $problem; continue; } // conflict with another root require/fixed package $problem = new \Composer\DependencyResolver\Problem(); $problem->addRule($rule); $problem->addRule($conflict); // push all of our rules (can only be root require/fixed package rules) // asserting this literal on the problem stack foreach ($this->rules->getIteratorFor(\Composer\DependencyResolver\RuleSet::TYPE_REQUEST) as $assertRule) { if ($assertRule->isDisabled() || !$assertRule->isAssertion()) { continue; } $assertRuleLiterals = $assertRule->getLiterals(); $assertRuleLiteral = $assertRuleLiterals[0]; if (\abs($literal) !== \abs($assertRuleLiteral)) { continue; } $problem->addRule($assertRule); $assertRule->disable(); } $this->problems[] = $problem; $this->decisions->resetToOffset($decisionStart); $ruleIndex = -1; } } protected function setupFixedMap(\Composer\DependencyResolver\Request $request) : void { $this->fixedMap = []; foreach ($request->getFixedPackages() as $package) { $this->fixedMap[$package->id] = $package; } } protected function checkForRootRequireProblems(\Composer\DependencyResolver\Request $request, PlatformRequirementFilterInterface $platformRequirementFilter) : void { foreach ($request->getRequires() as $packageName => $constraint) { if ($platformRequirementFilter->isIgnored($packageName)) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); } if (!$this->pool->whatProvides($packageName, $constraint)) { $problem = new \Composer\DependencyResolver\Problem(); $problem->addRule(new \Composer\DependencyResolver\GenericRule([], \Composer\DependencyResolver\Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint])); $this->problems[] = $problem; } } } public function solve(\Composer\DependencyResolver\Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null) : \Composer\DependencyResolver\LockTransaction { $platformRequirementFilter = $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing(); $this->setupFixedMap($request); $this->io->writeError('Generating rules', \true, IOInterface::DEBUG); $ruleSetGenerator = new \Composer\DependencyResolver\RuleSetGenerator($this->policy, $this->pool); $this->rules = $ruleSetGenerator->getRulesFor($request, $platformRequirementFilter); unset($ruleSetGenerator); $this->checkForRootRequireProblems($request, $platformRequirementFilter); $this->decisions = new \Composer\DependencyResolver\Decisions($this->pool); $this->watchGraph = new \Composer\DependencyResolver\RuleWatchGraph(); foreach ($this->rules as $rule) { $this->watchGraph->insert(new \Composer\DependencyResolver\RuleWatchNode($rule)); } /* make decisions based on root require/fix assertions */ $this->makeAssertionRuleDecisions(); $this->io->writeError('Resolving dependencies through SAT', \true, IOInterface::DEBUG); $before = \microtime(\true); $this->runSat(); $this->io->writeError('', \true, IOInterface::DEBUG); $this->io->writeError(\sprintf('Dependency resolution completed in %.3f seconds', \microtime(\true) - $before), \true, IOInterface::VERBOSE); if ($this->problems) { throw new \Composer\DependencyResolver\SolverProblemsException($this->problems, $this->learnedPool); } return new \Composer\DependencyResolver\LockTransaction($this->pool, $request->getPresentMap(), $request->getFixedPackagesMap(), $this->decisions); } /** * Makes a decision and propagates it to all rules. * * Evaluates each term affected by the decision (linked through watches) * If we find unit rules we make new decisions based on them * * @return Rule|null A rule on conflict, otherwise null. */ protected function propagate(int $level) : ?\Composer\DependencyResolver\Rule { while ($this->decisions->validOffset($this->propagateIndex)) { $decision = $this->decisions->atOffset($this->propagateIndex); $conflict = $this->watchGraph->propagateLiteral($decision[\Composer\DependencyResolver\Decisions::DECISION_LITERAL], $level, $this->decisions); $this->propagateIndex++; if ($conflict) { return $conflict; } } return null; } /** * Reverts a decision at the given level. */ private function revert(int $level) : void { while (!$this->decisions->isEmpty()) { $literal = $this->decisions->lastLiteral(); if ($this->decisions->undecided($literal)) { break; } $decisionLevel = $this->decisions->decisionLevel($literal); if ($decisionLevel <= $level) { break; } $this->decisions->revertLast(); $this->propagateIndex = \count($this->decisions); } while (!empty($this->branches) && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { \array_pop($this->branches); } } /** * setpropagatelearn * * add free decision (a positive literal) to decision queue * increase level and propagate decision * return if no conflict. * * in conflict case, analyze conflict rule, add resulting * rule to learnt rule set, make decision from learnt * rule (always unit) and re-propagate. * * returns the new solver level or 0 if unsolvable * * @param string|int $literal */ private function setPropagateLearn(int $level, $literal, \Composer\DependencyResolver\Rule $rule) : int { $level++; $this->decisions->decide($literal, $level, $rule); while (\true) { $rule = $this->propagate($level); if (null === $rule) { break; } if ($level === 1) { return $this->analyzeUnsolvable($rule); } // conflict [$learnLiteral, $newLevel, $newRule, $why] = $this->analyze($level, $rule); if ($newLevel <= 0 || $newLevel >= $level) { throw new \Composer\DependencyResolver\SolverBugException("Trying to revert to invalid level " . $newLevel . " from level " . $level . "."); } $level = $newLevel; $this->revert($level); $this->rules->add($newRule, \Composer\DependencyResolver\RuleSet::TYPE_LEARNED); $this->learnedWhy[\spl_object_hash($newRule)] = $why; $ruleNode = new \Composer\DependencyResolver\RuleWatchNode($newRule); $ruleNode->watch2OnHighest($this->decisions); $this->watchGraph->insert($ruleNode); $this->decisions->decide($learnLiteral, $level, $newRule); } return $level; } /** * @param int[] $decisionQueue */ private function selectAndInstall(int $level, array $decisionQueue, \Composer\DependencyResolver\Rule $rule) : int { // choose best package to install from decisionQueue $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage()); $selectedLiteral = \array_shift($literals); // if there are multiple candidates, then branch if (\count($literals)) { $this->branches[] = [$literals, $level]; } return $this->setPropagateLearn($level, $selectedLiteral, $rule); } /** * @return array{int, int, GenericRule, int} */ protected function analyze(int $level, \Composer\DependencyResolver\Rule $rule) : array { $analyzedRule = $rule; $ruleLevel = 1; $num = 0; $l1num = 0; $seen = []; $learnedLiterals = [null]; $decisionId = \count($this->decisions); $this->learnedPool[] = []; while (\true) { $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; foreach ($rule->getLiterals() as $literal) { // multiconflictrule is really a bunch of rules in one, so some may not have finished propagating yet if ($rule instanceof \Composer\DependencyResolver\MultiConflictRule && !$this->decisions->decided($literal)) { continue; } // skip the one true literal if ($this->decisions->satisfy($literal)) { continue; } if (isset($seen[\abs($literal)])) { continue; } $seen[\abs($literal)] = \true; $l = $this->decisions->decisionLevel($literal); if (1 === $l) { $l1num++; } elseif ($level === $l) { $num++; } else { // not level1 or conflict level, add to new rule $learnedLiterals[] = $literal; if ($l > $ruleLevel) { $ruleLevel = $l; } } } unset($literal); $l1retry = \true; while ($l1retry) { $l1retry = \false; if (0 === $num && 0 === --$l1num) { // all level 1 literals done break 2; } while (\true) { if ($decisionId <= 0) { throw new \Composer\DependencyResolver\SolverBugException("Reached invalid decision id {$decisionId} while looking through {$rule} for a literal present in the analyzed rule {$analyzedRule}."); } $decisionId--; $decision = $this->decisions->atOffset($decisionId); $literal = $decision[\Composer\DependencyResolver\Decisions::DECISION_LITERAL]; if (isset($seen[\abs($literal)])) { break; } } unset($seen[\abs($literal)]); if (0 !== $num && 0 === --$num) { if ($literal < 0) { $this->testFlagLearnedPositiveLiteral = \true; } $learnedLiterals[0] = -$literal; if (!$l1num) { break 2; } foreach ($learnedLiterals as $i => $learnedLiteral) { if ($i !== 0) { unset($seen[\abs($learnedLiteral)]); } } // only level 1 marks left $l1num++; $l1retry = \true; } else { $decision = $this->decisions->atOffset($decisionId); $rule = $decision[\Composer\DependencyResolver\Decisions::DECISION_REASON]; if ($rule instanceof \Composer\DependencyResolver\MultiConflictRule) { // there is only ever exactly one positive decision in a multiconflict rule foreach ($rule->getLiterals() as $literal) { if (!isset($seen[\abs($literal)]) && $this->decisions->satisfy(-$literal)) { $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; $l = $this->decisions->decisionLevel($literal); if (1 === $l) { $l1num++; } elseif ($level === $l) { $num++; } else { // not level1 or conflict level, add to new rule $learnedLiterals[] = $literal; if ($l > $ruleLevel) { $ruleLevel = $l; } } $seen[\abs($literal)] = \true; break; } } $l1retry = \true; } } } $decision = $this->decisions->atOffset($decisionId); $rule = $decision[\Composer\DependencyResolver\Decisions::DECISION_REASON]; } $why = \count($this->learnedPool) - 1; if (null === $learnedLiterals[0]) { throw new \Composer\DependencyResolver\SolverBugException("Did not find a learnable literal in analyzed rule {$analyzedRule}."); } $newRule = new \Composer\DependencyResolver\GenericRule($learnedLiterals, \Composer\DependencyResolver\Rule::RULE_LEARNED, $why); return [$learnedLiterals[0], $ruleLevel, $newRule, $why]; } /** * @param array $ruleSeen */ private function analyzeUnsolvableRule(\Composer\DependencyResolver\Problem $problem, \Composer\DependencyResolver\Rule $conflictRule, array &$ruleSeen) : void { $why = \spl_object_hash($conflictRule); $ruleSeen[$why] = \true; if ($conflictRule->getType() === \Composer\DependencyResolver\RuleSet::TYPE_LEARNED) { $learnedWhy = $this->learnedWhy[$why]; $problemRules = $this->learnedPool[$learnedWhy]; foreach ($problemRules as $problemRule) { if (!isset($ruleSeen[\spl_object_hash($problemRule)])) { $this->analyzeUnsolvableRule($problem, $problemRule, $ruleSeen); } } return; } if ($conflictRule->getType() === \Composer\DependencyResolver\RuleSet::TYPE_PACKAGE) { // package rules cannot be part of a problem return; } $problem->nextSection(); $problem->addRule($conflictRule); } private function analyzeUnsolvable(\Composer\DependencyResolver\Rule $conflictRule) : int { $problem = new \Composer\DependencyResolver\Problem(); $problem->addRule($conflictRule); $ruleSeen = []; $this->analyzeUnsolvableRule($problem, $conflictRule, $ruleSeen); $this->problems[] = $problem; $seen = []; $literals = $conflictRule->getLiterals(); foreach ($literals as $literal) { // skip the one true literal if ($this->decisions->satisfy($literal)) { continue; } $seen[\abs($literal)] = \true; } foreach ($this->decisions as $decision) { $literal = $decision[\Composer\DependencyResolver\Decisions::DECISION_LITERAL]; // skip literals that are not in this rule if (!isset($seen[\abs($literal)])) { continue; } $why = $decision[\Composer\DependencyResolver\Decisions::DECISION_REASON]; $problem->addRule($why); $this->analyzeUnsolvableRule($problem, $why, $ruleSeen); $literals = $why->getLiterals(); foreach ($literals as $literal) { // skip the one true literal if ($this->decisions->satisfy($literal)) { continue; } $seen[\abs($literal)] = \true; } } return 0; } private function runSat() : void { $this->propagateIndex = 0; /* * here's the main loop: * 1) propagate new decisions (only needed once) * 2) fulfill root requires/fixed packages * 3) fulfill all unresolved rules * 4) minimalize solution if we had choices * if we encounter a problem, we rewind to a safe level and restart * with step 1 */ $level = 1; $systemLevel = $level + 1; while (\true) { if (1 === $level) { $conflictRule = $this->propagate($level); if (null !== $conflictRule) { if ($this->analyzeUnsolvable($conflictRule)) { continue; } return; } } // handle root require/fixed package rules if ($level < $systemLevel) { $iterator = $this->rules->getIteratorFor(\Composer\DependencyResolver\RuleSet::TYPE_REQUEST); foreach ($iterator as $rule) { if ($rule->isEnabled()) { $decisionQueue = []; $noneSatisfied = \true; foreach ($rule->getLiterals() as $literal) { if ($this->decisions->satisfy($literal)) { $noneSatisfied = \false; break; } if ($literal > 0 && $this->decisions->undecided($literal)) { $decisionQueue[] = $literal; } } if ($noneSatisfied && \count($decisionQueue)) { // if any of the options in the decision queue are fixed, only use those $prunedQueue = []; foreach ($decisionQueue as $literal) { if (isset($this->fixedMap[\abs($literal)])) { $prunedQueue[] = $literal; } } if (!empty($prunedQueue)) { $decisionQueue = $prunedQueue; } } if ($noneSatisfied && \count($decisionQueue)) { $oLevel = $level; $level = $this->selectAndInstall($level, $decisionQueue, $rule); if (0 === $level) { return; } if ($level <= $oLevel) { break; } } } } $systemLevel = $level + 1; // root requires/fixed packages left $iterator->next(); if ($iterator->valid()) { continue; } } if ($level < $systemLevel) { $systemLevel = $level; } $rulesCount = \count($this->rules); $pass = 1; $this->io->writeError('Looking at all rules.', \true, IOInterface::DEBUG); for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { if ($i === $rulesCount) { if (1 === $pass) { $this->io->writeError("Something's changed, looking at all rules again (pass #{$pass})", \false, IOInterface::DEBUG); } else { $this->io->overwriteError("Something's changed, looking at all rules again (pass #{$pass})", \false, null, IOInterface::DEBUG); } $i = 0; $pass++; } $rule = $this->rules->ruleById[$i]; $literals = $rule->getLiterals(); if ($rule->isDisabled()) { continue; } $decisionQueue = []; // make sure that // * all negative literals are installed // * no positive literal is installed // i.e. the rule is not fulfilled and we // just need to decide on the positive literals // foreach ($literals as $literal) { if ($literal <= 0) { if (!$this->decisions->decidedInstall($literal)) { continue 2; // next rule } } else { if ($this->decisions->decidedInstall($literal)) { continue 2; // next rule } if ($this->decisions->undecided($literal)) { $decisionQueue[] = $literal; } } } // need to have at least 2 item to pick from if (\count($decisionQueue) < 2) { continue; } $level = $this->selectAndInstall($level, $decisionQueue, $rule); if (0 === $level) { return; } // something changed, so look at all rules again $rulesCount = \count($this->rules); $n = -1; } if ($level < $systemLevel) { continue; } // minimization step if (\count($this->branches)) { $lastLiteral = null; $lastLevel = null; $lastBranchIndex = 0; $lastBranchOffset = 0; for ($i = \count($this->branches) - 1; $i >= 0; $i--) { [$literals, $l] = $this->branches[$i]; foreach ($literals as $offset => $literal) { if ($literal && $literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { $lastLiteral = $literal; $lastBranchIndex = $i; $lastBranchOffset = $offset; $lastLevel = $l; } } } if ($lastLiteral) { unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]); $level = $lastLevel; $this->revert($level); $why = $this->decisions->lastReason(); $level = $this->setPropagateLearn($level, $lastLiteral, $why); if ($level === 0) { return; } continue; } } break; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Link; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositorySet; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; /** * @author Nils Adermann * @author Ruben Gonzalez * @phpstan-type ReasonData Link|BasePackage|string|int|array{packageName: string, constraint: ConstraintInterface}|array{package: BasePackage} */ abstract class Rule { // reason constants and // their reason data contents public const RULE_ROOT_REQUIRE = 2; // array{packageName: string, constraint: ConstraintInterface} public const RULE_FIXED = 3; // array{package: BasePackage} public const RULE_PACKAGE_CONFLICT = 6; // Link public const RULE_PACKAGE_REQUIRES = 7; // Link public const RULE_PACKAGE_SAME_NAME = 10; // string (package name) public const RULE_LEARNED = 12; // int (rule id) public const RULE_PACKAGE_ALIAS = 13; // BasePackage public const RULE_PACKAGE_INVERSE_ALIAS = 14; // BasePackage // bitfield defs private const BITFIELD_TYPE = 0; private const BITFIELD_REASON = 8; private const BITFIELD_DISABLED = 16; /** @var int */ protected $bitfield; /** @var Request */ protected $request; /** * @var Link|BasePackage|ConstraintInterface|string * @phpstan-var ReasonData */ protected $reasonData; /** * @param self::RULE_* $reason A RULE_* constant describing the reason for generating this rule * @param mixed $reasonData * * @phpstan-param ReasonData $reasonData */ public function __construct($reason, $reasonData) { $this->reasonData = $reasonData; $this->bitfield = 0 << self::BITFIELD_DISABLED | $reason << self::BITFIELD_REASON | 255 << self::BITFIELD_TYPE; } /** * @return list */ public abstract function getLiterals() : array; /** * @return int|string */ public abstract function getHash(); public abstract function __toString() : string; public abstract function equals(\Composer\DependencyResolver\Rule $rule) : bool; /** * @return self::RULE_* */ public function getReason() : int { return ($this->bitfield & 255 << self::BITFIELD_REASON) >> self::BITFIELD_REASON; } /** * @phpstan-return ReasonData */ public function getReasonData() { return $this->reasonData; } public function getRequiredPackage() : ?string { switch ($this->getReason()) { case self::RULE_ROOT_REQUIRE: return $this->getReasonData()['packageName']; case self::RULE_FIXED: return $this->getReasonData()['package']->getName(); case self::RULE_PACKAGE_REQUIRES: return $this->getReasonData()->getTarget(); } return null; } /** * @param RuleSet::TYPE_* $type */ public function setType($type) : void { $this->bitfield = $this->bitfield & ~(255 << self::BITFIELD_TYPE) | (255 & $type) << self::BITFIELD_TYPE; } public function getType() : int { return ($this->bitfield & 255 << self::BITFIELD_TYPE) >> self::BITFIELD_TYPE; } public function disable() : void { $this->bitfield = $this->bitfield & ~(255 << self::BITFIELD_DISABLED) | 1 << self::BITFIELD_DISABLED; } public function enable() : void { $this->bitfield &= ~(255 << self::BITFIELD_DISABLED); } public function isDisabled() : bool { return (bool) (($this->bitfield & 255 << self::BITFIELD_DISABLED) >> self::BITFIELD_DISABLED); } public function isEnabled() : bool { return !(($this->bitfield & 255 << self::BITFIELD_DISABLED) >> self::BITFIELD_DISABLED); } public abstract function isAssertion() : bool; public function isCausedByLock(RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : bool { if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) { if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) { return \false; } if ($request->getLockedRepository()) { foreach ($request->getLockedRepository()->getPackages() as $package) { if ($package->getName() === $this->getReasonData()->getTarget()) { if ($pool->isUnacceptableFixedOrLockedPackage($package)) { return \true; } if (!$this->getReasonData()->getConstraint()->matches(new Constraint('=', $package->getVersion()))) { return \true; } // required package was locked but has been unlocked and still matches if (!$request->isLockedPackage($package)) { return \true; } break; } } } } if ($this->getReason() === self::RULE_ROOT_REQUIRE) { if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) { return \false; } if ($request->getLockedRepository()) { foreach ($request->getLockedRepository()->getPackages() as $package) { if ($package->getName() === $this->getReasonData()['packageName']) { if ($pool->isUnacceptableFixedOrLockedPackage($package)) { return \true; } if (!$this->getReasonData()['constraint']->matches(new Constraint('=', $package->getVersion()))) { return \true; } break; } } } } return \false; } /** * @internal */ public function getSourcePackage(\Composer\DependencyResolver\Pool $pool) : BasePackage { $literals = $this->getLiterals(); switch ($this->getReason()) { case self::RULE_PACKAGE_CONFLICT: $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); $reasonData = $this->getReasonData(); // swap literals if they are not in the right order with package2 being the conflicter if ($reasonData->getSource() === $package1->getName()) { [$package2, $package1] = [$package1, $package2]; } return $package2; case self::RULE_PACKAGE_REQUIRES: $sourceLiteral = \array_shift($literals); $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); return $sourcePackage; default: throw new \LogicException('Not implemented'); } } /** * @param BasePackage[] $installedMap * @param array $learnedPool */ public function getPrettyString(RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []) : string { $literals = $this->getLiterals(); switch ($this->getReason()) { case self::RULE_ROOT_REQUIRE: $reasonData = $this->getReasonData(); $packageName = $reasonData['packageName']; $constraint = $reasonData['constraint']; $packages = $pool->whatProvides($packageName, $constraint); if (!$packages) { return 'No package found to satisfy root composer.json require ' . $packageName . ' ' . $constraint->getPrettyString(); } $packagesNonAlias = \array_values(\array_filter($packages, static function ($p) : bool { return !$p instanceof AliasPackage; })); if (\count($packagesNonAlias) === 1) { $package = $packagesNonAlias[0]; if ($request->isLockedPackage($package)) { return $package->getPrettyName() . ' is locked to version ' . $package->getPrettyVersion() . " and an update of this package was not requested."; } } return 'Root composer.json requires ' . $packageName . ' ' . $constraint->getPrettyString() . ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint) . '.'; case self::RULE_FIXED: $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']); if ($request->isLockedPackage($package)) { return $package->getPrettyName() . ' is locked to version ' . $package->getPrettyVersion() . ' and an update of this package was not requested.'; } return $package->getPrettyName() . ' is present at version ' . $package->getPrettyVersion() . ' and cannot be modified by Composer'; case self::RULE_PACKAGE_CONFLICT: $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); $conflictTarget = $package1->getPrettyString(); $reasonData = $this->getReasonData(); // swap literals if they are not in the right order with package2 being the conflicter if ($reasonData->getSource() === $package1->getName()) { [$package2, $package1] = [$package1, $package2]; $conflictTarget = $package1->getPrettyName() . ' ' . $reasonData->getPrettyConstraint(); } // if the conflict is not directly against the package but something it provides/replaces, // we try to find that link to display a better message if ($reasonData->getTarget() !== $package1->getName()) { $provideType = null; $provided = null; foreach ($package1->getProvides() as $provide) { if ($provide->getTarget() === $reasonData->getTarget()) { $provideType = 'provides'; $provided = $provide->getPrettyConstraint(); break; } } foreach ($package1->getReplaces() as $replace) { if ($replace->getTarget() === $reasonData->getTarget()) { $provideType = 'replaces'; $provided = $replace->getPrettyConstraint(); break; } } if (null !== $provideType) { $conflictTarget = $reasonData->getTarget() . ' ' . $reasonData->getPrettyConstraint() . ' (' . $package1->getPrettyString() . ' ' . $provideType . ' ' . $reasonData->getTarget() . ' ' . $provided . ')'; } } return $package2->getPrettyString() . ' conflicts with ' . $conflictTarget . '.'; case self::RULE_PACKAGE_REQUIRES: $sourceLiteral = \array_shift($literals); $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); $reasonData = $this->getReasonData(); $requires = []; foreach ($literals as $literal) { $requires[] = $pool->literalToPackage($literal); } $text = $reasonData->getPrettyString($sourcePackage); if ($requires) { $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.'; } else { $targetName = $reasonData->getTarget(); $reason = \Composer\DependencyResolver\Problem::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $targetName, $reasonData->getConstraint()); return $text . ' -> ' . $reason[1]; } return $text; case self::RULE_PACKAGE_SAME_NAME: $packageNames = []; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); $packageNames[$package->getName()] = \true; } $replacedName = $this->getReasonData(); if (\count($packageNames) > 1) { $reason = null; if (!isset($packageNames[$replacedName])) { $reason = 'They ' . (\count($literals) === 2 ? 'both' : 'all') . ' replace ' . $replacedName . ' and thus cannot coexist.'; } else { $replacerNames = $packageNames; unset($replacerNames[$replacedName]); $replacerNames = \array_keys($replacerNames); if (\count($replacerNames) === 1) { $reason = $replacerNames[0] . ' replaces '; } else { $reason = '[' . \implode(', ', $replacerNames) . '] replace '; } $reason .= $replacedName . ' and thus cannot coexist with it.'; } $installedPackages = []; $removablePackages = []; foreach ($literals as $literal) { if (isset($installedMap[\abs($literal)])) { $installedPackages[] = $pool->literalToPackage($literal); } else { $removablePackages[] = $pool->literalToPackage($literal); } } if ($installedPackages && $removablePackages) { return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, \true) . ' cannot be installed as that would require removing ' . $this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, \true) . '. ' . $reason; } return 'Only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, \true) . '. ' . $reason; } return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, \true) . '.'; case self::RULE_LEARNED: /** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */ // if (isset($learnedPool[$this->getReasonData()])) { // echo $this->getReasonData()."\n"; // $learnedString = ', learned rules:' . Problem::formatDeduplicatedRules($learnedPool[$this->getReasonData()], ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); // } else { // $learnedString = ' (reasoning unavailable)'; // } $learnedString = ' (conflict analysis result)'; if (\count($literals) === 1) { $ruleText = $pool->literalToPrettyString($literals[0], $installedMap); } else { $groups = []; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if (isset($installedMap[$package->id])) { $group = $literal > 0 ? 'keep' : 'remove'; } else { $group = $literal > 0 ? 'install' : 'don\'t install'; } $groups[$group][] = $this->deduplicateDefaultBranchAlias($package); } $ruleTexts = []; foreach ($groups as $group => $packages) { $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '') . ' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose); } $ruleText = \implode(' | ', $ruleTexts); } return 'Conclusion: ' . $ruleText . $learnedString; case self::RULE_PACKAGE_ALIAS: $aliasPackage = $pool->literalToPackage($literals[0]); // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { return ''; } $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); return $aliasPackage->getPrettyString() . ' is an alias of ' . $package->getPrettyString() . ' and thus requires it to be installed too.'; case self::RULE_PACKAGE_INVERSE_ALIAS: // inverse alias rules work the other way around than above $aliasPackage = $pool->literalToPackage($literals[1]); // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { return ''; } $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); return $aliasPackage->getPrettyString() . ' is an alias of ' . $package->getPrettyString() . ' and must be installed with it.'; default: $ruleText = ''; foreach ($literals as $i => $literal) { if ($i !== 0) { $ruleText .= '|'; } $ruleText .= $pool->literalToPrettyString($literal, $installedMap); } return '(' . $ruleText . ')'; } } /** * @param array $packages An array containing packages or literals */ protected function formatPackagesUnique(\Composer\DependencyResolver\Pool $pool, array $packages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = \false) : string { foreach ($packages as $index => $package) { if (!\is_object($package)) { $packages[$index] = $pool->literalToPackage($package); } } return \Composer\DependencyResolver\Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup); } private function deduplicateDefaultBranchAlias(BasePackage $package) : BasePackage { if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } return $package; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann * @phpstan-import-type ReasonData from Rule */ class Rule2Literals extends \Composer\DependencyResolver\Rule { /** @var int */ protected $literal1; /** @var int */ protected $literal2; /** * @param Rule::RULE_* $reason A RULE_* constant * @param mixed $reasonData * * @phpstan-param ReasonData $reasonData */ public function __construct(int $literal1, int $literal2, $reason, $reasonData) { parent::__construct($reason, $reasonData); if ($literal1 < $literal2) { $this->literal1 = $literal1; $this->literal2 = $literal2; } else { $this->literal1 = $literal2; $this->literal2 = $literal1; } } /** * @return list */ public function getLiterals() : array { return [$this->literal1, $this->literal2]; } /** * @inheritDoc */ public function getHash() { return $this->literal1 . ',' . $this->literal2; } /** * Checks if this rule is equal to another one * * Ignores whether either of the rules is disabled. * * @param Rule $rule The rule to check against * @return bool Whether the rules are equal */ public function equals(\Composer\DependencyResolver\Rule $rule) : bool { // specialized fast-case if ($rule instanceof self) { if ($this->literal1 !== $rule->literal1) { return \false; } if ($this->literal2 !== $rule->literal2) { return \false; } return \true; } $literals = $rule->getLiterals(); if (2 !== \count($literals)) { return \false; } if ($this->literal1 !== $literals[0]) { return \false; } if ($this->literal2 !== $literals[1]) { return \false; } return \true; } /** @return false */ public function isAssertion() : bool { return \false; } /** * Formats a rule as a string of the format (Literal1|Literal2|...) */ public function __toString() : string { $result = $this->isDisabled() ? 'disabled(' : '('; $result .= $this->literal1 . '|' . $this->literal2 . ')'; return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Util\IniHelper; use Composer\Repository\RepositorySet; /** * @author Nils Adermann * * @method self::ERROR_DEPENDENCY_RESOLUTION_FAILED getCode() */ class SolverProblemsException extends \RuntimeException { public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; /** @var Problem[] */ protected $problems; /** @var array */ protected $learnedPool; /** * @param Problem[] $problems * @param array $learnedPool */ public function __construct(array $problems, array $learnedPool) { $this->problems = $problems; $this->learnedPool = $learnedPool; parent::__construct('Failed resolving dependencies with ' . \count($problems) . ' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED); } public function getPrettyString(RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool, bool $isVerbose, bool $isDevExtraction = \false) : string { $installedMap = $request->getPresentMap(\true); $missingExtensions = []; $isCausedByLock = \false; $problems = []; foreach ($this->problems as $problem) { $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $this->learnedPool) . "\n"; $missingExtensions = \array_merge($missingExtensions, $this->getExtensionProblems($problem->getReasons())); $isCausedByLock = $isCausedByLock || $problem->isCausedByLock($repositorySet, $request, $pool); } $i = 1; $text = "\n"; foreach (\array_unique($problems) as $problem) { $text .= " Problem " . $i++ . $problem; } $hints = []; if (!$isDevExtraction && (\strpos($text, 'could not be found') || \strpos($text, 'no matching package found'))) { $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems."; } if (!empty($missingExtensions)) { $hints[] = $this->createExtensionHint($missingExtensions); } if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) { $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions."; } if (\strpos($text, 'found composer-plugin-api[2.0.0] but it does not match') && \strpos($text, '- ocramius/package-versions')) { $hints[] = "ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.\nIf you can not upgrade PHP you can require composer/package-versions-deprecated to resolve this with PHP 7.0+."; } if (!\class_exists('_ContaoManager\\PHPUnit\\Framework\\TestCase', \false)) { if (\strpos($text, 'found composer-plugin-api[2.0.0] but it does not match')) { $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2."; } } if ($hints) { $text .= "\n" . \implode("\n\n", $hints); } return $text; } /** * @return Problem[] */ public function getProblems() : array { return $this->problems; } /** * @param string[] $missingExtensions */ private function createExtensionHint(array $missingExtensions) : string { $paths = IniHelper::getAll(); if ('' === $paths[0]) { if (\count($paths) === 1) { return ''; } \array_shift($paths); } $ignoreExtensionsArguments = \implode(" ", \array_map(static function ($extension) { return "--ignore-platform-req={$extension}"; }, \array_unique($missingExtensions))); $text = "To enable extensions, verify that they are enabled in your .ini files:\n - "; $text .= \implode("\n - ", $paths); $text .= "\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode."; $text .= "\nAlternatively, you can run Composer with `{$ignoreExtensionsArguments}` to temporarily ignore these required extensions."; return $text; } /** * @param Rule[][] $reasonSets * @return string[] */ private function getExtensionProblems(array $reasonSets) : array { $missingExtensions = []; foreach ($reasonSets as $reasonSet) { foreach ($reasonSet as $rule) { $required = $rule->getRequiredPackage(); if (null !== $required && 0 === \strpos($required, 'ext-')) { $missingExtensions[$required] = 1; } } } return \array_keys($missingExtensions); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann */ class SolverBugException extends \RuntimeException { public function __construct(string $message) { parent::__construct($message . "\nThis exception was most likely caused by a bug in Composer.\n" . "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Semver\CompilingMatcher; use Composer\Semver\Constraint\Constraint; /** * @author Nils Adermann * @author Jordi Boggiano */ class DefaultPolicy implements \Composer\DependencyResolver\PolicyInterface { /** @var bool */ private $preferStable; /** @var bool */ private $preferLowest; /** @var array|null */ private $preferredVersions; /** @var array>> */ private $preferredPackageResultCachePerPool; /** @var array> */ private $sortingCachePerPool; /** * @param array|null $preferredVersions Must be an array of package name => normalized version */ public function __construct(bool $preferStable = \false, bool $preferLowest = \false, ?array $preferredVersions = null) { $this->preferStable = $preferStable; $this->preferLowest = $preferLowest; $this->preferredVersions = $preferredVersions; } /** * @param string $operator One of Constraint::STR_OP_* * * @phpstan-param Constraint::STR_OP_* $operator */ public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator) : bool { if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { return BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB]; } // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher if (\strpos($a->getVersion(), 'dev-') === 0 || \strpos($b->getVersion(), 'dev-') === 0) { $constraint = new Constraint($operator, $b->getVersion()); $version = new Constraint('==', $a->getVersion()); return $constraint->matchSpecific($version, \true); } return CompilingMatcher::match(new Constraint($operator, $b->getVersion()), Constraint::OP_EQ, $a->getVersion()); } /** * @param int[] $literals * @param string $requiredPackage * @return int[] */ public function selectPreferredPackages(\Composer\DependencyResolver\Pool $pool, array $literals, ?string $requiredPackage = null) : array { \sort($literals); $resultCacheKey = \implode(',', $literals) . $requiredPackage; $poolId = \spl_object_id($pool); if (isset($this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey])) { return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey]; } $packages = $this->groupLiteralsByName($pool, $literals); foreach ($packages as &$nameLiterals) { \usort($nameLiterals, function ($a, $b) use($pool, $requiredPackage, $poolId) : int { $cacheKey = 'i' . $a . '.' . $b . $requiredPackage; // i prefix -> ignoreReplace = true if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { return $this->sortingCachePerPool[$poolId][$cacheKey]; } return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, \true); }); } foreach ($packages as &$sortedLiterals) { $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals); $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals); } $selected = \array_merge(...\array_values($packages)); // now sort the result across all packages to respect replaces across packages \usort($selected, function ($a, $b) use($pool, $requiredPackage, $poolId) : int { $cacheKey = $a . '.' . $b . $requiredPackage; // no i prefix -> ignoreReplace = false if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { return $this->sortingCachePerPool[$poolId][$cacheKey]; } return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage); }); return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey] = $selected; } /** * @param int[] $literals * @return array */ protected function groupLiteralsByName(\Composer\DependencyResolver\Pool $pool, array $literals) : array { $packages = []; foreach ($literals as $literal) { $packageName = $pool->literalToPackage($literal)->getName(); if (!isset($packages[$packageName])) { $packages[$packageName] = []; } $packages[$packageName][] = $literal; } return $packages; } /** * @protected */ public function compareByPriority(\Composer\DependencyResolver\Pool $pool, BasePackage $a, BasePackage $b, ?string $requiredPackage = null, bool $ignoreReplace = \false) : int { // prefer aliases to the original package if ($a->getName() === $b->getName()) { $aAliased = $a instanceof AliasPackage; $bAliased = $b instanceof AliasPackage; if ($aAliased && !$bAliased) { return -1; // use a } if (!$aAliased && $bAliased) { return 1; // use b } } if (!$ignoreReplace) { // return original, not replaced if ($this->replaces($a, $b)) { return 1; // use b } if ($this->replaces($b, $a)) { return -1; // use a } // for replacers not replacing each other, put a higher prio on replacing // packages with the same vendor as the required package if ($requiredPackage && \false !== ($pos = \strpos($requiredPackage, '/'))) { $requiredVendor = \substr($requiredPackage, 0, $pos); $aIsSameVendor = \strpos($a->getName(), $requiredVendor) === 0; $bIsSameVendor = \strpos($b->getName(), $requiredVendor) === 0; if ($bIsSameVendor !== $aIsSameVendor) { return $aIsSameVendor ? -1 : 1; } } } // priority equal, sort by package id to make reproducible if ($a->id === $b->id) { return 0; } return $a->id < $b->id ? -1 : 1; } /** * Checks if source replaces a package with the same name as target. * * Replace constraints are ignored. This method should only be used for * prioritisation, not for actual constraint verification. */ protected function replaces(BasePackage $source, BasePackage $target) : bool { foreach ($source->getReplaces() as $link) { if ($link->getTarget() === $target->getName()) { return \true; } } return \false; } /** * @param int[] $literals * @return int[] */ protected function pruneToBestVersion(\Composer\DependencyResolver\Pool $pool, array $literals) : array { if ($this->preferredVersions !== null) { $name = $pool->literalToPackage($literals[0])->getName(); if (isset($this->preferredVersions[$name])) { $preferredVersion = $this->preferredVersions[$name]; $bestLiterals = []; foreach ($literals as $literal) { if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) { $bestLiterals[] = $literal; } } if (\count($bestLiterals) > 0) { return $bestLiterals; } } } $operator = $this->preferLowest ? '<' : '>'; $bestLiterals = [$literals[0]]; $bestPackage = $pool->literalToPackage($literals[0]); foreach ($literals as $i => $literal) { if (0 === $i) { continue; } $package = $pool->literalToPackage($literal); if ($this->versionCompare($package, $bestPackage, $operator)) { $bestPackage = $package; $bestLiterals = [$literal]; } elseif ($this->versionCompare($package, $bestPackage, '==')) { $bestLiterals[] = $literal; } } return $bestLiterals; } /** * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones * * If no package is a local alias, nothing happens * * @param int[] $literals * @return int[] */ protected function pruneRemoteAliases(\Composer\DependencyResolver\Pool $pool, array $literals) : array { $hasLocalAlias = \false; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { $hasLocalAlias = \true; break; } } if (!$hasLocalAlias) { return $literals; } $selected = []; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { $selected[] = $literal; } } return $selected; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * Wrapper around a Rule which keeps track of the two literals it watches * * Used by RuleWatchGraph to store rules in two RuleWatchChains. * * @author Nils Adermann */ class RuleWatchNode { /** @var int */ public $watch1; /** @var int */ public $watch2; /** @var Rule */ protected $rule; /** * Creates a new node watching the first and second literals of the rule. * * @param Rule $rule The rule to wrap */ public function __construct(\Composer\DependencyResolver\Rule $rule) { $this->rule = $rule; $literals = $rule->getLiterals(); $literalCount = \count($literals); $this->watch1 = $literalCount > 0 ? $literals[0] : 0; $this->watch2 = $literalCount > 1 ? $literals[1] : 0; } /** * Places the second watch on the rule's literal, decided at the highest level * * Useful for learned rules where the literal for the highest rule is most * likely to quickly lead to further decisions. * * @param Decisions $decisions The decisions made so far by the solver */ public function watch2OnHighest(\Composer\DependencyResolver\Decisions $decisions) : void { $literals = $this->rule->getLiterals(); // if there are only 2 elements, both are being watched anyway if (\count($literals) < 3 || $this->rule instanceof \Composer\DependencyResolver\MultiConflictRule) { return; } $watchLevel = 0; foreach ($literals as $literal) { $level = $decisions->decisionLevel($literal); if ($level > $watchLevel) { $this->watch2 = $literal; $watchLevel = $level; } } } /** * Returns the rule this node wraps */ public function getRule() : \Composer\DependencyResolver\Rule { return $this->rule; } /** * Given one watched literal, this method returns the other watched literal * * @param int $literal The watched literal that should not be returned * @return int A literal */ public function getOtherWatch(int $literal) : int { if ($this->watch1 === $literal) { return $this->watch2; } return $this->watch1; } /** * Moves a watch from one literal to another * * @param int $from The previously watched literal * @param int $to The literal to be watched now */ public function moveWatch(int $from, int $to) : void { if ($this->watch1 === $from) { $this->watch1 = $to; } else { $this->watch2 = $to; } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Repository\RepositorySet; /** * @author Nils Adermann * @implements \IteratorAggregate * @internal * @final */ class RuleSet implements \IteratorAggregate, \Countable { // highest priority => lowest number public const TYPE_PACKAGE = 0; public const TYPE_REQUEST = 1; public const TYPE_LEARNED = 4; /** * READ-ONLY: Lookup table for rule id to rule object * * @var array */ public $ruleById = []; const TYPES = [self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_REQUEST => 'REQUEST', self::TYPE_LEARNED => 'LEARNED']; /** @var array */ protected $rules; /** @var 0|positive-int */ protected $nextRuleId = 0; /** @var array */ protected $rulesByHash = []; public function __construct() { foreach ($this->getTypes() as $type) { $this->rules[$type] = []; } } /** * @param self::TYPE_* $type */ public function add(\Composer\DependencyResolver\Rule $rule, $type) : void { if (!isset(self::TYPES[$type])) { throw new \OutOfBoundsException('Unknown rule type: ' . $type); } $hash = $rule->getHash(); // Do not add if rule already exists if (isset($this->rulesByHash[$hash])) { $potentialDuplicates = $this->rulesByHash[$hash]; if (\is_array($potentialDuplicates)) { foreach ($potentialDuplicates as $potentialDuplicate) { if ($rule->equals($potentialDuplicate)) { return; } } } else { if ($rule->equals($potentialDuplicates)) { return; } } } if (!isset($this->rules[$type])) { $this->rules[$type] = []; } $this->rules[$type][] = $rule; $this->ruleById[$this->nextRuleId] = $rule; $rule->setType($type); $this->nextRuleId++; if (!isset($this->rulesByHash[$hash])) { $this->rulesByHash[$hash] = $rule; } elseif (\is_array($this->rulesByHash[$hash])) { $this->rulesByHash[$hash][] = $rule; } else { $originalRule = $this->rulesByHash[$hash]; $this->rulesByHash[$hash] = [$originalRule, $rule]; } } public function count() : int { return $this->nextRuleId; } public function ruleById(int $id) : \Composer\DependencyResolver\Rule { return $this->ruleById[$id]; } /** @return array */ public function getRules() : array { return $this->rules; } public function getIterator() : \Composer\DependencyResolver\RuleSetIterator { return new \Composer\DependencyResolver\RuleSetIterator($this->getRules()); } /** * @param self::TYPE_*|array $types */ public function getIteratorFor($types) : \Composer\DependencyResolver\RuleSetIterator { if (!\is_array($types)) { $types = [$types]; } $allRules = $this->getRules(); /** @var array $rules */ $rules = []; foreach ($types as $type) { $rules[$type] = $allRules[$type]; } return new \Composer\DependencyResolver\RuleSetIterator($rules); } /** * @param array|self::TYPE_* $types */ public function getIteratorWithout($types) : \Composer\DependencyResolver\RuleSetIterator { if (!\is_array($types)) { $types = [$types]; } $rules = $this->getRules(); foreach ($types as $type) { unset($rules[$type]); } return new \Composer\DependencyResolver\RuleSetIterator($rules); } /** * @return array{self::TYPE_PACKAGE, self::TYPE_REQUEST, self::TYPE_LEARNED} */ public function getTypes() : array { $types = self::TYPES; return \array_keys($types); } public function getPrettyString(?RepositorySet $repositorySet = null, ?\Composer\DependencyResolver\Request $request = null, ?\Composer\DependencyResolver\Pool $pool = null, bool $isVerbose = \false) : string { $string = "\n"; foreach ($this->rules as $type => $rules) { $string .= \str_pad(self::TYPES[$type], 8, ' ') . ": "; foreach ($rules as $rule) { $string .= ($repositorySet && $request && $pool ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule) . "\n"; } $string .= "\n\n"; } return $string; } public function __toString() : string { return $this->getPrettyString(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\BasePackage; use Composer\Package\Version\VersionParser; use Composer\Semver\CompilingMatcher; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; /** * A package pool contains all packages for dependency resolution * * @author Nils Adermann * @author Jordi Boggiano */ class Pool implements \Countable { /** @var BasePackage[] */ protected $packages = []; /** @var array */ protected $packageByName = []; /** @var VersionParser */ protected $versionParser; /** @var array> */ protected $providerCache = []; /** @var BasePackage[] */ protected $unacceptableFixedOrLockedPackages; /** @var array> Map of package name => normalized version => pretty version */ protected $removedVersions = []; /** @var array> Map of package object hash => removed normalized versions => removed pretty version */ protected $removedVersionsByPackage = []; /** * @param BasePackage[] $packages * @param BasePackage[] $unacceptableFixedOrLockedPackages * @param array> $removedVersions * @param array> $removedVersionsByPackage */ public function __construct(array $packages = [], array $unacceptableFixedOrLockedPackages = [], array $removedVersions = [], array $removedVersionsByPackage = []) { $this->versionParser = new VersionParser(); $this->setPackages($packages); $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages; $this->removedVersions = $removedVersions; $this->removedVersionsByPackage = $removedVersionsByPackage; } /** * @return array */ public function getRemovedVersions(string $name, ConstraintInterface $constraint) : array { if (!isset($this->removedVersions[$name])) { return []; } $result = []; foreach ($this->removedVersions[$name] as $version => $prettyVersion) { if ($constraint->matches(new Constraint('==', $version))) { $result[$version] = $prettyVersion; } } return $result; } /** * @return array */ public function getRemovedVersionsByPackage(string $objectHash) : array { if (!isset($this->removedVersionsByPackage[$objectHash])) { return []; } return $this->removedVersionsByPackage[$objectHash]; } /** * @param BasePackage[] $packages */ private function setPackages(array $packages) : void { $id = 1; foreach ($packages as $package) { $this->packages[] = $package; $package->id = $id++; foreach ($package->getNames() as $provided) { $this->packageByName[$provided][] = $package; } } } /** * @return BasePackage[] */ public function getPackages() : array { return $this->packages; } /** * Retrieves the package object for a given package id. */ public function packageById(int $id) : BasePackage { return $this->packages[$id - 1]; } /** * Returns how many packages have been loaded into the pool */ public function count() : int { return \count($this->packages); } /** * Searches all packages providing the given package name and match the constraint * * @param string $name The package name to be searched for * @param ?ConstraintInterface $constraint A constraint that all returned * packages must match or null to return all * @return BasePackage[] A set of packages */ public function whatProvides(string $name, ?ConstraintInterface $constraint = null) : array { $key = (string) $constraint; if (isset($this->providerCache[$name][$key])) { return $this->providerCache[$name][$key]; } return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint); } /** * @param string $name The package name to be searched for * @param ?ConstraintInterface $constraint A constraint that all returned * packages must match or null to return all * @return BasePackage[] */ private function computeWhatProvides(string $name, ?ConstraintInterface $constraint = null) : array { if (!isset($this->packageByName[$name])) { return []; } $matches = []; foreach ($this->packageByName[$name] as $candidate) { if ($this->match($candidate, $name, $constraint)) { $matches[] = $candidate; } } return $matches; } public function literalToPackage(int $literal) : BasePackage { $packageId = \abs($literal); return $this->packageById($packageId); } /** * @param array $installedMap */ public function literalToPrettyString(int $literal, array $installedMap) : string { $package = $this->literalToPackage($literal); if (isset($installedMap[$package->id])) { $prefix = $literal > 0 ? 'keep' : 'remove'; } else { $prefix = $literal > 0 ? 'install' : 'don\'t install'; } return $prefix . ' ' . $package->getPrettyString(); } /** * Checks if the package matches the given constraint directly or through * provided or replaced packages * * @param string $name Name of the package to be matched */ public function match(BasePackage $candidate, string $name, ?ConstraintInterface $constraint = null) : bool { $candidateName = $candidate->getName(); $candidateVersion = $candidate->getVersion(); if ($candidateName === $name) { return $constraint === null || CompilingMatcher::match($constraint, Constraint::OP_EQ, $candidateVersion); } $provides = $candidate->getProvides(); $replaces = $candidate->getReplaces(); // aliases create multiple replaces/provides for one target so they can not use the shortcut below if (isset($replaces[0]) || isset($provides[0])) { foreach ($provides as $link) { if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { return \true; } } foreach ($replaces as $link) { if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { return \true; } } return \false; } if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) { return \true; } if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) { return \true; } return \false; } public function isUnacceptableFixedOrLockedPackage(BasePackage $package) : bool { return \in_array($package, $this->unacceptableFixedOrLockedPackages, \true); } /** * @return BasePackage[] */ public function getUnacceptableFixedOrLockedPackages() : array { return $this->unacceptableFixedOrLockedPackages; } public function __toString() : string { $str = "Pool:\n"; foreach ($this->packages as $package) { $str .= '- ' . \str_pad((string) $package->id, 6, ' ', \STR_PAD_LEFT) . ': ' . $package->getName() . "\n"; } return $str; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann * * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C]) */ class MultiConflictRule extends \Composer\DependencyResolver\Rule { /** @var list */ protected $literals; /** * @param list $literals */ public function __construct(array $literals, $reason, $reasonData) { parent::__construct($reason, $reasonData); if (\count($literals) < 3) { throw new \RuntimeException("multi conflict rule requires at least 3 literals"); } // sort all packages ascending by id \sort($literals); $this->literals = $literals; } /** * @return list */ public function getLiterals() : array { return $this->literals; } /** * @inheritDoc */ public function getHash() { $data = \unpack('ihash', \md5('c:' . \implode(',', $this->literals), \true)); return $data['hash']; } /** * Checks if this rule is equal to another one * * Ignores whether either of the rules is disabled. * * @param Rule $rule The rule to check against * @return bool Whether the rules are equal */ public function equals(\Composer\DependencyResolver\Rule $rule) : bool { if ($rule instanceof \Composer\DependencyResolver\MultiConflictRule) { return $this->literals === $rule->getLiterals(); } return \false; } public function isAssertion() : bool { return \false; } /** * @return never * @throws \RuntimeException */ public function disable() : void { throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation."); } /** * Formats a rule as a string of the format (Literal1|Literal2|...) */ public function __toString() : string { // TODO multi conflict? $result = $this->isDisabled() ? 'disabled(multi(' : '(multi('; foreach ($this->literals as $i => $literal) { if ($i !== 0) { $result .= '|'; } $result .= $literal; } $result .= '))'; return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Repository\LockArrayRepository; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\MatchAllConstraint; /** * @author Nils Adermann */ class Request { /** * Identifies a partial update for listed packages only, all dependencies will remain at locked versions */ public const UPDATE_ONLY_LISTED = 0; /** * Identifies a partial update for listed packages and recursively all their dependencies, however dependencies * also directly required by the root composer.json and their dependencies will remain at the locked version. */ public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE = 1; /** * Identifies a partial update for listed packages and recursively all their dependencies, even dependencies * also directly required by the root composer.json will be updated. */ public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS = 2; /** @var ?LockArrayRepository */ protected $lockedRepository; /** @var array */ protected $requires = []; /** @var array */ protected $fixedPackages = []; /** @var array */ protected $lockedPackages = []; /** @var array */ protected $fixedLockedPackages = []; /** @var array */ protected $updateAllowList = []; /** @var false|self::UPDATE_* */ protected $updateAllowTransitiveDependencies = \false; /** @var non-empty-list|null */ private $restrictedPackages = null; public function __construct(?LockArrayRepository $lockedRepository = null) { $this->lockedRepository = $lockedRepository; } public function requireName(string $packageName, ?ConstraintInterface $constraint = null) : void { $packageName = \strtolower($packageName); if ($constraint === null) { $constraint = new MatchAllConstraint(); } if (isset($this->requires[$packageName])) { throw new \LogicException('Overwriting requires seems like a bug (' . $packageName . ' ' . $this->requires[$packageName]->getPrettyString() . ' => ' . $constraint->getPrettyString() . ', check why it is happening, might be a root alias'); } $this->requires[$packageName] = $constraint; } /** * Mark a package as currently present and having to remain installed * * This is used for platform packages which cannot be modified by Composer. A rule enforcing their installation is * generated for dependency resolution. Partial updates with dependencies cannot in any way modify these packages. */ public function fixPackage(BasePackage $package) : void { $this->fixedPackages[\spl_object_hash($package)] = $package; } /** * Mark a package as locked to a specific version but removable * * This is used for lock file packages which need to be treated similar to fixed packages by the pool builder in * that by default they should really only have the currently present version loaded and no remote alternatives. * * However unlike fixed packages there will not be a special rule enforcing their installation for the solver, so * if nothing requires these packages they will be removed. Additionally in a partial update these packages can be * unlocked, meaning other versions can be installed if explicitly requested as part of the update. */ public function lockPackage(BasePackage $package) : void { $this->lockedPackages[\spl_object_hash($package)] = $package; } /** * Marks a locked package fixed. So it's treated irremovable like a platform package. * * This is necessary for the composer install step which verifies the lock file integrity and should not allow * removal of any packages. At the same time lock packages there cannot simply be marked fixed, as error reporting * would then report them as platform packages, so this still marks them as locked packages at the same time. */ public function fixLockedPackage(BasePackage $package) : void { $this->fixedPackages[\spl_object_hash($package)] = $package; $this->fixedLockedPackages[\spl_object_hash($package)] = $package; } public function unlockPackage(BasePackage $package) : void { unset($this->lockedPackages[\spl_object_hash($package)]); } /** * @param array $updateAllowList * @param false|self::UPDATE_* $updateAllowTransitiveDependencies */ public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies) : void { $this->updateAllowList = $updateAllowList; $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; } /** * @return array */ public function getUpdateAllowList() : array { return $this->updateAllowList; } public function getUpdateAllowTransitiveDependencies() : bool { return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED; } public function getUpdateAllowTransitiveRootDependencies() : bool { return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; } /** * @return array */ public function getRequires() : array { return $this->requires; } /** * @return array */ public function getFixedPackages() : array { return $this->fixedPackages; } public function isFixedPackage(BasePackage $package) : bool { return isset($this->fixedPackages[\spl_object_hash($package)]); } /** * @return array */ public function getLockedPackages() : array { return $this->lockedPackages; } public function isLockedPackage(PackageInterface $package) : bool { return isset($this->lockedPackages[\spl_object_hash($package)]) || isset($this->fixedLockedPackages[\spl_object_hash($package)]); } /** * @return array */ public function getFixedOrLockedPackages() : array { return \array_merge($this->fixedPackages, $this->lockedPackages); } /** * @return array * * @TODO look into removing the packageIds option, the only place true is used * is for the installed map in the solver problems. * Some locked packages may not be in the pool, * so they have a package->id of -1 */ public function getPresentMap(bool $packageIds = \false) : array { $presentMap = []; if ($this->lockedRepository) { foreach ($this->lockedRepository->getPackages() as $package) { $presentMap[$packageIds ? $package->getId() : \spl_object_hash($package)] = $package; } } foreach ($this->fixedPackages as $package) { $presentMap[$packageIds ? $package->getId() : \spl_object_hash($package)] = $package; } return $presentMap; } /** * @return BasePackage[] */ public function getFixedPackagesMap() : array { $fixedPackagesMap = []; foreach ($this->fixedPackages as $package) { $fixedPackagesMap[$package->getId()] = $package; } return $fixedPackagesMap; } /** * @return ?LockArrayRepository */ public function getLockedRepository() : ?LockArrayRepository { return $this->lockedRepository; } /** * Restricts the pool builder from loading other packages than those listed here * * @param non-empty-list $names */ public function restrictPackages(array $names) : void { $this->restrictedPackages = $names; } /** * @return list */ public function getRestrictedPackages() : ?array { return $this->restrictedPackages; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Version\VersionParser; use Composer\Semver\CompilingMatcher; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Intervals; /** * Optimizes a given pool * * @author Yanick Witschi */ class PoolOptimizer { /** * @var PolicyInterface */ private $policy; /** * @var array */ private $irremovablePackages = []; /** * @var array> */ private $requireConstraintsPerPackage = []; /** * @var array> */ private $conflictConstraintsPerPackage = []; /** * @var array */ private $packagesToRemove = []; /** * @var array */ private $aliasesPerPackage = []; /** * @var array> */ private $removedVersionsByPackage = []; public function __construct(\Composer\DependencyResolver\PolicyInterface $policy) { $this->policy = $policy; } public function optimize(\Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : \Composer\DependencyResolver\Pool { $this->prepare($request, $pool); $this->optimizeByIdenticalDependencies($request, $pool); $this->optimizeImpossiblePackagesAway($request, $pool); $optimizedPool = $this->applyRemovalsToPool($pool); // No need to run this recursively at the moment // because the current optimizations cannot provide // even more gains when ran again. Might change // in the future with additional optimizations. $this->irremovablePackages = []; $this->requireConstraintsPerPackage = []; $this->conflictConstraintsPerPackage = []; $this->packagesToRemove = []; $this->aliasesPerPackage = []; $this->removedVersionsByPackage = []; return $optimizedPool; } private function prepare(\Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : void { $irremovablePackageConstraintGroups = []; // Mark fixed or locked packages as irremovable foreach ($request->getFixedOrLockedPackages() as $package) { $irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion()); } // Extract requested package requirements foreach ($request->getRequires() as $require => $constraint) { $this->extractRequireConstraintsPerPackage($require, $constraint); } // First pass over all packages to extract information and mark package constraints irremovable foreach ($pool->getPackages() as $package) { // Extract package requirements foreach ($package->getRequires() as $link) { $this->extractRequireConstraintsPerPackage($link->getTarget(), $link->getConstraint()); } // Extract package conflicts foreach ($package->getConflicts() as $link) { $this->extractConflictConstraintsPerPackage($link->getTarget(), $link->getConstraint()); } // Keep track of alias packages for every package so if either the alias or aliased is kept // we keep the others as they are a unit of packages really if ($package instanceof AliasPackage) { $this->aliasesPerPackage[$package->getAliasOf()->id][] = $package; } } $irremovablePackageConstraints = []; foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) { $irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, \false); } unset($irremovablePackageConstraintGroups); // Mark the packages as irremovable based on the constraints foreach ($pool->getPackages() as $package) { if (!isset($irremovablePackageConstraints[$package->getName()])) { continue; } if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) { $this->markPackageIrremovable($package); } } } private function markPackageIrremovable(BasePackage $package) : void { $this->irremovablePackages[$package->id] = \true; if ($package instanceof AliasPackage) { // recursing here so aliasesPerPackage for the aliasOf can be checked // and all its aliases marked as irremovable as well $this->markPackageIrremovable($package->getAliasOf()); } if (isset($this->aliasesPerPackage[$package->id])) { foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { $this->irremovablePackages[$aliasPackage->id] = \true; } } } /** * @return Pool Optimized pool */ private function applyRemovalsToPool(\Composer\DependencyResolver\Pool $pool) : \Composer\DependencyResolver\Pool { $packages = []; $removedVersions = []; foreach ($pool->getPackages() as $package) { if (!isset($this->packagesToRemove[$package->id])) { $packages[] = $package; } else { $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion(); } } $optimizedPool = new \Composer\DependencyResolver\Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage); return $optimizedPool; } private function optimizeByIdenticalDependencies(\Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : void { $identicalDefinitionsPerPackage = []; $packageIdenticalDefinitionLookup = []; foreach ($pool->getPackages() as $package) { // If that package was already marked irremovable, we can skip // the entire process for it if (isset($this->irremovablePackages[$package->id])) { continue; } $this->markPackageForRemoval($package->id); $dependencyHash = $this->calculateDependencyHash($package); foreach ($package->getNames(\false) as $packageName) { if (!isset($this->requireConstraintsPerPackage[$packageName])) { continue; } foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) { $groupHashParts = []; if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) { $groupHashParts[] = 'require:' . (string) $requireConstraint; } if ($package->getReplaces()) { foreach ($package->getReplaces() as $link) { if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) { // Use the same hash part as the regular require hash because that's what the replacement does $groupHashParts[] = 'require:' . (string) $link->getConstraint(); } } } if (isset($this->conflictConstraintsPerPackage[$packageName])) { foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) { if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) { $groupHashParts[] = 'conflict:' . (string) $conflictConstraint; } } } if (!$groupHashParts) { continue; } $groupHash = \implode('', $groupHashParts); $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package; $packageIdenticalDefinitionLookup[$package->id][$packageName] = ['groupHash' => $groupHash, 'dependencyHash' => $dependencyHash]; } } } foreach ($identicalDefinitionsPerPackage as $constraintGroups) { foreach ($constraintGroups as $constraintGroup) { foreach ($constraintGroup as $packages) { // Only one package in this constraint group has the same requirements, we're not allowed to remove that package if (1 === \count($packages)) { $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); continue; } // Otherwise we find out which one is the preferred package in this constraint group which is // then not allowed to be removed either $literals = []; foreach ($packages as $package) { $literals[] = $package->id; } foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) { $this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); } } } } } private function calculateDependencyHash(BasePackage $package) : string { $hash = ''; $hashRelevantLinks = ['requires' => $package->getRequires(), 'conflicts' => $package->getConflicts(), 'replaces' => $package->getReplaces(), 'provides' => $package->getProvides()]; foreach ($hashRelevantLinks as $key => $links) { if (0 === \count($links)) { continue; } // start new hash section $hash .= $key . ':'; $subhash = []; foreach ($links as $link) { // To get the best dependency hash matches we should use Intervals::compactConstraint() here. // However, the majority of projects are going to specify their constraints already pretty // much in the best variant possible. In other words, we'd be wasting time here and it would actually hurt // performance more than the additional few packages that could be filtered out would benefit the process. $subhash[$link->getTarget()] = (string) $link->getConstraint(); } // Sort for best result \ksort($subhash); foreach ($subhash as $target => $constraint) { $hash .= $target . '@' . $constraint; } } return $hash; } private function markPackageForRemoval(int $id) : void { // We are not allowed to remove packages if they have been marked as irremovable if (isset($this->irremovablePackages[$id])) { throw new \LogicException('Attempted removing a package which was previously marked irremovable'); } $this->packagesToRemove[$id] = \true; } /** * @param array>>> $identicalDefinitionsPerPackage * @param array> $packageIdenticalDefinitionLookup */ private function keepPackage(BasePackage $package, array $identicalDefinitionsPerPackage, array $packageIdenticalDefinitionLookup) : void { // Already marked to keep if (!isset($this->packagesToRemove[$package->id])) { return; } unset($this->packagesToRemove[$package->id]); if ($package instanceof AliasPackage) { // recursing here so aliasesPerPackage for the aliasOf can be checked // and all its aliases marked to be kept as well $this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); } // record all the versions of the package group so we can list them later in Problem output foreach ($package->getNames(\false) as $name) { if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) { $packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name]; $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; foreach ($packageGroup as $pkg) { if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $pkg = $pkg->getAliasOf(); } $this->removedVersionsByPackage[\spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion(); } } } if (isset($this->aliasesPerPackage[$package->id])) { foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { unset($this->packagesToRemove[$aliasPackage->id]); // record all the versions of the package group so we can list them later in Problem output foreach ($aliasPackage->getNames(\false) as $name) { if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) { $packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name]; $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; foreach ($packageGroup as $pkg) { if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $pkg = $pkg->getAliasOf(); } $this->removedVersionsByPackage[\spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion(); } } } } } } /** * Use the list of locked packages to constrain the loaded packages * This will reduce packages with significant numbers of historical versions to a smaller number * and reduce the resulting rule set that is generated */ private function optimizeImpossiblePackagesAway(\Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : void { if (\count($request->getLockedPackages()) === 0) { return; } $packageIndex = []; foreach ($pool->getPackages() as $package) { $id = $package->id; // Do not remove irremovable packages if (isset($this->irremovablePackages[$id])) { continue; } // Do not remove a package aliased by another package, nor aliases if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) { continue; } // Do not remove locked packages if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) { continue; } $packageIndex[$package->getName()][$package->id] = $package; } foreach ($request->getLockedPackages() as $package) { // If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply its requirements // In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still $isUnusedPackage = \true; foreach ($package->getNames(\false) as $packageName) { if (isset($this->requireConstraintsPerPackage[$packageName])) { $isUnusedPackage = \false; break; } } if ($isUnusedPackage) { continue; } foreach ($package->getRequires() as $link) { $require = $link->getTarget(); if (!isset($packageIndex[$require])) { continue; } $linkConstraint = $link->getConstraint(); foreach ($packageIndex[$require] as $id => $requiredPkg) { if (\false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) { $this->markPackageForRemoval($id); unset($packageIndex[$require][$id]); } } } } } /** * Disjunctive require constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate * two require constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd * only keep either one which can cause trouble (e.g. when using --prefer-lowest). * * @return void */ private function extractRequireConstraintsPerPackage(string $package, ConstraintInterface $constraint) { foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { $this->requireConstraintsPerPackage[$package][(string) $expanded] = $expanded; } } /** * Disjunctive conflict constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate * two conflict constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd * only keep either one which can cause trouble (e.g. when using --prefer-lowest). * * @return void */ private function extractConflictConstraintsPerPackage(string $package, ConstraintInterface $constraint) { foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { $this->conflictConstraintsPerPackage[$package][(string) $expanded] = $expanded; } } /** * @return ConstraintInterface[] */ private function expandDisjunctiveMultiConstraints(ConstraintInterface $constraint) { $constraint = Intervals::compactConstraint($constraint); if ($constraint instanceof MultiConstraint && $constraint->isDisjunctive()) { // No need to call ourselves recursively here because Intervals::compactConstraint() ensures that there // are no nested disjunctive MultiConstraint instances possible return $constraint->getConstraints(); } // Regular constraints and conjunctive MultiConstraints return [$constraint]; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; /** * @author Nils Adermann * @phpstan-import-type ReasonData from Rule */ class RuleSetGenerator { /** @var PolicyInterface */ protected $policy; /** @var Pool */ protected $pool; /** @var RuleSet */ protected $rules; /** @var array */ protected $addedMap = []; /** @var array */ protected $addedPackagesByNames = []; public function __construct(\Composer\DependencyResolver\PolicyInterface $policy, \Composer\DependencyResolver\Pool $pool) { $this->policy = $policy; $this->pool = $pool; $this->rules = new \Composer\DependencyResolver\RuleSet(); } /** * Creates a new rule for the requirements of a package * * This rule is of the form (-A|B|C), where B and C are the providers of * one requirement of the package A. * * @param BasePackage $package The package with a requirement * @param BasePackage[] $providers The providers of the requirement * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule * @param mixed $reasonData Any data, e.g. the requirement name, that goes with the reason * @return Rule|null The generated rule or null if tautological * * @phpstan-param ReasonData $reasonData */ protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData = null) : ?\Composer\DependencyResolver\Rule { $literals = [-$package->id]; foreach ($providers as $provider) { // self fulfilling rule? if ($provider === $package) { return null; } $literals[] = $provider->id; } return new \Composer\DependencyResolver\GenericRule($literals, $reason, $reasonData); } /** * Creates a rule to install at least one of a set of packages * * The rule is (A|B|C) with A, B and C different packages. If the given * set of packages is empty an impossible rule is generated. * * @param BasePackage[] $packages The set of packages to choose from * @param Rule::RULE_* $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Additional data like the root require or fix request info * @return Rule The generated rule * * @phpstan-param ReasonData $reasonData */ protected function createInstallOneOfRule(array $packages, $reason, $reasonData) : \Composer\DependencyResolver\Rule { $literals = []; foreach ($packages as $package) { $literals[] = $package->id; } return new \Composer\DependencyResolver\GenericRule($literals, $reason, $reasonData); } /** * Creates a rule for two conflicting packages * * The rule for conflicting packages A and B is (-A|-B). A is called the issuer * and B the provider. * * @param BasePackage $issuer The package declaring the conflict * @param BasePackage $provider The package causing the conflict * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with the reason * @return ?Rule The generated rule * * @phpstan-param ReasonData $reasonData */ protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData = null) : ?\Composer\DependencyResolver\Rule { // ignore self conflict if ($issuer === $provider) { return null; } return new \Composer\DependencyResolver\Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData); } /** * @param BasePackage[] $packages * @param Rule::RULE_* $reason A RULE_* constant * @param mixed $reasonData * * @phpstan-param ReasonData $reasonData */ protected function createMultiConflictRule(array $packages, $reason, $reasonData) : \Composer\DependencyResolver\Rule { $literals = []; foreach ($packages as $package) { $literals[] = -$package->id; } if (\count($literals) === 2) { return new \Composer\DependencyResolver\Rule2Literals($literals[0], $literals[1], $reason, $reasonData); } return new \Composer\DependencyResolver\MultiConflictRule($literals, $reason, $reasonData); } /** * Adds a rule unless it duplicates an existing one of any type * * To be able to directly pass in the result of one of the rule creation * methods null is allowed which will not insert a rule. * * @param RuleSet::TYPE_* $type A TYPE_* constant defining the rule type * @param Rule $newRule The rule about to be added */ private function addRule($type, ?\Composer\DependencyResolver\Rule $newRule = null) : void { if (!$newRule) { return; } $this->rules->add($newRule, $type); } protected function addRulesForPackage(BasePackage $package, PlatformRequirementFilterInterface $platformRequirementFilter) : void { /** @var \SplQueue */ $workQueue = new \SplQueue(); $workQueue->enqueue($package); while (!$workQueue->isEmpty()) { $package = $workQueue->dequeue(); if (isset($this->addedMap[$package->id])) { continue; } $this->addedMap[$package->id] = $package; if (!$package instanceof AliasPackage) { foreach ($package->getNames(\false) as $name) { $this->addedPackagesByNames[$name][] = $package; } } else { $workQueue->enqueue($package->getAliasOf()); $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, [$package->getAliasOf()], \Composer\DependencyResolver\Rule::RULE_PACKAGE_ALIAS, $package)); // aliases must be installed with their main package, so create a rule the other way around as well $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), [$package], \Composer\DependencyResolver\Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf())); // if alias package has no self.version requires, its requirements do not // need to be added as the aliased package processing will take care of it if (!$package->hasSelfVersionRequires()) { continue; } } foreach ($package->getRequires() as $link) { $constraint = $link->getConstraint(); if ($platformRequirementFilter->isIgnored($link->getTarget())) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint); } $possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint); $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, \Composer\DependencyResolver\Rule::RULE_PACKAGE_REQUIRES, $link)); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); } } } } protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter) : void { /** @var BasePackage $package */ foreach ($this->addedMap as $package) { foreach ($package->getConflicts() as $link) { // even if conflict ends up being with an alias, there would be at least one actual package by this name if (!isset($this->addedPackagesByNames[$link->getTarget()])) { continue; } $constraint = $link->getConstraint(); if ($platformRequirementFilter->isIgnored($link->getTarget())) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint, \false); } $conflicts = $this->pool->whatProvides($link->getTarget(), $constraint); foreach ($conflicts as $conflict) { // define the conflict rule for regular packages, for alias packages it's only needed if the name // matches the conflict exactly, otherwise the name match is by provide/replace which means the // package which this is an alias of will conflict anyway, so no need to create additional rules if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) { $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, \Composer\DependencyResolver\Rule::RULE_PACKAGE_CONFLICT, $link)); } } } } foreach ($this->addedPackagesByNames as $name => $packages) { if (\count($packages) > 1) { $reason = \Composer\DependencyResolver\Rule::RULE_PACKAGE_SAME_NAME; $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, $name)); } } } protected function addRulesForRequest(\Composer\DependencyResolver\Request $request, PlatformRequirementFilterInterface $platformRequirementFilter) : void { foreach ($request->getFixedPackages() as $package) { if ($package->id === -1) { // fixed package was not added to the pool as it did not pass the stability requirements, this is fine if ($this->pool->isUnacceptableFixedOrLockedPackage($package)) { continue; } // otherwise, looks like a bug throw new \LogicException("Fixed package " . $package->getPrettyString() . " was not added to solver pool."); } $this->addRulesForPackage($package, $platformRequirementFilter); $rule = $this->createInstallOneOfRule([$package], \Composer\DependencyResolver\Rule::RULE_FIXED, ['package' => $package]); $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_REQUEST, $rule); } foreach ($request->getRequires() as $packageName => $constraint) { if ($platformRequirementFilter->isIgnored($packageName)) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); } $packages = $this->pool->whatProvides($packageName, $constraint); if ($packages) { foreach ($packages as $package) { $this->addRulesForPackage($package, $platformRequirementFilter); } $rule = $this->createInstallOneOfRule($packages, \Composer\DependencyResolver\Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint]); $this->addRule(\Composer\DependencyResolver\RuleSet::TYPE_REQUEST, $rule); } } } protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter) : void { foreach ($this->pool->getPackages() as $package) { // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded // even if the alias itself isn't required, otherwise a package could be installed without its alias which // leads to unexpected behavior if (!isset($this->addedMap[$package->id]) && $package instanceof AliasPackage && ($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id]))) { $this->addRulesForPackage($package, $platformRequirementFilter); } } } public function getRulesFor(\Composer\DependencyResolver\Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null) : \Composer\DependencyResolver\RuleSet { $platformRequirementFilter = $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing(); $this->addRulesForRequest($request, $platformRequirementFilter); $this->addRulesForRootAliases($platformRequirementFilter); $this->addConflictRules($platformRequirementFilter); // Remove references to packages $this->addedMap = $this->addedPackagesByNames = []; $rules = $this->rules; $this->rules = new \Composer\DependencyResolver\RuleSet(); return $rules; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * Stores decisions on installing, removing or keeping packages * * @author Nils Adermann * @implements \Iterator */ class Decisions implements \Iterator, \Countable { public const DECISION_LITERAL = 0; public const DECISION_REASON = 1; /** @var Pool */ protected $pool; /** @var array */ protected $decisionMap; /** * @var array */ protected $decisionQueue = []; public function __construct(\Composer\DependencyResolver\Pool $pool) { $this->pool = $pool; $this->decisionMap = []; } public function decide(int $literal, int $level, \Composer\DependencyResolver\Rule $why) : void { $this->addDecision($literal, $level); $this->decisionQueue[] = [self::DECISION_LITERAL => $literal, self::DECISION_REASON => $why]; } public function satisfy(int $literal) : bool { $packageId = \abs($literal); return $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 || $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0; } public function conflict(int $literal) : bool { $packageId = \abs($literal); return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0 || isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0; } public function decided(int $literalOrPackageId) : bool { return !empty($this->decisionMap[\abs($literalOrPackageId)]); } public function undecided(int $literalOrPackageId) : bool { return empty($this->decisionMap[\abs($literalOrPackageId)]); } public function decidedInstall(int $literalOrPackageId) : bool { $packageId = \abs($literalOrPackageId); return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0; } public function decisionLevel(int $literalOrPackageId) : int { $packageId = \abs($literalOrPackageId); if (isset($this->decisionMap[$packageId])) { return \abs($this->decisionMap[$packageId]); } return 0; } public function decisionRule(int $literalOrPackageId) : ?\Composer\DependencyResolver\Rule { $packageId = \abs($literalOrPackageId); foreach ($this->decisionQueue as $decision) { if ($packageId === \abs($decision[self::DECISION_LITERAL])) { return $decision[self::DECISION_REASON]; } } return null; } /** * @return array{0: int, 1: Rule} a literal and decision reason */ public function atOffset(int $queueOffset) : array { return $this->decisionQueue[$queueOffset]; } public function validOffset(int $queueOffset) : bool { return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue); } public function lastReason() : \Composer\DependencyResolver\Rule { return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON]; } public function lastLiteral() : int { return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL]; } public function reset() : void { while ($decision = \array_pop($this->decisionQueue)) { $this->decisionMap[\abs($decision[self::DECISION_LITERAL])] = 0; } } /** * @param int<-1, max> $offset */ public function resetToOffset(int $offset) : void { while (\count($this->decisionQueue) > $offset + 1) { $decision = \array_pop($this->decisionQueue); $this->decisionMap[\abs($decision[self::DECISION_LITERAL])] = 0; } } public function revertLast() : void { $this->decisionMap[\abs($this->lastLiteral())] = 0; \array_pop($this->decisionQueue); } public function count() : int { return \count($this->decisionQueue); } public function rewind() : void { \end($this->decisionQueue); } /** * @return array{0: int, 1: Rule}|false */ #[\ReturnTypeWillChange] public function current() { return \current($this->decisionQueue); } public function key() : ?int { return \key($this->decisionQueue); } public function next() : void { \prev($this->decisionQueue); } public function valid() : bool { return \false !== \current($this->decisionQueue); } public function isEmpty() : bool { return \count($this->decisionQueue) === 0; } protected function addDecision(int $literal, int $level) : void { $packageId = \abs($literal); $previousDecision = $this->decisionMap[$packageId] ?? 0; if ($previousDecision !== 0) { $literalString = $this->pool->literalToPrettyString($literal, []); $package = $this->pool->literalToPackage($literal); throw new \Composer\DependencyResolver\SolverBugException("Trying to decide {$literalString} on level {$level}, even though {$package} was previously decided as " . $previousDecision . "."); } if ($literal > 0) { $this->decisionMap[$packageId] = $level; } else { $this->decisionMap[$packageId] = -$level; } } public function toString(?\Composer\DependencyResolver\Pool $pool = null) : string { $decisionMap = $this->decisionMap; \ksort($decisionMap); $str = '['; foreach ($decisionMap as $packageId => $level) { $str .= ($pool ? $pool->literalToPackage($packageId) : $packageId) . ':' . $level . ','; } $str .= ']'; return $str; } public function __toString() : string { return $this->toString(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; use Composer\Package\PackageInterface; use Composer\Package\Version\StabilityFilter; use Composer\Pcre\Preg; use Composer\Plugin\PluginEvents; use Composer\Plugin\PrePoolCreateEvent; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RootPackageRepository; use Composer\Semver\CompilingMatcher; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Intervals; /** * @author Nils Adermann */ class PoolBuilder { /** * @var int[] * @phpstan-var array */ private $acceptableStabilities; /** * @var int[] * @phpstan-var array */ private $stabilityFlags; /** * @var array[] * @phpstan-var array> */ private $rootAliases; /** * @var string[] * @phpstan-var array */ private $rootReferences; /** * @var array */ private $temporaryConstraints; /** * @var ?EventDispatcher */ private $eventDispatcher; /** * @var PoolOptimizer|null */ private $poolOptimizer; /** * @var IOInterface */ private $io; /** * @var array[] * @phpstan-var array */ private $aliasMap = []; /** * @var ConstraintInterface[] * @phpstan-var array */ private $packagesToLoad = []; /** * @var ConstraintInterface[] * @phpstan-var array */ private $loadedPackages = []; /** * @var array[] * @phpstan-var array>> */ private $loadedPerRepo = []; /** * @var BasePackage[] */ private $packages = []; /** * @var BasePackage[] */ private $unacceptableFixedOrLockedPackages = []; /** @var array */ private $updateAllowList = []; /** @var array> */ private $skippedLoad = []; /** * If provided, only these package names are loaded * * This is a special-use functionality of the Request class to optimize the pool creation process * when only a minimal subset of packages is needed and we do not need their dependencies. * * @var array|null */ private $restrictedPackagesList = null; /** * Keeps a list of dependencies which are locked but were auto-unlocked as they are path repositories * * This half-unlocked state means the package itself will update but the UPDATE_LISTED_WITH_TRANSITIVE_DEPS* * flags will not apply until the package really gets unlocked in some other way than being a path repo * * @var array */ private $pathRepoUnlocked = []; /** * Keeps a list of dependencies which are root requirements, and as such * have already their maximum required range loaded and can not be * extended by markPackageNameForLoading * * Packages get cleared from this list if they get unlocked as in that case * we need to actually load them * * @var array */ private $maxExtendedReqs = []; /** * @var array * @phpstan-var array */ private $updateAllowWarned = []; /** @var int */ private $indexCounter = 0; /** * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value * @phpstan-param array $acceptableStabilities * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array $stabilityFlags * @param array[] $rootAliases * @phpstan-param array> $rootAliases * @param string[] $rootReferences an array of package name => source reference * @phpstan-param array $rootReferences * @param array $temporaryConstraints Runtime temporary constraints that will be used to filter packages */ public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?\Composer\DependencyResolver\PoolOptimizer $poolOptimizer = null, array $temporaryConstraints = []) { $this->acceptableStabilities = $acceptableStabilities; $this->stabilityFlags = $stabilityFlags; $this->rootAliases = $rootAliases; $this->rootReferences = $rootReferences; $this->eventDispatcher = $eventDispatcher; $this->poolOptimizer = $poolOptimizer; $this->io = $io; $this->temporaryConstraints = $temporaryConstraints; } /** * @param RepositoryInterface[] $repositories */ public function buildPool(array $repositories, \Composer\DependencyResolver\Request $request) : \Composer\DependencyResolver\Pool { $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? \array_flip($request->getRestrictedPackages()) : null; if ($request->getUpdateAllowList()) { $this->updateAllowList = $request->getUpdateAllowList(); $this->warnAboutNonMatchingUpdateAllowList($request); foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) { if (!$this->isUpdateAllowed($lockedPackage)) { // remember which packages we skipped loading remote content for in this partial update $this->skippedLoad[$lockedPackage->getName()][] = $lockedPackage; foreach ($lockedPackage->getReplaces() as $link) { $this->skippedLoad[$link->getTarget()][] = $lockedPackage; } // Path repo packages are never loaded from lock, to force them to always remain in sync // unless symlinking is disabled in which case we probably should rather treat them like // regular packages. We mark them specially so they can be reloaded fully including update propagation // if they do get unlocked, but by default they are unlocked without update propagation. if ($lockedPackage->getDistType() === 'path') { $transportOptions = $lockedPackage->getTransportOptions(); if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== \false) { $this->pathRepoUnlocked[$lockedPackage->getName()] = \true; continue; } } $request->lockPackage($lockedPackage); } } } foreach ($request->getFixedOrLockedPackages() as $package) { // using MatchAllConstraint here because fixed packages do not need to retrigger // loading any packages $this->loadedPackages[$package->getName()] = new MatchAllConstraint(); // replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways foreach ($package->getReplaces() as $link) { $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint(); } // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to // specific versions while replace is a conflict with all versions of the name if ($package->getRepository() instanceof RootPackageRepository || $package->getRepository() instanceof PlatformRepository || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability())) { $this->loadPackage($request, $repositories, $package, \false); } else { $this->unacceptableFixedOrLockedPackages[] = $package; } } foreach ($request->getRequires() as $packageName => $constraint) { // fixed and locked packages have already been added, so if a root require needs one of them, no need to do anything if (isset($this->loadedPackages[$packageName])) { continue; } $this->packagesToLoad[$packageName] = $constraint; $this->maxExtendedReqs[$packageName] = \true; } // clean up packagesToLoad for anything we manually marked loaded above foreach ($this->packagesToLoad as $name => $constraint) { if (isset($this->loadedPackages[$name])) { unset($this->packagesToLoad[$name]); } } while (!empty($this->packagesToLoad)) { $this->loadPackagesMarkedForLoading($request, $repositories); } if (\count($this->temporaryConstraints) > 0) { foreach ($this->packages as $i => $package) { // we check all alias related packages at once, so no need to check individual aliases if (!isset($this->temporaryConstraints[$package->getName()]) || $package instanceof AliasPackage) { continue; } $constraint = $this->temporaryConstraints[$package->getName()]; $packageAndAliases = [$i => $package]; if (isset($this->aliasMap[\spl_object_hash($package)])) { $packageAndAliases += $this->aliasMap[\spl_object_hash($package)]; } $found = \false; foreach ($packageAndAliases as $packageOrAlias) { if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) { $found = \true; } } if (!$found) { foreach ($packageAndAliases as $index => $packageOrAlias) { unset($this->packages[$index]); } } } } if ($this->eventDispatcher) { $prePoolCreateEvent = new PrePoolCreateEvent(PluginEvents::PRE_POOL_CREATE, $repositories, $request, $this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $this->packages, $this->unacceptableFixedOrLockedPackages); $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent); $this->packages = $prePoolCreateEvent->getPackages(); $this->unacceptableFixedOrLockedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages(); } $pool = new \Composer\DependencyResolver\Pool($this->packages, $this->unacceptableFixedOrLockedPackages); $this->aliasMap = []; $this->packagesToLoad = []; $this->loadedPackages = []; $this->loadedPerRepo = []; $this->packages = []; $this->unacceptableFixedOrLockedPackages = []; $this->maxExtendedReqs = []; $this->skippedLoad = []; $this->indexCounter = 0; $this->io->debug('Built pool.'); $pool = $this->runOptimizer($request, $pool); Intervals::clear(); return $pool; } private function markPackageNameForLoading(\Composer\DependencyResolver\Request $request, string $name, ConstraintInterface $constraint) : void { // Skip platform requires at this stage if (PlatformRepository::isPlatformPackage($name)) { return; } // Root require (which was not unlocked) already loaded the maximum range so no // need to check anything here if (isset($this->maxExtendedReqs[$name])) { return; } // Root requires can not be overruled by dependencies so there is no point in // extending the loaded constraint for those. // This is triggered when loading a root require which was locked but got unlocked, then // we make sure that we load at most the intervals covered by the root constraint. $rootRequires = $request->getRequires(); if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) { $constraint = $rootRequires[$name]; } // Not yet loaded or already marked for a reload, set the constraint to be loaded if (!isset($this->loadedPackages[$name])) { // Maybe it was already marked before but not loaded yet. In that case // we have to extend the constraint (we don't check if they are identical because // MultiConstraint::create() will optimize anyway) if (isset($this->packagesToLoad[$name])) { // Already marked for loading and this does not expand the constraint to be loaded, nothing to do if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) { return; } // extend the constraint to be loaded $constraint = Intervals::compactConstraint(MultiConstraint::create([$this->packagesToLoad[$name], $constraint], \false)); } $this->packagesToLoad[$name] = $constraint; return; } // No need to load this package with this constraint because it is // a subset of the constraint with which we have already loaded packages if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) { return; } // We have already loaded that package but not in the constraint that's // required. We extend the constraint and mark that package as not being loaded // yet so we get the required package versions $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create([$this->loadedPackages[$name], $constraint], \false)); unset($this->loadedPackages[$name]); } /** * @param RepositoryInterface[] $repositories */ private function loadPackagesMarkedForLoading(\Composer\DependencyResolver\Request $request, array $repositories) : void { foreach ($this->packagesToLoad as $name => $constraint) { if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) { unset($this->packagesToLoad[$name]); continue; } $this->loadedPackages[$name] = $constraint; } $packageBatch = $this->packagesToLoad; $this->packagesToLoad = []; foreach ($repositories as $repoIndex => $repository) { if (empty($packageBatch)) { break; } // these repos have their packages fixed or locked if they need to be loaded so we // never need to load anything else from them if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) { continue; } $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, $this->loadedPerRepo[$repoIndex] ?? []); foreach ($result['namesFound'] as $name) { // avoid loading the same package again from other repositories once it has been found unset($packageBatch[$name]); } foreach ($result['packages'] as $package) { $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package; $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()])); } } } /** * @param RepositoryInterface[] $repositories */ private function loadPackage(\Composer\DependencyResolver\Request $request, array $repositories, BasePackage $package, bool $propagateUpdate) : void { $index = $this->indexCounter++; $this->packages[$index] = $package; if ($package instanceof AliasPackage) { $this->aliasMap[\spl_object_hash($package->getAliasOf())][$index] = $package; } $name = $package->getName(); // we're simply setting the root references on all versions for a name here and rely on the solver to pick the // right version. It'd be more work to figure out which versions and which aliases of those versions this may // apply to if (isset($this->rootReferences[$name])) { // do not modify the references on already locked or fixed packages if (!$request->isLockedPackage($package) && !$request->isFixedPackage($package)) { $package->setSourceDistReferences($this->rootReferences[$name]); } } // if propagateUpdate is false we are loading a fixed or locked package, root aliases do not apply as they are // manually loaded as separate packages in this case // // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to // false because their deps should not be unlocked, but that is irrelevant for root aliases if (($propagateUpdate || isset($this->pathRepoUnlocked[$package->getName()])) && isset($this->rootAliases[$name][$package->getVersion()])) { $alias = $this->rootAliases[$name][$package->getVersion()]; if ($package instanceof AliasPackage) { $basePackage = $package->getAliasOf(); } else { $basePackage = $package; } if ($basePackage instanceof CompletePackage) { $aliasPackage = new CompleteAliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); } else { $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); } $aliasPackage->setRootPackageAlias(\true); $newIndex = $this->indexCounter++; $this->packages[$newIndex] = $aliasPackage; $this->aliasMap[\spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage; } foreach ($package->getRequires() as $link) { $require = $link->getTarget(); $linkConstraint = $link->getConstraint(); // if the required package is loaded as a locked package only and hasn't had its deps analyzed if (isset($this->skippedLoad[$require])) { // if we're doing a full update or this is a partial update with transitive deps and we're currently // looking at a package which needs to be updated we need to unlock the package we now know is a // dependency of another package which we are trying to update, and then attempt to load it again if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { $skippedRootRequires = $this->getSkippedRootRequires($request, $require); if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) { $this->unlockPackage($request, $repositories, $require); $this->markPackageNameForLoading($request, $require, $linkConstraint); } else { foreach ($skippedRootRequires as $rootRequire) { if (!isset($this->updateAllowWarned[$rootRequire])) { $this->updateAllowWarned[$rootRequire] = \true; $this->io->writeError('Dependency ' . $rootRequire . ' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); } } } } elseif (isset($this->pathRepoUnlocked[$require]) && !isset($this->loadedPackages[$require])) { // if doing a partial update and a package depends on a path-repo-unlocked package which is not referenced by the root, we need to ensure it gets loaded as it was not loaded by the request's root requirements // and would not be loaded above if update propagation is not allowed (which happens if the requirer is itself a path-repo-unlocked package) or if transitive deps are not allowed to be unlocked $this->markPackageNameForLoading($request, $require, $linkConstraint); } } else { $this->markPackageNameForLoading($request, $require, $linkConstraint); } } // if we're doing a partial update with deps we also need to unlock packages which are being replaced in case // they are currently locked and thus prevent this updateable package from being installable/updateable if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { foreach ($package->getReplaces() as $link) { $replace = $link->getTarget(); if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) { $skippedRootRequires = $this->getSkippedRootRequires($request, $replace); if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) { $this->unlockPackage($request, $repositories, $replace); // the replaced package only needs to be loaded if something else requires it $this->markPackageNameForLoadingIfRequired($request, $replace); } else { foreach ($skippedRootRequires as $rootRequire) { if (!isset($this->updateAllowWarned[$rootRequire])) { $this->updateAllowWarned[$rootRequire] = \true; $this->io->writeError('Dependency ' . $rootRequire . ' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); } } } } } } } /** * Checks if a particular name is required directly in the request * * @param string $name packageName */ private function isRootRequire(\Composer\DependencyResolver\Request $request, string $name) : bool { $rootRequires = $request->getRequires(); return isset($rootRequires[$name]); } /** * @return string[] */ private function getSkippedRootRequires(\Composer\DependencyResolver\Request $request, string $name) : array { if (!isset($this->skippedLoad[$name])) { return []; } $rootRequires = $request->getRequires(); $matches = []; if (isset($rootRequires[$name])) { return \array_map(static function (PackageInterface $package) use($name) : string { if ($name !== $package->getName()) { return $package->getName() . ' (via replace of ' . $name . ')'; } return $package->getName(); }, $this->skippedLoad[$name]); } foreach ($this->skippedLoad[$name] as $packageOrReplacer) { if (isset($rootRequires[$packageOrReplacer->getName()])) { $matches[] = $packageOrReplacer->getName(); } foreach ($packageOrReplacer->getReplaces() as $link) { if (isset($rootRequires[$link->getTarget()])) { if ($name !== $packageOrReplacer->getName()) { $matches[] = $packageOrReplacer->getName() . ' (via replace of ' . $name . ')'; } else { $matches[] = $packageOrReplacer->getName(); } break; } } } return $matches; } /** * Checks whether the update allow list allows this package in the lock file to be updated */ private function isUpdateAllowed(BasePackage $package) : bool { foreach ($this->updateAllowList as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); if (Preg::isMatch($patternRegexp, $package->getName())) { return \true; } } return \false; } private function warnAboutNonMatchingUpdateAllowList(\Composer\DependencyResolver\Request $request) : void { foreach ($this->updateAllowList as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); // update pattern matches a locked package? => all good foreach ($request->getLockedRepository()->getPackages() as $package) { if (Preg::isMatch($patternRegexp, $package->getName())) { continue 2; } } // update pattern matches a root require? => all good, probably a new package foreach ($request->getRequires() as $packageName => $constraint) { if (Preg::isMatch($patternRegexp, $packageName)) { continue 2; } } if (\strpos($pattern, '*') !== \false) { $this->io->writeError('Pattern "' . $pattern . '" listed for update does not match any locked packages.'); } else { $this->io->writeError('Package "' . $pattern . '" listed for update is not locked.'); } } } /** * Reverts the decision to use a locked package if a partial update with transitive dependencies * found that this package actually needs to be updated * * @param RepositoryInterface[] $repositories */ private function unlockPackage(\Composer\DependencyResolver\Request $request, array $repositories, string $name) : void { foreach ($this->skippedLoad[$name] as $packageOrReplacer) { // if we unfixed a replaced package name, we also need to unfix the replacer itself // as long as it was not unfixed yet if ($packageOrReplacer->getName() !== $name && isset($this->skippedLoad[$packageOrReplacer->getName()])) { $replacerName = $packageOrReplacer->getName(); if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $replacerName)) { $this->unlockPackage($request, $repositories, $replacerName); if ($this->isRootRequire($request, $replacerName)) { $this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint()); } else { foreach ($this->packages as $loadedPackage) { $requires = $loadedPackage->getRequires(); if (isset($requires[$replacerName])) { $this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint()); } } } } } } if (isset($this->pathRepoUnlocked[$name])) { foreach ($this->packages as $index => $package) { if ($package->getName() === $name) { $this->removeLoadedPackage($request, $repositories, $package, $index); } } } unset($this->skippedLoad[$name], $this->loadedPackages[$name], $this->maxExtendedReqs[$name], $this->pathRepoUnlocked[$name]); // remove locked package by this name which was already initialized foreach ($request->getLockedPackages() as $lockedPackage) { if (!$lockedPackage instanceof AliasPackage && $lockedPackage->getName() === $name) { if (\false !== ($index = \array_search($lockedPackage, $this->packages, \true))) { $request->unlockPackage($lockedPackage); $this->removeLoadedPackage($request, $repositories, $lockedPackage, $index); // make sure that any requirements for this package by other locked or fixed packages are now // also loaded, as they were previously ignored because the locked (now unlocked) package already // satisfied their requirements // and if this package is replacing another that is required by a locked or fixed package, ensure // that we load that replaced package in case an update to this package removes the replacement foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) { if ($fixedOrLockedPackage === $lockedPackage) { continue; } if (isset($this->skippedLoad[$fixedOrLockedPackage->getName()])) { $requires = $fixedOrLockedPackage->getRequires(); if (isset($requires[$lockedPackage->getName()])) { $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint()); } foreach ($lockedPackage->getReplaces() as $replace) { if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) { $this->unlockPackage($request, $repositories, $replace->getTarget()); // this package is in $requires so no need to call markPackageNameForLoadingIfRequired $this->markPackageNameForLoading($request, $replace->getTarget(), $replace->getConstraint()); } } } } } } } } private function markPackageNameForLoadingIfRequired(\Composer\DependencyResolver\Request $request, string $name) : void { if ($this->isRootRequire($request, $name)) { $this->markPackageNameForLoading($request, $name, $request->getRequires()[$name]); } foreach ($this->packages as $package) { foreach ($package->getRequires() as $link) { if ($name === $link->getTarget()) { $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint()); } } } } /** * @param RepositoryInterface[] $repositories */ private function removeLoadedPackage(\Composer\DependencyResolver\Request $request, array $repositories, BasePackage $package, int $index) : void { $repoIndex = \array_search($package->getRepository(), $repositories, \true); unset($this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()]); unset($this->packages[$index]); if (isset($this->aliasMap[\spl_object_hash($package)])) { foreach ($this->aliasMap[\spl_object_hash($package)] as $aliasIndex => $aliasPackage) { unset($this->loadedPerRepo[$repoIndex][$aliasPackage->getName()][$aliasPackage->getVersion()]); unset($this->packages[$aliasIndex]); } unset($this->aliasMap[\spl_object_hash($package)]); } } private function runOptimizer(\Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : \Composer\DependencyResolver\Pool { if (null === $this->poolOptimizer) { return $pool; } $this->io->debug('Running pool optimizer.'); $before = \microtime(\true); $total = \count($pool->getPackages()); $pool = $this->poolOptimizer->optimize($request, $pool); $filtered = $total - \count($pool->getPackages()); if (0 === $filtered) { return $pool; } $this->io->write(\sprintf('Pool optimizer completed in %.3f seconds', \microtime(\true) - $before), \true, IOInterface::VERY_VERBOSE); $this->io->write(\sprintf('Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.', \number_format($total), \number_format($filtered), \round(100 / $total * $filtered)), \true, IOInterface::VERY_VERBOSE); return $pool; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; use Composer\Package\CompletePackageInterface; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Pcre\Preg; use Composer\Repository\RepositorySet; use Composer\Repository\LockArrayRepository; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Package\Version\VersionParser; use Composer\Repository\PlatformRepository; /** * Represents a problem detected while solving dependencies * * @author Nils Adermann */ class Problem { /** * A map containing the id of each rule part of this problem as a key * @var array */ protected $reasonSeen; /** * A set of reasons for the problem, each is a rule or a root require and a rule * @var array> */ protected $reasons = []; /** @var int */ protected $section = 0; /** * Add a rule as a reason * * @param Rule $rule A rule which is a reason for this problem */ public function addRule(\Composer\DependencyResolver\Rule $rule) : void { $this->addReason(\spl_object_hash($rule), $rule); } /** * Retrieve all reasons for this problem * * @return array> The problem's reasons */ public function getReasons() : array { return $this->reasons; } /** * A human readable textual representation of the problem's reasons * * @param array $installedMap A map of all present packages * @param array $learnedPool */ public function getPrettyString(RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []) : string { // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections? $reasons = \array_merge(...\array_reverse($this->reasons)); if (\count($reasons) === 1) { \reset($reasons); $rule = \current($reasons); if ($rule->getReason() !== \Composer\DependencyResolver\Rule::RULE_ROOT_REQUIRE) { throw new \LogicException("Single reason problems must contain a root require rule."); } $reasonData = $rule->getReasonData(); $packageName = $reasonData['packageName']; $constraint = $reasonData['constraint']; $packages = $pool->whatProvides($packageName, $constraint); if (\count($packages) === 0) { return "\n " . \implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint)); } } return self::formatDeduplicatedRules($reasons, ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); } /** * @param Rule[] $rules * @param array $installedMap A map of all present packages * @param array $learnedPool * @internal */ public static function formatDeduplicatedRules(array $rules, string $indent, RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []) : string { $messages = []; $templates = []; $parser = new VersionParser(); $deduplicatableRuleTypes = [\Composer\DependencyResolver\Rule::RULE_PACKAGE_REQUIRES, \Composer\DependencyResolver\Rule::RULE_PACKAGE_CONFLICT]; foreach ($rules as $rule) { $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); if (\in_array($rule->getReason(), $deduplicatableRuleTypes, \true) && Preg::isMatchStrictGroups('{^(?P\\S+) (?P\\S+) (?Prequires|conflicts)}', $message, $m)) { $message = \str_replace('%', '%%', $message); $template = Preg::replace('{^\\S+ \\S+ }', '%s%s ', $message); $messages[] = $template; $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2]; $sourcePackage = $rule->getSourcePackage($pool); foreach ($pool->getRemovedVersionsByPackage(\spl_object_hash($sourcePackage)) as $version => $prettyVersion) { $templates[$template][$m[1]][$version] = $prettyVersion; } } elseif ($message !== '') { $messages[] = $message; } } $result = []; foreach (\array_unique($messages) as $message) { if (isset($templates[$message])) { foreach ($templates[$message] as $package => $versions) { \uksort($versions, 'version_compare'); if (!$isVerbose) { $versions = self::condenseVersionList($versions, 1); } if (\count($versions) > 1) { // remove the s from requires/conflicts to correct grammar $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message); $result[] = \sprintf($message, $package, '[' . \implode(', ', $versions) . ']'); } else { $result[] = \sprintf($message, $package, ' ' . \reset($versions)); } } } else { $result[] = $message; } } return "\n{$indent}- " . \implode("\n{$indent}- ", $result); } public function isCausedByLock(RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool) : bool { foreach ($this->reasons as $sectionRules) { foreach ($sectionRules as $rule) { if ($rule->isCausedByLock($repositorySet, $request, $pool)) { return \true; } } } return \false; } /** * Store a reason descriptor but ignore duplicates * * @param string $id A canonical identifier for the reason * @param Rule $reason The reason descriptor */ protected function addReason(string $id, \Composer\DependencyResolver\Rule $reason) : void { // TODO: if a rule is part of a problem description in two sections, isn't this going to remove a message // that is important to understand the issue? if (!isset($this->reasonSeen[$id])) { $this->reasonSeen[$id] = \true; $this->reasons[$this->section][] = $reason; } } public function nextSection() : void { $this->section++; } /** * @internal * @return array{0: string, 1: string} */ public static function getMissingPackageReason(RepositorySet $repositorySet, \Composer\DependencyResolver\Request $request, \Composer\DependencyResolver\Pool $pool, bool $isVerbose, string $packageName, ?ConstraintInterface $constraint = null) : array { if (PlatformRepository::isPlatformPackage($packageName)) { // handle php/php-*/hhvm if (0 === \stripos($packageName, 'php') || $packageName === 'hhvm') { $version = self::getPlatformPackageVersion($pool, $packageName, \phpversion()); $msg = "- Root composer.json requires " . $packageName . self::constraintToText($constraint) . ' but '; if (\defined('_ContaoManager\\HHVM_VERSION') || $packageName === 'hhvm' && \count($pool->whatProvides($packageName)) > 0) { return [$msg, 'your HHVM version does not satisfy that requirement.']; } if ($packageName === 'hhvm') { return [$msg, 'HHVM was not detected on this machine, make sure it is in your PATH.']; } if (null === $version) { return [$msg, 'the ' . $packageName . ' package is disabled by your platform config. Enable it again with "composer config platform.' . $packageName . ' --unset".']; } return [$msg, 'your ' . $packageName . ' version (' . $version . ') does not satisfy that requirement.']; } // handle php extensions if (0 === \stripos($packageName, 'ext-')) { if (\false !== \strpos($packageName, ' ')) { return ['- ', "PHP extension " . $packageName . ' should be required as ' . \str_replace(' ', '-', $packageName) . '.']; } $ext = \substr($packageName, 4); $msg = "- Root composer.json requires PHP extension " . $packageName . self::constraintToText($constraint) . ' but '; $version = self::getPlatformPackageVersion($pool, $packageName, \phpversion($ext) ?: '0'); if (null === $version) { if (\extension_loaded($ext)) { return [$msg, 'the ' . $packageName . ' package is disabled by your platform config. Enable it again with "composer config platform.' . $packageName . ' --unset".']; } return [$msg, 'it is missing from your system. Install or enable PHP\'s ' . $ext . ' extension.']; } return [$msg, 'it has the wrong version installed (' . $version . ').']; } // handle linked libs if (0 === \stripos($packageName, 'lib-')) { if (\strtolower($packageName) === 'lib-icu') { $error = \extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.'; return ["- Root composer.json requires linked library " . $packageName . self::constraintToText($constraint) . ' but ', $error]; } return ["- Root composer.json requires linked library " . $packageName . self::constraintToText($constraint) . ' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.']; } } $lockedPackage = null; foreach ($request->getLockedPackages() as $package) { if ($package->getName() === $packageName) { $lockedPackage = $package; if ($pool->isUnacceptableFixedOrLockedPackage($package)) { return ["- ", $package->getPrettyName() . ' is fixed to ' . $package->getPrettyVersion() . ' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.']; } break; } } // first check if the actual requested package is found in normal conditions // if so it must mean it is rejected by another constraint than the one given here if ($packages = $repositorySet->findPackages($packageName, $constraint)) { $rootReqs = $repositorySet->getRootRequires(); if (isset($rootReqs[$packageName])) { $filtered = \array_filter($packages, static function ($p) use($rootReqs, $packageName) : bool { return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); }); if (0 === \count($filtered)) { return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' but ' . (self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts') . ' with your root composer.json require (' . $rootReqs[$packageName]->getPrettyString() . ').']; } } $tempReqs = $repositorySet->getTemporaryConstraints(); if (isset($tempReqs[$packageName])) { $filtered = \array_filter($packages, static function ($p) use($tempReqs, $packageName) : bool { return $tempReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); }); if (0 === \count($filtered)) { return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' but ' . (self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts') . ' with your temporary update constraint (' . $packageName . ':' . $tempReqs[$packageName]->getPrettyString() . ').']; } } if ($lockedPackage) { $fixedConstraint = new Constraint('==', $lockedPackage->getVersion()); $filtered = \array_filter($packages, static function ($p) use($fixedConstraint) : bool { return $fixedConstraint->matches(new Constraint('==', $p->getVersion())); }); if (0 === \count($filtered)) { return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' but the package is fixed to ' . $lockedPackage->getPrettyVersion() . ' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.']; } } $nonLockedPackages = \array_filter($packages, static function ($p) : bool { return !$p->getRepository() instanceof LockArrayRepository; }); if (!$nonLockedPackages) { return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.']; } return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' but these were not loaded, likely because ' . (self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts') . ' with another require.']; } // check if the package is found when bypassing stability checks if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { // we must first verify if a valid package would be found in a lower priority repository if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); } return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' but ' . (self::hasMultipleNames($packages) ? 'these do' : 'it does') . ' not match your minimum-stability.']; } // check if the package is found when bypassing the constraint and stability checks if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { // we must first verify if a valid package would be found in a lower priority repository if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); } $suffix = ''; if ($constraint instanceof Constraint && $constraint->getVersion() === 'dev-master') { foreach ($packages as $candidate) { if (\in_array($candidate->getVersion(), ['dev-default', 'dev-main'], \true)) { $suffix = ' Perhaps dev-master was renamed to ' . $candidate->getPrettyVersion() . '?'; break; } } } // check if the root package is a name match and hint the dependencies on root troubleshooting article $allReposPackages = $packages; $topPackage = \reset($allReposPackages); if ($topPackage instanceof RootPackageInterface) { $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.'; } return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($packages, $isVerbose, $pool, $constraint) . ' but ' . (self::hasMultipleNames($packages) ? 'these do' : 'it does') . ' not match the constraint.' . $suffix]; } if (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $packageName)) { $illegalChars = Preg::replace('{[A-Za-z0-9_./-]+}', '', $packageName); return ["- Root composer.json requires {$packageName}, it ", 'could not be found, it looks like its name is invalid, "' . $illegalChars . '" is not allowed in package names.']; } if ($providers = $repositorySet->getProviders($packageName)) { $maxProviders = 20; $providersStr = \implode(\array_map(static function ($p) : string { $description = $p['description'] ? ' ' . \substr($p['description'], 0, 100) : ''; return ' - ' . $p['name'] . $description . "\n"; }, \count($providers) > $maxProviders + 1 ? \array_slice($providers, 0, $maxProviders) : $providers)); if (\count($providers) > $maxProviders + 1) { $providersStr .= ' ... and ' . (\count($providers) - $maxProviders) . ' more.' . "\n"; } return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ", it ", "could not be found in any version, but the following packages provide it:\n" . $providersStr . " Consider requiring one of these to satisfy the {$packageName} requirement."]; } return ["- Root composer.json requires {$packageName}, it ", "could not be found in any version, there may be a typo in the package name."]; } /** * @internal * @param PackageInterface[] $packages */ public static function getPackageList(array $packages, bool $isVerbose, ?\Composer\DependencyResolver\Pool $pool = null, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = \false) : string { $prepared = []; $hasDefaultBranch = []; foreach ($packages as $package) { $prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion() . ($package instanceof AliasPackage ? ' (alias of ' . $package->getAliasOf()->getPrettyVersion() . ')' : ''); if ($pool && $constraint) { foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) { $prepared[$package->getName()]['versions'][$version] = $prettyVersion; } } if ($pool && $useRemovedVersionGroup) { foreach ($pool->getRemovedVersionsByPackage(\spl_object_hash($package)) as $version => $prettyVersion) { $prepared[$package->getName()]['versions'][$version] = $prettyVersion; } } if ($package->isDefaultBranch()) { $hasDefaultBranch[$package->getName()] = \true; } } $preparedStrings = []; foreach ($prepared as $name => $package) { // remove the implicit default branch alias to avoid cruft in the display if (isset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS], $hasDefaultBranch[$name])) { unset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS]); } \uksort($package['versions'], 'version_compare'); if (!$isVerbose) { $package['versions'] = self::condenseVersionList($package['versions'], 4); } $preparedStrings[] = $package['name'] . '[' . \implode(', ', $package['versions']) . ']'; } return \implode(', ', $preparedStrings); } /** * @param string $version the effective runtime version of the platform package * @return ?string a version string or null if it appears the package was artificially disabled */ private static function getPlatformPackageVersion(\Composer\DependencyResolver\Pool $pool, string $packageName, string $version) : ?string { $available = $pool->whatProvides($packageName); if (\count($available)) { $selected = null; foreach ($available as $pkg) { if ($pkg->getRepository() instanceof PlatformRepository) { $selected = $pkg; break; } } if ($selected === null) { $selected = \reset($available); } // must be a package providing/replacing and not a real platform package if ($selected->getName() !== $packageName) { /** @var Link $link */ foreach (\array_merge(\array_values($selected->getProvides()), \array_values($selected->getReplaces())) as $link) { if ($link->getTarget() === $packageName) { return $link->getPrettyConstraint() . ' ' . \substr($link->getDescription(), 0, -1) . 'd by ' . $selected->getPrettyString(); } } } $version = $selected->getPrettyVersion(); $extra = $selected->getExtra(); if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === \true) { $version .= '; ' . \str_replace('Package ', '', $selected->getDescription()); } } else { return null; } return $version; } /** * @param array $versions an array of pretty versions, with normalized versions as keys * @return list a list of pretty versions and '...' where versions were removed */ private static function condenseVersionList(array $versions, int $max, int $maxDev = 16) : array { if (\count($versions) <= $max) { return \array_values($versions); } $filtered = []; $byMajor = []; foreach ($versions as $version => $pretty) { if (0 === \stripos((string) $version, 'dev-')) { $byMajor['dev'][] = $pretty; } else { $byMajor[Preg::replace('{^(\\d+)\\..*}', '$1', (string) $version)][] = $pretty; } } foreach ($byMajor as $majorVersion => $versionsForMajor) { $maxVersions = $majorVersion === 'dev' ? $maxDev : $max; if (\count($versionsForMajor) > $maxVersions) { // output only 1st and last versions $filtered[] = $versionsForMajor[0]; $filtered[] = '...'; $filtered[] = $versionsForMajor[\count($versionsForMajor) - 1]; } else { $filtered = \array_merge($filtered, $versionsForMajor); } } return $filtered; } /** * @param PackageInterface[] $packages */ private static function hasMultipleNames(array $packages) : bool { $name = null; foreach ($packages as $package) { if ($name === null || $name === $package->getName()) { $name = $package->getName(); } else { return \true; } } return \false; } /** * @param PackageInterface[] $higherRepoPackages * @param PackageInterface[] $allReposPackages * @return array{0: string, 1: string} */ private static function computeCheckForLowerPrioRepo(\Composer\DependencyResolver\Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null) : array { $nextRepoPackages = []; $nextRepo = null; foreach ($allReposPackages as $package) { if ($nextRepo === null || $nextRepo === $package->getRepository()) { $nextRepoPackages[] = $package; $nextRepo = $package->getRepository(); } else { break; } } if ($higherRepoPackages) { $topPackage = \reset($higherRepoPackages); if ($topPackage instanceof RootPackageInterface) { return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', it is ', 'satisfiable by ' . self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint) . ' from ' . $nextRepo->getRepoName() . ' but ' . $topPackage->getPrettyName() . ' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.']; } } if ($nextRepo instanceof LockArrayRepository) { $singular = \count($higherRepoPackages) === 1; $suggestion = 'Make sure you either fix the ' . $reason . ' or avoid updating this package to keep the one present in the lock file (' . self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint) . ').'; // symlinked path repos cannot be locked so do not suggest keeping it locked if ($nextRepoPackages[0]->getDistType() === 'path') { $transportOptions = $nextRepoPackages[0]->getTransportOptions(); if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== \false) { $suggestion = 'Make sure you fix the ' . $reason . ' as packages installed from symlinked path repos are updated even in partial updates and the one from the lock file can thus not be used.'; } } return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint) . ' but ' . ($singular ? 'it does' : 'these do') . ' not match your ' . $reason . ' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. ' . $suggestion]; } return ["- Root composer.json requires {$packageName}" . self::constraintToText($constraint) . ', it is ', 'satisfiable by ' . self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint) . ' from ' . $nextRepo->getRepoName() . ' but ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint) . ' from ' . \reset($higherRepoPackages)->getRepository()->getRepoName() . ' has higher repository priority. The packages from the higher priority repository do not match your ' . $reason . ' and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.']; } /** * Turns a constraint into text usable in a sentence describing a request */ protected static function constraintToText(?ConstraintInterface $constraint = null) : string { if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !\str_starts_with($constraint->getVersion(), 'dev-')) { if (!Preg::isMatch('{^\\d+(?:\\.\\d+)*$}', $constraint->getPrettyString())) { return ' ' . $constraint->getPrettyString() . ' (exact version match)'; } $versions = [$constraint->getPrettyString()]; for ($i = 3 - \substr_count($versions[0], '.'); $i > 0; $i--) { $versions[] = \end($versions) . '.0'; } return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (\count($versions) > 1 ? \implode(', ', \array_slice($versions, 0, -1)) . ' or ' . \end($versions) : $versions[0]) . ')'; } return $constraint ? ' ' . $constraint->getPrettyString() : ''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann * @implements \Iterator */ class RuleSetIterator implements \Iterator { /** @var array */ protected $rules; /** @var array */ protected $types; /** @var int */ protected $currentOffset; /** @var RuleSet::TYPE_*|-1 */ protected $currentType; /** @var int */ protected $currentTypeOffset; /** * @param array $rules */ public function __construct(array $rules) { $this->rules = $rules; $this->types = \array_keys($rules); \sort($this->types); $this->rewind(); } public function current() : \Composer\DependencyResolver\Rule { return $this->rules[$this->currentType][$this->currentOffset]; } /** * @return RuleSet::TYPE_*|-1 */ public function key() : int { return $this->currentType; } public function next() : void { $this->currentOffset++; if (!isset($this->rules[$this->currentType])) { return; } if ($this->currentOffset >= \count($this->rules[$this->currentType])) { $this->currentOffset = 0; do { $this->currentTypeOffset++; if (!isset($this->types[$this->currentTypeOffset])) { $this->currentType = -1; break; } $this->currentType = $this->types[$this->currentTypeOffset]; } while (0 === \count($this->rules[$this->currentType])); } } public function rewind() : void { $this->currentOffset = 0; $this->currentTypeOffset = -1; $this->currentType = -1; do { $this->currentTypeOffset++; if (!isset($this->types[$this->currentTypeOffset])) { $this->currentType = -1; break; } $this->currentType = $this->types[$this->currentTypeOffset]; } while (0 === \count($this->rules[$this->currentType])); } public function valid() : bool { return isset($this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * An extension of SplDoublyLinkedList with seek and removal of current element * * SplDoublyLinkedList only allows deleting a particular offset and has no * method to set the internal iterator to a particular offset. * * @author Nils Adermann * @extends \SplDoublyLinkedList */ class RuleWatchChain extends \SplDoublyLinkedList { /** * Moves the internal iterator to the specified offset * * @param int $offset The offset to seek to. */ public function seek(int $offset) : void { $this->rewind(); for ($i = 0; $i < $offset; $i++, $this->next()) { } } /** * Removes the current element from the list * * As SplDoublyLinkedList only allows deleting a particular offset and * incorrectly sets the internal iterator if you delete the current value * this method sets the internal iterator back to the following element * using the seek method. */ public function remove() : void { $offset = $this->key(); $this->offsetUnset($offset); $this->seek($offset); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; use Composer\ClassMapGenerator\ClassMap; use Composer\ClassMapGenerator\ClassMapGenerator; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; use Composer\IO\NullIO; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Pcre\Preg; use Composer\Repository\InstalledRepositoryInterface; use Composer\Semver\Constraint\Bound; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Script\ScriptEvents; use Composer\Util\PackageSorter; use Composer\Json\JsonFile; use Composer\Package\Locker; /** * @author Igor Wiedler * @author Jordi Boggiano */ class AutoloadGenerator { /** * @var EventDispatcher */ private $eventDispatcher; /** * @var IOInterface */ private $io; /** * @var ?bool */ private $devMode = null; /** * @var bool */ private $classMapAuthoritative = \false; /** * @var bool */ private $apcu = \false; /** * @var string|null */ private $apcuPrefix; /** * @var bool */ private $dryRun = \false; /** * @var bool */ private $runScripts = \false; /** * @var PlatformRequirementFilterInterface */ private $platformRequirementFilter; public function __construct(EventDispatcher $eventDispatcher, ?IOInterface $io = null) { $this->eventDispatcher = $eventDispatcher; $this->io = $io ?? new NullIO(); $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); } /** * @return void */ public function setDevMode(bool $devMode = \true) { $this->devMode = $devMode; } /** * Whether generated autoloader considers the class map authoritative. * * @return void */ public function setClassMapAuthoritative(bool $classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Whether generated autoloader considers APCu caching. * * @return void */ public function setApcu(bool $apcu, ?string $apcuPrefix = null) { $this->apcu = $apcu; $this->apcuPrefix = $apcuPrefix; } /** * Whether to run scripts or not * * @return void */ public function setRunScripts(bool $runScripts = \true) { $this->runScripts = $runScripts; } /** * Whether to run in drymode or not */ public function setDryRun(bool $dryRun = \true) : void { $this->dryRun = $dryRun; } /** * Whether platform requirements should be ignored. * * If this is set to true, the platform check file will not be generated * If this is set to false, the platform check file will be generated with all requirements * If this is set to string[], those packages will be ignored from the platform check file * * @param bool|string[] $ignorePlatformReqs * @return void * * @deprecated use setPlatformRequirementFilter instead */ public function setIgnorePlatformRequirements($ignorePlatformReqs) { \trigger_error('AutoloadGenerator::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', \E_USER_DEPRECATED); $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); } /** * @return void */ public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter) { $this->platformRequirementFilter = $platformRequirementFilter; } /** * @return ClassMap * @throws \Seld\JsonLint\ParsingException * @throws \RuntimeException */ public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = \false, ?string $suffix = null, ?Locker $locker = null) { if ($this->classMapAuthoritative) { // Force scanPsrPackages when classmap is authoritative $scanPsrPackages = \true; } // auto-set devMode based on whether dev dependencies are installed or not if (null === $this->devMode) { // we assume no-dev mode if no vendor dir is present or it is too old to contain dev information $this->devMode = \false; $installedJson = new JsonFile($config->get('vendor-dir') . '/composer/installed.json'); if ($installedJson->exists()) { $installedJson = $installedJson->read(); if (isset($installedJson['dev'])) { $this->devMode = $installedJson['dev']; } } } if ($this->runScripts) { // set COMPOSER_DEV_MODE in case not set yet so it is available in the dump-autoload event listeners if (!isset($_SERVER['COMPOSER_DEV_MODE'])) { Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); } $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, [], ['optimize' => $scanPsrPackages]); } $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); $classMapGenerator->avoidDuplicateScans(); $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($config->get('vendor-dir')); // Do not remove double realpath() calls. // Fixes failing Windows realpath() implementation. // See https://bugs.php.net/bug.php?id=72738 $basePath = $filesystem->normalizePath(\realpath(\realpath(Platform::getCwd()))); $vendorPath = $filesystem->normalizePath(\realpath(\realpath($config->get('vendor-dir')))); $useGlobalIncludePath = $config->get('use-include-path'); $prependAutoloader = $config->get('prepend-autoloader') === \false ? 'false' : 'true'; $targetDir = $vendorPath . '/' . $targetDir; $filesystem->ensureDirectoryExists($targetDir); $vendorPathCode = $filesystem->findShortestPathCode(\realpath($targetDir), $vendorPath, \true); $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, \realpath($targetDir), \true); $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, \true); $appBaseDirCode = \str_replace('__DIR__', '$vendorDir', $appBaseDirCode); $namespacesFile = <<getDevPackageNames(); $packageMap = $this->buildPackageMap($installationManager, $rootPackage, $localRepo->getCanonicalPackages()); if ($this->devMode) { // if dev mode is enabled, then we do not filter any dev packages out so disable this entirely $filteredDevPackages = \false; } else { // if the list of dev package names is available we use that straight, otherwise pass true which means use legacy algo to figure them out $filteredDevPackages = $devPackageNames ?: \true; } $autoloads = $this->parseAutoloads($packageMap, $rootPackage, $filteredDevPackages); // Process the 'psr-0' base directories. foreach ($autoloads['psr-0'] as $namespace => $paths) { $exportedPaths = []; foreach ($paths as $path) { $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); } $exportedPrefix = \var_export($namespace, \true); $namespacesFile .= " {$exportedPrefix} => "; $namespacesFile .= "array(" . \implode(', ', $exportedPaths) . "),\n"; } $namespacesFile .= ");\n"; // Process the 'psr-4' base directories. foreach ($autoloads['psr-4'] as $namespace => $paths) { $exportedPaths = []; foreach ($paths as $path) { $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); } $exportedPrefix = \var_export($namespace, \true); $psr4File .= " {$exportedPrefix} => "; $psr4File .= "array(" . \implode(', ', $exportedPaths) . "),\n"; } $psr4File .= ");\n"; // add custom psr-0 autoloading if the root package has a target dir $targetDirLoader = null; $mainAutoload = $rootPackage->getAutoload(); if ($rootPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) { $levels = \substr_count($filesystem->normalizePath($rootPackage->getTargetDir()), '/') + 1; $prefixes = \implode(', ', \array_map(static function ($prefix) : string { return \var_export($prefix, \true); }, \array_keys($mainAutoload['psr-0']))); $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, \true); $targetDirLoader = <<scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); } if ($scanPsrPackages) { $namespacesToScan = []; // Scan the PSR-0/4 directories for class files, and add them to the class map foreach (['psr-4', 'psr-0'] as $psrType) { foreach ($autoloads[$psrType] as $namespace => $paths) { $namespacesToScan[$namespace][] = ['paths' => $paths, 'type' => $psrType]; } } \krsort($namespacesToScan); foreach ($namespacesToScan as $namespace => $groups) { foreach ($groups as $group) { foreach ($group['paths'] as $dir) { $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath . '/' . $dir); if (!\is_dir($dir)) { continue; } $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded), $group['type'], $namespace); } } } } $classMap = $classMapGenerator->getClassMap(); foreach ($classMap->getAmbiguousClasses() as $className => $ambiguousPaths) { if (\count($ambiguousPaths) > 1) { $this->io->writeError('Warning: Ambiguous class resolution, "' . $className . '"' . ' was found ' . (\count($ambiguousPaths) + 1) . 'x: in "' . $classMap->getClassPath($className) . '" and "' . \implode('", "', $ambiguousPaths) . '", the first will be used.'); } else { $this->io->writeError('Warning: Ambiguous class resolution, "' . $className . '"' . ' was found in both "' . $classMap->getClassPath($className) . '" and "' . \implode('", "', $ambiguousPaths) . '", the first will be used.'); } } foreach ($classMap->getPsrViolations() as $msg) { $this->io->writeError("{$msg}"); } $classMap->addClass('Composer\\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php'); $classMap->sort(); $classmapFile = <<getMap() as $className => $path) { $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; $classmapFile .= ' ' . \var_export($className, \true) . ' => ' . $pathCode; } $classmapFile .= ");\n"; if ('' === $suffix) { $suffix = null; } if (null === $suffix) { $suffix = $config->get('autoloader-suffix'); // carry over existing autoload.php's suffix if possible and none is configured if (null === $suffix && Filesystem::isReadable($vendorPath . '/autoload.php')) { $content = \file_get_contents($vendorPath . '/autoload.php'); if (Preg::isMatch('{ComposerAutoloaderInit([^:\\s]+)::}', $content, $match)) { $suffix = $match[1]; } } if (null === $suffix) { $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : \md5(\uniqid('', \true)); } } if ($this->dryRun) { return $classMap; } $filesystem->filePutContentsIfModified($targetDir . '/autoload_namespaces.php', $namespacesFile); $filesystem->filePutContentsIfModified($targetDir . '/autoload_psr4.php', $psr4File); $filesystem->filePutContentsIfModified($targetDir . '/autoload_classmap.php', $classmapFile); $includePathFilePath = $targetDir . '/include_paths.php'; if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents); } elseif (\file_exists($includePathFilePath)) { \unlink($includePathFilePath); } $includeFilesFilePath = $targetDir . '/autoload_files.php'; if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents); } elseif (\file_exists($includeFilesFilePath)) { \unlink($includeFilesFilePath); } $filesystem->filePutContentsIfModified($targetDir . '/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath)); $checkPlatform = $config->get('platform-check') !== \false && !$this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter; $platformCheckContent = null; if ($checkPlatform) { $platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames); if (null === $platformCheckContent) { $checkPlatform = \false; } } if ($checkPlatform) { $filesystem->filePutContentsIfModified($targetDir . '/platform_check.php', $platformCheckContent); } elseif (\file_exists($targetDir . '/platform_check.php')) { \unlink($targetDir . '/platform_check.php'); } $filesystem->filePutContentsIfModified($vendorPath . '/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); $filesystem->filePutContentsIfModified($targetDir . '/autoload_real.php', $this->getAutoloadRealFile(\true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $checkPlatform)); $filesystem->safeCopy(__DIR__ . '/ClassLoader.php', $targetDir . '/ClassLoader.php'); $filesystem->safeCopy(__DIR__ . '/../../../LICENSE', $targetDir . '/LICENSE'); if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, [], ['optimize' => $scanPsrPackages]); } return $classMap; } /** * @param array|null $excluded * @return non-empty-string|null */ private function buildExclusionRegex(string $dir, ?array $excluded) : ?string { if (null === $excluded) { return null; } // filter excluded patterns here to only use those matching $dir // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work // if $dir does not exist, it should anyway not find anything there so no trouble if (\file_exists($dir)) { // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other $dirMatch = \preg_quote(\strtr(\realpath($dir), '\\', '/')); foreach ($excluded as $index => $pattern) { // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character $pattern = Preg::replace('{^(([^.+*?\\[^\\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\\[^\\]$(){}=!<>|:#-])*).*}', '$1', $pattern); // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it if (0 !== \strpos($pattern, $dirMatch) && 0 !== \strpos($dirMatch, $pattern)) { unset($excluded[$index]); } } } return \count($excluded) > 0 ? '{(' . \implode('|', $excluded) . ')}' : null; } /** * @param PackageInterface[] $packages * @return non-empty-array */ public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages) { // build package => install path map $packageMap = [[$rootPackage, '']]; foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $this->validatePackage($package); $packageMap[] = [$package, $installationManager->getInstallPath($package)]; } return $packageMap; } /** * @return void * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings. */ protected function validatePackage(PackageInterface $package) { $autoload = $package->getAutoload(); if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) { $name = $package->getName(); $package->getTargetDir(); throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '{$name}'."); } if (!empty($autoload['psr-4'])) { foreach ($autoload['psr-4'] as $namespace => $dirs) { if ($namespace !== '' && '\\' !== \substr($namespace, -1)) { throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '{$namespace}' does not, use '{$namespace}\\'."); } } } } /** * Compiles an ordered list of namespace => path mappings * * @param non-empty-array $packageMap array of array(package, installDir-relative-to-composer.json or null for metapackages) * @param RootPackageInterface $rootPackage root package instance * @param bool|string[] $filteredDevPackages If an array, the list of packages that must be removed. If bool, whether to filter out require-dev packages * @return array * @phpstan-return array{ * 'psr-0': array>, * 'psr-4': array>, * 'classmap': array, * 'files': array, * 'exclude-from-classmap': array, * } */ public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = \false) { $rootPackageMap = \array_shift($packageMap); if (\is_array($filteredDevPackages)) { $packageMap = \array_filter($packageMap, static function ($item) use($filteredDevPackages) : bool { return !\in_array($item[0]->getName(), $filteredDevPackages, \true); }); } elseif ($filteredDevPackages) { $packageMap = $this->filterPackageMap($packageMap, $rootPackage); } $sortedPackageMap = $this->sortPackageMap($packageMap); $sortedPackageMap[] = $rootPackageMap; \array_unshift($packageMap, $rootPackageMap); $psr0 = $this->parseAutoloadsType($packageMap, 'psr-0', $rootPackage); $psr4 = $this->parseAutoloadsType($packageMap, 'psr-4', $rootPackage); $classmap = $this->parseAutoloadsType(\array_reverse($sortedPackageMap), 'classmap', $rootPackage); $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $rootPackage); $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $rootPackage); \krsort($psr0); \krsort($psr4); return ['psr-0' => $psr0, 'psr-4' => $psr4, 'classmap' => $classmap, 'files' => $files, 'exclude-from-classmap' => $exclude]; } /** * Registers an autoloader based on an autoload-map returned by parseAutoloads * * @param array $autoloads see parseAutoloads return value * @return ClassLoader */ public function createLoader(array $autoloads, ?string $vendorDir = null) { $loader = new \Composer\Autoload\ClassLoader($vendorDir); if (isset($autoloads['psr-0'])) { foreach ($autoloads['psr-0'] as $namespace => $path) { $loader->add($namespace, $path); } } if (isset($autoloads['psr-4'])) { foreach ($autoloads['psr-4'] as $namespace => $path) { $loader->addPsr4($namespace, $path); } } if (isset($autoloads['classmap'])) { $excluded = null; if (!empty($autoloads['exclude-from-classmap'])) { $excluded = $autoloads['exclude-from-classmap']; } $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); $classMapGenerator->avoidDuplicateScans(); foreach ($autoloads['classmap'] as $dir) { try { $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); } catch (\RuntimeException $e) { $this->io->writeError('' . $e->getMessage() . ''); } } $loader->addClassMap($classMapGenerator->getClassMap()->getMap()); } return $loader; } /** * @param array $packageMap * @return ?string */ protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) { $includePaths = []; foreach ($packageMap as $item) { [$package, $installPath] = $item; // packages that are not installed cannot autoload anything if (null === $installPath) { continue; } if (null !== $package->getTargetDir() && \strlen($package->getTargetDir()) > 0) { $installPath = \substr($installPath, 0, -\strlen('/' . $package->getTargetDir())); } foreach ($package->getIncludePaths() as $includePath) { $includePath = \trim($includePath, '/'); $includePaths[] = empty($installPath) ? $includePath : $installPath . '/' . $includePath; } } if (!$includePaths) { return null; } $includePathsCode = ''; foreach ($includePaths as $path) { $includePathsCode .= " " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; } return << $files * @return ?string */ protected function getIncludeFilesFile(array $files, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) { // Get the path to each file, and make sure these paths are unique. $files = \array_map(function (string $functionFile) use($filesystem, $basePath, $vendorPath) : string { return $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile); }, $files); $uniqueFiles = \array_unique($files); if (\count($uniqueFiles) < \count($files)) { $this->io->writeError('The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:'); foreach (\array_unique(\array_diff_assoc($files, $uniqueFiles)) as $duplicateFile) { $this->io->writeError(' - ' . $duplicateFile . ''); } } unset($uniqueFiles); $filesCode = ''; foreach ($files as $fileIdentifier => $functionFile) { $filesCode .= ' ' . \var_export($fileIdentifier, \true) . ' => ' . $functionFile . ",\n"; } if (!$filesCode) { return null; } return <<isAbsolutePath($path)) { $path = $basePath . '/' . $path; } $path = $filesystem->normalizePath($path); $baseDir = ''; if (\strpos($path . '/', $vendorPath . '/') === 0) { $path = (string) \substr($path, \strlen($vendorPath)); $baseDir = '$vendorDir . '; } else { $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, \true)); if (!$filesystem->isAbsolutePath($path)) { $baseDir = '$baseDir . '; $path = '/' . $path; } } if (\strpos($path, '.phar') !== \false) { $baseDir = "'phar://' . " . $baseDir; } return $baseDir . \var_export($path, \true); } /** * @param array $packageMap * @param bool|'php-only' $checkPlatform * @param string[] $devPackageNames * @return ?string */ protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames) { $lowestPhpVersion = Bound::zero(); $requiredPhp64bit = \false; $requiredExtensions = []; $extensionProviders = []; foreach ($packageMap as $item) { $package = $item[0]; foreach (\array_merge($package->getReplaces(), $package->getProvides()) as $link) { if (Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { $extensionProviders[$match[1]][] = $link->getConstraint(); } } } foreach ($packageMap as $item) { $package = $item[0]; // skip dev dependencies platform requirements as platform-check really should only be a production safeguard if (\in_array($package->getName(), $devPackageNames, \true)) { continue; } foreach ($package->getRequires() as $link) { if ($this->platformRequirementFilter->isIgnored($link->getTarget())) { continue; } if (\in_array($link->getTarget(), ['php', 'php-64bit'], \true)) { $constraint = $link->getConstraint(); if ($constraint->getLowerBound()->compareTo($lowestPhpVersion, '>')) { $lowestPhpVersion = $constraint->getLowerBound(); } } if ('php-64bit' === $link->getTarget()) { $requiredPhp64bit = \true; } if ($checkPlatform === \true && Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { // skip extension checks if they have a valid provider/replacer if (isset($extensionProviders[$match[1]])) { foreach ($extensionProviders[$match[1]] as $provided) { if ($provided->matches($link->getConstraint())) { continue 2; } } } if ($match[1] === 'zend-opcache') { $match[1] = 'zend opcache'; } $extension = \var_export($match[1], \true); if ($match[1] === 'pcntl' || $match[1] === 'readline') { $requiredExtensions[$extension] = "PHP_SAPI !== 'cli' || extension_loaded({$extension}) || \$missingExtensions[] = {$extension};\n"; } else { $requiredExtensions[$extension] = "extension_loaded({$extension}) || \$missingExtensions[] = {$extension};\n"; } } } } \ksort($requiredExtensions); $formatToPhpVersionId = static function (Bound $bound) : int { if ($bound->isZero()) { return 0; } if ($bound->isPositiveInfinity()) { return 99999; } $version = \str_replace('-', '.', $bound->getVersion()); $chunks = \array_map('intval', \explode('.', $version)); return $chunks[0] * 10000 + $chunks[1] * 100 + $chunks[2]; }; $formatToHumanReadable = static function (Bound $bound) { if ($bound->isZero()) { return 0; } if ($bound->isPositiveInfinity()) { return 99999; } $version = \str_replace('-', '.', $bound->getVersion()); $chunks = \explode('.', $version); $chunks = \array_slice($chunks, 0, 3); return \implode('.', $chunks); }; $requiredPhp = ''; $requiredPhpError = ''; if (!$lowestPhpVersion->isZero()) { $operator = $lowestPhpVersion->isInclusive() ? '>=' : '>'; $requiredPhp = 'PHP_VERSION_ID ' . $operator . ' ' . $formatToPhpVersionId($lowestPhpVersion); $requiredPhpError = '"' . $operator . ' ' . $formatToHumanReadable($lowestPhpVersion) . '"'; } if ($requiredPhp) { $requiredPhp = <<classMapAuthoritative) { $file .= <<<'CLASSMAPAUTHORITATIVE' $loader->setClassMapAuthoritative(true); CLASSMAPAUTHORITATIVE; } if ($this->apcu) { $apcuPrefix = \var_export($this->apcuPrefix !== null ? $this->apcuPrefix : \substr(\base64_encode(\md5(\uniqid('', \true), \true)), 0, -3), \true); $file .= <<setApcuPrefix({$apcuPrefix}); APCU; } if ($useGlobalIncludePath) { $file .= <<<'INCLUDEPATH' $loader->setUseIncludePath(true); INCLUDEPATH; } if ($targetDirLoader) { $file .= <<register({$prependAutoloader}); REGISTER_LOADER; if ($useIncludeFiles) { $file .= << \$file) { \$requireFile(\$fileIdentifier, \$file); } INCLUDE_FILES; } $file .= << $path) { $loader->set($namespace, $path); } $map = (require $targetDir . '/autoload_psr4.php'); foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } /** * @var string $vendorDir * @var string $baseDir */ $classMap = (require $targetDir . '/autoload_classmap.php'); if ($classMap) { $loader->addClassMap($classMap); } $filesystem = new Filesystem(); $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(\realpath($targetDir), $vendorPath, \true, \true) . " . '/"; $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(\realpath($targetDir), $vendorPath, \true, \true) . " . '/"; $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(\realpath($targetDir), $basePath, \true, \true) . " . '/"; $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(\realpath($targetDir), $basePath, \true, \true) . " . '/"; $absoluteVendorPathCode = ' => ' . \substr(\var_export(\rtrim($vendorDir, '\\/') . '/', \true), 0, -1); $absoluteVendorPharPathCode = ' => ' . \substr(\var_export(\rtrim('phar://' . $vendorDir, '\\/') . '/', \true), 0, -1); $absoluteAppBaseDirCode = ' => ' . \substr(\var_export(\rtrim($baseDir, '\\/') . '/', \true), 0, -1); $absoluteAppBaseDirPharCode = ' => ' . \substr(\var_export(\rtrim('phar://' . $baseDir, '\\/') . '/', \true), 0, -1); $initializer = ''; $prefix = "\x00Composer\\Autoload\\ClassLoader\x00"; $prefixLen = \strlen($prefix); if (\file_exists($targetDir . '/autoload_files.php')) { $maps = ['files' => require $targetDir . '/autoload_files.php']; } else { $maps = []; } foreach ((array) $loader as $prop => $value) { if (!\is_array($value) || \count($value) === 0 || !\str_starts_with($prop, $prefix)) { continue; } $maps[\substr($prop, $prefixLen)] = $value; } foreach ($maps as $prop => $value) { $value = \strtr(\var_export($value, \true), [$absoluteVendorPathCode => $vendorPathCode, $absoluteVendorPharPathCode => $vendorPharPathCode, $absoluteAppBaseDirCode => $appBaseDirCode, $absoluteAppBaseDirPharCode => $appBaseDirPharCode]); $value = \ltrim(Preg::replace('/^ */m', ' $0$0', $value)); $file .= \sprintf(" public static \$%s = %s;\n\n", $prop, $value); if ('files' !== $prop) { $initializer .= " \$loader->{$prop} = ComposerStaticInit{$suffix}::\${$prop};\n"; } } return $file . << $packageMap * @param string $type one of: 'psr-0'|'psr-4'|'classmap'|'files' * @return array|array>|array */ protected function parseAutoloadsType(array $packageMap, string $type, RootPackageInterface $rootPackage) { $autoloads = []; foreach ($packageMap as $item) { [$package, $installPath] = $item; // packages that are not installed cannot autoload anything if (null === $installPath) { continue; } $autoload = $package->getAutoload(); if ($this->devMode && $package === $rootPackage) { $autoload = \array_merge_recursive($autoload, $package->getDevAutoload()); } // skip misconfigured packages if (!isset($autoload[$type]) || !\is_array($autoload[$type])) { continue; } if (null !== $package->getTargetDir() && $package !== $rootPackage) { $installPath = \substr($installPath, 0, -\strlen('/' . $package->getTargetDir())); } foreach ($autoload[$type] as $namespace => $paths) { foreach ((array) $paths as $path) { if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath . '/' . $path)) { // remove target-dir from file paths of the root package if ($package === $rootPackage) { $targetDir = \str_replace('\\', '[\\\\/]', \preg_quote(\str_replace(['/', '\\'], '', $package->getTargetDir()))); $path = \ltrim(Preg::replace('{^' . $targetDir . '}', '', \ltrim($path, '\\/')), '\\/'); } else { // add target-dir from file paths that don't have it $path = $package->getTargetDir() . '/' . $path; } } if ($type === 'exclude-from-classmap') { // first escape user input $path = Preg::replace('{/+}', '/', \preg_quote(\trim(\strtr($path, '\\', '/'), '/'))); // add support for wildcards * and ** $path = \strtr($path, ['\\*\\*' => '.+?', '\\*' => '[^/]+?']); // add support for up-level relative paths $updir = null; $path = Preg::replaceCallback('{^((?:(?:\\\\\\.){1,2}+/)+)}', static function ($matches) use(&$updir) : string { if (isset($matches[1])) { // undo preg_quote for the matched string $updir = \str_replace('\\.', '.', $matches[1]); } return ''; }, $path); if (empty($installPath)) { $installPath = \strtr(Platform::getCwd(), '\\', '/'); } $resolvedPath = \realpath($installPath . '/' . $updir); if (\false === $resolvedPath) { continue; } $autoloads[] = \preg_quote(\strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)'; continue; } $relativePath = empty($installPath) ? empty($path) ? '.' : $path : $installPath . '/' . $path; if ($type === 'files') { $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath; continue; } if ($type === 'classmap') { $autoloads[] = $relativePath; continue; } $autoloads[$namespace][] = $relativePath; } } } return $autoloads; } /** * @return string */ protected function getFileIdentifier(PackageInterface $package, string $path) { return \md5($package->getName() . ':' . $path); } /** * Filters out dev-dependencies * * @param array $packageMap * @return array */ protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage) { $packages = []; $include = []; $replacedBy = []; foreach ($packageMap as $item) { $package = $item[0]; $name = $package->getName(); $packages[$name] = $package; foreach ($package->getReplaces() as $replace) { $replacedBy[$replace->getTarget()] = $name; } } $add = static function (PackageInterface $package) use(&$add, $packages, &$include, $replacedBy) : void { foreach ($package->getRequires() as $link) { $target = $link->getTarget(); if (isset($replacedBy[$target])) { $target = $replacedBy[$target]; } if (!isset($include[$target])) { $include[$target] = \true; if (isset($packages[$target])) { $add($packages[$target]); } } } }; $add($rootPackage); return \array_filter($packageMap, static function ($item) use($include) : bool { $package = $item[0]; foreach ($package->getNames() as $name) { if (isset($include[$name])) { return \true; } } return \false; }); } /** * Sorts packages by dependency weight * * Packages of equal weight are sorted alphabetically * * @param array $packageMap * @return array */ protected function sortPackageMap(array $packageMap) { $packages = []; $paths = []; foreach ($packageMap as $item) { [$package, $path] = $item; $name = $package->getName(); $packages[$name] = $package; $paths[$name] = $path; } $sortedPackages = PackageSorter::sortPackages($packages); $sortedPackageMap = []; foreach ($sortedPackages as $package) { $name = $package->getName(); $sortedPackageMap[] = [$packages[$name], $paths[$name]]; } return $sortedPackageMap; } } function composerRequire(string $fileIdentifier, string $file) : void { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = \true; require $file; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var \Closure(string):void */ private static $includeFile; /** @var string|null */ private $vendorDir; // PSR-4 /** * @var array> */ private $prefixLengthsPsr4 = array(); /** * @var array> */ private $prefixDirsPsr4 = array(); /** * @var list */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * List of PSR-0 prefixes * * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) * * @var array>> */ private $prefixesPsr0 = array(); /** * @var list */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = \false; /** * @var array */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = \false; /** * @var array */ private $missingClasses = array(); /** @var string|null */ private $apcuPrefix; /** * @var array */ private static $registeredLoaders = array(); /** * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; self::initializeIncludeClosure(); } /** * @return array> */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return \call_user_func_array('array_merge', \array_values($this->prefixesPsr0)); } return array(); } /** * @return array> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return list */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return list */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return array Array of classname => path */ public function getClassMap() { return $this->classMap; } /** * @param array $classMap Class to filename map * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = \array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param list|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = \false) { $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = \array_merge($paths, $this->fallbackDirsPsr0); } else { $this->fallbackDirsPsr0 = \array_merge($this->fallbackDirsPsr0, $paths); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = \array_merge($paths, $this->prefixesPsr0[$first][$prefix]); } else { $this->prefixesPsr0[$first][$prefix] = \array_merge($this->prefixesPsr0[$first][$prefix], $paths); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = \false) { $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = \array_merge($paths, $this->fallbackDirsPsr4); } else { $this->fallbackDirsPsr4 = \array_merge($this->fallbackDirsPsr4, $paths); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = \strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = \array_merge($paths, $this->prefixDirsPsr4[$prefix]); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = \array_merge($this->prefixDirsPsr4[$prefix], $paths); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param list|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = \strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = \function_exists('apcu_fetch') && \filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = \false) { \spl_autoload_register(array($this, 'loadClass'), \true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { \spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { $includeFile = self::$includeFile; $includeFile($file); return \true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return \false; } if (null !== $this->apcuPrefix) { $file = \apcu_fetch($this->apcuPrefix . $class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (\false === $file && \defined('_ContaoManager\\HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { \apcu_add($this->apcuPrefix . $class, $file); } if (\false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = \true; } return $file; } /** * Returns the currently registered loaders keyed by their corresponding vendor directories. * * @return array */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = \strtr($class, '\\', \DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (\false !== ($lastPos = \strrpos($subPath, '\\'))) { $subPath = \substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = \DIRECTORY_SEPARATOR . \substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (\file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (\file_exists($file = $dir . \DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (\false !== ($pos = \strrpos($class, '\\'))) { // namespaced class name $logicalPathPsr0 = \substr($logicalPathPsr4, 0, $pos + 1) . \strtr(\substr($logicalPathPsr4, $pos + 1), '_', \DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = \strtr($class, '_', \DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === \strpos($class, $prefix)) { foreach ($dirs as $dir) { if (\file_exists($file = $dir . \DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (\file_exists($file = $dir . \DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && ($file = \stream_resolve_include_path($logicalPathPsr0))) { return $file; } return \false; } /** * @return void */ private static function initializeIncludeClosure() { if (self::$includeFile !== null) { return; } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void */ self::$includeFile = \Closure::bind(static function ($file) { include $file; }, null, null); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /* * This file is copied from the Symfony package. * * (c) Fabien Potencier */ namespace Composer\Autoload; use Composer\ClassMapGenerator\FileList; use Composer\IO\IOInterface; /** * ClassMapGenerator * * @author Gyula Sallai * @author Jordi Boggiano * * @deprecated Since Composer 2.4.0 use the composer/class-map-generator package instead */ class ClassMapGenerator { /** * Generate a class map file * * @param \Traversable|array $dirs Directories or a single path to search in * @param string $file The name of the class map file */ public static function dump(iterable $dirs, string $file) : void { $maps = []; foreach ($dirs as $dir) { $maps = \array_merge($maps, static::createMap($dir)); } \file_put_contents($file, \sprintf('|string|array<\SplFileInfo> $path The path to search in or an iterator * @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap * @param ?IOInterface $io IO object * @param null|string $namespace Optional namespace prefix to filter by * @param null|'psr-0'|'psr-4'|'classmap' $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules * @param array $scannedFiles * @return array A class map array * @throws \RuntimeException When the path is neither an existing file nor directory */ public static function createMap($path, ?string $excluded = null, ?IOInterface $io = null, ?string $namespace = null, ?string $autoloadType = null, array &$scannedFiles = []) : array { $generator = new \Composer\ClassMapGenerator\ClassMapGenerator(['php', 'inc', 'hh']); $fileList = new FileList(); $fileList->files = $scannedFiles; $generator->avoidDuplicateScans($fileList); $generator->scanPaths($path, $excluded, $autoloadType ?? 'classmap', $namespace); $classMap = $generator->getClassMap(); $scannedFiles = $fileList->files; if ($io !== null) { foreach ($classMap->getPsrViolations() as $msg) { $io->writeError("{$msg}"); } foreach ($classMap->getAmbiguousClasses() as $class => $paths) { if (\count($paths) > 1) { $io->writeError('Warning: Ambiguous class resolution, "' . $class . '"' . ' was found ' . (\count($paths) + 1) . 'x: in "' . $classMap->getClassPath($class) . '" and "' . \implode('", "', $paths) . '", the first will be used.'); } else { $io->writeError('Warning: Ambiguous class resolution, "' . $class . '"' . ' was found in both "' . $classMap->getClassPath($class) . '" and "' . \implode('", "', $paths) . '", the first will be used.'); } } } return $classMap->getMap(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Repository\PlatformRepository; use Composer\Repository\RootPackageRepository; use Composer\Repository\InstalledRepository; use Composer\Installer\SuggestedPackagesReporter; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; class SuggestsCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; protected function configure() : void { $this->setName('suggests')->setDescription('Shows package suggestions')->setDefinition([new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'), new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'), new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'), new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage())])->setHelp(<<%command.name% command shows a sorted list of suggested packages. Read more at https://getcomposer.org/doc/03-cli.md#suggests EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); $installedRepos = [new RootPackageRepository(clone $composer->getPackage())]; $locker = $composer->getLocker(); if ($locker->isLocked()) { $installedRepos[] = new PlatformRepository([], $locker->getPlatformOverrides()); $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev')); } else { $installedRepos[] = new PlatformRepository([], $composer->getConfig()->get('platform')); $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); } $installedRepo = new InstalledRepository($installedRepos); $reporter = new SuggestedPackagesReporter($this->getIO()); $filter = $input->getArgument('packages'); $packages = $installedRepo->getPackages(); $packages[] = $composer->getPackage(); foreach ($packages as $package) { if (!empty($filter) && !\in_array($package->getName(), $filter)) { continue; } $reporter->addSuggestionsFromPackage($package); } // Determine output mode, default is by-package $mode = SuggestedPackagesReporter::MODE_BY_PACKAGE; // if by-suggestion is given we override the default if ($input->getOption('by-suggestion')) { $mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION; } // unless by-package is also present then we enable both if ($input->getOption('by-package')) { $mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE; } // list is exclusive and overrides everything else if ($input->getOption('list')) { $mode = SuggestedPackagesReporter::MODE_LIST; } $reporter->output($mode, $installedRepo, empty($filter) && !$input->getOption('all') ? $composer->getPackage() : null); return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\Json\JsonFile; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; /** * @author Robert Schönthal */ class SearchCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('search')->setDescription('Searches for packages')->setDefinition([new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'), new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'), new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for')])->setHelp(<<php composer.phar search symfony composer Read more at https://getcomposer.org/doc/03-cli.md#search EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { // init repos $platformRepo = new PlatformRepository(); $io = $this->getIO(); $format = $input->getOption('format'); if (!\in_array($format, ['text', 'json'])) { $io->writeError(\sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } if (!($composer = $this->tryComposer())) { $composer = Factory::create($this->getIO(), [], $input->hasParameterOption('--no-plugins')); } $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository([$localRepo, $platformRepo]); $repos = new CompositeRepository(\array_merge([$installedRepo], $composer->getRepositoryManager()->getRepositories())); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $mode = RepositoryInterface::SEARCH_FULLTEXT; if ($input->getOption('only-name') === \true) { if ($input->getOption('only-vendor') === \true) { throw new \InvalidArgumentException('--only-name and --only-vendor cannot be used together'); } $mode = RepositoryInterface::SEARCH_NAME; } elseif ($input->getOption('only-vendor') === \true) { $mode = RepositoryInterface::SEARCH_VENDOR; } $type = $input->getOption('type'); $query = \implode(' ', $input->getArgument('tokens')); if ($mode !== RepositoryInterface::SEARCH_FULLTEXT) { $query = \preg_quote($query); } $results = $repos->search($query, $mode, $type); if (\count($results) > 0 && $format === 'text') { $width = $this->getTerminalWidth(); $nameLength = 0; foreach ($results as $result) { $nameLength = \max(\strlen($result['name']), $nameLength); } $nameLength += 1; foreach ($results as $result) { $description = $result['description'] ?? ''; $warning = !empty($result['abandoned']) ? '! Abandoned ! ' : ''; $remaining = $width - $nameLength - \strlen($warning) - 2; if (\strlen($description) > $remaining) { $description = \substr($description, 0, $remaining - 3) . '...'; } $link = $result['url'] ?? null; if ($link !== null) { $io->write('' . $result['name'] . '' . \str_repeat(' ', $nameLength - \strlen($result['name'])) . $warning . $description); } else { $io->write(\str_pad($result['name'], $nameLength, ' ') . $warning . $description); } } } elseif ($format === 'json') { $io->write(JsonFile::encode($results)); } return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\Locker; use Composer\Package\Version\VersionBumper; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Factory; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Repository\PlatformRepository; use Composer\Util\Silencer; /** * @author Jordi Boggiano */ final class BumpCommand extends \Composer\Command\BaseCommand { private const ERROR_GENERIC = 1; private const ERROR_LOCK_OUTDATED = 2; use \Composer\Command\CompletionTrait; protected function configure() : void { $this->setName('bump')->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions')->setDefinition([new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()), new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'), new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.')])->setHelp(<<bump command increases the lower limit of your composer.json requirements to the currently installed versions. This helps to ensure your dependencies do not accidentally get downgraded due to some other conflict, and can slightly improve dependency resolution performance as it limits the amount of package versions Composer has to look at. Running this blindly on libraries is **NOT** recommended as it will narrow down your allowed dependencies, which may cause dependency hell for your users. Running it with --dev-only on libraries may be fine however as dev requirements are local to the library and do not affect consumers of the package. EOT ); } /** * @throws \Seld\JsonLint\ParsingException */ protected function execute(InputInterface $input, OutputInterface $output) : int { /** @readonly */ $composerJsonPath = Factory::getComposerFile(); $io = $this->getIO(); if (!Filesystem::isReadable($composerJsonPath)) { $io->writeError('' . $composerJsonPath . ' is not readable.'); return self::ERROR_GENERIC; } $composerJson = new JsonFile($composerJsonPath); $contents = \file_get_contents($composerJson->getPath()); if (\false === $contents) { $io->writeError('' . $composerJsonPath . ' is not readable.'); return self::ERROR_GENERIC; } // check for writability by writing to the file as is_writable can not be trusted on network-mounts // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 if (!\is_writable($composerJsonPath) && \false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) { $io->writeError('' . $composerJsonPath . ' is not writable.'); return self::ERROR_GENERIC; } unset($contents); $composer = $this->requireComposer(); if ($composer->getLocker()->isLocked()) { if (!$composer->getLocker()->isFresh()) { $io->writeError('The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.'); return self::ERROR_LOCK_OUTDATED; } $repo = $composer->getLocker()->getLockedRepository(\true); } else { $repo = $composer->getRepositoryManager()->getLocalRepository(); } if ($composer->getPackage()->getType() !== 'project' && !$input->getOption('dev-only')) { $io->writeError('Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.'); $contents = $composerJson->read(); if (!isset($contents['type'])) { $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); $io->writeError('Alternatively you can use --dev-only to only bump dependencies within "require-dev".'); } unset($contents); } $bumper = new VersionBumper(); $tasks = []; if (!$input->getOption('dev-only')) { $tasks['require'] = $composer->getPackage()->getRequires(); } if (!$input->getOption('no-dev-only')) { $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); } $packagesFilter = $input->getArgument('packages'); if (\count($packagesFilter) > 0) { $pattern = BasePackage::packageNamesToRegexp(\array_unique(\array_map('strtolower', $packagesFilter))); foreach ($tasks as $key => $reqs) { foreach ($reqs as $pkgName => $link) { if (!Preg::isMatch($pattern, $pkgName)) { unset($tasks[$key][$pkgName]); } } } } $updates = []; foreach ($tasks as $key => $reqs) { foreach ($reqs as $pkgName => $link) { if (PlatformRepository::isPlatformPackage($pkgName)) { continue; } $currentConstraint = $link->getPrettyConstraint(); $package = $repo->findPackage($pkgName, '*'); // name must be provided or replaced if (null === $package) { continue; } while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $bumped = $bumper->bumpRequirement($link->getConstraint(), $package); if ($bumped === $currentConstraint) { continue; } $updates[$key][$pkgName] = $bumped; } } $dryRun = $input->getOption('dry-run'); if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) { $composerDefinition = $composerJson->read(); foreach ($updates as $key => $packages) { foreach ($packages as $package => $version) { $composerDefinition[$key][$package] = $version; } } $composerJson->write($composerDefinition); } $changeCount = \array_sum(\array_map('count', $updates)); if ($changeCount > 0) { if ($dryRun) { $io->write('' . $composerJsonPath . ' would be updated with:'); foreach ($updates as $requireType => $packages) { foreach ($packages as $package => $version) { $io->write(\sprintf(' - %s.%s: %s', $requireType, $package, $version)); } } } else { $io->write('' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).'); } } else { $io->write('No requirements to update in ' . $composerJsonPath . '.'); } if (!$dryRun && $composer->getLocker()->isLocked() && $changeCount > 0) { $contents = \file_get_contents($composerJson->getPath()); if (\false === $contents) { throw new \RuntimeException('Unable to read ' . $composerJson->getPath() . ' contents to update the lock file hash.'); } $lock = new JsonFile(Factory::getLockFile($composerJsonPath)); $lockData = $lock->read(); $lockData['content-hash'] = Locker::getContentHash($contents); $lock->write($lockData); } if ($dryRun && $changeCount > 0) { return self::ERROR_GENERIC; } return 0; } /** * @param array<'require'|'require-dev', array> $updates */ private function updateFileCleanly(JsonFile $json, array $updates) : bool { $contents = \file_get_contents($json->getPath()); if (\false === $contents) { throw new \RuntimeException('Unable to read ' . $json->getPath() . ' contents.'); } $manipulator = new JsonManipulator($contents); foreach ($updates as $key => $packages) { foreach ($packages as $package => $version) { if (!$manipulator->addLink($key, $package, $version)) { return \false; } } } if (\false === \file_put_contents($json->getPath(), $manipulator->getContents())) { throw new \RuntimeException('Unable to write new ' . $json->getPath() . ' contents.'); } return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\Composer; use Composer\Package\BasePackage; use Composer\Package\CompletePackageInterface; use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionSelector; use Composer\Pcre\Preg; use Composer\Repository\CompositeRepository; use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositorySet; use Composer\Script\ScriptEvents; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\Filesystem; use Composer\Util\Loop; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Creates an archive of a package for distribution. * * @author Nils Adermann */ class ArchiveCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; private const FORMATS = ['tar', 'tar.gz', 'tar.bz2', 'zip']; protected function configure() : void { $this->setName('archive')->setDescription('Creates an archive of this composer package')->setDefinition([new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()), new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS), new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'), new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.' . ' Note that the format will be appended.'), new InputOption('ignore-filters', null, InputOption::VALUE_NONE, 'Ignore filters when saving package')])->setHelp(<<archive command creates an archive of the specified format containing the files and directories of the Composer project or the specified package in the specified version and writes it to the specified directory. php composer.phar archive [--format=zip] [--dir=/foo] [--file=filename] [package [version]] Read more at https://getcomposer.org/doc/03-cli.md#archive EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->tryComposer(); $config = null; if ($composer) { $config = $composer->getConfig(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); $eventDispatcher = $composer->getEventDispatcher(); $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); } if (!$config) { $config = Factory::createConfig(); } $format = $input->getOption('format') ?? $config->get('archive-format'); $dir = $input->getOption('dir') ?? $config->get('archive-dir'); $returnCode = $this->archive($this->getIO(), $config, $input->getArgument('package'), $input->getArgument('version'), $format, $dir, $input->getOption('file'), $input->getOption('ignore-filters'), $composer); if (0 === $returnCode && $composer) { $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD); } return $returnCode; } /** * @throws \Exception */ protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer) : int { if ($composer) { $archiveManager = $composer->getArchiveManager(); } else { $factory = new Factory(); $process = new ProcessExecutor(); $httpDownloader = Factory::createHttpDownloader($io, $config); $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader, $process); $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader, $process)); } if ($packageName) { $package = $this->selectPackage($io, $packageName, $version); if (!$package) { return 1; } } else { $package = $this->requireComposer()->getPackage(); } $io->writeError('Creating the archive into "' . $dest . '".'); $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters); $fs = new Filesystem(); $shortPath = $fs->findShortestPath(Platform::getCwd(), $packagePath, \true); $io->writeError('Created: ', \false); $io->write(\strlen($shortPath) < \strlen($packagePath) ? $shortPath : $packagePath); return 0; } /** * @return (BasePackage&CompletePackageInterface)|false */ protected function selectPackage(IOInterface $io, string $packageName, ?string $version = null) { $io->writeError('Searching for the specified package.'); if ($composer = $this->tryComposer()) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $repo = new CompositeRepository(\array_merge([$localRepo], $composer->getRepositoryManager()->getRepositories())); $minStability = $composer->getPackage()->getMinimumStability(); } else { $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $io->writeError('No composer.json found in the current directory, searching packages from ' . \implode(', ', \array_keys($defaultRepos))); $repo = new CompositeRepository($defaultRepos); $minStability = 'stable'; } if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) { $minStability = $match[1]; $version = (string) \substr($version, 0, -\strlen($match[0])); } $repoSet = new RepositorySet($minStability); $repoSet->addRepository($repo); $parser = new VersionParser(); $constraint = $version !== null ? $parser->parseConstraints($version) : null; $packages = $repoSet->findPackages(\strtolower($packageName), $constraint); if (\count($packages) > 1) { $versionSelector = new VersionSelector($repoSet); $package = $versionSelector->findBestCandidate(\strtolower($packageName), $version, $minStability); if ($package === \false) { $package = \reset($packages); } $io->writeError('Found multiple matches, selected ' . $package->getPrettyString() . '.'); $io->writeError('Alternatives were ' . \implode(', ', \array_map(static function ($p) : string { return $p->getPrettyString(); }, $packages)) . '.'); $io->writeError('Please use a more specific constraint to pick a different package.'); } elseif (\count($packages) === 1) { $package = \reset($packages); $io->writeError('Found an exact match ' . $package->getPrettyString() . '.'); } else { $io->writeError('Could not find a package matching ' . $packageName . '.'); return \false; } if (!$package instanceof CompletePackageInterface) { throw new \LogicException('Expected a CompletePackageInterface instance but found ' . \get_class($package)); } if (!$package instanceof BasePackage) { throw new \LogicException('Expected a BasePackage instance but found ' . \get_class($package)); } return $package; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Repository\RepositorySet; use Composer\Repository\RepositoryUtils; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepository; use Composer\Advisory\Auditor; use Composer\Console\Input\InputOption; class AuditCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('audit')->setDescription('Checks for security vulnerability advisories for installed packages')->setDefinition([new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS), new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.')])->setHelp(<<audit command checks for security vulnerability advisories for installed packages. If you do not want to include dev dependencies in the audit you can omit them with --no-dev Read more at https://getcomposer.org/doc/03-cli.md#audit EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); $packages = $this->getPackages($composer, $input); if (\count($packages) === 0) { $this->getIO()->writeError('No packages - skipping audit.'); return 0; } $auditor = new Auditor(); $repoSet = new RepositorySet(); foreach ($composer->getRepositoryManager()->getRepositories() as $repo) { $repoSet->addRepository($repo); } $auditConfig = $composer->getConfig()->get('audit'); return \min(255, $auditor->audit($this->getIO(), $repoSet, $packages, $this->getAuditFormat($input, 'format'), \false, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL)); } /** * @return PackageInterface[] */ private function getPackages(Composer $composer, InputInterface $input) : array { if ($input->getOption('locked')) { if (!$composer->getLocker()->isLocked()) { throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked'); } $locker = $composer->getLocker(); return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); } $rootPkg = $composer->getPackage(); $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]); if ($input->getOption('no-dev')) { return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg); } return $installedRepo->getPackages(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\InstalledRepository; use Composer\Repository\PlatformRepository; use Composer\Util\ConfigValidator; use Composer\Util\Filesystem; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * ValidateCommand * * @author Robert Schönthal * @author Jordi Boggiano */ class ValidateCommand extends \Composer\Command\BaseCommand { /** * configure */ protected function configure() : void { $this->setName('validate')->setDescription('Validates a composer.json and composer.lock')->setDefinition([new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'), new InputOption('check-lock', null, InputOption::VALUE_NONE, 'Check if lock file is up to date (even when config.lock is false)'), new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputOption('no-check-version', null, InputOption::VALUE_NONE, 'Do not report a warning if the version field is present'), new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file')])->setHelp(<<getArgument('file') ?: Factory::getComposerFile(); $io = $this->getIO(); if (!\file_exists($file)) { $io->writeError('' . $file . ' not found.'); return 3; } if (!Filesystem::isReadable($file)) { $io->writeError('' . $file . ' is not readable.'); return 3; } $validator = new ConfigValidator($io); $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; $checkPublish = !$input->getOption('no-check-publish'); $checkLock = !$input->getOption('no-check-lock'); $checkVersion = $input->getOption('no-check-version') ? 0 : ConfigValidator::CHECK_VERSION; $isStrict = $input->getOption('strict'); [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); $lockErrors = []; $composer = Factory::create($io, $file, $input->hasParameterOption('--no-plugins')); // config.lock = false ~= implicit --no-check-lock; --check-lock overrides $checkLock = $checkLock && $composer->getConfig()->get('lock') || $input->getOption('check-lock'); $locker = $composer->getLocker(); if ($locker->isLocked() && !$locker->isFresh()) { $lockErrors[] = '- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update `.'; } if ($locker->isLocked()) { $lockErrors = \array_merge($lockErrors, $locker->getMissingRequirementInfo($composer->getPackage(), \true)); } $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, \true); // $errors include publish and lock errors when exists $exitCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0); if ($input->getOption('with-dependencies')) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); foreach ($localRepo->getPackages() as $package) { $path = $composer->getInstallationManager()->getInstallPath($package); if (null === $path) { continue; } $file = $path . '/composer.json'; if (\is_dir($path) && \file_exists($file)) { [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); // $errors include publish errors when exists $depCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0); $exitCode = \max($depCode, $exitCode); } } } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output); $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); return \max($eventCode, $exitCode); } /** * @param string[] $errors * @param string[] $warnings * @param string[] $publishErrors * @param string[] $lockErrors */ private function outputResult(IOInterface $io, string $name, array &$errors, array &$warnings, bool $checkPublish = \false, array $publishErrors = [], bool $checkLock = \false, array $lockErrors = [], bool $printSchemaUrl = \false) : void { $doPrintSchemaUrl = \false; if ($errors) { $io->writeError('' . $name . ' is invalid, the following errors/warnings were found:'); } elseif ($publishErrors) { $io->writeError('' . $name . ' is valid for simple usage with Composer but has'); $io->writeError('strict errors that make it unable to be published as a package'); $doPrintSchemaUrl = $printSchemaUrl; } elseif ($warnings) { $io->writeError('' . $name . ' is valid, but with a few warnings'); $doPrintSchemaUrl = $printSchemaUrl; } elseif ($lockErrors) { $io->write('' . $name . ' is valid but your composer.lock has some ' . ($checkLock ? 'errors' : 'warnings') . ''); } else { $io->write('' . $name . ' is valid'); } if ($doPrintSchemaUrl) { $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); } if ($errors) { $errors = \array_map(static function ($err) : string { return '- ' . $err; }, $errors); \array_unshift($errors, '# General errors'); } if ($warnings) { $warnings = \array_map(static function ($err) : string { return '- ' . $err; }, $warnings); \array_unshift($warnings, '# General warnings'); } // Avoid setting the exit code to 1 in case --strict and --no-check-publish/--no-check-lock are combined $extraWarnings = []; // If checking publish errors, display them as errors, otherwise just show them as warnings if ($publishErrors) { $publishErrors = \array_map(static function ($err) : string { return '- ' . $err; }, $publishErrors); if ($checkPublish) { \array_unshift($publishErrors, '# Publish errors'); $errors = \array_merge($errors, $publishErrors); } else { \array_unshift($publishErrors, '# Publish warnings'); $extraWarnings = \array_merge($extraWarnings, $publishErrors); } } // If checking lock errors, display them as errors, otherwise just show them as warnings if ($lockErrors) { if ($checkLock) { \array_unshift($lockErrors, '# Lock file errors'); $errors = \array_merge($errors, $lockErrors); } else { \array_unshift($lockErrors, '# Lock file warnings'); $extraWarnings = \array_merge($extraWarnings, $lockErrors); } } $messages = ['error' => $errors, 'warning' => \array_merge($warnings, $extraWarnings)]; foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { if (\strpos($msg, '#') === 0) { $io->writeError('<' . $style . '>' . $msg . ''); } else { $io->writeError($msg); } } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Json\JsonFile; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\CompletePackageInterface; use Composer\Pcre\Preg; use Composer\Repository\CompositeRepository; use Composer\Semver\Constraint\MatchAllConstraint; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Nicolas Grekas * @author Jordi Boggiano */ class FundCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('fund')->setDescription('Discover how to help fund the maintenance of your dependencies')->setDefinition([new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json'])]); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); $repo = $composer->getRepositoryManager()->getLocalRepository(); $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $fundings = []; $packagesToLoad = []; foreach ($repo->getPackages() as $package) { if ($package instanceof AliasPackage) { continue; } $packagesToLoad[$package->getName()] = new MatchAllConstraint(); } // load all packages dev versions in parallel $result = $remoteRepos->loadPackages($packagesToLoad, ['dev' => BasePackage::STABILITY_DEV], []); // collect funding data from default branches foreach ($result['packages'] as $package) { if (!$package instanceof AliasPackage && $package instanceof CompletePackageInterface && $package->isDefaultBranch() && $package->getFunding() && isset($packagesToLoad[$package->getName()])) { $fundings = $this->insertFundingData($fundings, $package); unset($packagesToLoad[$package->getName()]); } } // collect funding from installed packages if none was found in the default branch above foreach ($repo->getPackages() as $package) { if ($package instanceof AliasPackage || !isset($packagesToLoad[$package->getName()])) { continue; } if ($package instanceof CompletePackageInterface && $package->getFunding()) { $fundings = $this->insertFundingData($fundings, $package); } } \ksort($fundings); $io = $this->getIO(); $format = $input->getOption('format'); if (!\in_array($format, ['text', 'json'])) { $io->writeError(\sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } if ($fundings && $format === 'text') { $prev = null; $io->write('The following packages were found in your dependencies which publish funding information:'); foreach ($fundings as $vendor => $links) { $io->write(''); $io->write(\sprintf("%s", $vendor)); foreach ($links as $url => $packages) { $line = \sprintf(' %s', \implode(', ', $packages)); if ($prev !== $line) { $io->write($line); $prev = $line; } $io->write(\sprintf(' %s', OutputFormatter::escape($url), $url)); } } $io->write(""); $io->write("Please consider following these links and sponsoring the work of package authors!"); $io->write("Thank you!"); } elseif ($format === 'json') { $io->write(JsonFile::encode($fundings)); } else { $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!"); } return 0; } /** * @param mixed[] $fundings * @return mixed[] */ private function insertFundingData(array $fundings, CompletePackageInterface $package) : array { foreach ($package->getFunding() as $fundingOption) { [$vendor, $packageName] = \explode('/', $package->getPrettyName()); // ignore malformed funding entries if (empty($fundingOption['url'])) { continue; } $url = $fundingOption['url']; if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && Preg::isMatch('{^https://github.com/([^/]+)$}', $url, $match)) { $url = 'https://github.com/sponsors/' . $match[1]; } $fundings[$vendor][$url][] = $packageName; } return $fundings; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\IO\IOInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionSelector; use Composer\Pcre\Preg; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; use Composer\Util\Filesystem; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @internal */ trait PackageDiscoveryTrait { /** @var ?CompositeRepository */ private $repos; /** @var RepositorySet[] */ private $repositorySets; protected function getRepos() : CompositeRepository { if (null === $this->repos) { $this->repos = new CompositeRepository(\array_merge([new PlatformRepository()], RepositoryFactory::defaultReposWithDefaultManager($this->getIO()))); } return $this->repos; } private function getRepositorySet(InputInterface $input, ?string $minimumStability = null) : RepositorySet { $key = $minimumStability ?? 'default'; if (!isset($this->repositorySets[$key])) { $this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input)); $repositorySet->addRepository($this->getRepos()); } return $this->repositorySets[$key]; } private function getMinimumStability(InputInterface $input) : string { if ($input->hasOption('stability')) { // @phpstan-ignore-line as InitCommand does have this option but not all classes using this trait do return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable'); } // @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there $file = Factory::getComposerFile(); if (\is_file($file) && Filesystem::isReadable($file) && \is_array($composer = \json_decode((string) \file_get_contents($file), \true))) { if (isset($composer['minimum-stability'])) { return VersionParser::normalizeStability($composer['minimum-stability']); } } return 'stable'; } /** * @param array $requires * * @return array * @throws \Exception */ protected final function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = [], ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $useBestVersionConstraint = \true, bool $fixed = \false) : array { if (\count($requires) > 0) { $requires = $this->normalizeRequirements($requires); $result = []; $io = $this->getIO(); foreach ($requires as $requirement) { if (isset($requirement['version']) && Preg::isMatch('{^\\d+(\\.\\d+)?$}', $requirement['version'])) { $io->writeError('The "' . $requirement['version'] . '" constraint for "' . $requirement['name'] . '" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints'); } if (!isset($requirement['version'])) { // determine the best version automatically [$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed); // replace package name from packagist.org $requirement['name'] = $name; if ($useBestVersionConstraint) { $requirement['version'] = $version; $io->writeError(\sprintf('Using version %s for %s', $requirement['version'], $requirement['name'])); } else { $requirement['version'] = 'guess'; } } $result[] = $requirement['name'] . ' ' . $requirement['version']; } return $result; } $versionParser = new VersionParser(); // Collect existing packages $composer = $this->tryComposer(); $installedRepo = null; if (null !== $composer) { $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); } $existingPackages = []; if (null !== $installedRepo) { foreach ($installedRepo->getPackages() as $package) { $existingPackages[] = $package->getName(); } } unset($composer, $installedRepo); $io = $this->getIO(); while (null !== ($package = $io->ask('Search for a package: '))) { $matches = $this->getRepos()->search($package); if (\count($matches) > 0) { // Remove existing packages from search results. foreach ($matches as $position => $foundPackage) { if (\in_array($foundPackage['name'], $existingPackages, \true)) { unset($matches[$position]); } } $matches = \array_values($matches); $exactMatch = \false; foreach ($matches as $match) { if ($match['name'] === $package) { $exactMatch = \true; break; } } // no match, prompt which to pick if (!$exactMatch) { $providers = $this->getRepos()->getProviders($package); if (\count($providers) > 0) { \array_unshift($matches, ['name' => $package, 'description' => '']); } $choices = []; foreach ($matches as $position => $foundPackage) { $abandoned = ''; if (isset($foundPackage['abandoned'])) { if (\is_string($foundPackage['abandoned'])) { $replacement = \sprintf('Use %s instead', $foundPackage['abandoned']); } else { $replacement = 'No replacement was suggested'; } $abandoned = \sprintf('Abandoned. %s.', $replacement); } $choices[] = \sprintf(' %5s %s %s', "[{$position}]", $foundPackage['name'], $abandoned); } $io->writeError(['', \sprintf('Found %s packages matching %s', \count($matches), $package), '']); $io->writeError($choices); $io->writeError(''); $validator = static function (string $selection) use($matches, $versionParser) { if ('' === $selection) { return \false; } if (\is_numeric($selection) && isset($matches[(int) $selection])) { $package = $matches[(int) $selection]; return $package['name']; } if (Preg::isMatch('{^\\s*(?P[\\S/]+)(?:\\s+(?P\\S+))?\\s*$}', $selection, $packageMatches)) { if (isset($packageMatches['version'])) { // parsing `acme/example ~2.3` // validate version constraint $versionParser->parseConstraints($packageMatches['version']); return $packageMatches['name'] . ' ' . $packageMatches['version']; } // parsing `acme/example` return $packageMatches['name']; } throw new \Exception('Not a valid selection'); }; $package = $io->askAndValidate('Enter package # to add, or the complete package name if it is not listed: ', $validator, 3, ''); } // no constraint yet, determine the best version automatically if (\false !== $package && \false === \strpos($package, ' ')) { $validator = static function (string $input) { $input = \trim($input); return \strlen($input) > 0 ? $input : \false; }; $constraint = $io->askAndValidate('Enter the version constraint to require (or leave blank to use the latest version): ', $validator, 3, ''); if (\false === $constraint) { [, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability); $io->writeError(\sprintf('Using version %s for %s', $constraint, $package)); } $package .= ' ' . $constraint; } if (\false !== $package) { $requires[] = $package; $existingPackages[] = \explode(' ', $package)[0]; } } } return $requires; } /** * Given a package name, this determines the best version to use in the require key. * * This returns a version with the ~ operator prefixed when possible. * * @throws \InvalidArgumentException * @return array{string, string} name version */ private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = \false) : array { // handle ignore-platform-reqs flag if present if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) { $platformRequirementFilter = $this->getPlatformRequirementFilter($input); } else { $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); } // find the latest version allowed in this repo set $repoSet = $this->getRepositorySet($input); $versionSelector = new VersionSelector($repoSet, $platformRepo); $effectiveMinimumStability = $this->getMinimumStability($input); $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, 0, $this->getIO()); if (\false === $package) { // platform packages can not be found in the pool in versions other than the local platform's has // so if platform reqs are ignored we just take the user's word for it if ($platformRequirementFilter->isIgnored($name)) { return [$name, '*']; } // Check if it is a virtual package provided by others $providers = $repoSet->getProviders($name); if (\count($providers) > 0) { $constraint = '*'; if ($input->isInteractive()) { $constraint = $this->getIO()->askAndValidate('Package "' . $name . '" does not exist but is provided by ' . \count($providers) . ' packages. Which version constraint would you like to use? [*] ', static function ($value) { $parser = new VersionParser(); $parser->parseConstraints($value); return $value; }, 3, '*'); } return [$name, $constraint]; } // Check whether the package requirements were the problem if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && \false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) { throw new \InvalidArgumentException(\sprintf('Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo), $name)); } // Check whether the minimum stability was the problem but the package exists if (\false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { // we must first verify if a valid package would be found in a lower priority repository if (\false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { throw new \InvalidArgumentException('Package ' . $name . ' exists in ' . $allReposPackage->getRepository()->getRepoName() . ' and ' . $package->getRepository()->getRepoName() . ' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'); } throw new \InvalidArgumentException(\sprintf('Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.', $name, $effectiveMinimumStability)); } // Check whether the PHP version was the problem for all versions if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && \false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { $additional = ''; if (\false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) { $additional = \PHP_EOL . \PHP_EOL . 'Additionally, the package was only found with a stability of "' . $candidate->getStability() . '" while your minimum stability is "' . $effectiveMinimumStability . '".'; } throw new \InvalidArgumentException(\sprintf('Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s', $name, $additional)); } // Check for similar names/typos $similar = $this->findSimilar($name); if (\count($similar) > 0) { if (\in_array($name, $similar, \true)) { throw new \InvalidArgumentException(\sprintf("Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.", $name)); } if ($input->isInteractive()) { $result = $io->select("Could not find package {$name}.\nPick one of these or leave empty to abort:", $similar, \false, 1); if ($result !== \false) { return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed); } } throw new \InvalidArgumentException(\sprintf("Could not find package %s.\n\nDid you mean " . (\count($similar) > 1 ? 'one of these' : 'this') . "?\n %s", $name, \implode("\n ", $similar))); } throw new \InvalidArgumentException(\sprintf('Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).', $name, $effectiveMinimumStability)); } return [$package->getPrettyName(), $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package)]; } /** * @return array */ private function findSimilar(string $package) : array { try { if (null === $this->repos) { throw new \LogicException('findSimilar was called before $this->repos was initialized'); } $results = $this->repos->search($package); } catch (\Throwable $e) { if ($e instanceof \LogicException) { throw $e; } // ignore search errors return []; } $similarPackages = []; $installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository(); foreach ($results as $result) { if (null !== $installedRepo->findPackage($result['name'], '*')) { // Ignore installed package continue; } $similarPackages[$result['name']] = \levenshtein($package, $result['name']); } \asort($similarPackages); return \array_keys(\array_slice($similarPackages, 0, 5)); } private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null) : string { $details = []; if (null === $platformRepo) { return ''; } foreach ($candidate->getRequires() as $link) { if (!PlatformRepository::isPlatformPackage($link->getTarget())) { continue; } $platformPkg = $platformRepo->findPackage($link->getTarget(), '*'); if (null === $platformPkg) { if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) { $details[] = $candidate->getPrettyName() . ' ' . $candidate->getPrettyVersion() . ' requires ' . $link->getTarget() . ' ' . $link->getPrettyConstraint() . ' but it is disabled by your platform config. Enable it again with "composer config platform.' . $link->getTarget() . ' --unset".'; } else { $details[] = $candidate->getPrettyName() . ' ' . $candidate->getPrettyVersion() . ' requires ' . $link->getTarget() . ' ' . $link->getPrettyConstraint() . ' but it is not present.'; } continue; } if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) { $platformPkgVersion = $platformPkg->getPrettyVersion(); $platformExtra = $platformPkg->getExtra(); if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) { $platformPkgVersion .= ' (' . $platformPkg->getDescription() . ')'; } $details[] = $candidate->getPrettyName() . ' ' . $candidate->getPrettyVersion() . ' requires ' . $link->getTarget() . ' ' . $link->getPrettyConstraint() . ' which does not match your installed version ' . $platformPkgVersion . '.'; } } if (\count($details) === 0) { return ''; } return ':' . \PHP_EOL . ' - ' . \implode(\PHP_EOL . ' - ', $details); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class ScriptAliasCommand extends \Composer\Command\BaseCommand { /** @var string */ private $script; /** @var string */ private $description; /** @var string[] */ private $aliases; /** * @param string[] $aliases */ public function __construct(string $script, ?string $description, array $aliases = []) { $this->script = $script; $this->description = $description ?? 'Runs the ' . $script . ' script as defined in composer.json'; $this->aliases = $aliases; foreach ($this->aliases as $alias) { if (!\is_string($alias)) { throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings'); } } $this->ignoreValidationErrors(); parent::__construct(); } protected function configure() : void { $this->setName($this->script)->setDescription($this->description)->setAliases($this->aliases)->setDefinition([new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, '')])->setHelp(<<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd Read more at https://getcomposer.org/doc/03-cli.md#run-script-run EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); $args = $input->getArguments(); // TODO remove for Symfony 6+ as it is then in the interface if (!\method_exists($input, '__toString')) { // @phpstan-ignore-line throw new \LogicException('Expected an Input instance that is stringable, got ' . \get_class($input)); } return $composer->getEventDispatcher()->dispatchScript($this->script, $input->getOption('dev') || !$input->getOption('no-dev'), $args['args'], ['script-alias-input' => Preg::replace('{^\\S+ ?}', '', $input->__toString(), 1)]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Config; use Composer\Factory; use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\Installer; use Composer\Installer\ProjectInstaller; use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; use Composer\Package\BasePackage; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\Package\Version\VersionSelector; use Composer\Package\AliasPackage; use Composer\Pcre\Preg; use Composer\Plugin\PluginBlockedException; use Composer\Repository\RepositoryFactory; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\RepositorySet; use Composer\Script\ScriptEvents; use Composer\Util\Silencer; use Composer\Console\Input\InputArgument; use _ContaoManager\Seld\Signal\SignalHandler; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Finder\Finder; use Composer\Json\JsonFile; use Composer\Config\JsonConfigSource; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Package\Version\VersionParser; use Composer\Advisory\Auditor; /** * Install a package as new project into new directory. * * @author Benjamin Eberlei * @author Jordi Boggiano * @author Tobias Munk * @author Nils Adermann */ class CreateProjectCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; /** * @var SuggestedPackagesReporter */ protected $suggestedPackagesReporter; protected function configure() : void { $this->setName('create-project')->setDescription('Creates new project from a package into given directory')->setDefinition([new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()), new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'), new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'), new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'), new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json" or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.')])->setHelp(<<create-project command creates a new project from a given package into a new directory. If executed without params and in a directory with a composer.json file it installs the packages for the current project. You can use this command to bootstrap new projects or setup a clean version-controlled installation for developers of your project. php composer.phar create-project vendor/project target-directory [version] You can also specify the version with the package name using = or : as separator. php composer.phar create-project vendor/project:version target-directory To install unstable packages, either specify the version you want, or use the --stability=dev (where dev can be one of RC, beta, alpha or dev). To setup a developer workable version you should create the project using the source controlled code by appending the '--prefer-source' flag. To install a package from another repository than the default one you can pass the '--repository=https://myrepository.org' flag. Read more at https://getcomposer.org/doc/03-cli.md#create-project EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $config = Factory::createConfig(); $io = $this->getIO(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input, \true); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } if ($input->getOption('no-custom-installers')) { $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', \true); } if ($input->isInteractive() && $input->getOption('ask')) { $package = $input->getArgument('package'); if (null === $package) { throw new \RuntimeException('Not enough arguments (missing: "package").'); } $parts = \explode("/", \strtolower($package), 2); $input->setArgument('directory', $io->ask('New project directory [' . \array_pop($parts) . ']: ')); } return $this->installProject($io, $config, $input, $input->getArgument('package'), $input->getArgument('directory'), $input->getArgument('version'), $input->getOption('stability'), $preferSource, $preferDist, !$input->getOption('no-dev'), \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'), $input->getOption('no-plugins'), $input->getOption('no-scripts'), $input->getOption('no-progress'), $input->getOption('no-install'), $this->getPlatformRequirementFilter($input), !$input->getOption('no-secure-http'), $input->getOption('add-repository')); } /** * @param string|array|null $repositories * * @throws \Exception */ public function installProject(IOInterface $io, Config $config, InputInterface $input, ?string $packageName = null, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = \false, bool $preferDist = \false, bool $installDevPackages = \false, $repositories = null, bool $disablePlugins = \false, bool $disableScripts = \false, bool $noProgress = \false, bool $noInstall = \false, ?PlatformRequirementFilterInterface $platformRequirementFilter = null, bool $secureHttp = \true, bool $addRepository = \false) : int { $oldCwd = Platform::getCwd(); if ($repositories !== null && !\is_array($repositories)) { $repositories = (array) $repositories; } $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); // we need to manually load the configuration to pass the auth credentials to the io interface! $io->loadConfiguration($config); $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); if ($packageName !== null) { $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp); } else { $installedFromVcs = \false; } if ($repositories !== null && $addRepository && \is_file('composer.lock')) { \unlink('composer.lock'); } $composer = Factory::create($io, null, $disablePlugins, $disableScripts); // add the repository to the composer.json and use it for the install run later if ($repositories !== null && $addRepository) { foreach ($repositories as $index => $repo) { $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repo, \true); $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories(); $name = RepositoryFactory::generateRepositoryName($index, $repoConfig, $composerJsonRepositoriesConfig); $configSource = new JsonConfigSource(new JsonFile('composer.json')); if (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => \false] || isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => \false]) { $configSource->addRepository('packagist.org', \false); } else { $configSource->addRepository($name, $repoConfig, \false); } $composer = Factory::create($io, null, $disablePlugins); } } $process = $composer->getLoop()->getProcessExecutor(); $fs = new Filesystem($process); // dispatch event $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); // use the new config including the newly installed project $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); // install dependencies of the created project if ($noInstall === \false) { $composer->getInstallationManager()->setOutputProgress(!$noProgress); $installer = Installer::create($io, $composer); $installer->setPreferSource($preferSource)->setPreferDist($preferDist)->setDevMode($installDevPackages)->setPlatformRequirementFilter($platformRequirementFilter)->setSuggestedPackagesReporter($this->suggestedPackagesReporter)->setOptimizeAutoloader($config->get('optimize-autoloader'))->setClassMapAuthoritative($config->get('classmap-authoritative'))->setApcuAutoloader($config->get('apcu-autoloader'))->setAudit(!$input->getOption('no-audit'))->setAuditFormat($this->getAuditFormat($input)); if (!$composer->getLocker()->isLocked()) { $installer->setUpdate(\true); } if ($disablePlugins) { $installer->disablePlugins(); } try { $status = $installer->run(); if (0 !== $status) { return $status; } } catch (PluginBlockedException $e) { $io->writeError('Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.'); $io->writeError('You can then cd into ' . \getcwd() . ', configure allow-plugins, and finally run a composer install to complete the process.'); throw $e; } } $hasVcs = $installedFromVcs; if (!$input->getOption('keep-vcs') && $installedFromVcs && ($input->getOption('remove-vcs') || !$io->isInteractive() || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? '))) { $finder = new Finder(); $finder->depth(0)->directories()->in(Platform::getCwd())->ignoreVCS(\false)->ignoreDotFiles(\false); foreach (['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_'] as $vcsName) { $finder->name($vcsName); } try { $dirs = \iterator_to_array($finder); unset($finder); foreach ($dirs as $dir) { if (!$fs->removeDirectory((string) $dir)) { throw new \RuntimeException('Could not remove ' . $dir); } } } catch (\Exception $e) { $io->writeError('An error occurred while removing the VCS metadata: ' . $e->getMessage() . ''); } $hasVcs = \false; } // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone if (!$hasVcs) { $package = $composer->getPackage(); $configSource = new JsonConfigSource(new JsonFile('composer.json')); foreach (BasePackage::$supportedLinkTypes as $type => $meta) { foreach ($package->{'get' . $meta['method']}() as $link) { if ($link->getPrettyConstraint() === 'self.version') { $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion()); } } } } // dispatch event $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); \chdir($oldCwd); $vendorComposerDir = $config->get('vendor-dir') . '/composer'; if (\is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) { Silencer::call('rmdir', $vendorComposerDir); $vendorDir = $config->get('vendor-dir'); if (\is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) { Silencer::call('rmdir', $vendorDir); } } return 0; } /** * @param array|null $repositories * * @throws \Exception */ protected function installRootPackage(IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = \false, bool $preferDist = \false, bool $installDevPackages = \false, ?array $repositories = null, bool $disablePlugins = \false, bool $disableScripts = \false, bool $noProgress = \false, bool $secureHttp = \true) : bool { if (!$secureHttp) { $config->merge(['config' => ['secure-http' => \false]], Config::SOURCE_COMMAND); } $parser = new VersionParser(); $requirements = $parser->parseNameVersionPairs([$packageName]); $name = \strtolower($requirements[0]['name']); if (!$packageVersion && isset($requirements[0]['version'])) { $packageVersion = $requirements[0]['version']; } // if no directory was specified, use the 2nd part of the package name if (null === $directory) { $parts = \explode("/", $name, 2); $directory = Platform::getCwd() . \DIRECTORY_SEPARATOR . \array_pop($parts); } $process = new ProcessExecutor($io); $fs = new Filesystem($process); if (!$fs->isAbsolutePath($directory)) { $directory = Platform::getCwd() . \DIRECTORY_SEPARATOR . $directory; } $io->writeError('Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, \true) . '"'); if (\file_exists($directory)) { if (!\is_dir($directory)) { throw new \InvalidArgumentException('Cannot create project directory at "' . $directory . '", it exists as a file.'); } if (!$fs->isDirEmpty($directory)) { throw new \InvalidArgumentException('Project directory "' . $directory . '" is not empty.'); } } if (null === $stability) { if (null === $packageVersion) { $stability = 'stable'; } elseif (Preg::isMatchStrictGroups('{^[^,\\s]*?@(' . \implode('|', \array_keys(BasePackage::$stabilities)) . ')$}i', $packageVersion, $match)) { $stability = $match[1]; } else { $stability = VersionParser::parseStability($packageVersion); } } $stability = VersionParser::normalizeStability($stability); if (!isset(BasePackage::$stabilities[$stability])) { throw new \InvalidArgumentException('Invalid stability provided (' . $stability . '), must be one of: ' . \implode(', ', \array_keys(BasePackage::$stabilities))); } $composer = Factory::create($io, $config->all(), $disablePlugins, $disableScripts); $config = $composer->getConfig(); $rm = $composer->getRepositoryManager(); $repositorySet = new RepositorySet($stability); if (null === $repositories) { $repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm))); } else { foreach ($repositories as $repo) { $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, \true); if (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => \false] || isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => \false]) { continue; } $repositorySet->addRepository(RepositoryFactory::createRepo($io, $config, $repoConfig, $rm)); } } $platformOverrides = $config->get('platform'); $platformRepo = new PlatformRepository([], $platformOverrides); // find the latest version if there are multiple $versionSelector = new VersionSelector($repositorySet, $platformRepo); $package = $versionSelector->findBestCandidate($name, $packageVersion, $stability, $platformRequirementFilter, 0, $io); if (!$package) { $errorMessage = "Could not find package {$name} with " . ($packageVersion ? "version {$packageVersion}" : "stability {$stability}"); if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && $versionSelector->findBestCandidate($name, $packageVersion, $stability, PlatformRequirementFilterFactory::ignoreAll())) { throw new \InvalidArgumentException($errorMessage . ' in a version installable using your PHP version, PHP extensions and Composer version.'); } throw new \InvalidArgumentException($errorMessage . '.'); } // handler Ctrl+C aborts gracefully @\mkdir($directory, 0777, \true); if (\false !== ($realDir = \realpath($directory))) { $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use($realDir) { $this->getIO()->writeError('Received ' . $signal . ', aborting', \true, IOInterface::DEBUG); $fs = new Filesystem(); $fs->removeDirectory($realDir); $handler->exitWithLastSignal(); }); } // avoid displaying 9999999-dev as version if default-branch was selected if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(\false) . ')'); if ($disablePlugins) { $io->writeError('Plugins have been disabled.'); } if ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $dm = $composer->getDownloadManager(); $dm->setPreferSource($preferSource)->setPreferDist($preferDist); $projectInstaller = new ProjectInstaller($directory, $dm, $fs); $im = $composer->getInstallationManager(); $im->setOutputProgress(!$noProgress); $im->addInstaller($projectInstaller); $im->execute(new InstalledArrayRepository(), [new InstallOperation($package)]); $im->notifyInstalls($io); // collect suggestions $this->suggestedPackagesReporter->addSuggestionsFromPackage($package); $installedFromVcs = 'source' === $package->getInstallationSource(); $io->writeError('Created project in ' . $directory . ''); \chdir($directory); // ensure that the env var being set does not interfere with create-project // as it is probably not meant to be used here, so we do not use it if a composer.json can be found // in the project if (\file_exists($directory . '/composer.json') && Platform::getEnv('COMPOSER') !== \false) { Platform::clearEnv('COMPOSER'); } Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion()); // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install if (isset($signalHandler)) { $signalHandler->unregister(); } return $installedFromVcs; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Config; use Composer\Console\Application; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; use Composer\Factory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\IO\IOInterface; use Composer\IO\NullIO; use Composer\Plugin\PreCommandRunEvent; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginEvents; use Composer\Advisory\Auditor; use Composer\Util\Platform; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Helper\TableSeparator; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Terminal; /** * Base class for Composer commands * * @author Ryan Weaver * @author Konstantin Kudryashov */ abstract class BaseCommand extends Command { /** * @var Composer|null */ private $composer; /** * @var IOInterface */ private $io; /** * Gets the application instance for this command. */ public function getApplication() : Application { $application = parent::getApplication(); if (!$application instanceof Application) { throw new \RuntimeException('Composer commands can only work with an ' . Application::class . ' instance set'); } return $application; } /** * @param bool $required Should be set to false, or use `requireComposer` instead * @param bool|null $disablePlugins If null, reads --no-plugins as default * @param bool|null $disableScripts If null, reads --no-scripts as default * @throws \RuntimeException * @return Composer|null * @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false */ public function getComposer(bool $required = \true, ?bool $disablePlugins = null, ?bool $disableScripts = null) { if ($required) { return $this->requireComposer($disablePlugins, $disableScripts); } return $this->tryComposer($disablePlugins, $disableScripts); } /** * Retrieves the default Composer\Composer instance or throws * * Use this instead of getComposer if you absolutely need an instance * * @param bool|null $disablePlugins If null, reads --no-plugins as default * @param bool|null $disableScripts If null, reads --no-scripts as default * @throws \RuntimeException */ public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null) : Composer { if (null === $this->composer) { $application = parent::getApplication(); if ($application instanceof Application) { $this->composer = $application->getComposer(\true, $disablePlugins, $disableScripts); \assert($this->composer instanceof Composer); } else { throw new \RuntimeException('Could not create a Composer\\Composer instance, you must inject ' . 'one if this command is not used with a Composer\\Console\\Application instance'); } } return $this->composer; } /** * Retrieves the default Composer\Composer instance or null * * Use this instead of getComposer(false) * * @param bool|null $disablePlugins If null, reads --no-plugins as default * @param bool|null $disableScripts If null, reads --no-scripts as default */ public function tryComposer(?bool $disablePlugins = null, ?bool $disableScripts = null) : ?Composer { if (null === $this->composer) { $application = parent::getApplication(); if ($application instanceof Application) { $this->composer = $application->getComposer(\false, $disablePlugins, $disableScripts); } } return $this->composer; } /** * @return void */ public function setComposer(Composer $composer) { $this->composer = $composer; } /** * Removes the cached composer instance * * @return void */ public function resetComposer() { $this->composer = null; $this->getApplication()->resetComposer(); } /** * Whether or not this command is meant to call another command. * * This is mainly needed to avoid duplicated warnings messages. * * @return bool */ public function isProxyCommand() { return \false; } /** * @return IOInterface */ public function getIO() { if (null === $this->io) { $application = parent::getApplication(); if ($application instanceof Application) { $this->io = $application->getIO(); } else { $this->io = new NullIO(); } } return $this->io; } /** * @return void */ public function setIO(IOInterface $io) { $this->io = $io; } /** * @inheritdoc * * Backport suggested values definition from symfony/console 6.1+ * * TODO drop when PHP 8.1 / symfony 6.1+ can be required */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { $definition = $this->getDefinition(); $name = (string) $input->getCompletionName(); if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($name) && ($option = $definition->getOption($name)) instanceof InputOption) { $option->complete($input, $suggestions); } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($name) && ($argument = $definition->getArgument($name)) instanceof InputArgument) { $argument->complete($input, $suggestions); } else { parent::complete($input, $suggestions); } } /** * @inheritDoc * * @return void */ protected function initialize(InputInterface $input, OutputInterface $output) { // initialize a plugin-enabled Composer instance, either local or global $disablePlugins = $input->hasParameterOption('--no-plugins'); $disableScripts = $input->hasParameterOption('--no-scripts'); $application = parent::getApplication(); if ($application instanceof Application && $application->getDisablePluginsByDefault()) { $disablePlugins = \true; } if ($application instanceof Application && $application->getDisableScriptsByDefault()) { $disableScripts = \true; } if ($this instanceof \Composer\Command\SelfUpdateCommand) { $disablePlugins = \true; $disableScripts = \true; } $composer = $this->tryComposer($disablePlugins, $disableScripts); $io = $this->getIO(); if (null === $composer) { $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts); } if ($composer) { $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); } if (\true === $input->hasParameterOption(['--no-ansi']) && $input->hasOption('no-progress')) { $input->setOption('no-progress', \true); } $envOptions = ['COMPOSER_NO_AUDIT' => ['no-audit'], 'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'], 'COMPOSER_PREFER_STABLE' => ['prefer-stable'], 'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'], 'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes']]; foreach ($envOptions as $envName => $optionNames) { foreach ($optionNames as $optionName) { if (\true === $input->hasOption($optionName)) { if (\false === $input->getOption($optionName) && (bool) Platform::getEnv($envName)) { $input->setOption($optionName, \true); } } } } if (\true === $input->hasOption('ignore-platform-reqs')) { if (!$input->getOption('ignore-platform-reqs') && (bool) Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQS')) { $input->setOption('ignore-platform-reqs', \true); $io->writeError('COMPOSER_IGNORE_PLATFORM_REQS is set. You may experience unexpected errors.'); } } if (\true === $input->hasOption('ignore-platform-req') && (!$input->hasOption('ignore-platform-reqs') || !$input->getOption('ignore-platform-reqs'))) { $ignorePlatformReqEnv = Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQ'); if (0 === \count($input->getOption('ignore-platform-req')) && \is_string($ignorePlatformReqEnv) && '' !== $ignorePlatformReqEnv) { $input->setOption('ignore-platform-req', \explode(',', $ignorePlatformReqEnv)); $io->writeError('COMPOSER_IGNORE_PLATFORM_REQ is set to ignore ' . $ignorePlatformReqEnv . '. You may experience unexpected errors.'); } } parent::initialize($input, $output); } /** * Returns preferSource and preferDist values based on the configuration. * * @return bool[] An array composed of the preferSource and preferDist values */ protected function getPreferredInstallOptions(Config $config, InputInterface $input, bool $keepVcsRequiresPreferSource = \false) { $preferSource = \false; $preferDist = \false; switch ($config->get('preferred-install')) { case 'source': $preferSource = \true; break; case 'dist': $preferDist = \true; break; case 'auto': default: // noop break; } if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) { return [$preferSource, $preferDist]; } if ($input->hasOption('prefer-install') && \is_string($input->getOption('prefer-install'))) { if ($input->getOption('prefer-source')) { throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install'); } if ($input->getOption('prefer-dist')) { throw new \InvalidArgumentException('--prefer-dist can not be used together with --prefer-install'); } switch ($input->getOption('prefer-install')) { case 'dist': $input->setOption('prefer-dist', \true); break; case 'source': $input->setOption('prefer-source', \true); break; case 'auto': $preferDist = \false; $preferSource = \false; break; default: throw new \UnexpectedValueException('--prefer-install accepts one of "dist", "source" or "auto", got ' . $input->getOption('prefer-install')); } } if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || $keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')) { $preferSource = $input->getOption('prefer-source') || $keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'); $preferDist = $input->getOption('prefer-dist'); } return [$preferSource, $preferDist]; } protected function getPlatformRequirementFilter(InputInterface $input) : PlatformRequirementFilterInterface { if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) { throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.'); } if (\true === $input->getOption('ignore-platform-reqs')) { return PlatformRequirementFilterFactory::ignoreAll(); } $ignores = $input->getOption('ignore-platform-req'); if (\count($ignores) > 0) { return PlatformRequirementFilterFactory::fromBoolOrList($ignores); } return PlatformRequirementFilterFactory::ignoreNothing(); } /** * @param array $requirements * * @return array */ protected function formatRequirements(array $requirements) { $requires = []; $requirements = $this->normalizeRequirements($requirements); foreach ($requirements as $requirement) { if (!isset($requirement['version'])) { throw new \UnexpectedValueException('Option ' . $requirement['name'] . ' is missing a version constraint, use e.g. ' . $requirement['name'] . ':^1.0'); } $requires[$requirement['name']] = $requirement['version']; } return $requires; } /** * @param array $requirements * * @return list */ protected function normalizeRequirements(array $requirements) { $parser = new VersionParser(); return $parser->parseNameVersionPairs($requirements); } /** * @param array $table * * @return void */ protected function renderTable(array $table, OutputInterface $output) { $renderer = new Table($output); $renderer->setStyle('compact'); $renderer->setRows($table)->render(); } /** * @return int */ protected function getTerminalWidth() { $terminal = new Terminal(); $width = $terminal->getWidth(); if (Platform::isWindows()) { $width--; } else { $width = \max(80, $width); } return $width; } /** * @internal * @param 'format'|'audit-format' $optName * @return Auditor::FORMAT_* */ protected function getAuditFormat(InputInterface $input, string $optName = 'audit-format') : string { if (!$input->hasOption($optName)) { throw new \LogicException('This should not be called on a Command which has no ' . $optName . ' option defined.'); } $val = $input->getOption($optName); if (!\in_array($val, Auditor::FORMATS, \true)) { throw new \InvalidArgumentException('--' . $optName . ' must be one of ' . \implode(', ', Auditor::FORMATS) . '.'); } return $val; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class OutdatedCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; protected function configure() : void { $this->setName('outdated')->setDescription('Shows a list of installed packages that have updates available, including their latest version')->setDefinition([new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage(\false)), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'), new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'), new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'), new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(\false)), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option')])->setHelp(<<green (=): Dependency is in the latest version and is up to date. - yellow (~): Dependency has a new version available that includes backwards compatibility breaks according to semver, so upgrade when you can but it may involve work. - red (!): Dependency has a new version that is semver-compatible and you should upgrade it. Read more at https://getcomposer.org/doc/03-cli.md#outdated EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $args = ['command' => 'show', '--latest' => \true]; if (!$input->getOption('all')) { $args['--outdated'] = \true; } if ($input->getOption('direct')) { $args['--direct'] = \true; } if (null !== $input->getArgument('package')) { $args['package'] = $input->getArgument('package'); } if ($input->getOption('strict')) { $args['--strict'] = \true; } if ($input->getOption('major-only')) { $args['--major-only'] = \true; } if ($input->getOption('minor-only')) { $args['--minor-only'] = \true; } if ($input->getOption('patch-only')) { $args['--patch-only'] = \true; } if ($input->getOption('locked')) { $args['--locked'] = \true; } if ($input->getOption('no-dev')) { $args['--no-dev'] = \true; } if ($input->getOption('sort-by-age')) { $args['--sort-by-age'] = \true; } $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req'); if ($input->getOption('ignore-platform-reqs')) { $args['--ignore-platform-reqs'] = \true; } $args['--format'] = $input->getOption('format'); $args['--ignore'] = $input->getOption('ignore'); $input = new ArrayInput($args); return $this->getApplication()->run($input, $output); } /** * @inheritDoc */ public function isProxyCommand() : bool { return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class DumpAutoloadCommand extends \Composer\Command\BaseCommand { /** * @return void */ protected function configure() { $this->setName('dump-autoload')->setAliases(['dumpautoload'])->setDescription('Dumps the autoloader')->setDefinition([new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'), new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.')])->setHelp(<<php composer.phar dump-autoload Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $installationManager = $composer->getInstallationManager(); $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $package = $composer->getPackage(); $config = $composer->getConfig(); $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu') || $config->get('apcu-autoloader'); if ($input->getOption('strict-psr') && !$optimize && !$authoritative) { throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); } if ($authoritative) { $this->getIO()->write('Generating optimized autoload files (authoritative)'); } elseif ($optimize) { $this->getIO()->write('Generating optimized autoload files'); } else { $this->getIO()->write('Generating autoload files'); } $generator = $composer->getAutoloadGenerator(); if ($input->getOption('dry-run')) { $generator->setDryRun(\true); } if ($input->getOption('no-dev')) { $generator->setDevMode(\false); } if ($input->getOption('dev')) { if ($input->getOption('no-dev')) { throw new \InvalidArgumentException('You can not use both --no-dev and --dev as they conflict with each other.'); } $generator->setDevMode(\true); } $generator->setClassMapAuthoritative($authoritative); $generator->setRunScripts(\true); $generator->setApcu($apcu, $apcuPrefix); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); $classMap = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize, null, $composer->getLocker()); $numberOfClasses = \count($classMap); if ($authoritative) { $this->getIO()->write('Generated optimized autoload files (authoritative) containing ' . $numberOfClasses . ' classes'); } elseif ($optimize) { $this->getIO()->write('Generated optimized autoload files containing ' . $numberOfClasses . ' classes'); } else { $this->getIO()->write('Generated autoload files'); } if ($input->getOption('strict-psr') && \count($classMap->getPsrViolations()) > 0) { return 1; } return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Package\CompletePackageInterface; use Composer\Repository\RepositoryInterface; use Composer\Repository\RootPackageRepository; use Composer\Repository\RepositoryFactory; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Robert Schönthal */ class HomeCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; /** * @inheritDoc */ protected function configure() : void { $this->setName('browse')->setAliases(['home'])->setDescription('Opens the package\'s repository URL or homepage in your browser')->setDefinition([new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.')])->setHelp(<<initializeRepos(); $io = $this->getIO(); $return = 0; $packages = $input->getArgument('packages'); if (\count($packages) === 0) { $io->writeError('No package specified, opening homepage for the root package'); $packages = [$this->requireComposer()->getPackage()->getName()]; } foreach ($packages as $packageName) { $handled = \false; $packageExists = \false; foreach ($repos as $repo) { foreach ($repo->findPackages($packageName) as $package) { $packageExists = \true; if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) { $handled = \true; break 2; } } } if (!$packageExists) { $return = 1; $io->writeError('Package ' . $packageName . ' not found'); } if (!$handled) { $return = 1; $io->writeError('' . ($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL') . ' for ' . $packageName . ''); } } return $return; } private function handlePackage(CompletePackageInterface $package, bool $showHomepage, bool $showOnly) : bool { $support = $package->getSupport(); $url = $support['source'] ?? $package->getSourceUrl(); if (!$url || $showHomepage) { $url = $package->getHomepage(); } if (!$url || !\filter_var($url, \FILTER_VALIDATE_URL)) { return \false; } if ($showOnly) { $this->getIO()->write(\sprintf('%s', $url)); } else { $this->openBrowser($url); } return \true; } /** * opens a url in your system default browser */ private function openBrowser(string $url) : void { $url = ProcessExecutor::escape($url); $process = new ProcessExecutor($this->getIO()); if (Platform::isWindows()) { $process->execute('start "web" explorer ' . $url, $output); return; } $linux = $process->execute('which xdg-open', $output); $osx = $process->execute('which open', $output); if (0 === $linux) { $process->execute('xdg-open ' . $url, $output); } elseif (0 === $osx) { $process->execute('open ' . $url, $output); } else { $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url); } } /** * Initializes repositories * * Returns an array of repos in order they should be checked in * * @return RepositoryInterface[] */ private function initializeRepos() : array { $composer = $this->tryComposer(); if ($composer) { return \array_merge( [new RootPackageRepository(clone $composer->getPackage())], // root package [$composer->getRepositoryManager()->getLocalRepository()], // installed packages $composer->getRepositoryManager()->getRepositories() ); } return RepositoryFactory::defaultReposWithDefaultManager($this->getIO()); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Transaction; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Pcre\Preg; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Script\ScriptEvents; use Composer\Util\Platform; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class ReinstallCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; protected function configure() : void { $this->setName('reinstall')->setDescription('Uninstalls and reinstalls the given package names')->setDefinition([new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(\false))])->setHelp(<<reinstall command looks up installed packages by name, uninstalls them and reinstalls them. This lets you do a clean install of a package if you messed with its files, or if you wish to change the installation type using --prefer-install. php composer.phar reinstall acme/foo "acme/bar-*" Read more at https://getcomposer.org/doc/03-cli.md#reinstall EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = $this->getIO(); $composer = $this->requireComposer(); $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $packagesToReinstall = []; $packageNamesToReinstall = []; foreach ($input->getArgument('packages') as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); $matched = \false; foreach ($localRepo->getCanonicalPackages() as $package) { if (Preg::isMatch($patternRegexp, $package->getName())) { $matched = \true; $packagesToReinstall[] = $package; $packageNamesToReinstall[] = $package->getName(); } } if (!$matched) { $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); } } if (!$packagesToReinstall) { $io->writeError('Found no packages to reinstall, aborting.'); return 1; } $uninstallOperations = []; foreach ($packagesToReinstall as $package) { $uninstallOperations[] = new UninstallOperation($package); } // make sure we have a list of install operations ordered by dependency/plugins $presentPackages = $localRepo->getPackages(); $resultPackages = $presentPackages; foreach ($presentPackages as $index => $package) { if (\in_array($package->getName(), $packageNamesToReinstall, \true)) { unset($presentPackages[$index]); } } $transaction = new Transaction($presentPackages, $resultPackages); $installOperations = $transaction->getOperations(); // reverse-sort the uninstalls based on the install order $installOrder = []; foreach ($installOperations as $index => $op) { if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) { $installOrder[$op->getPackage()->getName()] = $index; } } \usort($uninstallOperations, static function ($a, $b) use($installOrder) : int { return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()]; }); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output); $eventDispatcher = $composer->getEventDispatcher(); $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); $installationManager = $composer->getInstallationManager(); $downloadManager = $composer->getDownloadManager(); $package = $composer->getPackage(); $installationManager->setOutputProgress(!$input->getOption('no-progress')); if ($input->getOption('no-plugins')) { $installationManager->disablePlugins(); } $downloadManager->setPreferSource($preferSource); $downloadManager->setPreferDist($preferDist); $devMode = $localRepo->getDevMode() !== null ? $localRepo->getDevMode() : \true; Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); $eventDispatcher->dispatchScript(ScriptEvents::PRE_INSTALL_CMD, $devMode); $installationManager->execute($localRepo, $uninstallOperations, $devMode); $installationManager->execute($localRepo, $installOperations, $devMode); if (!$input->getOption('no-autoloader')) { $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $generator = $composer->getAutoloadGenerator(); $generator->setClassMapAuthoritative($authoritative); $generator->setApcu($apcu, $apcuPrefix); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize, null, $composer->getLocker()); } $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode); return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Config\JsonConfigSource; use Composer\DependencyResolver\Request; use Composer\Installer; use Composer\Pcre\Preg; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Json\JsonFile; use Composer\Factory; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Package\BasePackage; use Composer\Advisory\Auditor; /** * @author Pierre du Plessis * @author Jordi Boggiano */ class RemoveCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; /** * @return void */ protected function configure() { $this->setName('remove')->setAliases(['rm'])->setDescription('Removes a package from the require or require-dev')->setDefinition([new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()), new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies. (Deprecated, is now default behavior)'), new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'), new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader')])->setHelp(<<remove command removes a package from the current list of installed packages php composer.phar remove Read more at https://getcomposer.org/doc/03-cli.md#remove-rm EOT ); } /** * @throws \Seld\JsonLint\ParsingException */ protected function execute(InputInterface $input, OutputInterface $output) : int { if ($input->getArgument('packages') === [] && !$input->getOption('unused')) { throw new InvalidArgumentException('Not enough arguments (missing: "packages").'); } $packages = $input->getArgument('packages'); $packages = \array_map('strtolower', $packages); if ($input->getOption('unused')) { $composer = $this->requireComposer(); $locker = $composer->getLocker(); if (!$locker->isLocked()) { throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused'); } $lockedPackages = $locker->getLockedRepository()->getPackages(); $required = []; foreach (\array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()) as $link) { $required[$link->getTarget()] = \true; } do { $found = \false; foreach ($lockedPackages as $index => $package) { foreach ($package->getNames() as $name) { if (isset($required[$name])) { foreach ($package->getRequires() as $link) { $required[$link->getTarget()] = \true; } $found = \true; unset($lockedPackages[$index]); break; } } } } while ($found); $unused = []; foreach ($lockedPackages as $package) { $unused[] = $package->getName(); } $packages = \array_merge($packages, $unused); if (\count($packages) === 0) { $this->getIO()->writeError('No unused packages to remove'); return 0; } } $file = Factory::getComposerFile(); $jsonFile = new JsonFile($file); /** @var array{require?: array, require-dev?: array} $composer */ $composer = $jsonFile->read(); $composerBackup = \file_get_contents($jsonFile->getPath()); $json = new JsonConfigSource($jsonFile); $type = $input->getOption('dev') ? 'require-dev' : 'require'; $altType = !$input->getOption('dev') ? 'require-dev' : 'require'; $io = $this->getIO(); if ($input->getOption('update-with-dependencies')) { $io->writeError('You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.'); } // make sure name checks are done case insensitively foreach (['require', 'require-dev'] as $linkType) { if (isset($composer[$linkType])) { foreach ($composer[$linkType] as $name => $version) { $composer[$linkType][\strtolower($name)] = $name; } } } $dryRun = $input->getOption('dry-run'); $toRemove = []; foreach ($packages as $package) { if (isset($composer[$type][$package])) { if ($dryRun) { $toRemove[$type][] = $composer[$type][$package]; } else { $json->removeLink($type, $composer[$type][$package]); } } elseif (isset($composer[$altType][$package])) { $io->writeError('' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { if ($dryRun) { $toRemove[$altType][] = $composer[$altType][$package]; } else { $json->removeLink($altType, $composer[$altType][$package]); } } } } elseif (isset($composer[$type]) && \count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), \array_keys($composer[$type]))) > 0) { foreach ($matches as $matchedPackage) { if ($dryRun) { $toRemove[$type][] = $matchedPackage; } else { $json->removeLink($type, $matchedPackage); } } } elseif (isset($composer[$altType]) && \count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), \array_keys($composer[$altType]))) > 0) { foreach ($matches as $matchedPackage) { $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { if ($dryRun) { $toRemove[$altType][] = $matchedPackage; } else { $json->removeLink($altType, $matchedPackage); } } } } } else { $io->writeError('' . $package . ' is not required in your composer.json and has not been removed'); } } $io->writeError('' . $file . ' has been updated'); if ($input->getOption('no-update')) { return 0; } if ($composer = $this->tryComposer()) { $composer->getPluginManager()->deactivateInstalledPlugins(); } // Update packages $this->resetComposer(); $composer = $this->requireComposer(); if ($dryRun) { $rootPackage = $composer->getPackage(); $links = ['require' => $rootPackage->getRequires(), 'require-dev' => $rootPackage->getDevRequires()]; foreach ($toRemove as $type => $names) { foreach ($names as $name) { unset($links[$type][$name]); } } $rootPackage->setRequires($links['require']); $rootPackage->setDevRequires($links['require-dev']); } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $allowPlugins = $composer->getConfig()->get('allow-plugins'); $removedPlugins = \is_array($allowPlugins) ? \array_intersect(\array_keys($allowPlugins), $packages) : []; if (!$dryRun && \is_array($allowPlugins) && \count($removedPlugins) > 0) { if (\count($allowPlugins) === \count($removedPlugins)) { $json->removeConfigSetting('allow-plugins'); } else { foreach ($removedPlugins as $plugin) { $json->removeConfigSetting('allow-plugins.' . $plugin); } } } $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install = Installer::create($io, $composer); $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; $flags = ''; if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; $flags .= ' --with-all-dependencies'; } elseif ($input->getOption('no-update-with-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; $flags .= ' --with-dependencies'; } $io->writeError('Running composer update ' . \implode(' ', $packages) . $flags . ''); $install->setVerbose($input->getOption('verbose'))->setDevMode($updateDevMode)->setOptimizeAutoloader($optimize)->setClassMapAuthoritative($authoritative)->setApcuAutoloader($apcu, $apcuPrefix)->setUpdate(\true)->setInstall(!$input->getOption('no-install'))->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))->setDryRun($dryRun)->setAudit(!$input->getOption('no-audit'))->setAuditFormat($this->getAuditFormat($input))->setMinimalUpdate($input->getOption('minimal-changes')); // if no lock is present, we do not do a partial update as // this is not supported by the Installer if ($composer->getLocker()->isLocked()) { $install->setUpdateAllowList($packages); } $status = $install->run(); if ($status !== 0) { $io->writeError("\n" . 'Removal failed, reverting ' . $file . ' to its original content.'); \file_put_contents($jsonFile->getPath(), $composerBackup); } if (!$dryRun) { foreach ($packages as $package) { if ($composer->getRepositoryManager()->getLocalRepository()->findPackages($package)) { $io->writeError('Removal failed, ' . $package . ' is still present, it may be required by another package. See `composer why ' . $package . '`.'); return 2; } } } return $status; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\DependencyResolver\DefaultPolicy; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\BasePackage; use Composer\Package\CompletePackageInterface; use Composer\Package\Link; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionSelector; use Composer\Pcre\Preg; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\ArrayRepository; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\ComposerRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\FilterRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Repository\InstalledRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositorySet; use Composer\Repository\RepositoryUtils; use Composer\Repository\RootPackageRepository; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Semver; use Composer\Spdx\SpdxLicenses; use Composer\Util\PackageInfo; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Robert Schönthal * @author Jordi Boggiano * @author Jérémy Romey * @author Mihai Plasoianu * * @phpstan-import-type AutoloadRules from PackageInterface * @phpstan-type JsonStructure array|AutoloadRules> */ class ShowCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; /** @var VersionParser */ protected $versionParser; /** @var string[] */ protected $colors; /** @var ?RepositorySet */ private $repositorySet; /** * @return void */ protected function configure() { $this->setName('show')->setAliases(['info'])->setDescription('Shows information about packages')->setDefinition([new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()), new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'), new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'), new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'), new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'), new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'), new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'), new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'), new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'), new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'), new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(\false)), new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'), new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'), new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option')])->setHelp(<<getOption('available') || $input->getOption('all')) { return $this->suggestAvailablePackageInclPlatform()($input); } if ($input->getOption('platform')) { return $this->suggestPlatformPackage()($input); } return $this->suggestInstalledPackage(\false)($input); }; } protected function execute(InputInterface $input, OutputInterface $output) : int { $this->versionParser = new VersionParser(); if ($input->getOption('tree')) { $this->initStyles($output); } $composer = $this->tryComposer(); $io = $this->getIO(); if ($input->getOption('installed') && !$input->getOption('self')) { $io->writeError('You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.'); } if ($input->getOption('outdated')) { $input->setOption('latest', \true); } elseif (\count($input->getOption('ignore')) > 0) { $io->writeError('You are using the option "ignore" for action other than "outdated", it will be ignored.'); } if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)'); return 1; } if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) { $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)'); return 1; } if (\count(\array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) { $io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once'); return 1; } if ($input->getOption('tree') && $input->getOption('latest')) { $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)'); return 1; } if ($input->getOption('tree') && $input->getOption('path')) { $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)'); return 1; } $format = $input->getOption('format'); if (!\in_array($format, ['text', 'json'])) { $io->writeError(\sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } $platformReqFilter = $this->getPlatformRequirementFilter($input); // init repos $platformOverrides = []; if ($composer) { $platformOverrides = $composer->getConfig()->get('platform'); } $platformRepo = new PlatformRepository([], $platformOverrides); $lockedRepo = null; if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) { $package = clone $this->requireComposer()->getPackage(); if ($input->getOption('name-only')) { $io->write($package->getName()); return 0; } if ($input->getArgument('package')) { throw new \InvalidArgumentException('You cannot use --self together with a package name'); } $repos = $installedRepo = new InstalledRepository([new RootPackageRepository($package)]); } elseif ($input->getOption('platform')) { $repos = $installedRepo = new InstalledRepository([$platformRepo]); } elseif ($input->getOption('available')) { $installedRepo = new InstalledRepository([$platformRepo]); if ($composer) { $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository()); } else { $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $repos = new CompositeRepository($defaultRepos); $io->writeError('No composer.json found in the current directory, showing available packages from ' . \implode(', ', \array_keys($defaultRepos))); } } elseif ($input->getOption('all') && $composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $locker = $composer->getLocker(); if ($locker->isLocked()) { $lockedRepo = $locker->getLockedRepository(\true); $installedRepo = new InstalledRepository([$lockedRepo, $localRepo, $platformRepo]); } else { $installedRepo = new InstalledRepository([$localRepo, $platformRepo]); } $repos = new CompositeRepository(\array_merge([new FilterRepository($installedRepo, ['canonical' => \false])], $composer->getRepositoryManager()->getRepositories())); } elseif ($input->getOption('all')) { $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $io->writeError('No composer.json found in the current directory, showing available packages from ' . \implode(', ', \array_keys($defaultRepos))); $installedRepo = new InstalledRepository([$platformRepo]); $repos = new CompositeRepository(\array_merge([$installedRepo], $defaultRepos)); } elseif ($input->getOption('locked')) { if (!$composer || !$composer->getLocker()->isLocked()) { throw new \UnexpectedValueException('A valid composer.json and composer.lock files is required to run this command with --locked'); } $locker = $composer->getLocker(); $lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev')); if ($input->getOption('self')) { $lockedRepo->addPackage(clone $composer->getPackage()); } $repos = $installedRepo = new InstalledRepository([$lockedRepo]); } else { // --installed / default case if (!$composer) { $composer = $this->requireComposer(); } $rootPkg = $composer->getPackage(); $rootRepo = new InstalledArrayRepository(); if ($input->getOption('self')) { $rootRepo = new RootPackageRepository(clone $rootPkg); } if ($input->getOption('no-dev')) { $packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg); $repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(\array_map(static function ($pkg) : PackageInterface { return clone $pkg; }, $packages))]); } else { $repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]); } if (!$installedRepo->getPackages()) { $hasNonPlatformReqs = static function (array $reqs) : bool { return (bool) \array_filter(\array_keys($reqs), function (string $name) { return !PlatformRepository::isPlatformPackage($name); }); }; if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) { $io->writeError('No dependencies installed. Try running composer install or update.'); } } } if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); } if ($input->getOption('latest') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "latest" option'); $input->setOption('latest', \false); } $packageFilter = $input->getArgument('package'); // show single package or single version if (isset($package)) { $versions = [$package->getPrettyVersion() => $package->getVersion()]; } elseif (null !== $packageFilter && !\str_contains($packageFilter, '*')) { [$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version')); if (isset($package) && $input->getOption('direct')) { if (!\in_array($package->getName(), $this->getRootRequires(), \true)) { throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.'); } } if (!isset($package)) { $options = $input->getOptions(); $hint = ''; if ($input->getOption('locked')) { $hint .= ' in lock file'; } if (isset($options['working-dir'])) { $hint .= ' in ' . $options['working-dir'] . '/composer.json'; } if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) { $hint .= ', try using --platform (-p) to show platform packages'; } if (!$input->getOption('all') && !$input->getOption('available')) { $hint .= ', try using --available (-a) to show all available packages'; } throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found' . $hint . '.'); } } if (isset($package)) { \assert(isset($versions)); $exitCode = 0; if ($input->getOption('tree')) { $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos); if ('json' === $format) { $io->write(JsonFile::encode(['installed' => [$arrayTree]])); } else { $this->displayPackageTree([$arrayTree]); } return $exitCode; } $latestPackage = null; if ($input->getOption('latest')) { $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter); } if ($input->getOption('outdated') && $input->getOption('strict') && null !== $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned())) { $exitCode = 1; } if ($input->getOption('path')) { $io->write($package->getName(), \false); $path = $composer->getInstallationManager()->getInstallPath($package); if (\is_string($path)) { $io->write(' ' . \strtok(\realpath($path), "\r\n")); } else { $io->write(' null'); } return $exitCode; } if ('json' === $format) { $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null); } else { $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null); } return $exitCode; } // show tree view if requested if ($input->getOption('tree')) { $rootRequires = $this->getRootRequires(); $packages = $installedRepo->getPackages(); \usort($packages, static function (BasePackage $a, BasePackage $b) : int { return \strcmp((string) $a, (string) $b); }); $arrayTree = []; foreach ($packages as $package) { if (\in_array($package->getName(), $rootRequires, \true)) { $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos); } } if ('json' === $format) { $io->write(JsonFile::encode(['installed' => $arrayTree])); } else { $this->displayPackageTree($arrayTree); } return 0; } // list packages /** @var array> $packages */ $packages = []; $packageFilterRegex = null; if (null !== $packageFilter) { $packageFilterRegex = '{^' . \str_replace('\\*', '.*?', \preg_quote($packageFilter)) . '$}i'; } $packageListFilter = null; if ($input->getOption('direct')) { $packageListFilter = $this->getRootRequires(); } if ($input->getOption('path') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "path" option'); $input->setOption('path', \false); } foreach (RepositoryUtils::flattenRepositories($repos) as $repo) { if ($repo === $platformRepo) { $type = 'platform'; } elseif ($lockedRepo !== null && $repo === $lockedRepo) { $type = 'locked'; } elseif ($repo === $installedRepo || \in_array($repo, $installedRepo->getRepositories(), \true)) { $type = 'installed'; } else { $type = 'available'; } if ($repo instanceof ComposerRepository) { foreach ($repo->getPackageNames($packageFilter) as $name) { $packages[$type][$name] = $name; } } else { foreach ($repo->getPackages() as $package) { if (!isset($packages[$type][$package->getName()]) || !\is_object($packages[$type][$package->getName()]) || \version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) { if (null === $packageListFilter || \in_array($package->getName(), $packageListFilter, \true)) { $packages[$type][$package->getName()] = $package; } } } } if ($repo === $platformRepo) { foreach ($platformRepo->getDisabledPackages() as $name => $package) { $packages[$type][$name] = $package; } } } } $showAllTypes = $input->getOption('all'); $showLatest = $input->getOption('latest'); $showMajorOnly = $input->getOption('major-only'); $showMinorOnly = $input->getOption('minor-only'); $showPatchOnly = $input->getOption('patch-only'); $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(\array_map('strtolower', $input->getOption('ignore'))); $indent = $showAllTypes ? ' ' : ''; /** @var PackageInterface[] $latestPackages */ $latestPackages = []; $exitCode = 0; $viewData = []; $viewMetaData = []; $writeVersion = \false; $writeDescription = \false; foreach (['platform' => \true, 'locked' => \true, 'available' => \false, 'installed' => \true] as $type => $showVersion) { if (isset($packages[$type])) { \ksort($packages[$type]); $nameLength = $versionLength = $latestLength = $releaseDateLength = 0; if ($showLatest && $showVersion) { foreach ($packages[$type] as $package) { if (\is_object($package)) { $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter); if ($latestPackage === null) { continue; } $latestPackages[$package->getPrettyName()] = $latestPackage; } } } $writePath = !$input->getOption('name-only') && $input->getOption('path'); $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion; $writeLatest = $writeVersion && $showLatest; $writeDescription = !$input->getOption('name-only') && !$input->getOption('path'); $writeReleaseDate = $writeLatest && $input->getOption('sort-by-age'); $hasOutdatedPackages = \false; if ($input->getOption('sort-by-age')) { \usort($packages[$type], function ($a, $b) { if (\is_object($a) && \is_object($b)) { return $a->getReleaseDate() <=> $b->getReleaseDate(); } return 0; }); } $viewData[$type] = []; foreach ($packages[$type] as $package) { $packageViewData = []; if (\is_object($package)) { $latestPackage = null; if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { $latestPackage = $latestPackages[$package->getPrettyName()]; } // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()); // When using --major-only, and no bigger version than current major is found then it is considered up to date $packageIsUpToDate = $packageIsUpToDate || $latestPackage === null && $showMajorOnly; $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName()); if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) { continue; } if ($input->getOption('outdated') || $input->getOption('strict')) { $hasOutdatedPackages = \true; } $packageViewData['name'] = $package->getPrettyName(); $packageViewData['direct-dependency'] = \in_array($package->getName(), $this->getRootRequires(), \true); if ($format !== 'json' || \true !== $input->getOption('name-only')) { $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null; $packageViewData['source'] = PackageInfo::getViewSourceUrl($package); } $nameLength = \max($nameLength, \strlen($package->getPrettyName())); if ($writeVersion) { $packageViewData['version'] = $package->getFullPrettyVersion(); $versionLength = \max($versionLength, \strlen($package->getFullPrettyVersion())); } if ($writeReleaseDate) { if ($package->getReleaseDate() !== null) { $packageViewData['release-age'] = \str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate())); if (!\str_contains($packageViewData['release-age'], ' old')) { $packageViewData['release-age'] = 'from ' . $packageViewData['release-age']; } $releaseDateLength = \max($releaseDateLength, \strlen($packageViewData['release-age'])); } else { $packageViewData['release-age'] = ''; } } if ($writeLatest && $latestPackage) { $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); $latestLength = \max($latestLength, \strlen($packageViewData['latest'])); } elseif ($writeLatest) { $packageViewData['latest'] = '[none matched]'; $packageViewData['latest-status'] = 'up-to-date'; $latestLength = \max($latestLength, \strlen($packageViewData['latest'])); } if ($writeDescription && $package instanceof CompletePackageInterface) { $packageViewData['description'] = $package->getDescription(); } if ($writePath) { $path = $composer->getInstallationManager()->getInstallPath($package); if (\is_string($path)) { $packageViewData['path'] = \strtok(\realpath($path), "\r\n"); } else { $packageViewData['path'] = null; } } $packageIsAbandoned = \false; if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { $replacementPackageName = $latestPackage->getReplacementPackage(); $replacement = $replacementPackageName !== null ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $packageWarning = \sprintf('Package %s is abandoned, you should avoid using it. %s.', $package->getPrettyName(), $replacement); $packageViewData['warning'] = $packageWarning; $packageIsAbandoned = $replacementPackageName ?? \true; } $packageViewData['abandoned'] = $packageIsAbandoned; } else { $packageViewData['name'] = $package; $nameLength = \max($nameLength, \strlen($package)); } $viewData[$type][] = $packageViewData; } $viewMetaData[$type] = ['nameLength' => $nameLength, 'versionLength' => $versionLength, 'latestLength' => $latestLength, 'releaseDateLength' => $releaseDateLength, 'writeLatest' => $writeLatest, 'writeReleaseDate' => $writeReleaseDate]; if ($input->getOption('strict') && $hasOutdatedPackages) { $exitCode = 1; break; } } } if ('json' === $format) { $io->write(JsonFile::encode($viewData)); } else { if ($input->getOption('latest') && \array_filter($viewData)) { if (!$io->isDecorated()) { $io->writeError('Legend:'); $io->writeError('! patch or minor release available - update recommended'); $io->writeError('~ major release available - update possible'); if (!$input->getOption('outdated')) { $io->writeError('= up to date version'); } } else { $io->writeError('Color legend:'); $io->writeError('- patch or minor release available - update recommended'); $io->writeError('- major release available - update possible'); if (!$input->getOption('outdated')) { $io->writeError('- up to date version'); } } } $width = $this->getTerminalWidth(); foreach ($viewData as $type => $packages) { $nameLength = $viewMetaData[$type]['nameLength']; $versionLength = $viewMetaData[$type]['versionLength']; $latestLength = $viewMetaData[$type]['latestLength']; $releaseDateLength = $viewMetaData[$type]['releaseDateLength']; $writeLatest = $viewMetaData[$type]['writeLatest']; $writeReleaseDate = $viewMetaData[$type]['writeReleaseDate']; $versionFits = $nameLength + $versionLength + 3 <= $width; $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width; $releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width; $descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width; if ($latestFits && !$io->isDecorated()) { $latestLength += 2; } if ($showAllTypes) { if ('available' === $type) { $io->write('' . $type . ':'); } else { $io->write('' . $type . ':'); } } if ($writeLatest && !$input->getOption('direct')) { $directDeps = []; $transitiveDeps = []; foreach ($packages as $pkg) { if ($pkg['direct-dependency'] ?? \false) { $directDeps[] = $pkg; } else { $transitiveDeps[] = $pkg; } } $io->writeError(''); $io->writeError('Direct dependencies required in composer.json:'); if (\count($directDeps) > 0) { $this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); } else { $io->writeError('Everything up to date'); } $io->writeError(''); $io->writeError('Transitive dependencies not required in composer.json:'); if (\count($transitiveDeps) > 0) { $this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); } else { $io->writeError('Everything up to date'); } } else { if ($writeLatest && \count($packages) === 0) { $io->writeError('All your direct dependencies are up to date'); } else { $this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); } } if ($showAllTypes) { $io->write(''); } } } return $exitCode; } /** * @param array $packages */ private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength) : void { $padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription; $padVersion = $writeLatest || $writeReleaseDate || $writeDescription; $padLatest = $writeDescription || $writeReleaseDate; $padReleaseDate = $writeDescription; foreach ($packages as $package) { $link = $package['source'] ?? $package['homepage'] ?? ''; if ($link !== '') { $io->write($indent . '' . $package['name'] . '' . \str_repeat(' ', $padName ? $nameLength - \strlen($package['name']) : 0), \false); } else { $io->write($indent . \str_pad($package['name'], $padName ? $nameLength : 0, ' '), \false); } if (isset($package['version']) && $writeVersion) { $io->write(' ' . \str_pad($package['version'], $padVersion ? $versionLength : 0, ' '), \false); } if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) { $latestVersion = $package['latest']; $updateStatus = $package['latest-status']; $style = $this->updateStatusToVersionStyle($updateStatus); if (!$io->isDecorated()) { $latestVersion = \str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion; } $io->write(' <' . $style . '>' . \str_pad($latestVersion, $padLatest ? $latestLength : 0, ' ') . '', \false); if ($writeReleaseDate && isset($package['release-age'])) { $io->write(' ' . \str_pad($package['release-age'], $padReleaseDate ? $releaseDateLength : 0, ' '), \false); } } if (isset($package['description']) && $writeDescription) { $description = \strtok($package['description'], "\r\n"); $remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4; if ($writeLatest) { $remaining -= $latestLength; } if (\strlen($description) > $remaining) { $description = \substr($description, 0, $remaining - 3) . '...'; } $io->write(' ' . $description, \false); } if (\array_key_exists('path', $package)) { $io->write(' ' . (\is_string($package['path']) ? $package['path'] : 'null'), \false); } $io->write(''); if (isset($package['warning'])) { $io->write('' . $package['warning'] . ''); } } } /** * @return string[] */ protected function getRootRequires() : array { $composer = $this->tryComposer(); if ($composer === null) { return []; } $rootPackage = $composer->getPackage(); return \array_map('strtolower', \array_keys(\array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))); } /** * @return array|string|string[] */ protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package) { return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package)); } /** * finds a package by name and version if provided * * @param ConstraintInterface|string $version * @throws \InvalidArgumentException * @return array{CompletePackageInterface|null, array} */ protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, string $name, $version = null) : array { $name = \strtolower($name); $constraint = \is_string($version) ? $this->versionParser->parseConstraints($version) : $version; $policy = new DefaultPolicy(); $repositorySet = new RepositorySet('dev'); $repositorySet->allowInstalledRepositories(); $repositorySet->addRepository($repos); $matchedPackage = null; $versions = []; if (PlatformRepository::isPlatformPackage($name)) { $pool = $repositorySet->createPoolWithAllPackages(); } else { $pool = $repositorySet->createPoolForPackage($name); } $matches = $pool->whatProvides($name, $constraint); foreach ($matches as $index => $package) { // avoid showing the 9999999-dev alias if the default branch has no branch-alias set if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } // select an exact match if it is in the installed repo and no specific version was required if (null === $version && $installedRepo->hasPackage($package)) { $matchedPackage = $package; } $versions[$package->getPrettyVersion()] = $package->getVersion(); $matches[$index] = $package->getId(); } // select preferred package according to policy rules if (null === $matchedPackage && $matches && ($preferred = $policy->selectPreferredPackages($pool, $matches))) { $matchedPackage = $pool->literalToPackage($preferred[0]); } if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) { throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got ' . \get_class($matchedPackage)); } return [$matchedPackage, $versions]; } /** * Prints package info. * * @param array $versions */ protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null) : void { $io = $this->getIO(); $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); $this->printLinks($package, Link::TYPE_REQUIRE); $this->printLinks($package, Link::TYPE_DEV_REQUIRE, 'requires (dev)'); if ($package->getSuggests()) { $io->write("\nsuggests"); foreach ($package->getSuggests() as $suggested => $reason) { $io->write($suggested . ' ' . $reason . ''); } } $this->printLinks($package, Link::TYPE_PROVIDE); $this->printLinks($package, Link::TYPE_CONFLICT); $this->printLinks($package, Link::TYPE_REPLACE); } /** * Prints package metadata. * * @param array $versions */ protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null) : void { $isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package); $io = $this->getIO(); $io->write('name : ' . $package->getPrettyName()); $io->write('descrip. : ' . $package->getDescription()); $io->write('keywords : ' . \implode(', ', $package->getKeywords() ?: [])); $this->printVersions($package, $versions, $installedRepo); if ($isInstalledPackage && $package->getReleaseDate() !== null) { $io->write('released : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate())); } if ($latestPackage) { $style = $this->getVersionStyle($latestPackage, $package); $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate()); $io->write('latest : <' . $style . '>' . $latestPackage->getPrettyVersion() . '' . $releasedTime); } else { $latestPackage = $package; } $io->write('type : ' . $package->getType()); $this->printLicenses($package); $io->write('homepage : ' . $package->getHomepage()); $io->write('source : ' . \sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . \sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); if ($isInstalledPackage) { $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); if (\is_string($path)) { $io->write('path : ' . \realpath($path)); } else { $io->write('path : null'); } } $io->write('names : ' . \implode(', ', $package->getNames())); if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { $replacement = $latestPackage->getReplacementPackage() !== null ? ' The author suggests using the ' . $latestPackage->getReplacementPackage() . ' package instead.' : null; $io->writeError(\sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement)); } if ($package->getSupport()) { $io->write("\nsupport"); foreach ($package->getSupport() as $type => $value) { $io->write('' . $type . ' : ' . $value); } } if (\count($package->getAutoload()) > 0) { $io->write("\nautoload"); $autoloadConfig = $package->getAutoload(); foreach ($autoloadConfig as $type => $autoloads) { $io->write('' . $type . ''); if ($type === 'psr-0' || $type === 'psr-4') { foreach ($autoloads as $name => $path) { $io->write(($name ?: '*') . ' => ' . (\is_array($path) ? \implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'classmap') { $io->write(\implode(', ', $autoloadConfig[$type])); } } if ($package->getIncludePaths()) { $io->write('include-path'); $io->write(\implode(', ', $package->getIncludePaths())); } } } /** * Prints all available versions of this package and highlights the installed one if any. * * @param array $versions */ protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo) : void { $versions = \array_keys($versions); $versions = Semver::rsort($versions); // highlight installed version if ($installedPackages = $installedRepo->findPackages($package->getName())) { foreach ($installedPackages as $installedPackage) { $installedVersion = $installedPackage->getPrettyVersion(); $key = \array_search($installedVersion, $versions); if (\false !== $key) { $versions[$key] = '* ' . $installedVersion . ''; } } } $versions = \implode(', ', $versions); $this->getIO()->write('versions : ' . $versions); } /** * print link objects * * @param string $title */ protected function printLinks(CompletePackageInterface $package, string $linkType, ?string $title = null) : void { $title = $title ?: $linkType; $io = $this->getIO(); if ($links = $package->{'get' . \ucfirst($linkType)}()) { $io->write("\n" . $title . ""); foreach ($links as $link) { $io->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); } } } /** * Prints the licenses of a package with metadata */ protected function printLicenses(CompletePackageInterface $package) : void { $spdxLicenses = new SpdxLicenses(); $licenses = $package->getLicense(); $io = $this->getIO(); foreach ($licenses as $licenseId) { $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url if (!$license) { $out = $licenseId; } else { // is license OSI approved? if ($license[1] === \true) { $out = \sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]); } else { $out = \sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]); } } $io->write('license : ' . $out); } } /** * Prints package info in JSON format. * * @param array $versions */ protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null) : void { $json = ['name' => $package->getPrettyName(), 'description' => $package->getDescription(), 'keywords' => $package->getKeywords() ?: [], 'type' => $package->getType(), 'homepage' => $package->getHomepage(), 'names' => $package->getNames()]; $json = $this->appendVersions($json, $versions); $json = $this->appendLicenses($json, $package); if ($latestPackage) { $json['latest'] = $latestPackage->getPrettyVersion(); } else { $latestPackage = $package; } if (null !== $package->getSourceType()) { $json['source'] = ['type' => $package->getSourceType(), 'url' => $package->getSourceUrl(), 'reference' => $package->getSourceReference()]; } if (null !== $package->getDistType()) { $json['dist'] = ['type' => $package->getDistType(), 'url' => $package->getDistUrl(), 'reference' => $package->getDistReference()]; } if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) { $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); if (\is_string($path)) { $path = \realpath($path); if ($path !== \false) { $json['path'] = $path; } } else { $json['path'] = null; } if ($package->getReleaseDate() !== null) { $json['released'] = $package->getReleaseDate()->format(\DATE_ATOM); } } if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { $json['replacement'] = $latestPackage->getReplacementPackage(); } if ($package->getSuggests()) { $json['suggests'] = $package->getSuggests(); } if ($package->getSupport()) { $json['support'] = $package->getSupport(); } $json = $this->appendAutoload($json, $package); if ($package->getIncludePaths()) { $json['include_path'] = $package->getIncludePaths(); } $json = $this->appendLinks($json, $package); $this->getIO()->write(JsonFile::encode($json)); } /** * @param JsonStructure $json * @param array $versions * @return JsonStructure */ private function appendVersions(array $json, array $versions) : array { \uasort($versions, 'version_compare'); $versions = \array_keys(\array_reverse($versions)); $json['versions'] = $versions; return $json; } /** * @param JsonStructure $json * @return JsonStructure */ private function appendLicenses(array $json, CompletePackageInterface $package) : array { if ($licenses = $package->getLicense()) { $spdxLicenses = new SpdxLicenses(); $json['licenses'] = \array_map(static function ($licenseId) use($spdxLicenses) { $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url if (!$license) { return $licenseId; } return ['name' => $license[0], 'osi' => $licenseId, 'url' => $license[2]]; }, $licenses); } return $json; } /** * @param JsonStructure $json * @return JsonStructure */ private function appendAutoload(array $json, CompletePackageInterface $package) : array { if (\count($package->getAutoload()) > 0) { $autoload = []; foreach ($package->getAutoload() as $type => $autoloads) { if ($type === 'psr-0' || $type === 'psr-4') { $psr = []; foreach ($autoloads as $name => $path) { if (!$path) { $path = '.'; } $psr[$name ?: '*'] = $path; } $autoload[$type] = $psr; } elseif ($type === 'classmap') { $autoload['classmap'] = $autoloads; } } $json['autoload'] = $autoload; } return $json; } /** * @param JsonStructure $json * @return JsonStructure */ private function appendLinks(array $json, CompletePackageInterface $package) : array { foreach (Link::$TYPES as $linkType) { $json = $this->appendLink($json, $package, $linkType); } return $json; } /** * @param JsonStructure $json * @return JsonStructure */ private function appendLink(array $json, CompletePackageInterface $package, string $linkType) : array { $links = $package->{'get' . \ucfirst($linkType)}(); if ($links) { $json[$linkType] = []; foreach ($links as $link) { $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint(); } } return $json; } /** * Init styles for tree */ protected function initStyles(OutputInterface $output) : void { $this->colors = ['green', 'yellow', 'cyan', 'magenta', 'blue']; foreach ($this->colors as $color) { $style = new OutputFormatterStyle($color); $output->getFormatter()->setStyle($color, $style); } } /** * Display the tree * * @param array> $arrayTree */ protected function displayPackageTree(array $arrayTree) : void { $io = $this->getIO(); foreach ($arrayTree as $package) { $io->write(\sprintf('%s', $package['name']), \false); $io->write(' ' . $package['version'], \false); if (isset($package['description'])) { $io->write(' ' . \strtok($package['description'], "\r\n")); } else { // output newline $io->write(''); } if (isset($package['requires'])) { $requires = $package['requires']; $treeBar = '├'; $j = 0; $total = \count($requires); foreach ($requires as $require) { $requireName = $require['name']; $j++; if ($j === $total) { $treeBar = '└'; } $level = 1; $color = $this->colors[$level]; $info = \sprintf('%s──<%s>%s %s', $treeBar, $color, $requireName, $color, $require['version']); $this->writeTreeLine($info); $treeBar = \str_replace('└', ' ', $treeBar); $packagesInTree = [$package['name'], $requireName]; $this->displayTree($require, $packagesInTree, $treeBar, $level + 1); } } } } /** * Generate the package tree * * @return array>|string|null> */ protected function generatePackageTree(PackageInterface $package, InstalledRepository $installedRepo, RepositoryInterface $remoteRepos) : array { $requires = $package->getRequires(); \ksort($requires); $children = []; foreach ($requires as $requireName => $require) { $packagesInTree = [$package->getName(), $requireName]; $treeChildDesc = ['name' => $requireName, 'version' => $require->getPrettyConstraint()]; $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree); if ($deepChildren) { $treeChildDesc['requires'] = $deepChildren; } $children[] = $treeChildDesc; } $tree = ['name' => $package->getPrettyName(), 'version' => $package->getPrettyVersion(), 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : '']; if ($children) { $tree['requires'] = $children; } return $tree; } /** * Display a package tree * * @param array>|string|null>|string $package * @param array $packagesInTree */ protected function displayTree($package, array $packagesInTree, string $previousTreeBar = '├', int $level = 1) : void { $previousTreeBar = \str_replace('├', '│', $previousTreeBar); if (\is_array($package) && isset($package['requires'])) { $requires = $package['requires']; $treeBar = $previousTreeBar . ' ├'; $i = 0; $total = \count($requires); foreach ($requires as $require) { $currentTree = $packagesInTree; $i++; if ($i === $total) { $treeBar = $previousTreeBar . ' └'; } $colorIdent = $level % \count($this->colors); $color = $this->colors[$colorIdent]; \assert(\is_string($require['name'])); \assert(\is_string($require['version'])); $circularWarn = \in_array($require['name'], $currentTree, \true) ? '(circular dependency aborted here)' : ''; $info = \rtrim(\sprintf('%s──<%s>%s %s %s', $treeBar, $color, $require['name'], $color, $require['version'], $circularWarn)); $this->writeTreeLine($info); $treeBar = \str_replace('└', ' ', $treeBar); $currentTree[] = $require['name']; $this->displayTree($require, $currentTree, $treeBar, $level + 1); } } } /** * Display a package tree * * @param string[] $packagesInTree * @return array>|string>> */ protected function addTree(string $name, Link $link, InstalledRepository $installedRepo, RepositoryInterface $remoteRepos, array $packagesInTree) : array { $children = []; [$package] = $this->getPackage($installedRepo, $remoteRepos, $name, $link->getPrettyConstraint() === 'self.version' ? $link->getConstraint() : $link->getPrettyConstraint()); if (\is_object($package)) { $requires = $package->getRequires(); \ksort($requires); foreach ($requires as $requireName => $require) { $currentTree = $packagesInTree; $treeChildDesc = ['name' => $requireName, 'version' => $require->getPrettyConstraint()]; if (!\in_array($requireName, $currentTree, \true)) { $currentTree[] = $requireName; $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree); if ($deepChildren) { $treeChildDesc['requires'] = $deepChildren; } } $children[] = $treeChildDesc; } } return $children; } private function updateStatusToVersionStyle(string $updateStatus) : string { // 'up-to-date' is printed green // 'semver-safe-update' is printed red // 'update-possible' is printed yellow return \str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['info', 'highlight', 'comment'], $updateStatus); } private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package) : string { if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) { return 'up-to-date'; } $constraint = $package->getVersion(); if (0 !== \strpos($constraint, 'dev-')) { $constraint = '^' . $constraint; } if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) { // it needs an immediate semver-compliant upgrade return 'semver-safe-update'; } // it needs an upgrade but has potential BC breaks so is not urgent return 'update-possible'; } private function writeTreeLine(string $line) : void { $io = $this->getIO(); if (!$io->isDecorated()) { $line = \str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); } $io->write($line); } /** * Given a package, this finds the latest package matching it */ private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter) : ?PackageInterface { // find the latest version allowed in this repo set $name = $package->getName(); $versionSelector = new VersionSelector($this->getRepositorySet($composer), $platformRepo); $stability = $composer->getPackage()->getMinimumStability(); $flags = $composer->getPackage()->getStabilityFlags(); if (isset($flags[$name])) { $stability = \array_search($flags[$name], BasePackage::$stabilities, \true); } $bestStability = $stability; if ($composer->getPackage()->getPreferStable()) { $bestStability = $package->getStability(); } $targetVersion = null; if (0 === \strpos($package->getVersion(), 'dev-')) { $targetVersion = $package->getVersion(); // dev-x branches are considered to be on the latest major version always, do not look up for a new commit as that is deemed a minor upgrade (albeit risky) if ($majorOnly) { return null; } } if ($targetVersion === null) { if ($majorOnly && Preg::isMatch('{^(?P(?:0\\.)+)?(?P\\d+)\\.}', $package->getVersion(), $match)) { $targetVersion = '>=' . $match['zero_major'] . ((int) $match['first_meaningful'] + 1) . ',<9999999-dev'; } if ($minorOnly) { $targetVersion = '^' . $package->getVersion(); } if ($patchOnly) { $trimmedVersion = Preg::replace('{(\\.0)+$}D', '', $package->getVersion()); $partsNeeded = \substr($trimmedVersion, 0, 1) === '0' ? 4 : 3; while (\substr_count($trimmedVersion, '.') + 1 < $partsNeeded) { $trimmedVersion .= '.0'; } $targetVersion = '~' . $trimmedVersion; } } if ($this->getIO()->isVerbose()) { $showWarnings = \true; } else { $showWarnings = static function (PackageInterface $candidate) use($package) : bool { if (\str_starts_with($candidate->getVersion(), 'dev-') || \str_starts_with($package->getVersion(), 'dev-')) { return \false; } return \version_compare($candidate->getVersion(), $package->getVersion(), '<='); }; } $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings); while ($candidate instanceof AliasPackage) { $candidate = $candidate->getAliasOf(); } return $candidate !== \false ? $candidate : null; } private function getRepositorySet(Composer $composer) : RepositorySet { if (!$this->repositorySet) { $this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } return $this->repositorySet; } private function getRelativeTime(\DateTimeInterface $releaseDate) : string { if ($releaseDate->format('Ymd') === \date('Ymd')) { return 'today'; } $diff = $releaseDate->diff(new \DateTimeImmutable()); if ($diff->days < 7) { return 'this week'; } if ($diff->days < 14) { return 'last week'; } if ($diff->m < 1 && $diff->days < 31) { return \floor($diff->days / 7) . ' weeks ago'; } if ($diff->y < 1) { return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago'; } return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago'; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Advisory\Auditor; use Composer\Util\HttpDownloader; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano * @author Ryan Weaver * @author Konstantin Kudryashov * @author Nils Adermann */ class InstallCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; /** * @return void */ protected function configure() { $this->setName('install')->setAliases(['i'])->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json')->setDefinition([new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'), new InputOption('audit', null, InputOption::VALUE_NONE, 'Run an audit after installation is complete.'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.')])->setHelp(<<install command reads the composer.lock file from the current directory, processes it, and downloads and installs all the libraries and dependencies outlined in that file. If the file does not exist it will look for composer.json and do the same. php composer.phar install Read more at https://getcomposer.org/doc/03-cli.md#install-i EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = $this->getIO(); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); } if ($input->getOption('no-suggest')) { $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); } $args = $input->getArgument('packages'); if (\count($args) > 0) { $io->writeError('Invalid argument ' . \implode(' ', $args) . '. Use "composer require ' . \implode(' ', $args) . '" instead to add packages to your composer.json.'); return 1; } if ($input->getOption('no-install')) { $io->writeError('Invalid option "--no-install". Use "composer update --no-install" instead if you are trying to update the composer.lock file.'); return 1; } $composer = $this->requireComposer(); if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) { $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install->setDryRun($input->getOption('dry-run'))->setDownloadOnly($input->getOption('download-only'))->setVerbose($input->getOption('verbose'))->setPreferSource($preferSource)->setPreferDist($preferDist)->setDevMode(!$input->getOption('no-dev'))->setDumpAutoloader(!$input->getOption('no-autoloader'))->setOptimizeAutoloader($optimize)->setClassMapAuthoritative($authoritative)->setApcuAutoloader($apcu, $apcuPrefix)->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))->setAudit($input->getOption('audit'))->setErrorOnAudit($input->getOption('audit'))->setAuditFormat($this->getAuditFormat($input)); if ($input->getOption('no-plugins')) { $install->disablePlugins(); } return $install->run(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\Json\JsonFile; use Composer\Json\JsonValidationException; use Composer\Package\BasePackage; use Composer\Package\Package; use Composer\Pcre\Preg; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Util\Filesystem; use Composer\Util\Silencer; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Process\ExecutableFinder; use _ContaoManager\Symfony\Component\Process\Process; use _ContaoManager\Symfony\Component\Console\Helper\FormatterHelper; /** * @author Justin Rainbow * @author Jordi Boggiano */ class InitCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; use \Composer\Command\PackageDiscoveryTrait; /** @var array */ private $gitConfig; /** * @inheritDoc * * @return void */ protected function configure() { $this->setName('init')->setDescription('Creates a basic composer.json file in current directory')->setDefinition([new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: ' . \implode(', ', \array_keys(BasePackage::$stabilities)) . ')'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)')])->setHelp(<<init command creates a basic composer.json file in the current directory. php composer.phar init Read more at https://getcomposer.org/doc/03-cli.md#init EOT ); } /** * @throws \Seld\JsonLint\ParsingException */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = $this->getIO(); $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload']; $options = \array_filter(\array_intersect_key($input->getOptions(), \array_flip($allowlist))); if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $options['name'])) { throw new \InvalidArgumentException('The package name ' . $options['name'] . ' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'); } if (isset($options['author'])) { $options['authors'] = $this->formatAuthors($options['author']); unset($options['author']); } $repositories = $input->getOption('repository'); if (\count($repositories) > 0) { $config = Factory::createConfig($io); foreach ($repositories as $repo) { $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, \true); } } if (isset($options['stability'])) { $options['minimum-stability'] = $options['stability']; unset($options['stability']); } $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass(); if ([] === $options['require']) { $options['require'] = new \stdClass(); } if (isset($options['require-dev'])) { $options['require-dev'] = $this->formatRequirements($options['require-dev']); if ([] === $options['require-dev']) { $options['require-dev'] = new \stdClass(); } } // --autoload - create autoload object $autoloadPath = null; if (isset($options['autoload'])) { $autoloadPath = $options['autoload']; $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $options['autoload'] = (object) ['psr-4' => [$namespace . '\\' => $autoloadPath]]; } $file = new JsonFile(Factory::getComposerFile()); $json = JsonFile::encode($options); if ($input->isInteractive()) { $io->writeError(['', $json, '']); if (!$io->askConfirmation('Do you confirm generation [yes]? ')) { $io->writeError('Command aborted'); return 1; } } else { if (\json_encode($options) === '{"require":{}}') { throw new \RuntimeException('You have to run this command in interactive mode, or specify at least some data using --name, --require, etc.'); } $io->writeError('Writing ' . $file->getPath()); } $file->write($options); try { $file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { $io->writeError('Schema validation error, aborting'); $errors = ' - ' . \implode(\PHP_EOL . ' - ', $e->getErrors()); $io->writeError($e->getMessage() . ':' . \PHP_EOL . $errors); Silencer::call('unlink', $file->getPath()); return 1; } // --autoload - Create src folder if ($autoloadPath) { $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($autoloadPath); // dump-autoload only for projects without added dependencies. if (!$this->hasDependencies($options)) { $this->runDumpAutoloadCommand($output); } } if ($input->isInteractive() && \is_dir('.git')) { $ignoreFile = \realpath('.gitignore'); if (\false === $ignoreFile) { $ignoreFile = \realpath('.') . '/.gitignore'; } if (!$this->hasVendorIgnore($ignoreFile)) { $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; if ($io->askConfirmation($question)) { $this->addVendorIgnore($ignoreFile); } } } $question = 'Would you like to install dependencies now [yes]? '; if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) { $this->updateDependencies($output); } // --autoload - Show post-install configuration info if ($autoloadPath) { $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $io->writeError('PSR-4 autoloading configured. Use "namespace ' . $namespace . ';" in ' . $autoloadPath); $io->writeError('Include the Composer autoloader with: require \'vendor/autoload.php\';'); } return 0; } /** * @inheritDoc * * @return void */ protected function interact(InputInterface $input, OutputInterface $output) { $git = $this->getGitConfig(); $io = $this->getIO(); /** @var FormatterHelper $formatter */ $formatter = $this->getHelperSet()->get('formatter'); // initialize repos if configured $repositories = $input->getOption('repository'); if (\count($repositories) > 0) { $config = Factory::createConfig($io); $io->loadConfiguration($config); $repoManager = RepositoryFactory::manager($io, $config); $repos = [new PlatformRepository()]; $createDefaultPackagistRepo = \true; foreach ($repositories as $repo) { $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, \true); if (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => \false] || isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => \false]) { $createDefaultPackagistRepo = \false; continue; } $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager); } if ($createDefaultPackagistRepo) { $repos[] = RepositoryFactory::createRepo($io, $config, ['type' => 'composer', 'url' => 'https://repo.packagist.org'], $repoManager); } $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); } $io->writeError(['', $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', \true), '']); // namespace $io->writeError(['', 'This command will guide you through creating your composer.json config.', '']); $cwd = \realpath("."); $name = $input->getOption('name'); if (null === $name) { $name = \basename($cwd); $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '_ContaoManager\\1\\3-\\2\\4', $name); $name = \strtolower($name); if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) { $name = $_SERVER['COMPOSER_DEFAULT_VENDOR'] . '/' . $name; } elseif (isset($git['github.user'])) { $name = $git['github.user'] . '/' . $name; } elseif (!empty($_SERVER['USERNAME'])) { $name = $_SERVER['USERNAME'] . '/' . $name; } elseif (!empty($_SERVER['USER'])) { $name = $_SERVER['USER'] . '/' . $name; } elseif (\get_current_user()) { $name = \get_current_user() . '/' . $name; } else { // package names must be in the format foo/bar $name .= '/' . $name; } $name = \strtolower($name); } $name = $io->askAndValidate('Package name (/) [' . $name . ']: ', static function ($value) use($name) { if (null === $value) { return $name; } if (!Preg::isMatch('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $value)) { throw new \InvalidArgumentException('The package name ' . $value . ' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'); } return $value; }, null, $name); $input->setOption('name', $name); $description = $input->getOption('description') ?: null; $description = $io->ask('Description [' . $description . ']: ', $description); $input->setOption('description', $description); if (null === ($author = $input->getOption('author'))) { if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) { $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR']; } elseif (isset($git['user.name'])) { $author_name = $git['user.name']; } if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) { $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL']; } elseif (isset($git['user.email'])) { $author_email = $git['user.email']; } if (isset($author_name, $author_email)) { $author = \sprintf('%s <%s>', $author_name, $author_email); } } $author = $io->askAndValidate('Author [' . (\is_string($author) ? '' . $author . ', ' : '') . 'n to skip]: ', function ($value) use($author) { if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $author; $author = $this->parseAuthorString($value ?? ''); if ($author['email'] === null) { return $author['name']; } return \sprintf('%s <%s>', $author['name'], $author['email']); }, null, $author); $input->setOption('author', $author); $minimumStability = $input->getOption('stability') ?: null; $minimumStability = $io->askAndValidate('Minimum Stability [' . $minimumStability . ']: ', static function ($value) use($minimumStability) { if (null === $value) { return $minimumStability; } if (!isset(BasePackage::$stabilities[$value])) { throw new \InvalidArgumentException('Invalid minimum stability "' . $value . '". Must be empty or one of: ' . \implode(', ', \array_keys(BasePackage::$stabilities))); } return $value; }, null, $minimumStability); $input->setOption('stability', $minimumStability); $type = $input->getOption('type') ?: \false; $type = $io->ask('Package Type (e.g. library, project, metapackage, composer-plugin) [' . $type . ']: ', $type); $input->setOption('type', $type); if (null === ($license = $input->getOption('license'))) { if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) { $license = $_SERVER['COMPOSER_DEFAULT_LICENSE']; } } $license = $io->ask('License [' . $license . ']: ', $license); $input->setOption('license', $license); $io->writeError(['', 'Define your dependencies.', '']); // prepare to resolve dependencies $repos = $this->getRepos(); $preferredStability = $minimumStability ?: 'stable'; $platformRepo = null; if ($repos instanceof CompositeRepository) { foreach ($repos->getRepositories() as $candidateRepo) { if ($candidateRepo instanceof PlatformRepository) { $platformRepo = $candidateRepo; break; } } } $question = 'Would you like to define your dependencies (require) interactively [yes]? '; $require = $input->getOption('require'); $requirements = []; if (\count($require) > 0 || $io->askConfirmation($question)) { $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability); } $input->setOption('require', $requirements); $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; $requireDev = $input->getOption('require-dev'); $devRequirements = []; if (\count($requireDev) > 0 || $io->askConfirmation($question)) { $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability); } $input->setOption('require-dev', $devRequirements); // --autoload - input and validation $autoload = $input->getOption('autoload') ?: 'src/'; $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $autoload = $io->askAndValidate('Add PSR-4 autoload mapping? Maps namespace "' . $namespace . '" to the entered relative path. [' . $autoload . ', n to skip]: ', static function ($value) use($autoload) { if (null === $value) { return $autoload; } if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $autoload; if (!Preg::isMatch('{^[^/][A-Za-z0-9\\-_/]+/$}', $value)) { throw new \InvalidArgumentException(\sprintf('The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/', $value)); } return $value; }, null, $autoload); $input->setOption('autoload', $autoload); } /** * @return array{name: string, email: string|null} */ private function parseAuthorString(string $author) : array { if (Preg::isMatch('/^(?P[- .,\\p{L}\\p{N}\\p{Mn}\'’"()]+)(?:\\s+<(?P.+?)>)?$/u', $author, $match)) { \assert(\is_string($match['name'])); if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { throw new \InvalidArgumentException('Invalid email "' . $match['email'] . '"'); } return ['name' => \trim($match['name']), 'email' => $match['email']]; } throw new \InvalidArgumentException('Invalid author string. Must be in the formats: ' . 'Jane Doe or John Smith '); } /** * @return array */ protected function formatAuthors(string $author) : array { $author = $this->parseAuthorString($author); if (null === $author['email']) { unset($author['email']); } return [$author]; } /** * Extract namespace from package's vendor name. * * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName" */ public function namespaceFromPackageName(string $packageName) : ?string { if (!$packageName || \strpos($packageName, '/') === \false) { return null; } $namespace = \array_map(static function ($part) : string { $part = Preg::replace('/[^a-z0-9]/i', ' ', $part); $part = \ucwords($part); return \str_replace(' ', '', $part); }, \explode('/', $packageName)); return \implode('\\', $namespace); } /** * @return array */ protected function getGitConfig() : array { if (null !== $this->gitConfig) { return $this->gitConfig; } $finder = new ExecutableFinder(); $gitBin = $finder->find('git'); $cmd = new Process([$gitBin, 'config', '-l']); $cmd->run(); if ($cmd->isSuccessful()) { $this->gitConfig = []; Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches); foreach ($matches[1] as $key => $match) { $this->gitConfig[$match] = $matches[2][$key]; } return $this->gitConfig; } return $this->gitConfig = []; } /** * Checks the local .gitignore file for the Composer vendor directory. * * Tested patterns include: * "/$vendor" * "$vendor" * "$vendor/" * "/$vendor/" * "/$vendor/*" * "$vendor/*" */ protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor') : bool { if (!\file_exists($ignoreFile)) { return \false; } $pattern = \sprintf('{^/?%s(/\\*?)?$}', \preg_quote($vendor)); $lines = \file($ignoreFile, \FILE_IGNORE_NEW_LINES); foreach ($lines as $line) { if (Preg::isMatch($pattern, $line)) { return \true; } } return \false; } protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/') : void { $contents = ""; if (\file_exists($ignoreFile)) { $contents = \file_get_contents($ignoreFile); if (\strpos($contents, "\n") !== 0) { $contents .= "\n"; } } \file_put_contents($ignoreFile, $contents . $vendor . "\n"); } protected function isValidEmail(string $email) : bool { // assume it's valid if we can't validate it if (!\function_exists('filter_var')) { return \true; } return \false !== \filter_var($email, \FILTER_VALIDATE_EMAIL); } private function updateDependencies(OutputInterface $output) : void { try { $updateCommand = $this->getApplication()->find('update'); $this->getApplication()->resetComposer(); $updateCommand->run(new ArrayInput([]), $output); } catch (\Exception $e) { $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.'); } } private function runDumpAutoloadCommand(OutputInterface $output) : void { try { $command = $this->getApplication()->find('dump-autoload'); $this->getApplication()->resetComposer(); $command->run(new ArrayInput([]), $output); } catch (\Exception $e) { $this->getIO()->writeError('Could not run dump-autoload.'); } } /** * @param array> $options */ private function hasDependencies(array $options) : bool { $requires = (array) $options['require']; $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : []; return !empty($requires) || !empty($devRequires); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Script\Event as ScriptEvent; use Composer\Script\ScriptEvents; use Composer\Util\ProcessExecutor; use Composer\Util\Platform; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Fabien Potencier */ class RunScriptCommand extends \Composer\Command\BaseCommand { /** * @var string[] Array with command events */ protected $scriptEvents = [ScriptEvents::PRE_INSTALL_CMD, ScriptEvents::POST_INSTALL_CMD, ScriptEvents::PRE_UPDATE_CMD, ScriptEvents::POST_UPDATE_CMD, ScriptEvents::PRE_STATUS_CMD, ScriptEvents::POST_STATUS_CMD, ScriptEvents::POST_ROOT_PACKAGE_INSTALL, ScriptEvents::POST_CREATE_PROJECT_CMD, ScriptEvents::PRE_ARCHIVE_CMD, ScriptEvents::POST_ARCHIVE_CMD, ScriptEvents::PRE_AUTOLOAD_DUMP, ScriptEvents::POST_AUTOLOAD_DUMP]; protected function configure() : void { $this->setName('run-script')->setAliases(['run'])->setDescription('Runs the scripts defined in composer.json')->setDefinition([new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () { return \array_map(static function ($script) { return $script['name']; }, $this->getScripts()); }), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.')])->setHelp(<<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd Read more at https://getcomposer.org/doc/03-cli.md#run-script-run EOT ); } protected function interact(InputInterface $input, OutputInterface $output) : void { $scripts = $this->getScripts(); if (\count($scripts) === 0) { return; } if ($input->getArgument('script') !== null || $input->getOption('list')) { return; } $options = []; foreach ($scripts as $script) { $options[$script['name']] = $script['description']; } $io = $this->getIO(); $script = $io->select('Script to run: ', $options, '', 1, 'Invalid script name "%s"'); $input->setArgument('script', $script); } protected function execute(InputInterface $input, OutputInterface $output) : int { if ($input->getOption('list')) { return $this->listScripts($output); } $script = $input->getArgument('script'); if ($script === null) { throw new \RuntimeException('Missing required argument "script"'); } if (!\in_array($script, $this->scriptEvents)) { if (\defined('Composer\\Script\\ScriptEvents::' . \str_replace('-', '_', \strtoupper($script)))) { throw new \InvalidArgumentException(\sprintf('Script "%s" cannot be run with this command', $script)); } } $composer = $this->requireComposer(); $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode); $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event); if (!$hasListeners) { throw new \InvalidArgumentException(\sprintf('Script "%s" is not defined in this package', $script)); } $args = $input->getArgument('args'); if (null !== ($timeout = $input->getOption('timeout'))) { if (!\ctype_digit($timeout)) { throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever'); } // Override global timeout set before in Composer by environment or config ProcessExecutor::setTimeout((int) $timeout); } Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); } protected function listScripts(OutputInterface $output) : int { $scripts = $this->getScripts(); if (\count($scripts) === 0) { return 0; } $io = $this->getIO(); $io->writeError('scripts:'); $table = []; foreach ($scripts as $script) { $table[] = [' ' . $script['name'], $script['description']]; } $this->renderTable($table, $output); return 0; } /** * @return list */ private function getScripts() : array { $scripts = $this->requireComposer()->getPackage()->getScripts(); if (\count($scripts) === 0) { return []; } $result = []; foreach ($scripts as $name => $script) { $description = ''; try { $cmd = $this->getApplication()->find($name); if ($cmd instanceof \Composer\Command\ScriptAliasCommand) { $description = $cmd->getDescription(); } } catch (\_ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException $e) { // ignore scripts that have no command associated, like native Composer script listeners } $result[] = ['name' => $name, 'description' => $description]; } return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Advisory\Auditor; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\Silencer; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Config; use Composer\Config\JsonConfigSource; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Semver\VersionParser; use Composer\Package\BasePackage; /** * @author Joshua Estes * @author Jordi Boggiano */ class ConfigCommand extends \Composer\Command\BaseCommand { /** * List of additional configurable package-properties * * @var string[] */ protected const CONFIGURABLE_PACKAGE_PROPERTIES = ['name', 'type', 'description', 'homepage', 'version', 'minimum-stability', 'prefer-stable', 'keywords', 'license', 'repositories', 'suggest', 'extra']; /** * @var Config */ protected $config; /** * @var JsonFile */ protected $configFile; /** * @var JsonConfigSource */ protected $configSource; /** * @var JsonFile */ protected $authConfigFile; /** * @var JsonConfigSource */ protected $authConfigSource; protected function configure() : void { $this->setName('config')->setDescription('Sets config options')->setDefinition([new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'), new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* keys in combination with --json'), new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'), new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'), new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value')])->setHelp(<<%command.full_name% bin-dir bin/ To read a config setting: %command.full_name% bin-dir Outputs: bin To edit the global config.json file: %command.full_name% --global To add a repository: %command.full_name% repositories.foo vcs https://bar.com To remove a repository (repo is a short alias for repositories): %command.full_name% --unset repo.foo To disable packagist: %command.full_name% repo.packagist false You can alter repositories in the global config.json file by passing in the --global option. To add or edit suggested packages you can use: %command.full_name% suggest.package reason for the suggestion To add or edit extra properties you can use: %command.full_name% extra.property value Or to add a complex value you can use json with: %command.full_name% extra.property --json '{"foo":true, "bar": []}' To edit the file in an external editor: %command.full_name% --editor To choose your editor you can set the "EDITOR" env variable. To get a list of configuration values in the file: %command.full_name% --list You can always pass more than one option. As an example, if you want to edit the global config.json file. %command.full_name% --editor --global Read more at https://getcomposer.org/doc/03-cli.md#config EOT ); } /** * @throws \Exception */ protected function initialize(InputInterface $input, OutputInterface $output) : void { parent::initialize($input, $output); if ($input->getOption('global') && null !== $input->getOption('file')) { throw new \RuntimeException('--file and --global can not be combined'); } $io = $this->getIO(); $this->config = Factory::createConfig($io); $configFile = $this->getComposerConfigFile($input, $this->config); // Create global composer.json if this was invoked using `composer global config` if (($configFile === 'composer.json' || $configFile === './composer.json') && !\file_exists($configFile) && \realpath(Platform::getCwd()) === \realpath($this->config->get('home'))) { \file_put_contents($configFile, "{\n}\n"); } $this->configFile = new JsonFile($configFile, null, $io); $this->configSource = new JsonConfigSource($this->configFile); $authConfigFile = $this->getAuthConfigFile($input, $this->config); $this->authConfigFile = new JsonFile($authConfigFile, null, $io); $this->authConfigSource = new JsonConfigSource($this->authConfigFile, \true); // Initialize the global file if it's not there, ignoring any warnings or notices if ($input->getOption('global') && !$this->configFile->exists()) { \touch($this->configFile->getPath()); $this->configFile->write(['config' => new \ArrayObject()]); Silencer::call('chmod', $this->configFile->getPath(), 0600); } if ($input->getOption('global') && !$this->authConfigFile->exists()) { \touch($this->authConfigFile->getPath()); $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject(), 'github-oauth' => new \ArrayObject(), 'gitlab-oauth' => new \ArrayObject(), 'gitlab-token' => new \ArrayObject(), 'http-basic' => new \ArrayObject(), 'bearer' => new \ArrayObject()]); Silencer::call('chmod', $this->authConfigFile->getPath(), 0600); } if (!$this->configFile->exists()) { throw new \RuntimeException(\sprintf('File "%s" cannot be found in the current directory', $configFile)); } } /** * @throws \Seld\JsonLint\ParsingException */ protected function execute(InputInterface $input, OutputInterface $output) : int { // Open file in editor if (\true === $input->getOption('editor')) { $editor = Platform::getEnv('EDITOR'); if (\false === $editor || '' === $editor) { if (Platform::isWindows()) { $editor = 'notepad'; } else { foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) { if (\exec('which ' . $candidate)) { $editor = $candidate; break; } } } } else { $editor = \escapeshellcmd($editor); } $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); \system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); return 0; } if (\false === $input->getOption('global')) { $this->config->merge($this->configFile->read(), $this->configFile->getPath()); $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath()); } $this->getIO()->loadConfiguration($this->config); // List the configuration of the file settings if (\true === $input->getOption('list')) { $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source')); return 0; } $settingKey = $input->getArgument('setting-key'); if (!\is_string($settingKey)) { return 0; } // If the user enters in a config variable, parse it and save to file if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) { throw new \RuntimeException('You can not combine a setting value with --unset'); } // show the value if no value is provided if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) { $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES; $propertiesDefaults = ['type' => 'library', 'description' => '', 'homepage' => '', 'minimum-stability' => 'stable', 'prefer-stable' => \false, 'keywords' => [], 'license' => [], 'suggest' => [], 'extra' => []]; $rawData = $this->configFile->read(); $data = $this->config->all(); $source = $this->config->getSourceOfValue($settingKey); if (Preg::isMatch('/^repos?(?:itories)?(?:\\.(.+))?/', $settingKey, $matches)) { if (!isset($matches[1]) || $matches[1] === '') { $value = $data['repositories'] ?? []; } else { if (!isset($data['repositories'][$matches[1]])) { throw new \InvalidArgumentException('There is no ' . $matches[1] . ' repository defined'); } $value = $data['repositories'][$matches[1]]; } } elseif (\strpos($settingKey, '.')) { $bits = \explode('.', $settingKey); if ($bits[0] === 'extra' || $bits[0] === 'suggest') { $data = $rawData; } else { $data = $data['config']; } $match = \false; foreach ($bits as $bit) { $key = isset($key) ? $key . '.' . $bit : $bit; $match = \false; if (isset($data[$key])) { $match = \true; $data = $data[$key]; unset($key); } } if (!$match) { throw new \RuntimeException($settingKey . ' is not defined.'); } $value = $data; } elseif (isset($data['config'][$settingKey])) { $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); // ensure we get {} output for properties which are objects if ($value === []) { $schema = JsonFile::parseJson((string) \file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); if (isset($schema['properties']['config']['properties'][$settingKey]['type']) && \in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], \true)) { $value = new \stdClass(); } } } elseif (isset($rawData[$settingKey]) && \in_array($settingKey, $properties, \true)) { $value = $rawData[$settingKey]; $source = $this->configFile->getPath(); } elseif (isset($propertiesDefaults[$settingKey])) { $value = $propertiesDefaults[$settingKey]; $source = 'defaults'; } else { throw new \RuntimeException($settingKey . ' is not defined'); } if (\is_array($value) || \is_object($value) || \is_bool($value)) { $value = JsonFile::encode($value, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); } $sourceOfConfigValue = ''; if ($input->getOption('source')) { $sourceOfConfigValue = ' (' . $source . ')'; } $this->getIO()->write($value . $sourceOfConfigValue, \true, IOInterface::QUIET); return 0; } $values = $input->getArgument('setting-value'); // what the user is trying to add/change $booleanValidator = static function ($val) : bool { return \in_array($val, ['true', 'false', '1', '0'], \true); }; $booleanNormalizer = static function ($val) : bool { return $val !== 'false' && (bool) $val; }; // handle config values $uniqueConfigValues = ['process-timeout' => ['is_numeric', 'intval'], 'use-include-path' => [$booleanValidator, $booleanNormalizer], 'use-github-api' => [$booleanValidator, $booleanNormalizer], 'preferred-install' => [static function ($val) : bool { return \in_array($val, ['auto', 'source', 'dist'], \true); }, static function ($val) { return $val; }], 'gitlab-protocol' => [static function ($val) : bool { return \in_array($val, ['git', 'http', 'https'], \true); }, static function ($val) { return $val; }], 'store-auths' => [static function ($val) : bool { return \in_array($val, ['true', 'false', 'prompt'], \true); }, static function ($val) { if ('prompt' === $val) { return 'prompt'; } return $val !== 'false' && (bool) $val; }], 'notify-on-install' => [$booleanValidator, $booleanNormalizer], 'vendor-dir' => ['is_string', static function ($val) { return $val; }], 'bin-dir' => ['is_string', static function ($val) { return $val; }], 'archive-dir' => ['is_string', static function ($val) { return $val; }], 'archive-format' => ['is_string', static function ($val) { return $val; }], 'data-dir' => ['is_string', static function ($val) { return $val; }], 'cache-dir' => ['is_string', static function ($val) { return $val; }], 'cache-files-dir' => ['is_string', static function ($val) { return $val; }], 'cache-repo-dir' => ['is_string', static function ($val) { return $val; }], 'cache-vcs-dir' => ['is_string', static function ($val) { return $val; }], 'cache-ttl' => ['is_numeric', 'intval'], 'cache-files-ttl' => ['is_numeric', 'intval'], 'cache-files-maxsize' => [static function ($val) : bool { return Preg::isMatch('/^\\s*([0-9.]+)\\s*(?:([kmg])(?:i?b)?)?\\s*$/i', $val); }, static function ($val) { return $val; }], 'bin-compat' => [static function ($val) : bool { return \in_array($val, ['auto', 'full', 'proxy', 'symlink']); }, static function ($val) { return $val; }], 'discard-changes' => [static function ($val) : bool { return \in_array($val, ['stash', 'true', 'false', '1', '0'], \true); }, static function ($val) { if ('stash' === $val) { return 'stash'; } return $val !== 'false' && (bool) $val; }], 'autoloader-suffix' => ['is_string', static function ($val) { return $val === 'null' ? null : $val; }], 'sort-packages' => [$booleanValidator, $booleanNormalizer], 'optimize-autoloader' => [$booleanValidator, $booleanNormalizer], 'classmap-authoritative' => [$booleanValidator, $booleanNormalizer], 'apcu-autoloader' => [$booleanValidator, $booleanNormalizer], 'prepend-autoloader' => [$booleanValidator, $booleanNormalizer], 'disable-tls' => [$booleanValidator, $booleanNormalizer], 'secure-http' => [$booleanValidator, $booleanNormalizer], 'cafile' => [static function ($val) : bool { return \file_exists($val) && Filesystem::isReadable($val); }, static function ($val) { return $val === 'null' ? null : $val; }], 'capath' => [static function ($val) : bool { return \is_dir($val) && Filesystem::isReadable($val); }, static function ($val) { return $val === 'null' ? null : $val; }], 'github-expose-hostname' => [$booleanValidator, $booleanNormalizer], 'htaccess-protect' => [$booleanValidator, $booleanNormalizer], 'lock' => [$booleanValidator, $booleanNormalizer], 'allow-plugins' => [$booleanValidator, $booleanNormalizer], 'platform-check' => [static function ($val) : bool { return \in_array($val, ['php-only', 'true', 'false', '1', '0'], \true); }, static function ($val) { if ('php-only' === $val) { return 'php-only'; } return $val !== 'false' && (bool) $val; }], 'use-parent-dir' => [static function ($val) : bool { return \in_array($val, ['true', 'false', 'prompt'], \true); }, static function ($val) { if ('prompt' === $val) { return 'prompt'; } return $val !== 'false' && (bool) $val; }], 'audit.abandoned' => [static function ($val) : bool { return \in_array($val, [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], \true); }, static function ($val) { return $val; }]]; $multiConfigValues = ['github-protocols' => [static function ($vals) { if (!\is_array($vals)) { return 'array expected'; } foreach ($vals as $val) { if (!\in_array($val, ['git', 'https', 'ssh'])) { return 'valid protocols include: git, https, ssh'; } } return \true; }, static function ($vals) { return $vals; }], 'github-domains' => [static function ($vals) { if (!\is_array($vals)) { return 'array expected'; } return \true; }, static function ($vals) { return $vals; }], 'gitlab-domains' => [static function ($vals) { if (!\is_array($vals)) { return 'array expected'; } return \true; }, static function ($vals) { return $vals; }], 'audit.ignore' => [static function ($vals) { if (!\is_array($vals)) { return 'array expected'; } return \true; }, static function ($vals) { return $vals; }]]; // allow unsetting audit config entirely if ($input->getOption('unset') && $settingKey === 'audit') { $this->configSource->removeConfigSetting($settingKey); return 0; } if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) { $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); } $this->configSource->removeConfigSetting($settingKey); return 0; } if (isset($uniqueConfigValues[$settingKey])) { $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); return 0; } if (isset($multiConfigValues[$settingKey])) { $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); return 0; } // handle preferred-install per-package config if (Preg::isMatch('/^preferred-install\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } [$validator] = $uniqueConfigValues['preferred-install']; if (!$validator($values[0])) { throw new \RuntimeException('Invalid value for ' . $settingKey . '. Should be one of: auto, source, or dist'); } $this->configSource->addConfigSetting($settingKey, $values[0]); return 0; } // handle allow-plugins config setting elements true or false to add/remove if (Preg::isMatch('{^allow-plugins\\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } if (\true !== $booleanValidator($values[0])) { throw new \RuntimeException(\sprintf('"%s" is an invalid value', $values[0])); } $normalizedValue = $booleanNormalizer($values[0]); $this->configSource->addConfigSetting($settingKey, $normalizedValue); return 0; } // handle properties $uniqueProps = ['name' => ['is_string', static function ($val) { return $val; }], 'type' => ['is_string', static function ($val) { return $val; }], 'description' => ['is_string', static function ($val) { return $val; }], 'homepage' => ['is_string', static function ($val) { return $val; }], 'version' => ['is_string', static function ($val) { return $val; }], 'minimum-stability' => [static function ($val) : bool { return isset(BasePackage::$stabilities[VersionParser::normalizeStability($val)]); }, static function ($val) : string { return VersionParser::normalizeStability($val); }], 'prefer-stable' => [$booleanValidator, $booleanNormalizer]]; $multiProps = ['keywords' => [static function ($vals) { if (!\is_array($vals)) { return 'array expected'; } return \true; }, static function ($vals) { return $vals; }], 'license' => [static function ($vals) { if (!\is_array($vals)) { return 'array expected'; } return \true; }, static function ($vals) { return $vals; }]]; if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || \strpos($settingKey, 'extra.') === 0)) { throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); } if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { $this->configSource->removeProperty($settingKey); return 0; } if (isset($uniqueProps[$settingKey])) { $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); return 0; } if (isset($multiProps[$settingKey])) { $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); return 0; } // handle repositories if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeRepository($matches[1]); return 0; } if (2 === \count($values)) { $this->configSource->addRepository($matches[1], ['type' => $values[0], 'url' => $values[1]], $input->getOption('append')); return 0; } if (1 === \count($values)) { $value = \strtolower($values[0]); if (\true === $booleanValidator($value)) { if (\false === $booleanNormalizer($value)) { $this->configSource->addRepository($matches[1], \false, $input->getOption('append')); return 0; } } else { $value = JsonFile::parseJson($values[0]); $this->configSource->addRepository($matches[1], $value, $input->getOption('append')); return 0; } } throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); } // handle extra if (Preg::isMatch('/^extra\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } $value = $values[0]; if ($input->getOption('json')) { $value = JsonFile::parseJson($value); if ($input->getOption('merge')) { $currentValue = $this->configFile->read(); $bits = \explode('.', $settingKey); foreach ($bits as $bit) { $currentValue = $currentValue[$bit] ?? null; } if (\is_array($currentValue)) { $value = \array_merge($currentValue, $value); } } } $this->configSource->addProperty($settingKey, $value); return 0; } // handle suggest if (Preg::isMatch('/^suggest\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } $this->configSource->addProperty($settingKey, \implode(' ', $values)); return 0; } // handle unsetting extra/suggest if (\in_array($settingKey, ['suggest', 'extra'], \true) && $input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } // handle platform if (Preg::isMatch('/^platform\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? \false : $values[0]); return 0; } // handle unsetting platform if ($settingKey === 'platform' && $input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } // handle auth if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|bearer)\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->authConfigSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); return 0; } if ($matches[1] === 'bitbucket-oauth') { if (2 !== \count($values)) { throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got ' . \count($values)); } $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->authConfigSource->addConfigSetting($matches[1] . '.' . $matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]); } elseif ($matches[1] === 'gitlab-token' && 2 === \count($values)) { $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->authConfigSource->addConfigSetting($matches[1] . '.' . $matches[2], ['username' => $values[0], 'token' => $values[1]]); } elseif (\in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], \true)) { if (1 !== \count($values)) { throw new \RuntimeException('Too many arguments, expected only one token'); } $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->authConfigSource->addConfigSetting($matches[1] . '.' . $matches[2], $values[0]); } elseif ($matches[1] === 'http-basic') { if (2 !== \count($values)) { throw new \RuntimeException('Expected two arguments (username, password), got ' . \count($values)); } $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->authConfigSource->addConfigSetting($matches[1] . '.' . $matches[2], ['username' => $values[0], 'password' => $values[1]]); } return 0; } // handle script if (Preg::isMatch('/^scripts\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } $this->configSource->addProperty($settingKey, \count($values) > 1 ? $values : $values[0]); return 0; } // handle unsetting other top level properties if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } throw new \InvalidArgumentException('Setting ' . $settingKey . ' does not exist or is not supported by this command'); } /** * @param array{callable, callable} $callbacks Validator and normalizer callbacks * @param array $values */ protected function handleSingleValue(string $key, array $callbacks, array $values, string $method) : void { [$validator, $normalizer] = $callbacks; if (1 !== \count($values)) { throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); } if (\true !== ($validation = $validator($values[0]))) { throw new \RuntimeException(\sprintf('"%s" is an invalid value' . ($validation ? ' (' . $validation . ')' : ''), $values[0])); } $normalizedValue = $normalizer($values[0]); if ($key === 'disable-tls') { if (!$normalizedValue && $this->config->get('disable-tls')) { $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); } elseif ($normalizedValue && !$this->config->get('disable-tls')) { $this->getIO()->writeError('You are now running Composer with SSL/TLS protection disabled.'); } } \call_user_func([$this->configSource, $method], $key, $normalizedValue); } /** * @param array{callable, callable} $callbacks Validator and normalizer callbacks * @param array $values */ protected function handleMultiValue(string $key, array $callbacks, array $values, string $method) : void { [$validator, $normalizer] = $callbacks; if (\true !== ($validation = $validator($values))) { throw new \RuntimeException(\sprintf('%s is an invalid value' . ($validation ? ' (' . $validation . ')' : ''), \json_encode($values))); } \call_user_func([$this->configSource, $method], $key, $normalizer($values)); } /** * Display the contents of the file in a pretty formatted way * * @param array $contents * @param array $rawContents */ protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = \false) : void { $origK = $k; $io = $this->getIO(); foreach ($contents as $key => $value) { if ($k === null && !\in_array($key, ['config', 'repositories'])) { continue; } $rawVal = $rawContents[$key] ?? null; if (\is_array($value) && (!\is_numeric(\key($value)) || $key === 'repositories' && null === $k)) { $k .= Preg::replace('{^config\\.}', '', $key . '.'); $this->listConfiguration($value, $rawVal, $output, $k, $showSource); $k = $origK; continue; } if (\is_array($value)) { $value = \array_map(static function ($val) { return \is_array($val) ? \json_encode($val) : $val; }, $value); $value = '[' . \implode(', ', $value) . ']'; } if (\is_bool($value)) { $value = \var_export($value, \true); } $source = ''; if ($showSource) { $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')'; } if (null !== $k && 0 === \strpos($k, 'repositories')) { $link = 'https://getcomposer.org/doc/05-repositories.md'; } else { $id = Preg::replace('{\\..*$}', '', $k === '' || $k === null ? (string) $key : $k); $id = Preg::replace('{[^a-z0-9]}i', '-', \strtolower(\trim($id))); $id = Preg::replace('{-+}', '-', $id); $link = 'https://getcomposer.org/doc/06-config.md#' . $id; } if (\is_string($rawVal) && $rawVal !== $value) { $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')' . $source, \true, IOInterface::QUIET); } else { $io->write('[' . $k . $key . '] ' . $value . '' . $source, \true, IOInterface::QUIET); } } } /** * Get the local composer.json, global config.json, or the file passed by the user */ private function getComposerConfigFile(InputInterface $input, Config $config) : string { return $input->getOption('global') ? $config->get('home') . '/config.json' : ($input->getOption('file') ?: Factory::getComposerFile()); } /** * Get the local auth.json or global auth.json, or if the user passed in a file to use, * the corresponding auth.json */ private function getAuthConfigFile(InputInterface $input, Config $config) : string { return $input->getOption('global') ? $config->get('home') . '/auth.json' : \dirname($this->getComposerConfigFile($input, $config)) . '/auth.json'; } /** * Suggest setting-keys, while taking given options in acount. */ private function suggestSettingKeys() : \Closure { return function (CompletionInput $input) : array { if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) { return []; } // initialize configuration $config = Factory::createConfig(); // load configuration $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); if ($configFile->exists()) { $config->merge($configFile->read(), $configFile->getPath()); } // load auth-configuration $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config)); if ($authConfigFile->exists()) { $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath()); } // collect all configuration setting-keys $rawConfig = $config->raw(); $keys = \array_merge($this->flattenSettingKeys($rawConfig['config']), $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.')); // if unsetting … if ($input->getOption('unset')) { // … keep only the currently customized setting-keys … $sources = [$configFile->getPath(), $authConfigFile->getPath()]; $keys = \array_filter($keys, static function (string $key) use($config, $sources) : bool { return \in_array($config->getSourceOfValue($key), $sources, \true); }); // … else if showing or setting a value … } else { // … add all configurable package-properties, no matter if it exist $keys = \array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES); // it would be nice to distinguish between showing and setting // a value, but that makes the implementation much more complex // and partially impossible because symfony's implementation // does not complete arguments followed by other arguments } // add all existing configurable package-properties if ($configFile->exists()) { $properties = \array_filter($configFile->read(), static function (string $key) : bool { return \in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, \true); }, \ARRAY_FILTER_USE_KEY); $keys = \array_merge($keys, $this->flattenSettingKeys($properties)); } // filter settings-keys by completion value $completionValue = $input->getCompletionValue(); if ($completionValue !== '') { $keys = \array_filter($keys, static function (string $key) use($completionValue) : bool { return \str_starts_with($key, $completionValue); }); } \sort($keys); return \array_unique($keys); }; } /** * build a flat list of dot-separated setting-keys from given config * * @param array $config * @return string[] */ private function flattenSettingKeys(array $config, string $prefix = '') : array { $keys = []; foreach ($config as $key => $value) { $keys[] = [$prefix . $key]; // array-lists must not be added to completion // sub-keys of repository-keys must not be added to completion if (\is_array($value) && !\array_is_list($value) && $prefix !== 'repositories.') { $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.'); } } return \array_merge(...$keys); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Console\Input\InputOption; use Composer\Json\JsonFile; use Composer\Package\CompletePackageInterface; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\RepositoryUtils; use Composer\Util\PackageInfo; use Composer\Util\PackageSorter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Benoît Merlet */ class LicensesCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('licenses')->setDescription('Shows information about licenses of dependencies')->setDefinition([new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text, json or summary', 'text', ['text', 'json', 'summary']), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.')])->setHelp(<<requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $root = $composer->getPackage(); $repo = $composer->getRepositoryManager()->getLocalRepository(); if ($input->getOption('no-dev')) { $packages = RepositoryUtils::filterRequiredPackages($repo->getPackages(), $root); } else { $packages = $repo->getPackages(); } $packages = PackageSorter::sortPackagesAlphabetically($packages); $io = $this->getIO(); switch ($format = $input->getOption('format')) { case 'text': $io->write('Name: ' . $root->getPrettyName() . ''); $io->write('Version: ' . $root->getFullPrettyVersion() . ''); $io->write('Licenses: ' . (\implode(', ', $root->getLicense()) ?: 'none') . ''); $io->write('Dependencies:'); $io->write(''); $table = new Table($output); $table->setStyle('compact'); $table->setHeaders(['Name', 'Version', 'Licenses']); foreach ($packages as $package) { $link = PackageInfo::getViewSourceOrHomepageUrl($package); if ($link !== null) { $name = '' . $package->getPrettyName() . ''; } else { $name = $package->getPrettyName(); } $table->addRow([$name, $package->getFullPrettyVersion(), \implode(', ', $package instanceof CompletePackageInterface ? $package->getLicense() : []) ?: 'none']); } $table->render(); break; case 'json': $dependencies = []; foreach ($packages as $package) { $dependencies[$package->getPrettyName()] = ['version' => $package->getFullPrettyVersion(), 'license' => $package instanceof CompletePackageInterface ? $package->getLicense() : []]; } $io->write(JsonFile::encode(['name' => $root->getPrettyName(), 'version' => $root->getFullPrettyVersion(), 'license' => $root->getLicense(), 'dependencies' => $dependencies])); break; case 'summary': $usedLicenses = []; foreach ($packages as $package) { $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : []; if (\count($licenses) === 0) { $licenses[] = 'none'; } foreach ($licenses as $licenseName) { if (!isset($usedLicenses[$licenseName])) { $usedLicenses[$licenseName] = 0; } $usedLicenses[$licenseName]++; } } // Sort licenses so that the most used license will appear first \arsort($usedLicenses, \SORT_NUMERIC); $rows = []; foreach ($usedLicenses as $usedLicense => $numberOfDependencies) { $rows[] = [$usedLicense, $numberOfDependencies]; } $symfonyIo = new SymfonyStyle($input, $output); $symfonyIo->table(['License', 'Number of dependencies'], $rows); break; default: throw new \RuntimeException(\sprintf('Unsupported format "%s". See help for supported formats.', $format)); } return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\DependencyResolver\Request; use Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\Loader\RootPackageLoader; use Composer\Pcre\Preg; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\Version\VersionParser; use Composer\Semver\Intervals; use Composer\Util\HttpDownloader; use Composer\Advisory\Auditor; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Question\Question; /** * @author Jordi Boggiano * @author Nils Adermann */ class UpdateCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; /** * @return void */ protected function configure() { $this->setName('update')->setAliases(['u', 'upgrade'])->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file')->setDefinition([new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage(\false)), new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('with-dependencies', 'w', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements.'), new InputOption('with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.')])->setHelp(<<update command reads the composer.json file from the current directory, processes it, and updates, removes or installs all the dependencies. php composer.phar update To limit the update operation to a few packages, you can list the package(s) you want to update as such: php composer.phar update vendor/package1 foo/mypackage [...] You may also use an asterisk (*) pattern to limit the update operation to package(s) from a specific vendor: php composer.phar update vendor/package1 foo/* [...] To run an update with more restrictive constraints you can use: php composer.phar update --with vendor/package:1.0.* To run a partial update with more restrictive constraints you can use the shorthand: php composer.phar update vendor/package:1.0.* To select packages names interactively with auto-completion use -i. Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = $this->getIO(); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); } if ($input->getOption('no-suggest')) { $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); } $composer = $this->requireComposer(); if (!HttpDownloader::isCurlEnabled()) { $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); } $packages = $input->getArgument('packages'); $reqs = $this->formatRequirements($input->getOption('with')); // extract --with shorthands from the allowlist if (\count($packages) > 0) { $allowlistPackagesWithRequirements = \array_filter($packages, static function ($pkg) : bool { return Preg::isMatch('{\\S+[ =:]\\S+}', $pkg); }); foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) { $reqs[$package] = $constraint; } // replace the foo/bar:req by foo/bar in the allowlist foreach ($allowlistPackagesWithRequirements as $package) { $packageName = Preg::replace('{^([^ =:]+)[ =:].*$}', '$1', $package); $index = \array_search($package, $packages); $packages[$index] = $packageName; } } $rootPackage = $composer->getPackage(); $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences())); $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags())); $parser = new VersionParser(); $temporaryConstraints = []; $rootRequirements = \array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()); foreach ($reqs as $package => $constraint) { $package = \strtolower($package); $parsedConstraint = $parser->parseConstraints($constraint); $temporaryConstraints[$package] = $parsedConstraint; if (isset($rootRequirements[$package]) && !Intervals::haveIntersections($parsedConstraint, $rootRequirements[$package]->getConstraint())) { $io->writeError('The temporary constraint "' . $constraint . '" for "' . $package . '" must be a subset of the constraint in your composer.json (' . $rootRequirements[$package]->getPrettyConstraint() . ')'); $io->write('Run `composer require ' . $package . '` or `composer require ' . $package . ':' . $constraint . '` instead to replace the constraint'); return self::FAILURE; } } if ($input->getOption('interactive')) { $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); } if ($input->getOption('root-reqs')) { $requires = \array_keys($rootPackage->getRequires()); if (!$input->getOption('no-dev')) { $requires = \array_merge($requires, \array_keys($rootPackage->getDevRequires())); } if (!empty($packages)) { $packages = \array_intersect($packages, $requires); } else { $packages = $requires; } } // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead // they are further mutually exclusive with listing actual package names $filteredPackages = \array_filter($packages, static function ($package) : bool { return !\in_array($package, ['lock', 'nothing', 'mirrors'], \true); }); $updateMirrors = $input->getOption('lock') || \count($filteredPackages) !== \count($packages); $packages = $filteredPackages; if ($updateMirrors && !empty($packages)) { $io->writeError('You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.'); return -1; } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install = Installer::create($io, $composer); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; if ($input->getOption('with-all-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; } elseif ($input->getOption('with-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; } $install->setDryRun($input->getOption('dry-run'))->setVerbose($input->getOption('verbose'))->setPreferSource($preferSource)->setPreferDist($preferDist)->setDevMode(!$input->getOption('no-dev'))->setDumpAutoloader(!$input->getOption('no-autoloader'))->setOptimizeAutoloader($optimize)->setClassMapAuthoritative($authoritative)->setApcuAutoloader($apcu, $apcuPrefix)->setUpdate(\true)->setInstall(!$input->getOption('no-install'))->setUpdateMirrors($updateMirrors)->setUpdateAllowList($packages)->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))->setPreferStable($input->getOption('prefer-stable'))->setPreferLowest($input->getOption('prefer-lowest'))->setTemporaryConstraints($temporaryConstraints)->setAudit(!$input->getOption('no-audit'))->setAuditFormat($this->getAuditFormat($input))->setMinimalUpdate($input->getOption('minimal-changes')); if ($input->getOption('no-plugins')) { $install->disablePlugins(); } return $install->run(); } /** * @param array $packages * @return array */ private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages) : array { if (!$input->isInteractive()) { throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); } $requires = \array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()); $autocompleterValues = []; foreach ($requires as $require) { $target = $require->getTarget(); $autocompleterValues[\strtolower($target)] = $target; } $installedPackages = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); foreach ($installedPackages as $package) { $autocompleterValues[$package->getName()] = $package->getPrettyName(); } $helper = $this->getHelper('question'); $question = new Question('Enter package name: ', null); $io->writeError('Press enter without value to end submission'); do { $autocompleterValues = \array_diff($autocompleterValues, $packages); $question->setAutocompleterValues($autocompleterValues); $addedPackage = $helper->ask($input, $output, $question); if (!\is_string($addedPackage) || empty($addedPackage)) { break; } $addedPackage = \strtolower($addedPackage); if (!\in_array($addedPackage, $packages)) { $packages[] = $addedPackage; } } while (\true); $packages = \array_filter($packages); if (!$packages) { throw new \InvalidArgumentException('You must enter minimum one package.'); } $table = new Table($output); $table->setHeaders(['Selected packages']); foreach ($packages as $package) { $table->addRow([$package]); } $table->render(); if ($io->askConfirmation(\sprintf('Would you like to continue and update the above package%s [yes]? ', 1 === \count($packages) ? '' : 's'))) { return $packages; } throw new \RuntimeException('Installation aborted.'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Downloader\ChangeReportInterface; use Composer\Downloader\DvcsDownloaderInterface; use Composer\Downloader\VcsCapableDownloaderInterface; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Script\ScriptEvents; use Composer\Util\ProcessExecutor; /** * @author Tiago Ribeiro * @author Rui Marinho */ class StatusCommand extends \Composer\Command\BaseCommand { private const EXIT_CODE_ERRORS = 1; private const EXIT_CODE_UNPUSHED_CHANGES = 2; private const EXIT_CODE_VERSION_CHANGES = 4; /** * @throws \Symfony\Component\Console\Exception\InvalidArgumentException */ protected function configure() : void { $this->setName('status')->setDescription('Shows a list of locally modified packages')->setDefinition([new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.')])->setHelp(<<requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); // Dispatch pre-status-command $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, \true); $exitCode = $this->doExecute($input); // Dispatch post-status-command $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, \true); return $exitCode; } private function doExecute(InputInterface $input) : int { // init repos $composer = $this->requireComposer(); $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); $dm = $composer->getDownloadManager(); $im = $composer->getInstallationManager(); $errors = []; $io = $this->getIO(); $unpushedChanges = []; $vcsVersionChanges = []; $parser = new VersionParser(); $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser); $dumper = new ArrayDumper(); // list packages foreach ($installedRepo->getCanonicalPackages() as $package) { $downloader = $dm->getDownloaderForPackage($package); $targetDir = $im->getInstallPath($package); if ($targetDir === null) { continue; } if ($downloader instanceof ChangeReportInterface) { if (\is_link($targetDir)) { $errors[$targetDir] = $targetDir . ' is a symbolic link.'; } if (null !== ($changes = $downloader->getLocalChanges($package, $targetDir))) { $errors[$targetDir] = $changes; } } if ($downloader instanceof VcsCapableDownloaderInterface) { if ($downloader->getVcsReference($package, $targetDir)) { switch ($package->getInstallationSource()) { case 'source': $previousRef = $package->getSourceReference(); break; case 'dist': $previousRef = $package->getDistReference(); break; default: $previousRef = null; } $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef && $currentVersion['pretty_version'] !== $previousRef) { $vcsVersionChanges[$targetDir] = ['previous' => ['version' => $package->getPrettyVersion(), 'ref' => $previousRef], 'current' => ['version' => $currentVersion['pretty_version'], 'ref' => $currentVersion['commit']]]; } } } if ($downloader instanceof DvcsDownloaderInterface) { if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { $unpushedChanges[$targetDir] = $unpushed; } } } // output errors/warnings if (!$errors && !$unpushedChanges && !$vcsVersionChanges) { $io->writeError('No local changes'); return 0; } if ($errors) { $io->writeError('You have changes in the following dependencies:'); foreach ($errors as $path => $changes) { if ($input->getOption('verbose')) { $indentedChanges = \implode("\n", \array_map(static function ($line) : string { return ' ' . \ltrim($line); }, \explode("\n", $changes))); $io->write('' . $path . ':'); $io->write($indentedChanges); } else { $io->write($path); } } } if ($unpushedChanges) { $io->writeError('You have unpushed changes on the current branch in the following dependencies:'); foreach ($unpushedChanges as $path => $changes) { if ($input->getOption('verbose')) { $indentedChanges = \implode("\n", \array_map(static function ($line) : string { return ' ' . \ltrim($line); }, \explode("\n", $changes))); $io->write('' . $path . ':'); $io->write($indentedChanges); } else { $io->write($path); } } } if ($vcsVersionChanges) { $io->writeError('You have version variations in the following dependencies:'); foreach ($vcsVersionChanges as $path => $changes) { if ($input->getOption('verbose')) { // If we don't can't find a version, use the ref instead. $currentVersion = $changes['current']['version'] ?: $changes['current']['ref']; $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref']; if ($io->isVeryVerbose()) { // Output the ref regardless of whether or not it's being used as the version $currentVersion .= \sprintf(' (%s)', $changes['current']['ref']); $previousVersion .= \sprintf(' (%s)', $changes['previous']['ref']); } $io->write('' . $path . ':'); $io->write(\sprintf(' From %s to %s', $previousVersion, $currentVersion)); } else { $io->write($path); } } } if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) { $io->writeError('Use --verbose (-v) to see a list of files'); } return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\RootPackage; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\RootPackageRepository; use Composer\Repository\InstalledRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\Package\Version\VersionParser; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Util\PackageInfo; /** * Base implementation for commands mapping dependency relationships. * * @author Niels Keurentjes */ abstract class BaseDependencyCommand extends \Composer\Command\BaseCommand { protected const ARGUMENT_PACKAGE = 'package'; protected const ARGUMENT_CONSTRAINT = 'version'; protected const OPTION_RECURSIVE = 'recursive'; protected const OPTION_TREE = 'tree'; /** @var string[] */ protected $colors; /** * Execute the command. * * @param bool $inverted Whether to invert matching process (why-not vs why behaviour) * @return int Exit code of the operation. */ protected function doExecute(InputInterface $input, OutputInterface $output, bool $inverted = \false) : int { // Emit command event on startup $composer = $this->requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $repos = []; $repos[] = new RootPackageRepository(clone $composer->getPackage()); if ($input->getOption('locked')) { $locker = $composer->getLocker(); if (!$locker->isLocked()) { throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked'); } $repos[] = $locker->getLockedRepository(\true); $repos[] = new PlatformRepository([], $locker->getPlatformOverrides()); } else { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $rootPkg = $composer->getPackage(); if (\count($localRepo->getPackages()) === 0 && (\count($rootPkg->getRequires()) > 0 || \count($rootPkg->getDevRequires()) > 0)) { $output->writeln('No dependencies installed. Try running composer install or update, or use --locked.'); return 1; } $repos[] = $localRepo; $platformOverrides = $composer->getConfig()->get('platform') ?: []; $repos[] = new PlatformRepository([], $platformOverrides); } $installedRepo = new InstalledRepository($repos); // Parse package name and constraint $needle = $input->getArgument(self::ARGUMENT_PACKAGE); $textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*'; // Find packages that are or provide the requested package first $packages = $installedRepo->findPackagesWithReplacersAndProviders($needle); if (empty($packages)) { throw new \InvalidArgumentException(\sprintf('Could not find package "%s" in your project', $needle)); } // If the version we ask for is not installed then we need to locate it in remote repos and add it. // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. if (!$installedRepo->findPackage($needle, $textConstraint)) { $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { $installedRepo->addRepository(new InstalledArrayRepository([clone $match])); } else { $this->getIO()->writeError('Package "' . $needle . '" could not be found with constraint "' . $textConstraint . '", results below will most likely be incomplete.'); } } // Include replaced packages for inverted lookups as they are then the actual starting point to consider $needles = [$needle]; if ($inverted) { foreach ($packages as $package) { $needles = \array_merge($needles, \array_map(static function (Link $link) : string { return $link->getTarget(); }, $package->getReplaces())); } } // Parse constraint if one was supplied if ('*' !== $textConstraint) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($textConstraint); } else { $constraint = null; } // Parse rendering options $renderTree = $input->getOption(self::OPTION_TREE); $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); $return = $inverted ? 1 : 0; // Resolve dependencies $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive); if (empty($results)) { $extra = null !== $constraint ? \sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; $this->getIO()->writeError(\sprintf('There is no installed package depending on "%s"%s', $needle, $extra)); $return = $inverted ? 0 : 1; } elseif ($renderTree) { $this->initStyles($output); $root = $packages[0]; $this->getIO()->write(\sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : '')); $this->printTree($results); } else { $this->printTable($output, $results); } if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT)) { $composerCommand = 'update'; foreach ($composer->getPackage()->getRequires() as $rootRequirement) { if ($rootRequirement->getTarget() === $needle) { $composerCommand = 'require'; break; } } foreach ($composer->getPackage()->getDevRequires() as $rootRequirement) { if ($rootRequirement->getTarget() === $needle) { $composerCommand = 'require --dev'; break; } } $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer ' . $composerCommand . ' "' . $needle . ':' . $textConstraint . '" --dry-run` to get another view on the problem.'); } return $return; } /** * Assembles and prints a bottom-up table of the dependencies. * * @param array{PackageInterface, Link, mixed}[] $results */ protected function printTable(OutputInterface $output, $results) : void { $table = []; $doubles = []; do { $queue = []; $rows = []; foreach ($results as $result) { /** * @var PackageInterface $package * @var Link $link */ [$package, $link, $children] = $result; $unique = (string) $link; if (isset($doubles[$unique])) { continue; } $doubles[$unique] = \true; $version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion(); $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); $rows[] = [$nameWithLink, $version, $link->getDescription(), \sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())]; if ($children) { $queue = \array_merge($queue, $children); } } $results = $queue; $table = \array_merge($rows, $table); } while (!empty($results)); $this->renderTable($table, $output); } /** * Init styles for tree */ protected function initStyles(OutputInterface $output) : void { $this->colors = ['green', 'yellow', 'cyan', 'magenta', 'blue']; foreach ($this->colors as $color) { $style = new OutputFormatterStyle($color); $output->getFormatter()->setStyle($color, $style); } } /** * Recursively prints a tree of the selected results. * * @param array{PackageInterface, Link, mixed[]|bool}[] $results Results to be printed at this level. * @param string $prefix Prefix of the current tree level. * @param int $level Current level of recursion. */ protected function printTree(array $results, string $prefix = '', int $level = 1) : void { $count = \count($results); $idx = 0; foreach ($results as $result) { [$package, $link, $children] = $result; $color = $this->colors[$level % \count($this->colors)]; $prevColor = $this->colors[($level - 1) % \count($this->colors)]; $isLast = ++$idx === $count; $versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion(); $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); $packageText = \rtrim(\sprintf('<%s>%s %s', $color, $nameWithLink, $versionText)); $linkText = \sprintf('%s <%s>%s %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint()); $circularWarn = $children === \false ? '(circular dependency aborted here)' : ''; $this->writeTreeLine(\rtrim(\sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn))); if ($children) { $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1); } } } private function writeTreeLine(string $line) : void { $io = $this->getIO(); if (!$io->isDecorated()) { $line = \str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); } $io->write($line); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; /** * @author Niels Keurentjes */ class DependsCommand extends \Composer\Command\BaseDependencyCommand { use \Composer\Command\CompletionTrait; /** * Configure command metadata. */ protected function configure() : void { $this->setName('depends')->setAliases(['why'])->setDescription('Shows which packages cause the given package to be installed')->setDefinition([new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(\true, \true)), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock')])->setHelp(<<php composer.phar depends composer/composer Read more at https://getcomposer.org/doc/03-cli.md#depends-why EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { return parent::doExecute($input, $output); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; /** * @author Niels Keurentjes */ class ProhibitsCommand extends \Composer\Command\BaseDependencyCommand { use \Composer\Command\CompletionTrait; /** * Configure command metadata. */ protected function configure() : void { $this->setName('prohibits')->setAliases(['why-not'])->setDescription('Shows which packages prevent the given package from being installed')->setDefinition([new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()), new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock')])->setHelp(<<php composer.phar prohibits composer/composer Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { return parent::doExecute($input, $output, \true); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\PlatformRepository; use Composer\Repository\RootPackageRepository; use Composer\Repository\InstalledRepository; use Composer\Json\JsonFile; class CheckPlatformReqsCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('check-platform-reqs')->setDescription('Check that platform requirements are satisfied')->setDefinition([new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Checks requirements only from the lock file, not from installed packages.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text'])])->setHelp(<<php composer.phar check-platform-reqs EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); $requires = []; $removePackages = []; if ($input->getOption('lock')) { $this->getIO()->writeError('Checking ' . ($input->getOption('no-dev') ? 'non-dev ' : '') . 'platform requirements using the lock file'); $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); } else { $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); // fallback to lockfile if installed repo is empty if (!$installedRepo->getPackages()) { $this->getIO()->writeError('No vendor dir present, checking ' . ($input->getOption('no-dev') ? 'non-dev ' : '') . 'platform requirements from the lock file'); $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); } else { if ($input->getOption('no-dev')) { $removePackages = $installedRepo->getDevPackageNames(); } $this->getIO()->writeError('Checking ' . ($input->getOption('no-dev') ? 'non-dev ' : '') . 'platform requirements for packages in the vendor dir'); } } if (!$input->getOption('no-dev')) { foreach ($composer->getPackage()->getDevRequires() as $require => $link) { $requires[$require] = [$link]; } } $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]); foreach ($installedRepo->getPackages() as $package) { if (\in_array($package->getName(), $removePackages, \true)) { continue; } foreach ($package->getRequires() as $require => $link) { $requires[$require][] = $link; } } \ksort($requires); $installedRepo->addRepository(new PlatformRepository([], [])); $results = []; $exitCode = 0; /** * @var Link[] $links */ foreach ($requires as $require => $links) { if (PlatformRepository::isPlatformPackage($require)) { $candidates = $installedRepo->findPackagesWithReplacersAndProviders($require); if ($candidates) { $reqResults = []; foreach ($candidates as $candidate) { $candidateConstraint = null; if ($candidate->getName() === $require) { $candidateConstraint = new Constraint('=', $candidate->getVersion()); $candidateConstraint->setPrettyString($candidate->getPrettyVersion()); } else { foreach (\array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { if ($link->getTarget() === $require) { $candidateConstraint = $link->getConstraint(); break; } } } // safety check for phpstan, but it should not be possible to get a candidate out of findPackagesWithReplacersAndProviders without a constraint matching $require if (!$candidateConstraint) { continue; } foreach ($links as $link) { if (!$link->getConstraint()->matches($candidateConstraint)) { $reqResults[] = [$candidate->getName() === $require ? $candidate->getPrettyName() : $require, $candidateConstraint->getPrettyString(), $link, 'failed', $candidate->getName() === $require ? '' : 'provided by ' . $candidate->getPrettyName() . '']; // skip to next candidate continue 2; } } $results[] = [$candidate->getName() === $require ? $candidate->getPrettyName() : $require, $candidateConstraint->getPrettyString(), null, 'success', $candidate->getName() === $require ? '' : 'provided by ' . $candidate->getPrettyName() . '']; // candidate matched, skip to next requirement continue 2; } // show the first error from every failed candidate $results = \array_merge($results, $reqResults); $exitCode = \max($exitCode, 1); continue; } $results[] = [$require, 'n/a', $links[0], 'missing', '']; $exitCode = \max($exitCode, 2); } } $this->printTable($output, $results, $input->getOption('format')); return $exitCode; } /** * @param mixed[] $results */ protected function printTable(OutputInterface $output, array $results, string $format) : void { $rows = []; foreach ($results as $result) { /** * @var Link|null $link */ [$platformPackage, $version, $link, $status, $provider] = $result; if ('json' === $format) { $rows[] = ["name" => $platformPackage, "version" => $version, "status" => \strip_tags($status), "failed_requirement" => $link instanceof Link ? ['source' => $link->getSource(), 'type' => $link->getDescription(), 'target' => $link->getTarget(), 'constraint' => $link->getPrettyConstraint()] : null, "provider" => $provider === '' ? null : \strip_tags($provider)]; } else { $rows[] = [$platformPackage, $version, $link, $link ? \sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '', \rtrim($status . ' ' . $provider)]; } } if ('json' === $format) { $this->getIO()->write(JsonFile::encode($rows)); } else { $this->renderTable($rows, $output); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Factory; use Composer\Config; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\SelfUpdate\Keys; use Composer\SelfUpdate\Versions; use Composer\IO\IOInterface; use Composer\Downloader\FilesystemException; use Composer\Downloader\TransportException; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Finder\Finder; /** * @author Igor Wiedler * @author Kevin Ran * @author Jordi Boggiano */ class SelfUpdateCommand extends \Composer\Command\BaseCommand { private const HOMEPAGE = 'getcomposer.org'; private const OLD_INSTALL_EXT = '-old.phar'; protected function configure() : void { $this->setName('self-update')->setAliases(['selfupdate'])->setDescription('Updates composer.phar to the latest version')->setDefinition([new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'), new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'), new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'), new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'), new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'), new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'), new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'), new InputOption('2.2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.2.x LTS versions'), new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit')])->setHelp(<<self-update command checks getcomposer.org for newer versions of composer and if found, installs the latest. php composer.phar self-update Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate EOT ); } /** * @throws FilesystemException */ protected function execute(InputInterface $input, OutputInterface $output) : int { if ($_SERVER['argv'][0] === 'Standard input code') { return 1; } // trigger autoloading of a few classes which may be needed when verifying/swapping the phar file // to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252 \class_exists('Composer\\Util\\Platform'); \class_exists('Composer\\Downloader\\FilesystemException'); $config = Factory::createConfig(); if ($config->get('disable-tls') === \true) { $baseUrl = 'http://' . self::HOMEPAGE; } else { $baseUrl = 'https://' . self::HOMEPAGE; } $io = $this->getIO(); $httpDownloader = Factory::createHttpDownloader($io, $config); $versionsUtil = new Versions($config, $httpDownloader); // switch channel if requested $requestedChannel = null; foreach (Versions::CHANNELS as $channel) { if ($input->getOption($channel)) { $requestedChannel = $channel; $versionsUtil->setChannel($channel, $io); break; } } if ($input->getOption('set-channel-only')) { return 0; } $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('data-dir'); $home = $config->get('home'); $localFilename = \realpath($_SERVER['argv'][0]); if (\false === $localFilename) { $localFilename = $_SERVER['argv'][0]; } if ($input->getOption('update-keys')) { $this->fetchKeys($io, $config); return 0; } // ensure composer.phar location is accessible if (!\file_exists($localFilename)) { throw new FilesystemException('Composer update failed: the "' . $localFilename . '" is not accessible'); } // check if current dir is writable and if not try the cache dir from settings $tmpDir = \is_writable(\dirname($localFilename)) ? \dirname($localFilename) : $cacheDir; // check for permissions in local filesystem before start connection process if (!\is_writable($tmpDir)) { throw new FilesystemException('Composer update failed: the "' . $tmpDir . '" directory used to download the temp file could not be written'); } // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable if (\function_exists('posix_getpwuid') && \function_exists('posix_geteuid')) { $composerUser = \posix_getpwuid(\posix_geteuid()); $homeDirOwnerId = \fileowner($home); if (\is_array($composerUser) && $homeDirOwnerId !== \false) { $homeOwner = \posix_getpwuid($homeDirOwnerId); if (\is_array($homeOwner) && isset($composerUser['name'], $homeOwner['name']) && $composerUser['name'] !== $homeOwner['name']) { $io->writeError('You are running Composer as "' . $composerUser['name'] . '", while "' . $home . '" is owned by "' . $homeOwner['name'] . '"'); } } } if ($input->getOption('rollback')) { return $this->rollback($output, $rollbackDir, $localFilename); } if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') { $input->setArgument('version', null); } $latest = $versionsUtil->getLatest(); $latestStable = $versionsUtil->getLatest('stable'); try { $latestPreview = $versionsUtil->getLatest('preview'); } catch (\UnexpectedValueException $e) { $latestPreview = $latestStable; } $latestVersion = $latest['version']; $updateVersion = $input->getArgument('version') ?? $latestVersion; $currentMajorVersion = Preg::replace('{^(\\d+).*}', '$1', Composer::getVersion()); $updateMajorVersion = Preg::replace('{^(\\d+).*}', '$1', $updateVersion); $previewMajorVersion = Preg::replace('{^(\\d+).*}', '$1', $latestPreview['version']); if ($versionsUtil->getChannel() === 'stable' && null === $input->getArgument('version')) { // if requesting stable channel and no specific version, avoid automatically upgrading to the next major // simply output a warning that the next major stable is available and let users upgrade to it manually if ($currentMajorVersion < $updateMajorVersion) { $skippedVersion = $updateVersion; $versionsUtil->setChannel($currentMajorVersion); $latest = $versionsUtil->getLatest(); $latestStable = $versionsUtil->getLatest('stable'); $latestVersion = $latest['version']; $updateVersion = $latestVersion; $io->writeError('A new stable major version of Composer is available (' . $skippedVersion . '), run "composer self-update --' . $updateMajorVersion . '" to update to it. See also https://getcomposer.org/' . $updateMajorVersion . ''); } elseif ($currentMajorVersion < $previewMajorVersion) { // promote next major version if available in preview $io->writeError('A preview release of the next major version of Composer is available (' . $latestPreview['version'] . '), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.'); } } $effectiveChannel = $requestedChannel === null ? $versionsUtil->getChannel() : $requestedChannel; if (\is_numeric($effectiveChannel) && \strpos($latestStable['version'], $effectiveChannel) !== 0) { $io->writeError('Warning: You forced the install of ' . $latestVersion . ' via --' . $effectiveChannel . ', but ' . $latestStable['version'] . ' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); } if (isset($latest['eol'])) { $io->writeError('Warning: Version ' . $latestVersion . ' is EOL / End of Life. ' . $latestStable['version'] . ' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); } if (Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { $io->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); return 1; } $channelString = $versionsUtil->getChannel(); if (\is_numeric($channelString)) { $channelString .= '.x'; } if (Composer::VERSION === $updateVersion) { $io->writeError(\sprintf('You are already using the latest available Composer version %s (%s channel).', $updateVersion, $channelString)); // remove all backups except for the most recent, if any if ($input->getOption('clean-backups')) { $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir)); } return 0; } $tempFilename = $tmpDir . '/' . \basename($localFilename, '.phar') . '-temp' . \random_int(0, 10000000) . '.phar'; $backupFile = \sprintf('%s/%s-%s%s', $rollbackDir, \strtr(Composer::RELEASE_DATE, ' :', '_-'), Preg::replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION), self::OLD_INSTALL_EXT); $updatingToTag = !Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion); $io->write(\sprintf("Upgrading to version %s (%s channel).", $updateVersion, $channelString)); $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); try { $signature = $httpDownloader->get($remoteFilename . '.sig')->getBody(); } catch (TransportException $e) { if ($e->getStatusCode() === 404) { throw new \InvalidArgumentException('Version "' . $updateVersion . '" could not be found.', 0, $e); } throw $e; } $io->writeError(' ', \false); $httpDownloader->copy($remoteFilename, $tempFilename); $io->writeError(''); if (!\file_exists($tempFilename) || null === $signature || '' === $signature) { $io->writeError('The download of the new composer version failed for an unexpected reason'); return 1; } // verify phar signature if (!\extension_loaded('openssl') && $config->get('disable-tls')) { $io->writeError('Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls'); } else { if (!\extension_loaded('openssl')) { throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } $sigFile = 'file://' . $home . '/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); if (!\file_exists($sigFile)) { \file_put_contents($home . '/keys.dev.pub', <<getOption('clean-backups')) { $this->cleanBackups($rollbackDir); } if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { @\unlink($tempFilename); return 1; } if (\file_exists($backupFile)) { $io->writeError(\sprintf('Use composer self-update --rollback to return to version %s', Composer::VERSION)); } else { $io->writeError('A backup of the current version could not be written to ' . $backupFile . ', no rollback possible'); } return 0; } /** * @throws \Exception */ protected function fetchKeys(IOInterface $io, Config $config) : void { if (!$io->isInteractive()) { throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively'); } $io->write('Open https://composer.github.io/pubkeys.html to find the latest keys'); $validator = static function ($value) : string { $value = (string) $value; if (!Preg::isMatch('{^-----BEGIN PUBLIC KEY-----$}', \trim($value))) { throw new \UnexpectedValueException('Invalid input'); } return \trim($value) . "\n"; }; $devKey = ''; while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) { $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator); while ($line = $io->ask('', '')) { $devKey .= \trim($line) . "\n"; if (\trim($line) === '-----END PUBLIC KEY-----') { break; } } } \file_put_contents($keyPath = $config->get('home') . '/keys.dev.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $tagsKey = ''; while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) { $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator); while ($line = $io->ask('', '')) { $tagsKey .= \trim($line) . "\n"; if (\trim($line) === '-----END PUBLIC KEY-----') { break; } } } \file_put_contents($keyPath = $config->get('home') . '/keys.tags.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $io->write('Public keys stored in ' . $config->get('home')); } /** * @throws FilesystemException */ protected function rollback(OutputInterface $output, string $rollbackDir, string $localFilename) : int { $rollbackVersion = $this->getLastBackupVersion($rollbackDir); if (null === $rollbackVersion) { throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "' . $rollbackDir . '"'); } $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT; if (!\is_file($oldFile)) { throw new FilesystemException('Composer rollback failed: "' . $oldFile . '" could not be found'); } if (!Filesystem::isReadable($oldFile)) { throw new FilesystemException('Composer rollback failed: "' . $oldFile . '" could not be read'); } $io = $this->getIO(); $io->writeError(\sprintf("Rolling back to version %s.", $rollbackVersion)); if (!$this->setLocalPhar($localFilename, $oldFile)) { return 1; } return 0; } /** * Checks if the downloaded/rollback phar is valid then moves it * * @param string $localFilename The composer.phar location * @param string $newFilename The downloaded or backup phar * @param string $backupTarget The filename to use for the backup * @throws FilesystemException If the file cannot be moved * @return bool Whether the phar is valid and has been moved */ protected function setLocalPhar(string $localFilename, string $newFilename, ?string $backupTarget = null) : bool { $io = $this->getIO(); $perms = @\fileperms($localFilename); if ($perms !== \false) { @\chmod($newFilename, $perms); } // check phar validity if (!$this->validatePhar($newFilename, $error)) { $io->writeError('The ' . ($backupTarget !== null ? 'update' : 'backup') . ' file is corrupted (' . $error . ')'); if ($backupTarget !== null) { $io->writeError('Please re-run the self-update command to try again.'); } return \false; } // copy current file into backups dir if ($backupTarget !== null) { @\copy($localFilename, $backupTarget); } try { if (Platform::isWindows()) { // use copy to apply permissions from the destination directory // as rename uses source permissions and may block other users \copy($newFilename, $localFilename); @\unlink($newFilename); } else { \rename($newFilename, $localFilename); } return \true; } catch (\Exception $e) { // see if we can run this operation as an Admin on Windows if (!\is_writable(\dirname($localFilename)) && $io->isInteractive() && $this->isWindowsNonAdminUser()) { return $this->tryAsWindowsAdmin($localFilename, $newFilename); } @\unlink($newFilename); $action = 'Composer ' . ($backupTarget !== null ? 'update' : 'rollback'); throw new FilesystemException($action . ' failed: "' . $localFilename . '" could not be written.' . \PHP_EOL . $e->getMessage()); } } protected function cleanBackups(string $rollbackDir, ?string $except = null) : void { $finder = $this->getOldInstallationFinder($rollbackDir); $io = $this->getIO(); $fs = new Filesystem(); foreach ($finder as $file) { if ($file->getBasename(self::OLD_INSTALL_EXT) === $except) { continue; } $file = (string) $file; $io->writeError('Removing: ' . $file . ''); $fs->remove($file); } } protected function getLastBackupVersion(string $rollbackDir) : ?string { $finder = $this->getOldInstallationFinder($rollbackDir); $finder->sortByName(); $files = \iterator_to_array($finder); if (\count($files) > 0) { return \end($files)->getBasename(self::OLD_INSTALL_EXT); } return null; } protected function getOldInstallationFinder(string $rollbackDir) : Finder { return Finder::create()->depth(0)->files()->name('*' . self::OLD_INSTALL_EXT)->in($rollbackDir); } /** * Validates the downloaded/backup phar file * * @param string $pharFile The downloaded or backup phar * @param null|string $error Set by method on failure * * Code taken from getcomposer.org/installer. Any changes should be made * there and replicated here * * @throws \Exception * @return bool If the operation succeeded */ protected function validatePhar(string $pharFile, ?string &$error) : bool { if ((bool) \ini_get('phar.readonly')) { return \true; } try { // Test the phar validity $phar = new \Phar($pharFile); // Free the variable to unlock the file unset($phar); $result = \true; } catch (\Exception $e) { if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) { throw $e; } $error = $e->getMessage(); $result = \false; } return $result; } /** * Returns true if this is a non-admin Windows user account */ protected function isWindowsNonAdminUser() : bool { if (!Platform::isWindows()) { return \false; } // fltmc.exe manages filter drivers and errors without admin privileges \exec('fltmc.exe filters', $output, $exitCode); return $exitCode !== 0; } /** * Invokes a UAC prompt to update composer.phar as an admin * * Uses a .vbs script to elevate and run the cmd.exe copy command. * * @param string $localFilename The composer.phar location * @param string $newFilename The downloaded or backup phar * @return bool Whether composer.phar has been updated */ protected function tryAsWindowsAdmin(string $localFilename, string $newFilename) : bool { $io = $this->getIO(); $io->writeError('Unable to write "' . $localFilename . '". Access is denied.'); $helpMessage = 'Please run the self-update command as an Administrator.'; $question = 'Complete this operation with Administrator privileges [Y,n]? '; if (!$io->askConfirmation($question, \true)) { $io->writeError('Operation cancelled. ' . $helpMessage . ''); return \false; } $tmpFile = \tempnam(\sys_get_temp_dir(), ''); if (\false === $tmpFile) { $io->writeError('Operation failed.' . $helpMessage . ''); return \false; } $script = $tmpFile . '.vbs'; \rename($tmpFile, $script); $checksum = \hash_file('sha256', $newFilename); // cmd's internal copy is fussy about backslashes $source = \str_replace('/', '\\', $newFilename); $destination = \str_replace('/', '\\', $localFilename); $vbs = <<writeError('Operation succeeded.'); @\unlink($newFilename); } else { $io->writeError('Operation failed.' . $helpMessage . ''); } return $result; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\DependencyResolver\Request; use Composer\Package\AliasPackage; use Composer\Package\CompletePackageInterface; use Composer\Package\Loader\RootPackageLoader; use Composer\Package\Locker; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionBumper; use Composer\Package\Version\VersionSelector; use Composer\Pcre\Preg; use Composer\Repository\RepositorySet; use Composer\Util\Filesystem; use Composer\Util\PackageSorter; use _ContaoManager\Seld\Signal\SignalHandler; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Factory; use Composer\Installer; use Composer\Installer\InstallerEvents; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; use Composer\Package\Version\VersionParser; use Composer\Package\Loader\ArrayLoader; use Composer\Package\BasePackage; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\IO\IOInterface; use Composer\Advisory\Auditor; use Composer\Util\Silencer; /** * @author Jérémy Romey * @author Jordi Boggiano */ class RequireCommand extends \Composer\Command\BaseCommand { use \Composer\Command\CompletionTrait; use \Composer\Command\PackageDiscoveryTrait; /** @var bool */ private $newlyCreated; /** @var bool */ private $firstRequire; /** @var JsonFile */ private $json; /** @var string */ private $file; /** @var string */ private $composerBackup; /** @var string file name */ private $lock; /** @var ?string contents before modification if the lock file exists */ private $lockBackup; /** @var bool */ private $dependencyResolutionCompleted = \false; /** * @return void */ protected function configure() { $this->setName('require')->setAliases(['r'])->setDescription('Adds required packages to your composer.json and installs them')->setDefinition([new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'), new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'), new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'), new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader')])->setHelp(<<file = Factory::getComposerFile(); $io = $this->getIO(); if ($input->getOption('no-suggest')) { $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); } $this->newlyCreated = !\file_exists($this->file); if ($this->newlyCreated && !\file_put_contents($this->file, "{\n}\n")) { $io->writeError('' . $this->file . ' could not be created.'); return 1; } if (!Filesystem::isReadable($this->file)) { $io->writeError('' . $this->file . ' is not readable.'); return 1; } if (\filesize($this->file) === 0) { \file_put_contents($this->file, "{\n}\n"); } $this->json = new JsonFile($this->file); $this->lock = Factory::getLockFile($this->file); $this->composerBackup = \file_get_contents($this->json->getPath()); $this->lockBackup = \file_exists($this->lock) ? \file_get_contents($this->lock) : null; $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { $this->getIO()->writeError('Received ' . $signal . ', aborting', \true, IOInterface::DEBUG); $this->revertComposerFile(); $handler->exitWithLastSignal(); }); // check for writability by writing to the file as is_writable can not be trusted on network-mounts // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 if (!\is_writable($this->file) && \false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { $io->writeError('' . $this->file . ' is not writable.'); return 1; } if ($input->getOption('fixed') === \true) { $config = $this->json->read(); $packageType = empty($config['type']) ? 'library' : $config['type']; /** * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955 */ if ($packageType !== 'project' && !$input->getOption('dev')) { $io->writeError('The "--fixed" option is only allowed for packages with a "project" type or for dev dependencies to prevent possible misuses.'); if (!isset($config['type'])) { $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); } return 1; } } $composer = $this->requireComposer(); $repos = $composer->getRepositoryManager()->getRepositories(); $platformOverrides = $composer->getConfig()->get('platform'); // initialize $this->repos as it is used by the PackageDiscoveryTrait $this->repos = new CompositeRepository(\array_merge([$platformRepo = new PlatformRepository([], $platformOverrides)], $repos)); if ($composer->getPackage()->getPreferStable()) { $preferredStability = 'stable'; } else { $preferredStability = $composer->getPackage()->getMinimumStability(); } try { $requirements = $this->determineRequirements( $input, $output, $input->getArgument('packages'), $platformRepo, $preferredStability, $input->getOption('no-update'), // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint $input->getOption('fixed') ); } catch (\Exception $e) { if ($this->newlyCreated) { $this->revertComposerFile(); throw new \RuntimeException('No composer.json present in the current directory (' . $this->file . '), this may be the cause of the following exception.', 0, $e); } throw $e; } $requirements = $this->formatRequirements($requirements); if (!$input->getOption('dev') && $io->isInteractive()) { $devPackages = []; $devTags = ['dev', 'testing', 'static analysis']; $currentRequiresByKey = $this->getPackagesByRequireKey(); foreach ($requirements as $name => $version) { // skip packages which are already in the composer.json as those have already been decided if (isset($currentRequiresByKey[$name])) { continue; } $pkg = PackageSorter::getMostCurrentVersion($this->getRepos()->findPackages($name)); if ($pkg instanceof CompletePackageInterface) { $pkgDevTags = \array_intersect($devTags, \array_map('strtolower', $pkg->getKeywords())); if (\count($pkgDevTags) > 0) { $devPackages[] = $pkgDevTags; } } } if (\count($devPackages) === \count($requirements)) { $plural = \count($requirements) > 1 ? 's' : ''; $plural2 = \count($requirements) > 1 ? 'are' : 'is'; $plural3 = \count($requirements) > 1 ? 'they are' : 'it is'; $pkgDevTags = \array_unique(\array_merge(...$devPackages)); $io->warning('The package' . $plural . ' you required ' . $plural2 . ' recommended to be placed in require-dev (because ' . $plural3 . ' tagged as "' . \implode('", "', $pkgDevTags) . '") but you did not use --dev.'); if ($io->askConfirmation('Do you want to re-run the command with --dev? [yes]? ')) { $input->setOption('dev', \true); } } unset($devPackages, $pkgDevTags); } $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; // check which requirements need the version guessed $requirementsToGuess = []; foreach ($requirements as $package => $constraint) { if ($constraint === 'guess') { $requirements[$package] = '*'; $requirementsToGuess[] = $package; } } // validate requirements format $versionParser = new VersionParser(); foreach ($requirements as $package => $constraint) { if (\strtolower($package) === $composer->getPackage()->getName()) { $io->writeError(\sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); return 1; } if ($constraint === 'self.version') { continue; } $versionParser->parseConstraints($constraint); } $inconsistentRequireKeys = $this->getInconsistentRequireKeys($requirements, $requireKey); if (\count($inconsistentRequireKeys) > 0) { foreach ($inconsistentRequireKeys as $package) { $io->warning(\sprintf('%s is currently present in the %s key and you ran the command %s the --dev flag, which will move it to the %s key.', $package, $removeKey, $input->getOption('dev') ? 'with' : 'without', $requireKey)); } if ($io->isInteractive()) { if (!$io->askConfirmation(\sprintf('Do you want to move %s? [no]? ', \count($inconsistentRequireKeys) > 1 ? 'these requirements' : 'this requirement'), \false)) { if (!$io->askConfirmation(\sprintf('Do you want to re-run the command %s --dev? [yes]? ', $input->getOption('dev') ? 'without' : 'with'), \true)) { return 0; } $input->setOption('dev', \true); [$requireKey, $removeKey] = [$removeKey, $requireKey]; } } } $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); $this->firstRequire = $this->newlyCreated; if (!$this->firstRequire) { $composerDefinition = $this->json->read(); if (\count($composerDefinition['require'] ?? []) === 0 && \count($composerDefinition['require-dev'] ?? []) === 0) { $this->firstRequire = \true; } } if (!$input->getOption('dry-run')) { $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); } $io->writeError('' . $this->file . ' has been ' . ($this->newlyCreated ? 'created' : 'updated') . ''); if ($input->getOption('no-update')) { return 0; } $composer->getPluginManager()->deactivateInstalledPlugins(); try { $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); if ($result === 0 && \count($requirementsToGuess) > 0) { $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run'), $input->getOption('fixed')); } return $result; } catch (\Exception $e) { if (!$this->dependencyResolutionCompleted) { $this->revertComposerFile(); } throw $e; } finally { if ($input->getOption('dry-run') && $this->newlyCreated) { @\unlink($this->json->getPath()); } $signalHandler->unregister(); } } /** * @param array $newRequirements * @return string[] */ private function getInconsistentRequireKeys(array $newRequirements, string $requireKey) : array { $requireKeys = $this->getPackagesByRequireKey(); $inconsistentRequirements = []; foreach ($requireKeys as $package => $packageRequireKey) { if (!isset($newRequirements[$package])) { continue; } if ($requireKey !== $packageRequireKey) { $inconsistentRequirements[] = $package; } } return $inconsistentRequirements; } /** * @return array */ private function getPackagesByRequireKey() : array { $composerDefinition = $this->json->read(); $require = []; $requireDev = []; if (isset($composerDefinition['require'])) { $require = $composerDefinition['require']; } if (isset($composerDefinition['require-dev'])) { $requireDev = $composerDefinition['require-dev']; } return \array_merge(\array_fill_keys(\array_keys($require), 'require'), \array_fill_keys(\array_keys($requireDev), 'require-dev')); } /** * @param array $requirements * @throws \Exception */ private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey) : int { // Update packages $this->resetComposer(); $composer = $this->requireComposer(); $this->dependencyResolutionCompleted = \false; $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, function () : void { $this->dependencyResolutionCompleted = \true; }, 10000); if ($input->getOption('dry-run')) { $rootPackage = $composer->getPackage(); $links = ['require' => $rootPackage->getRequires(), 'require-dev' => $rootPackage->getDevRequires()]; $loader = new ArrayLoader(); $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['method'], $requirements); $links[$requireKey] = \array_merge($links[$requireKey], $newLinks); foreach ($requirements as $package => $constraint) { unset($links[$removeKey][$package]); } $rootPackage->setRequires($links['require']); $rootPackage->setDevRequires($links['require-dev']); // extract stability flags & references as they weren't present when loading the unmodified composer.json $references = $rootPackage->getReferences(); $references = RootPackageLoader::extractReferences($requirements, $references); $rootPackage->setReferences($references); $stabilityFlags = $rootPackage->getStabilityFlags(); $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $rootPackage->getMinimumStability(), $stabilityFlags); $rootPackage->setStabilityFlags($stabilityFlags); unset($stabilityFlags, $references); } $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; $flags = ''; if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; $flags .= ' --with-all-dependencies'; } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; $flags .= ' --with-dependencies'; } $io->writeError('Running composer update ' . \implode(' ', \array_keys($requirements)) . $flags . ''); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install = Installer::create($io, $composer); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($composer->getConfig(), $input); $install->setDryRun($input->getOption('dry-run'))->setVerbose($input->getOption('verbose'))->setPreferSource($preferSource)->setPreferDist($preferDist)->setDevMode($updateDevMode)->setOptimizeAutoloader($optimize)->setClassMapAuthoritative($authoritative)->setApcuAutoloader($apcu, $apcuPrefix)->setUpdate(\true)->setInstall(!$input->getOption('no-install'))->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))->setPreferStable($input->getOption('prefer-stable'))->setPreferLowest($input->getOption('prefer-lowest'))->setAudit(!$input->getOption('no-audit'))->setAuditFormat($this->getAuditFormat($input))->setMinimalUpdate($input->getOption('minimal-changes')); // if no lock is present, or the file is brand new, we do not do a // partial update as this is not supported by the Installer if (!$this->firstRequire && $composer->getLocker()->isLocked()) { $install->setUpdateAllowList(\array_keys($requirements)); } $status = $install->run(); if ($status !== 0 && $status !== Installer::ERROR_AUDIT_FAILED) { if ($status === Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED) { foreach ($this->normalizeRequirements($input->getArgument('packages')) as $req) { if (!isset($req['version'])) { $io->writeError('You can also try re-running composer require with an explicit version constraint, e.g. "composer require ' . $req['name'] . ':*" to figure out if any version is installable, or "composer require ' . $req['name'] . ':^2.1" if you know which you need.'); break; } } } $this->revertComposerFile(); } return $status; } /** * @param list $requirementsToUpdate */ private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun, bool $fixed) : int { $composer = $this->requireComposer(); $locker = $composer->getLocker(); $requirements = []; $versionSelector = new VersionSelector(new RepositorySet()); $repo = $locker->isLocked() ? $composer->getLocker()->getLockedRepository(\true) : $composer->getRepositoryManager()->getLocalRepository(); foreach ($requirementsToUpdate as $packageName) { $package = $repo->findPackage($packageName, '*'); while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if (!$package instanceof PackageInterface) { continue; } if ($fixed) { $requirements[$packageName] = $package->getPrettyVersion(); } else { $requirements[$packageName] = $versionSelector->findRecommendedRequireVersion($package); } $this->getIO()->writeError(\sprintf('Using version %s for %s', $requirements[$packageName], $packageName)); if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) { $this->getIO()->warning('Version ' . $requirements[$packageName] . ' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state'); if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (Y) or would you rather abort (n) the whole operation [Y,n]? ')) { $this->revertComposerFile(); return 1; } } } if (!$dryRun) { $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); if ($locker->isLocked()) { $contents = \file_get_contents($this->json->getPath()); if (\false === $contents) { throw new \RuntimeException('Unable to read ' . $this->json->getPath() . ' contents to update the lock file hash.'); } $lockFile = Factory::getLockFile($this->json->getPath()); if (\file_exists($lockFile)) { $lockMtime = \filemtime($lockFile); $lock = new JsonFile($lockFile); $lockData = $lock->read(); $lockData['content-hash'] = Locker::getContentHash($contents); $lock->write($lockData); if (\is_int($lockMtime)) { @\touch($lockFile, $lockMtime); } } } } return 0; } /** * @param array $new */ private function updateFile(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages) : void { if ($this->updateFileCleanly($json, $new, $requireKey, $removeKey, $sortPackages)) { return; } $composerDefinition = $this->json->read(); foreach ($new as $package => $version) { $composerDefinition[$requireKey][$package] = $version; unset($composerDefinition[$removeKey][$package]); if (isset($composerDefinition[$removeKey]) && \count($composerDefinition[$removeKey]) === 0) { unset($composerDefinition[$removeKey]); } } $this->json->write($composerDefinition); } /** * @param array $new */ private function updateFileCleanly(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages) : bool { $contents = \file_get_contents($json->getPath()); $manipulator = new JsonManipulator($contents); foreach ($new as $package => $constraint) { if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) { return \false; } if (!$manipulator->removeSubNode($removeKey, $package)) { return \false; } } $manipulator->removeMainKeyIfEmpty($removeKey); \file_put_contents($json->getPath(), $manipulator->getContents()); return \true; } protected function interact(InputInterface $input, OutputInterface $output) : void { } private function revertComposerFile() : void { $io = $this->getIO(); if ($this->newlyCreated) { $io->writeError("\n" . 'Installation failed, deleting ' . $this->file . '.'); \unlink($this->json->getPath()); if (\file_exists($this->lock)) { \unlink($this->lock); } } else { $msg = ' to its '; if ($this->lockBackup) { $msg = ' and ' . $this->lock . ' to their '; } $io->writeError("\n" . 'Installation failed, reverting ' . $this->file . $msg . 'original content.'); \file_put_contents($this->json->getPath(), $this->composerBackup); if ($this->lockBackup) { \file_put_contents($this->lock, $this->lockBackup); } } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Repository\CompositeRepository; use Composer\Repository\InstalledRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RootPackageRepository; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; /** * Adds completion to arguments and options. * * @internal */ trait CompletionTrait { /** * @see BaseCommand::requireComposer() */ public abstract function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null) : Composer; /** * Suggestion values for "prefer-install" option * * @return list */ private function suggestPreferInstall() : array { return ['dist', 'source', 'auto']; } /** * Suggest package names from root requirements. */ private function suggestRootRequirement() : \Closure { return function (CompletionInput $input) : array { $composer = $this->requireComposer(); return \array_merge(\array_keys($composer->getPackage()->getRequires()), \array_keys($composer->getPackage()->getDevRequires())); }; } /** * Suggest package names from installed. */ private function suggestInstalledPackage(bool $includeRootPackage = \true, bool $includePlatformPackages = \false) : \Closure { return function (CompletionInput $input) use($includeRootPackage, $includePlatformPackages) : array { $composer = $this->requireComposer(); $installedRepos = []; if ($includeRootPackage) { $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); } $locker = $composer->getLocker(); if ($locker->isLocked()) { $installedRepos[] = $locker->getLockedRepository(\true); } else { $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); } $platformHint = []; if ($includePlatformPackages) { if ($locker->isLocked()) { $platformRepo = new PlatformRepository([], $locker->getPlatformOverrides()); } else { $platformRepo = new PlatformRepository([], $composer->getConfig()->get('platform')); } if ($input->getCompletionValue() === '') { // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99]; foreach ($platformRepo->getPackages() as $pkg) { foreach ($hintsToFind as $hintPrefix => $hintCount) { if (\str_starts_with($pkg->getName(), $hintPrefix)) { if ($hintCount === 0 || $hintCount >= 99) { $platformHint[] = $pkg->getName(); $hintsToFind[$hintPrefix]++; } elseif ($hintCount === 1) { unset($hintsToFind[$hintPrefix]); $platformHint[] = \substr($pkg->getName(), 0, \max(\strlen($pkg->getName()) - 3, \strlen($hintPrefix) + 1)) . '...'; } continue 2; } } } } else { $installedRepos[] = $platformRepo; } } $installedRepo = new InstalledRepository($installedRepos); return \array_merge(\array_map(static function (PackageInterface $package) { return $package->getName(); }, $installedRepo->getPackages()), $platformHint); }; } /** * Suggest package names available on all configured repositories. */ private function suggestAvailablePackage(int $max = 99) : \Closure { return function (CompletionInput $input) use($max) : array { if ($max < 1) { return []; } $composer = $this->requireComposer(); $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $results = []; $showVendors = \false; if (!\str_contains($input->getCompletionValue(), '/')) { $results = $repos->search('^' . \preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR); $showVendors = \true; } // if we get a single vendor, we expand it into its contents already if (\count($results) <= 1) { $results = $repos->search('^' . \preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); $showVendors = \false; } $results = \array_column($results, 'name'); if ($showVendors) { $results = \array_map(static function (string $name) : string { return $name . '/'; }, $results); // sort shorter results first to avoid auto-expanding the completion to a longer string than needed \usort($results, static function (string $a, string $b) { $lenA = \strlen($a); $lenB = \strlen($b); if ($lenA === $lenB) { return $a <=> $b; } return $lenA - $lenB; }); $pinned = []; // ensure if the input is an exact match that it is always in the result set $completionInput = $input->getCompletionValue() . '/'; if (\false !== ($exactIndex = \array_search($completionInput, $results, \true))) { $pinned[] = $completionInput; \array_splice($results, $exactIndex, 1); } return \array_merge($pinned, \array_slice($results, 0, $max - \count($pinned))); } return \array_slice($results, 0, $max); }; } /** * Suggest package names available on all configured repositories or * platform packages from the ones available on the currently-running PHP */ private function suggestAvailablePackageInclPlatform() : \Closure { return function (CompletionInput $input) : array { if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) { $matches = $this->suggestPlatformPackage()($input); } else { $matches = []; } return \array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input)); }; } /** * Suggest platform packages from the ones available on the currently-running PHP */ private function suggestPlatformPackage() : \Closure { return function (CompletionInput $input) : array { $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform')); $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue() . '*'); return \array_filter(\array_map(static function (PackageInterface $package) { return $package->getName(); }, $repos->getPackages()), static function (string $name) use($pattern) : bool { return Preg::isMatch($pattern, $name); }); }; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Cache; use Composer\Factory; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author David Neilsen */ class ClearCacheCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('clear-cache')->setAliases(['clearcache', 'cc'])->setDescription('Clears composer\'s internal package cache')->setDefinition([new InputOption('gc', null, InputOption::VALUE_NONE, 'Only run garbage collection, not a full cache clear')])->setHelp(<<clear-cache deletes all cached packages from composer's cache directory. Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $config = Factory::createConfig(); $io = $this->getIO(); $cachePaths = ['cache-vcs-dir' => $config->get('cache-vcs-dir'), 'cache-repo-dir' => $config->get('cache-repo-dir'), 'cache-files-dir' => $config->get('cache-files-dir'), 'cache-dir' => $config->get('cache-dir')]; foreach ($cachePaths as $key => $cachePath) { // only individual dirs get garbage collected if ($key === 'cache-dir' && $input->getOption('gc')) { continue; } $cachePath = \realpath($cachePath); if (!$cachePath) { $io->writeError("Cache directory does not exist ({$key}): {$cachePath}"); continue; } $cache = new Cache($io, $cachePath); $cache->setReadOnly($config->get('cache-read-only')); if (!$cache->isEnabled()) { $io->writeError("Cache is not enabled ({$key}): {$cachePath}"); continue; } if ($input->getOption('gc')) { $io->writeError("Garbage-collecting cache ({$key}): {$cachePath}"); if ($key === 'cache-files-dir') { $cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); } elseif ($key === 'cache-repo-dir') { $cache->gc($config->get('cache-ttl'), 1024 * 1024 * 1024); } elseif ($key === 'cache-vcs-dir') { $cache->gcVcsCache($config->get('cache-ttl')); } } else { $io->writeError("Clearing cache ({$key}): {$cachePath}"); $cache->clear(); } } if ($input->getOption('gc')) { $io->writeError('All caches garbage-collected.'); } else { $io->writeError('All caches cleared.'); } return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Advisory\Auditor; use Composer\Composer; use Composer\Factory; use Composer\Config; use Composer\Downloader\TransportException; use Composer\IO\BufferIO; use Composer\Json\JsonFile; use Composer\Package\RootPackage; use Composer\Package\Version\VersionParser; use Composer\Pcre\Preg; use Composer\Repository\ComposerRepository; use Composer\Repository\FilesystemRepository; use Composer\Repository\PlatformRepository; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\RepositorySet; use Composer\Repository\RootPackageRepository; use Composer\Util\ConfigValidator; use Composer\Util\Git; use Composer\Util\IniHelper; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\Util\StreamContextFactory; use Composer\Util\Platform; use Composer\SelfUpdate\Keys; use Composer\SelfUpdate\Versions; use Composer\IO\NullIO; use Composer\Package\CompletePackageInterface; use Composer\XdebugHandler\XdebugHandler; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Process\ExecutableFinder; /** * @author Jordi Boggiano */ class DiagnoseCommand extends \Composer\Command\BaseCommand { /** @var HttpDownloader */ protected $httpDownloader; /** @var ProcessExecutor */ protected $process; /** @var int */ protected $exitCode = 0; protected function configure() : void { $this->setName('diagnose')->setDescription('Diagnoses the system to identify common errors')->setHelp(<<diagnose command checks common errors to help debugging problems. The process exit code will be 1 in case of warnings and 2 for errors. Read more at https://getcomposer.org/doc/03-cli.md#diagnose EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->tryComposer(); $io = $this->getIO(); if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $io->write('Checking composer.json: ', \false); $this->outputResult($this->checkComposerSchema()); $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io); } else { $this->process = new ProcessExecutor($io); } if ($composer) { $config = $composer->getConfig(); } else { $config = Factory::createConfig(); } $config->merge(['config' => ['secure-http' => \false]], Config::SOURCE_COMMAND); $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO()); $this->httpDownloader = Factory::createHttpDownloader($io, $config); $io->write('Checking platform settings: ', \false); $this->outputResult($this->checkPlatform()); $io->write('Checking git settings: ', \false); $this->outputResult($this->checkGit()); $io->write('Checking http connectivity to packagist: ', \false); $this->outputResult($this->checkHttp('http', $config)); $io->write('Checking https connectivity to packagist: ', \false); $this->outputResult($this->checkHttp('https', $config)); $opts = \stream_context_get_options(StreamContextFactory::getContext('http://example.org')); if (!empty($opts['http']['proxy'])) { $io->write('Checking HTTP proxy: ', \false); $this->outputResult($this->checkHttpProxy()); } if (\count($oauth = $config->get('github-oauth')) > 0) { foreach ($oauth as $domain => $token) { $io->write('Checking ' . $domain . ' oauth access: ', \false); $this->outputResult($this->checkGithubOauth($domain, $token)); } } else { $io->write('Checking github.com rate limit: ', \false); try { $rate = $this->getGithubRateLimit('github.com'); if (!\is_array($rate)) { $this->outputResult($rate); } elseif (10 > $rate['remaining']) { $io->write('WARNING'); $io->write(\sprintf('Github has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . \PHP_EOL . 'See https://developer.github.com/v3/#rate-limiting and also' . \PHP_EOL . ' https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens', $rate['remaining'], $rate['limit'])); } else { $this->outputResult(\true); } } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { $this->outputResult('The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it'); } else { $this->outputResult($e); } } } $io->write('Checking disk free space: ', \false); $this->outputResult($this->checkDiskSpace($config)); if (\strpos(__FILE__, 'phar:') === 0) { $io->write('Checking pubkeys: ', \false); $this->outputResult($this->checkPubKeys($config)); $io->write('Checking Composer version: ', \false); $this->outputResult($this->checkVersion($config)); } $io->write('Checking Composer and its dependencies for vulnerabilities: ', \false); $this->outputResult($this->checkComposerAudit($config)); $io->write(\sprintf('Composer version: %s', Composer::getVersion())); $platformOverrides = $config->get('platform') ?: []; $platformRepo = new PlatformRepository([], $platformOverrides); $phpPkg = $platformRepo->findPackage('php', '*'); $phpVersion = $phpPkg->getPrettyVersion(); if ($phpPkg instanceof CompletePackageInterface && \str_contains((string) $phpPkg->getDescription(), 'overridden')) { $phpVersion .= ' - ' . $phpPkg->getDescription(); } $io->write(\sprintf('PHP version: %s', $phpVersion)); if (\defined('PHP_BINARY')) { $io->write(\sprintf('PHP binary path: %s', \PHP_BINARY)); } $io->write('OpenSSL version: ' . (\defined('OPENSSL_VERSION_TEXT') ? '' . \OPENSSL_VERSION_TEXT . '' : 'missing')); $io->write('cURL version: ' . $this->getCurlVersion()); $finder = new ExecutableFinder(); $hasSystemUnzip = (bool) $finder->find('unzip'); $bin7zip = ''; if ($hasSystem7zip = (bool) $finder->find('7z', null, ['C:\\Program Files\\7-Zip'])) { $bin7zip = '7z'; } if (!Platform::isWindows() && !$hasSystem7zip && ($hasSystem7zip = (bool) $finder->find('7zz'))) { $bin7zip = '7zz'; } $io->write('zip: ' . (\extension_loaded('zip') ? 'extension present' : 'extension not loaded') . ', ' . ($hasSystemUnzip ? 'unzip present' : 'unzip not available') . ', ' . ($hasSystem7zip ? '7-Zip present (' . $bin7zip . ')' : '7-Zip not available') . (($hasSystem7zip || $hasSystemUnzip) && !\function_exists('proc_open') ? ', proc_open is disabled or not present, unzip/7-z will not be usable' : '')); return $this->exitCode; } /** * @return string|true */ private function checkComposerSchema() { $validator = new ConfigValidator($this->getIO()); [$errors, , $warnings] = $validator->validate(Factory::getComposerFile()); if ($errors || $warnings) { $messages = ['error' => $errors, 'warning' => $warnings]; $output = ''; foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { $output .= '<' . $style . '>' . $msg . '' . \PHP_EOL; } } return \rtrim($output); } return \true; } private function checkGit() : string { if (!\function_exists('proc_open')) { return 'proc_open is not available, git cannot be used'; } $this->process->execute('git config color.ui', $output); if (\strtolower(\trim($output)) === 'always') { return 'Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.'; } $gitVersion = Git::getVersion($this->process); if (null === $gitVersion) { return 'No git process found'; } if (\version_compare('2.24.0', $gitVersion, '>')) { return 'Your git version (' . $gitVersion . ') is too old and possibly will cause issues. Please upgrade to git 2.24 or above'; } return 'OK git version ' . $gitVersion . ''; } /** * @return string|string[]|true */ private function checkHttp(string $proto, Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } $result = []; if ($proto === 'https' && $config->get('disable-tls') === \true) { $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; } try { $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json'); } catch (TransportException $e) { $hints = HttpDownloader::getExceptionHints($e); if (null !== $hints && \count($hints) > 0) { foreach ($hints as $hint) { $result[] = $hint; } } $result[] = '[' . \get_class($e) . '] ' . $e->getMessage() . ''; } if (isset($tlsWarning)) { $result[] = $tlsWarning; } if (\count($result) > 0) { return $result; } return \true; } /** * @return string|true|\Exception */ private function checkHttpProxy() { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } $protocol = \extension_loaded('openssl') ? 'https' : 'http'; try { $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson(); $hash = \reset($json['provider-includes']); $hash = $hash['sha256']; $path = \str_replace('%hash%', $hash, \key($json['provider-includes'])); $provider = $this->httpDownloader->get($protocol . '://repo.packagist.org/' . $path)->getBody(); if (\hash('sha256', $provider) !== $hash) { return 'It seems that your proxy is modifying http traffic on the fly'; } } catch (\Exception $e) { return $e; } return \true; } /** * @return string|\Exception */ private function checkGithubOauth(string $domain, string $token) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); try { $url = $domain === 'github.com' ? 'https://api.' . $domain . '/' : 'https://' . $domain . '/api/v3/'; $response = $this->httpDownloader->get($url, ['retry-auth-failure' => \false]); $expiration = $response->getHeader('github-authentication-token-expiration'); if ($expiration === null) { return 'OK does not expire'; } return 'OK expires on ' . $expiration . ''; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { return 'The oauth token for ' . $domain . ' seems invalid, run "composer config --global --unset github-oauth.' . $domain . '" to remove it'; } return $e; } } /** * @param string $token * @throws TransportException * @return mixed|string */ private function getGithubRateLimit(string $domain, ?string $token = null) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } if ($token) { $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); } $url = $domain === 'github.com' ? 'https://api.' . $domain . '/rate_limit' : 'https://' . $domain . '/api/rate_limit'; $data = $this->httpDownloader->get($url, ['retry-auth-failure' => \false])->decodeJson(); return $data['resources']['core']; } /** * @return string|true */ private function checkDiskSpace(Config $config) { if (!\function_exists('disk_free_space')) { return \true; } $minSpaceFree = 1024 * 1024; if (($df = @\disk_free_space($dir = $config->get('home'))) !== \false && $df < $minSpaceFree || ($df = @\disk_free_space($dir = $config->get('vendor-dir'))) !== \false && $df < $minSpaceFree) { return 'The disk hosting ' . $dir . ' is full'; } return \true; } /** * @return string[]|true */ private function checkPubKeys(Config $config) { $home = $config->get('home'); $errors = []; $io = $this->getIO(); if (\file_exists($home . '/keys.tags.pub') && \file_exists($home . '/keys.dev.pub')) { $io->write(''); } if (\file_exists($home . '/keys.tags.pub')) { $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home . '/keys.tags.pub')); } else { $errors[] = 'Missing pubkey for tags verification'; } if (\file_exists($home . '/keys.dev.pub')) { $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home . '/keys.dev.pub')); } else { $errors[] = 'Missing pubkey for dev verification'; } if ($errors) { $errors[] = 'Run composer self-update --update-keys to set them up'; } return $errors ?: \true; } /** * @return string|\Exception|true */ private function checkVersion(Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } $versionsUtil = new Versions($config, $this->httpDownloader); try { $latest = $versionsUtil->getLatest(); } catch (\Exception $e) { return $e; } if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { return 'You are not running the latest ' . $versionsUtil->getChannel() . ' version, run `composer self-update` to update (' . Composer::VERSION . ' => ' . $latest['version'] . ')'; } return \true; } /** * @return string|true */ private function checkComposerAudit(Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } $auditor = new Auditor(); $repoSet = new RepositorySet(); $installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json'); if (!$installedJson->exists()) { return 'Could not find Composer\'s installed.json, this must be a non-standard Composer installation.'; } $localRepo = new FilesystemRepository($installedJson); $version = Composer::getVersion(); $packages = $localRepo->getCanonicalPackages(); if ($version !== '@package_version@') { $versionParser = new VersionParser(); $normalizedVersion = $versionParser->normalize($version); $rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version); $packages[] = $rootPkg; } $repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader)); try { $io = new BufferIO(); $result = $auditor->audit($io, $repoSet, $packages, Auditor::FORMAT_TABLE, \true, [], Auditor::ABANDONED_IGNORE); } catch (\Throwable $e) { return 'Failed performing audit: ' . $e->getMessage() . ''; } if ($result > 0) { return 'Audit found some issues:' . \PHP_EOL . $io->getOutput(); } return \true; } private function getCurlVersion() : string { if (\extension_loaded('curl')) { if (!HttpDownloader::isCurlEnabled()) { return 'disabled via disable_functions, using php streams fallback, which reduces performance'; } $version = \curl_version(); return '' . $version['version'] . ' ' . 'libz ' . (!empty($version['libz_version']) ? $version['libz_version'] : 'missing') . ' ' . 'ssl ' . ($version['ssl_version'] ?? 'missing') . ''; } return 'missing, using php streams fallback, which reduces performance'; } /** * @param bool|string|string[]|\Exception $result */ private function outputResult($result) : void { $io = $this->getIO(); if (\true === $result) { $io->write('OK'); return; } $hadError = \false; $hadWarning = \false; if ($result instanceof \Exception) { $result = '[' . \get_class($result) . '] ' . $result->getMessage() . ''; } if (!$result) { // falsey results should be considered as an error, even if there is nothing to output $hadError = \true; } else { if (!\is_array($result)) { $result = [$result]; } foreach ($result as $message) { if (\false !== \strpos($message, '')) { $hadError = \true; } elseif (\false !== \strpos($message, '')) { $hadWarning = \true; } } } if ($hadError) { $io->write('FAIL'); $this->exitCode = \max($this->exitCode, 2); } elseif ($hadWarning) { $io->write('WARNING'); $this->exitCode = \max($this->exitCode, 1); } if ($result) { foreach ($result as $message) { $io->write(\trim($message)); } } } /** * @return string|true */ private function checkPlatform() { $output = ''; $out = static function ($msg, $style) use(&$output) : void { $output .= '<' . $style . '>' . $msg . '' . \PHP_EOL; }; // code below taken from getcomposer.org/installer, any changes should be made there and replicated here $errors = []; $warnings = []; $displayIniMessage = \false; $iniMessage = \PHP_EOL . \PHP_EOL . IniHelper::getMessage(); $iniMessage .= \PHP_EOL . 'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; if (!\function_exists('json_decode')) { $errors['json'] = \true; } if (!\extension_loaded('Phar')) { $errors['phar'] = \true; } if (!\extension_loaded('filter')) { $errors['filter'] = \true; } if (!\extension_loaded('hash')) { $errors['hash'] = \true; } if (!\extension_loaded('iconv') && !\extension_loaded('mbstring')) { $errors['iconv_mbstring'] = \true; } if (!\filter_var(\ini_get('allow_url_fopen'), \FILTER_VALIDATE_BOOLEAN)) { $errors['allow_url_fopen'] = \true; } if (\extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { $errors['ioncube'] = ioncube_loader_version(); } if (\PHP_VERSION_ID < 70205) { $errors['php'] = \PHP_VERSION; } if (!\extension_loaded('openssl')) { $errors['openssl'] = \true; } if (\extension_loaded('openssl') && \OPENSSL_VERSION_NUMBER < 0x1000100f) { $warnings['openssl_version'] = \true; } if (!\defined('_ContaoManager\\HHVM_VERSION') && !\extension_loaded('apcu') && \filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { $warnings['apc_cli'] = \true; } if (!\extension_loaded('zlib')) { $warnings['zlib'] = \true; } \ob_start(); \phpinfo(\INFO_GENERAL); $phpinfo = \ob_get_clean(); if (\is_string($phpinfo) && Preg::isMatchStrictGroups('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { $configure = $match[1]; if (\str_contains($configure, '--enable-sigchild')) { $warnings['sigchild'] = \true; } if (\str_contains($configure, '--with-curlwrappers')) { $warnings['curlwrappers'] = \true; } } if (\filter_var(\ini_get('xdebug.profiler_enabled'), \FILTER_VALIDATE_BOOLEAN)) { $warnings['xdebug_profile'] = \true; } elseif (XdebugHandler::isXdebugActive()) { $warnings['xdebug_loaded'] = \true; } if (\defined('PHP_WINDOWS_VERSION_BUILD') && (\version_compare(\PHP_VERSION, '7.2.23', '<') || \version_compare(\PHP_VERSION, '7.3.0', '>=') && \version_compare(\PHP_VERSION, '7.3.10', '<'))) { $warnings['onedrive'] = \PHP_VERSION; } if (!empty($errors)) { foreach ($errors as $error => $current) { switch ($error) { case 'json': $text = \PHP_EOL . "The json extension is missing." . \PHP_EOL; $text .= "Install it or recompile php without --disable-json"; break; case 'phar': $text = \PHP_EOL . "The phar extension is missing." . \PHP_EOL; $text .= "Install it or recompile php without --disable-phar"; break; case 'filter': $text = \PHP_EOL . "The filter extension is missing." . \PHP_EOL; $text .= "Install it or recompile php without --disable-filter"; break; case 'hash': $text = \PHP_EOL . "The hash extension is missing." . \PHP_EOL; $text .= "Install it or recompile php without --disable-hash"; break; case 'iconv_mbstring': $text = \PHP_EOL . "The iconv OR mbstring extension is required and both are missing." . \PHP_EOL; $text .= "Install either of them or recompile php without --disable-iconv"; break; case 'php': $text = \PHP_EOL . "Your PHP ({$current}) is too old, you must upgrade to PHP 7.2.5 or higher."; break; case 'allow_url_fopen': $text = \PHP_EOL . "The allow_url_fopen setting is incorrect." . \PHP_EOL; $text .= "Add the following to the end of your `php.ini`:" . \PHP_EOL; $text .= " allow_url_fopen = On"; $displayIniMessage = \true; break; case 'ioncube': $text = \PHP_EOL . "Your ionCube Loader extension ({$current}) is incompatible with Phar files." . \PHP_EOL; $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:" . \PHP_EOL; $text .= " zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so"; $displayIniMessage = \true; break; case 'openssl': $text = \PHP_EOL . "The openssl extension is missing, which means that secure HTTPS transfers are impossible." . \PHP_EOL; $text .= "If possible you should enable it or recompile php with --with-openssl"; break; default: throw new \InvalidArgumentException(\sprintf("DiagnoseCommand: Unknown error type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $error)); } $out($text, 'error'); } $output .= \PHP_EOL; } if (!empty($warnings)) { foreach ($warnings as $warning => $current) { switch ($warning) { case 'apc_cli': $text = "The apc.enable_cli setting is incorrect." . \PHP_EOL; $text .= "Add the following to the end of your `php.ini`:" . \PHP_EOL; $text .= " apc.enable_cli = Off"; $displayIniMessage = \true; break; case 'zlib': $text = 'The zlib extension is not loaded, this can slow down Composer a lot.' . \PHP_EOL; $text .= 'If possible, enable it or recompile php with --with-zlib' . \PHP_EOL; $displayIniMessage = \true; break; case 'sigchild': $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms." . \PHP_EOL; $text .= "Recompile it without this flag if possible, see also:" . \PHP_EOL; $text .= " https://bugs.php.net/bug.php?id=22999"; break; case 'curlwrappers': $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub." . \PHP_EOL; $text .= " Recompile it without this flag if possible"; break; case 'openssl_version': // Attempt to parse version number out, fallback to whole string value. $opensslVersion = \strstr(\trim(\strstr(\OPENSSL_VERSION_TEXT, ' ')), ' ', \true); $opensslVersion = $opensslVersion ?: \OPENSSL_VERSION_TEXT; $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1." . \PHP_EOL; $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; break; case 'xdebug_loaded': $text = "The xdebug extension is loaded, this can slow down Composer a little." . \PHP_EOL; $text .= " Disabling it when using Composer is recommended."; break; case 'xdebug_profile': $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot." . \PHP_EOL; $text .= "Add the following to the end of your `php.ini` to disable it:" . \PHP_EOL; $text .= " xdebug.profiler_enabled = 0"; $displayIniMessage = \true; break; case 'onedrive': $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10." . \PHP_EOL; $text .= "Upgrade your PHP ({$current}) to use this location with Composer." . \PHP_EOL; break; default: throw new \InvalidArgumentException(\sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning)); } $out($text, 'comment'); } } if ($displayIniMessage) { $out($iniMessage, 'comment'); } if (\in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], \true)) { $warnings['ipresolve'] = \true; $out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') . ' which may result in network failures below.', 'comment'); } return \count($warnings) === 0 && \count($errors) === 0 ? \true : $output; } /** * Check if allow_url_fopen is ON * * @return string|true */ private function checkConnectivity() { if (!\ini_get('allow_url_fopen')) { return 'SKIP Because allow_url_fopen is missing.'; } return \true; } /** * @return string|true */ private function checkConnectivityAndComposerNetworkHttpEnablement() { $result = $this->checkConnectivity(); if ($result !== \true) { return $result; } $result = $this->checkComposerNetworkHttpEnablement(); if ($result !== \true) { return $result; } return \true; } /** * Check if Composer network is enabled for HTTP/S * * @return string|true */ private function checkComposerNetworkHttpEnablement() { if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { return 'SKIP Network is disabled by COMPOSER_DISABLE_NETWORK.'; } return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Factory; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\StringInput; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class GlobalCommand extends \Composer\Command\BaseCommand { public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { $application = $this->getApplication(); if ($input->mustSuggestArgumentValuesFor('command-name')) { $suggestions->suggestValues(\array_values(\array_filter(\array_map(static function (Command $command) { return $command->isHidden() ? null : $command->getName(); }, $application->all())))); return; } if ($application->has($commandName = $input->getArgument('command-name'))) { $input = $this->prepareSubcommandInput($input, \true); $input = CompletionInput::fromString($input->__toString(), 2); $command = $application->find($commandName); $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); $command->complete($input, $suggestions); } } protected function configure() : void { $this->setName('global')->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME)')->setDefinition([new InputArgument('command-name', InputArgument::REQUIRED, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, '')])->setHelp(<<\\AppData\\Roaming\\Composer on Windows and /home//.composer on unix systems. If your system uses freedesktop.org standards, then it will first check XDG_CONFIG_HOME or default to /home//.config/composer Note: This path may vary depending on customizations to bin-dir in composer.json or the environmental variable COMPOSER_BIN_DIR. Read more at https://getcomposer.org/doc/03-cli.md#global EOT ); } /** * @throws \Symfony\Component\Console\Exception\ExceptionInterface */ public function run(InputInterface $input, OutputInterface $output) : int { // TODO remove for Symfony 6+ as it is then in the interface if (!\method_exists($input, '__toString')) { // @phpstan-ignore-line throw new \LogicException('Expected an Input instance that is stringable, got ' . \get_class($input)); } // extract real command name $tokens = Preg::split('{\\s+}', $input->__toString()); $args = []; foreach ($tokens as $token) { if ($token && $token[0] !== '-') { $args[] = $token; if (\count($args) >= 2) { break; } } } // show help for this command if no command was found if (\count($args) < 2) { return parent::run($input, $output); } $input = $this->prepareSubcommandInput($input); return $this->getApplication()->run($input, $output); } private function prepareSubcommandInput(InputInterface $input, bool $quiet = \false) : StringInput { // TODO remove for Symfony 6+ as it is then in the interface if (!\method_exists($input, '__toString')) { // @phpstan-ignore-line throw new \LogicException('Expected an Input instance that is stringable, got ' . \get_class($input)); } // The COMPOSER env var should not apply to the global execution scope if (Platform::getEnv('COMPOSER')) { Platform::clearEnv('COMPOSER'); } // change to global dir $config = Factory::createConfig(); $home = $config->get('home'); if (!\is_dir($home)) { $fs = new Filesystem(); $fs->ensureDirectoryExists($home); if (!\is_dir($home)) { throw new \RuntimeException('Could not create home directory'); } } try { \chdir($home); } catch (\Exception $e) { throw new \RuntimeException('Could not switch to home directory "' . $home . '"', 0, $e); } if (!$quiet) { $this->getIO()->writeError('Changed current directory to ' . $home . ''); } // create new input without "global" command prefix $input = new StringInput(Preg::replace('{\\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\\b}', '', $input->__toString(), 1)); $this->getApplication()->resetComposer(); return $input; } /** * @inheritDoc */ public function isProxyCommand() : bool { return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use Composer\Composer; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jordi Boggiano */ class AboutCommand extends \Composer\Command\BaseCommand { protected function configure() : void { $this->setName('about')->setDescription('Shows a short information about Composer')->setHelp(<<php composer.phar about EOT ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composerVersion = Composer::getVersion(); $this->getIO()->write(<<Composer - Dependency Manager for PHP - version {$composerVersion} Composer is a dependency manager tracking local dependencies of your projects and libraries. See https://getcomposer.org/ for more information. EOT ); return 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use Composer\Console\Input\InputArgument; /** * @author Davey Shafik */ class ExecCommand extends \Composer\Command\BaseCommand { /** * @return void */ protected function configure() { $this->setName('exec')->setDescription('Executes a vendored binary/script')->setDefinition([new InputOption('list', 'l', InputOption::VALUE_NONE), new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () { return $this->getBinaries(\false); }), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Arguments to pass to the binary. Use -- to separate from composer arguments')])->setHelp(<<getBinaries(\false); if (\count($binaries) === 0) { return; } if ($input->getArgument('binary') !== null || $input->getOption('list')) { return; } $io = $this->getIO(); /** @var int $binary */ $binary = $io->select('Binary to run: ', $binaries, '', 1, 'Invalid binary name "%s"'); $input->setArgument('binary', $binaries[$binary]); } protected function execute(InputInterface $input, OutputInterface $output) : int { $composer = $this->requireComposer(); if ($input->getOption('list') || null === $input->getArgument('binary')) { $bins = $this->getBinaries(\true); if ([] === $bins) { $binDir = $composer->getConfig()->get('bin-dir'); throw new \RuntimeException("No binaries found in composer.json or in bin-dir ({$binDir})"); } $this->getIO()->write(<<Available binaries: EOT ); foreach ($bins as $bin) { $this->getIO()->write(<<- {$bin} EOT ); } return 0; } $binary = $input->getArgument('binary'); $dispatcher = $composer->getEventDispatcher(); $dispatcher->addListener('__exec_command', $binary); // If the CWD was modified, we restore it to what it was initially, as it was // most likely modified by the global command, and we want exec to run in the local working directory // not the global one if (\getcwd() !== $this->getApplication()->getInitialWorkingDirectory() && $this->getApplication()->getInitialWorkingDirectory() !== \false) { try { \chdir($this->getApplication()->getInitialWorkingDirectory()); } catch (\Exception $e) { throw new \RuntimeException('Could not switch back to working directory "' . $this->getApplication()->getInitialWorkingDirectory() . '"', 0, $e); } } return $dispatcher->dispatchScript('__exec_command', \true, $input->getArgument('args')); } /** * @return list */ private function getBinaries(bool $forDisplay) : array { $composer = $this->requireComposer(); $binDir = $composer->getConfig()->get('bin-dir'); $bins = \glob($binDir . '/*'); $localBins = $composer->getPackage()->getBinaries(); if ($forDisplay) { $localBins = \array_map(static function ($e) { return "{$e} (local)"; }, $localBins); } $binaries = []; foreach (\array_merge($bins, $localBins) as $bin) { // skip .bat copies if (isset($previousBin) && $bin === $previousBin . '.bat') { continue; } $previousBin = $bin; $binaries[] = \basename($bin); } return $binaries; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\AutoloadGenerator; use Composer\Console\GithubActionError; use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\LocalRepoTransaction; use Composer\DependencyResolver\LockTransaction; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\PoolOptimizer; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\DependencyResolver\PolicyInterface; use Composer\Downloader\DownloadManager; use Composer\Downloader\TransportException; use Composer\EventDispatcher\EventDispatcher; use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\Installer\InstallationManager; use Composer\Installer\InstallerEvents; use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; use Composer\Package\RootAliasPackage; use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\CompletePackageInterface; use Composer\Package\Link; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Version\VersionParser; use Composer\Package\Package; use Composer\Repository\ArrayRepository; use Composer\Repository\RepositorySet; use Composer\Repository\CompositeRepository; use Composer\Semver\Constraint\Constraint; use Composer\Package\Locker; use Composer\Package\RootPackageInterface; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepository; use Composer\Repository\RootPackageRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryManager; use Composer\Repository\LockArrayRepository; use Composer\Script\ScriptEvents; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Advisory\Auditor; use Composer\Util\Platform; /** * @author Jordi Boggiano * @author Beau Simensen * @author Konstantin Kudryashov * @author Nils Adermann */ class Installer { public const ERROR_NONE = 0; // no error/success state public const ERROR_GENERIC_FAILURE = 1; public const ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE = 3; public const ERROR_LOCK_FILE_INVALID = 4; // used/declared in SolverProblemsException, carried over here for completeness public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; public const ERROR_AUDIT_FAILED = 5; /** * @var IOInterface */ protected $io; /** * @var Config */ protected $config; /** * @var RootPackageInterface&BasePackage */ protected $package; // TODO can we get rid of the below and just use the package itself? /** * @var RootPackageInterface&BasePackage */ protected $fixedRootPackage; /** * @var DownloadManager */ protected $downloadManager; /** * @var RepositoryManager */ protected $repositoryManager; /** * @var Locker */ protected $locker; /** * @var InstallationManager */ protected $installationManager; /** * @var EventDispatcher */ protected $eventDispatcher; /** * @var AutoloadGenerator */ protected $autoloadGenerator; /** @var bool */ protected $preferSource = \false; /** @var bool */ protected $preferDist = \false; /** @var bool */ protected $optimizeAutoloader = \false; /** @var bool */ protected $classMapAuthoritative = \false; /** @var bool */ protected $apcuAutoloader = \false; /** @var string|null */ protected $apcuAutoloaderPrefix = null; /** @var bool */ protected $devMode = \false; /** @var bool */ protected $dryRun = \false; /** @var bool */ protected $downloadOnly = \false; /** @var bool */ protected $verbose = \false; /** @var bool */ protected $update = \false; /** @var bool */ protected $install = \true; /** @var bool */ protected $dumpAutoloader = \true; /** @var bool */ protected $runScripts = \true; /** @var bool */ protected $preferStable = \false; /** @var bool */ protected $preferLowest = \false; /** @var bool */ protected $minimalUpdate = \false; /** @var bool */ protected $writeLock; /** @var bool */ protected $executeOperations = \true; /** @var bool */ protected $audit = \true; /** @var bool */ protected $errorOnAudit = \false; /** @var Auditor::FORMAT_* */ protected $auditFormat = Auditor::FORMAT_SUMMARY; /** @var bool */ protected $updateMirrors = \false; /** * Array of package names/globs flagged for update * * @var non-empty-list|null */ protected $updateAllowList = null; /** @var Request::UPDATE_* */ protected $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; /** * @var SuggestedPackagesReporter */ protected $suggestedPackagesReporter; /** * @var PlatformRequirementFilterInterface */ protected $platformRequirementFilter; /** * @var ?RepositoryInterface */ protected $additionalFixedRepository; /** @var array */ protected $temporaryConstraints = []; /** * Constructor * * @param RootPackageInterface&BasePackage $package */ public function __construct(IOInterface $io, \Composer\Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator) { $this->io = $io; $this->config = $config; $this->package = $package; $this->downloadManager = $downloadManager; $this->repositoryManager = $repositoryManager; $this->locker = $locker; $this->installationManager = $installationManager; $this->eventDispatcher = $eventDispatcher; $this->autoloadGenerator = $autoloadGenerator; $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io); $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); $this->writeLock = $config->get('lock'); } /** * Run installation (or update) * * @throws \Exception * @return int 0 on success or a positive error code on failure * @phpstan-return self::ERROR_* */ public function run() : int { // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands // of PHP objects, the GC can spend quite some time walking the tree of references looking // for stuff to collect while there is nothing to collect. This slows things down dramatically // and turning it off results in much better performance. Do not try this at home however. \gc_collect_cycles(); \gc_disable(); if ($this->updateAllowList !== null && $this->updateMirrors) { throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive."); } $isFreshInstall = $this->repositoryManager->getLocalRepository()->isFresh(); // Force update if there is no lock file present if (!$this->update && !$this->locker->isLocked()) { $this->io->writeError('No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.'); $this->update = \true; } if ($this->dryRun) { $this->verbose = \true; $this->runScripts = \false; $this->executeOperations = \false; $this->writeLock = \false; $this->dumpAutoloader = \false; $this->mockLocalRepositories($this->repositoryManager); } if ($this->downloadOnly) { $this->dumpAutoloader = \false; } if ($this->update && !$this->install) { $this->dumpAutoloader = \false; } if ($this->runScripts) { Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); // dispatch pre event // should we treat this more strictly as running an update and then running an install, triggering events multiple times? $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } $this->downloadManager->setPreferSource($this->preferSource); $this->downloadManager->setPreferDist($this->preferDist); $localRepo = $this->repositoryManager->getLocalRepository(); try { if ($this->update) { $res = $this->doUpdate($localRepo, $this->install); } else { $res = $this->doInstall($localRepo); } if ($res !== 0) { return $res; } } catch (\Exception $e) { if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { $this->installationManager->notifyInstalls($this->io); } throw $e; } if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { $this->installationManager->notifyInstalls($this->io); } if ($this->update) { $installedRepo = new InstalledRepository([$this->locker->getLockedRepository($this->devMode), $this->createPlatformRepo(\false), new RootPackageRepository(clone $this->package)]); if ($isFreshInstall) { $this->suggestedPackagesReporter->addSuggestionsFromPackage($this->package); } $this->suggestedPackagesReporter->outputMinimalistic($installedRepo); } // Find abandoned packages and warn user $lockedRepository = $this->locker->getLockedRepository(\true); foreach ($lockedRepository->getPackages() as $package) { if (!$package instanceof CompletePackage || !$package->isAbandoned()) { continue; } $replacement = \is_string($package->getReplacementPackage()) ? 'Use ' . $package->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $this->io->writeError(\sprintf("Package %s is abandoned, you should avoid using it. %s.", $package->getPrettyName(), $replacement)); } if ($this->dumpAutoloader) { // write autoloader if ($this->optimizeAutoloader) { $this->io->writeError('Generating optimized autoload files'); } else { $this->io->writeError('Generating autoload files'); } $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix); $this->autoloadGenerator->setRunScripts($this->runScripts); $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader, null, $this->locker); } if ($this->install && $this->executeOperations) { // force binaries re-generation in case they are missing foreach ($localRepo->getPackages() as $package) { $this->installationManager->ensureBinariesPresence($package); } } $fundEnv = Platform::getEnv('COMPOSER_FUND'); $showFunding = \true; if (\is_numeric($fundEnv)) { $showFunding = \intval($fundEnv) !== 0; } if ($showFunding) { $fundingCount = 0; foreach ($localRepo->getPackages() as $package) { if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) { $fundingCount++; } } if ($fundingCount > 0) { $this->io->writeError([\sprintf("%d package%s you are using %s looking for funding.", $fundingCount, 1 === $fundingCount ? '' : 's', 1 === $fundingCount ? 'is' : 'are'), 'Use the `composer fund` command to find out more!']); } } if ($this->runScripts) { // dispatch post event $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } // re-enable GC except on HHVM which triggers a warning here if (!\defined('_ContaoManager\\HHVM_VERSION')) { \gc_enable(); } if ($this->audit) { if ($this->update && !$this->install) { $packages = $lockedRepository->getCanonicalPackages(); $target = 'locked'; } else { $packages = $localRepo->getCanonicalPackages(); $target = 'installed'; } if (\count($packages) > 0) { try { $auditor = new Auditor(); $repoSet = new RepositorySet(); foreach ($this->repositoryManager->getRepositories() as $repo) { $repoSet->addRepository($repo); } $auditConfig = $this->config->get('audit'); return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat, \true, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0; } catch (TransportException $e) { $this->io->error('Failed to audit ' . $target . ' packages.'); if ($this->io->isVerbose()) { $this->io->error('[' . \get_class($e) . '] ' . $e->getMessage()); } } } else { $this->io->writeError('No ' . $target . ' packages - skipping audit.'); } } return 0; } /** * @phpstan-return self::ERROR_* */ protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doInstall) : int { $platformRepo = $this->createPlatformRepo(\true); $aliases = $this->getRootAliases(\true); $lockedRepository = null; try { if ($this->locker->isLocked()) { $lockedRepository = $this->locker->getLockedRepository(\true); } } catch (\_ContaoManager\Seld\JsonLint\ParsingException $e) { if ($this->updateAllowList !== null || $this->updateMirrors) { // in case we are doing a partial update or updating mirrors, the lock file is needed so we error throw $e; } // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when // doing a full update } if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) { $this->io->writeError('Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.', \true, IOInterface::QUIET); return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE; } $this->io->writeError('Loading composer repositories with package information'); // creating repository set $policy = $this->createPolicy(\true, $lockedRepository); $repositorySet = $this->createRepositorySet(\true, $platformRepo, $aliases); $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { $repositorySet->addRepository($repository); } if ($lockedRepository) { $repositorySet->addRepository($lockedRepository); } $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); $this->requirePackagesForUpdate($request, $lockedRepository, \true); // pass the allow list into the request, so the pool builder can apply it if ($this->updateAllowList !== null) { $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies); } $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy)); $this->io->writeError('Updating dependencies'); // solve dependencies $solver = new Solver($policy, $pool, $this->io); try { $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); $ruleSetSize = $solver->getRuleSetSize(); $solver = null; } catch (SolverProblemsException $e) { $err = 'Your requirements could not be resolved to an installable set of packages.'; $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); $this->io->writeError('' . $err . '', \true, IOInterface::QUIET); $this->io->writeError($prettyProblem); if (!$this->devMode) { $this->io->writeError('Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.', \true, IOInterface::QUIET); } $ghe = new GithubActionError($this->io); $ghe->emit($err . "\n" . $prettyProblem); return \max(self::ERROR_GENERIC_FAILURE, $e->getCode()); } $this->io->writeError("Analyzed " . \count($pool) . " packages to resolve dependencies", \true, IOInterface::VERBOSE); $this->io->writeError("Analyzed " . $ruleSetSize . " rules to resolve dependencies", \true, IOInterface::VERBOSE); $pool = null; if (!$lockTransaction->getOperations()) { $this->io->writeError('Nothing to modify in lock file'); } $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository); if ($exitCode !== 0) { return $exitCode; } // exists as of composer/semver 3.3.0 if (\method_exists('Composer\\Semver\\CompilingMatcher', 'clear')) { // @phpstan-ignore-line \Composer\Semver\CompilingMatcher::clear(); } // write lock $platformReqs = $this->extractPlatformRequirements($this->package->getRequires()); $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires()); $installsUpdates = $uninstalls = []; if ($lockTransaction->getOperations()) { $installNames = $updateNames = $uninstallNames = []; foreach ($lockTransaction->getOperations() as $operation) { if ($operation instanceof InstallOperation) { $installsUpdates[] = $operation; $installNames[] = $operation->getPackage()->getPrettyName() . ':' . $operation->getPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UpdateOperation) { // when mirrors/metadata from a package gets updated we do not want to list it as an // update in the output as it is only an internal lock file metadata update if ($this->updateMirrors && $operation->getInitialPackage()->getName() === $operation->getTargetPackage()->getName() && $operation->getInitialPackage()->getVersion() === $operation->getTargetPackage()->getVersion()) { continue; } $installsUpdates[] = $operation; $updateNames[] = $operation->getTargetPackage()->getPrettyName() . ':' . $operation->getTargetPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UninstallOperation) { $uninstalls[] = $operation; $uninstallNames[] = $operation->getPackage()->getPrettyName(); } } if ($this->config->get('lock')) { $this->io->writeError(\sprintf("Lock file operations: %d install%s, %d update%s, %d removal%s", \count($installNames), 1 === \count($installNames) ? '' : 's', \count($updateNames), 1 === \count($updateNames) ? '' : 's', \count($uninstalls), 1 === \count($uninstalls) ? '' : 's')); if ($installNames) { $this->io->writeError("Installs: " . \implode(', ', $installNames), \true, IOInterface::VERBOSE); } if ($updateNames) { $this->io->writeError("Updates: " . \implode(', ', $updateNames), \true, IOInterface::VERBOSE); } if ($uninstalls) { $this->io->writeError("Removals: " . \implode(', ', $uninstallNames), \true, IOInterface::VERBOSE); } } } $sortByName = static function ($a, $b) : int { if ($a instanceof UpdateOperation) { $a = $a->getTargetPackage()->getName(); } else { $a = $a->getPackage()->getName(); } if ($b instanceof UpdateOperation) { $b = $b->getTargetPackage()->getName(); } else { $b = $b->getPackage()->getName(); } return \strcmp($a, $b); }; \usort($uninstalls, $sortByName); \usort($installsUpdates, $sortByName); foreach (\array_merge($uninstalls, $installsUpdates) as $operation) { // collect suggestions if ($operation instanceof InstallOperation) { $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage()); } // output op if lock file is enabled, but alias op only in debug verbosity if ($this->config->get('lock') && (\false === \strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) { $sourceRepo = ''; if ($this->io->isVeryVerbose() && \false === \strpos($operation->getOperationType(), 'Alias')) { $operationPkg = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); if ($operationPkg->getRepository() !== null) { $sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName(); } } $this->io->writeError(' - ' . $operation->show(\true) . $sourceRepo); } } $updatedLock = $this->locker->setLockData($lockTransaction->getNewLockPackages(\false, $this->updateMirrors), $lockTransaction->getNewLockPackages(\true, $this->updateMirrors), $platformReqs, $platformDevReqs, $lockTransaction->getAliases($aliases), $this->package->getMinimumStability(), $this->package->getStabilityFlags(), $this->preferStable || $this->package->getPreferStable(), $this->preferLowest, $this->config->get('platform') ?: [], $this->writeLock && $this->executeOperations); if ($updatedLock && $this->writeLock && $this->executeOperations) { $this->io->writeError('Writing lock file'); } if ($doInstall) { // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false return $this->doInstall($localRepo, \true); } return 0; } /** * Run the solver a second time on top of the existing update result with only the current result set in the pool * and see what packages would get removed if we only had the non-dev packages in the solver request * * @param array> $aliases * * @phpstan-param list $aliases * @phpstan-return self::ERROR_* */ protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, ?LockArrayRepository $lockedRepository = null) : int { if (!$this->package->getDevRequires()) { return 0; } $resultRepo = new ArrayRepository([]); $loader = new ArrayLoader(null, \true); $dumper = new ArrayDumper(); foreach ($lockTransaction->getNewLockPackages(\false) as $pkg) { $resultRepo->addPackage($loader->load($dumper->dump($pkg))); } $repositorySet = $this->createRepositorySet(\true, $platformRepo, $aliases); $repositorySet->addRepository($resultRepo); $request = $this->createRequest($this->fixedRootPackage, $platformRepo); $this->requirePackagesForUpdate($request, $lockedRepository, \false); $pool = $repositorySet->createPoolWithAllPackages(); $solver = new Solver($policy, $pool, $this->io); try { $nonDevLockTransaction = $solver->solve($request, $this->platformRequirementFilter); $solver = null; } catch (SolverProblemsException $e) { $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.'; $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), \true); $this->io->writeError('' . $err . '', \true, IOInterface::QUIET); $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.'); $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.'); $this->io->writeError($prettyProblem); $ghe = new GithubActionError($this->io); $ghe->emit($err . "\n" . $prettyProblem); return $e->getCode(); } $lockTransaction->setNonDevPackages($nonDevLockTransaction); return 0; } /** * @param bool $alreadySolved Whether the function is called as part of an update command or independently * @return int exit code * @phpstan-return self::ERROR_* */ protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alreadySolved = \false) : int { if ($this->config->get('lock')) { $this->io->writeError('Installing dependencies from lock file' . ($this->devMode ? ' (including require-dev)' : '') . ''); } $lockedRepository = $this->locker->getLockedRepository($this->devMode); // verify that the lock file works with the current platform repository // we can skip this part if we're doing this as the second step after an update if (!$alreadySolved) { $this->io->writeError('Verifying lock file contents can be installed on current platform.'); $platformRepo = $this->createPlatformRepo(\false); // creating repository set $policy = $this->createPolicy(\false); // use aliases from lock file only, so empty root aliases here $repositorySet = $this->createRepositorySet(\false, $platformRepo, [], $lockedRepository); $repositorySet->addRepository($lockedRepository); // creating requirements request $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); if (!$this->locker->isFresh()) { $this->io->writeError('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update `.', \true, IOInterface::QUIET); } $missingRequirementInfo = $this->locker->getMissingRequirementInfo($this->package, $this->devMode); if ($missingRequirementInfo !== []) { $this->io->writeError($missingRequirementInfo); return self::ERROR_LOCK_FILE_INVALID; } foreach ($lockedRepository->getPackages() as $package) { $request->fixLockedPackage($package); } foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) { $request->requireName($link->getTarget(), $link->getConstraint()); } $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher); // solve dependencies $solver = new Solver($policy, $pool, $this->io); try { $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); $solver = null; // installing the locked packages on this platform resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system if (0 !== \count($lockTransaction->getOperations())) { $this->io->writeError('Your lock file cannot be installed on this system without changes. Please run composer update.', \true, IOInterface::QUIET); return self::ERROR_LOCK_FILE_INVALID; } } catch (SolverProblemsException $e) { $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.'; $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); $this->io->writeError('' . $err . '', \true, IOInterface::QUIET); $this->io->writeError($prettyProblem); $ghe = new GithubActionError($this->io); $ghe->emit($err . "\n" . $prettyProblem); return \max(self::ERROR_GENERIC_FAILURE, $e->getCode()); } } // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly? $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction); $installs = $updates = $uninstalls = []; foreach ($localRepoTransaction->getOperations() as $operation) { if ($operation instanceof InstallOperation) { $installs[] = $operation->getPackage()->getPrettyName() . ':' . $operation->getPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UpdateOperation) { $updates[] = $operation->getTargetPackage()->getPrettyName() . ':' . $operation->getTargetPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UninstallOperation) { $uninstalls[] = $operation->getPackage()->getPrettyName(); } } if ($installs === [] && $updates === [] && $uninstalls === []) { $this->io->writeError('Nothing to install, update or remove'); } else { $this->io->writeError(\sprintf("Package operations: %d install%s, %d update%s, %d removal%s", \count($installs), 1 === \count($installs) ? '' : 's', \count($updates), 1 === \count($updates) ? '' : 's', \count($uninstalls), 1 === \count($uninstalls) ? '' : 's')); if ($installs) { $this->io->writeError("Installs: " . \implode(', ', $installs), \true, IOInterface::VERBOSE); } if ($updates) { $this->io->writeError("Updates: " . \implode(', ', $updates), \true, IOInterface::VERBOSE); } if ($uninstalls) { $this->io->writeError("Removals: " . \implode(', ', $uninstalls), \true, IOInterface::VERBOSE); } } if ($this->executeOperations) { $localRepo->setDevPackageNames($this->locker->getDevPackageNames()); $this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly); // see https://github.com/composer/composer/issues/2764 if (\count($localRepoTransaction->getOperations()) > 0) { $vendorDir = $this->config->get('vendor-dir'); if (\is_dir($vendorDir)) { // suppress errors as this fails sometimes on OSX for no apparent reason // see https://github.com/composer/composer/issues/4070#issuecomment-129792748 @\touch($vendorDir); } } } else { foreach ($localRepoTransaction->getOperations() as $operation) { // output op, but alias op only in debug verbosity if (\false === \strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) { $this->io->writeError(' - ' . $operation->show(\false)); } } } return 0; } protected function createPlatformRepo(bool $forUpdate) : PlatformRepository { if ($forUpdate) { $platformOverrides = $this->config->get('platform') ?: []; } else { $platformOverrides = $this->locker->getPlatformOverrides(); } return new PlatformRepository([], $platformOverrides); } /** * @param array> $rootAliases * * @phpstan-param list $rootAliases */ private function createRepositorySet(bool $forUpdate, PlatformRepository $platformRepo, array $rootAliases = [], ?RepositoryInterface $lockedRepository = null) : RepositorySet { if ($forUpdate) { $minimumStability = $this->package->getMinimumStability(); $stabilityFlags = $this->package->getStabilityFlags(); $requires = \array_merge($this->package->getRequires(), $this->package->getDevRequires()); } else { $minimumStability = $this->locker->getMinimumStability(); $stabilityFlags = $this->locker->getStabilityFlags(); $requires = []; foreach ($lockedRepository->getPackages() as $package) { $constraint = new Constraint('=', $package->getVersion()); $constraint->setPrettyString($package->getPrettyVersion()); $requires[$package->getName()] = $constraint; } } $rootRequires = []; foreach ($requires as $req => $constraint) { if ($constraint instanceof Link) { $constraint = $constraint->getConstraint(); } // skip platform requirements from the root package to avoid filtering out existing platform packages if ($this->platformRequirementFilter->isIgnored($req)) { continue; } elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint); } $rootRequires[$req] = $constraint; } $this->fixedRootPackage = clone $this->package; $this->fixedRootPackage->setRequires([]); $this->fixedRootPackage->setDevRequires([]); $stabilityFlags[$this->package->getName()] = BasePackage::$stabilities[VersionParser::parseStability($this->package->getVersion())]; $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints); $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage)); $repositorySet->addRepository($platformRepo); if ($this->additionalFixedRepository) { // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet // see https://github.com/composer/composer/pull/9574 $additionalFixedRepositories = $this->additionalFixedRepository; if ($additionalFixedRepositories instanceof CompositeRepository) { $additionalFixedRepositories = $additionalFixedRepositories->getRepositories(); } else { $additionalFixedRepositories = [$additionalFixedRepositories]; } foreach ($additionalFixedRepositories as $additionalFixedRepository) { if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) { $repositorySet->allowInstalledRepositories(); break; } } $repositorySet->addRepository($this->additionalFixedRepository); } return $repositorySet; } private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null) : DefaultPolicy { $preferStable = null; $preferLowest = null; if (!$forUpdate) { $preferStable = $this->locker->getPreferStable(); $preferLowest = $this->locker->getPreferLowest(); } // old lock file without prefer stable/lowest will return null // so in this case we use the composer.json info if (null === $preferStable) { $preferStable = $this->preferStable || $this->package->getPreferStable(); } if (null === $preferLowest) { $preferLowest = $this->preferLowest; } $preferredVersions = null; if ($forUpdate && $this->minimalUpdate && $this->updateAllowList !== null && $lockedRepo !== null) { $preferredVersions = []; foreach ($lockedRepo->getPackages() as $pkg) { if ($pkg instanceof AliasPackage || \in_array($pkg->getName(), $this->updateAllowList, \true)) { continue; } $preferredVersions[$pkg->getName()] = $pkg->getVersion(); } } return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions); } /** * @param RootPackageInterface&BasePackage $rootPackage */ private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, ?LockArrayRepository $lockedRepository = null) : Request { $request = new Request($lockedRepository); $request->fixPackage($rootPackage); if ($rootPackage instanceof RootAliasPackage) { $request->fixPackage($rootPackage->getAliasOf()); } $fixedPackages = $platformRepo->getPackages(); if ($this->additionalFixedRepository) { $fixedPackages = \array_merge($fixedPackages, $this->additionalFixedRepository->getPackages()); } // fix the version of all platform packages + additionally installed packages // to prevent the solver trying to remove or update those // TODO why not replaces? $provided = $rootPackage->getProvides(); foreach ($fixedPackages as $package) { // skip platform packages that are provided by the root package if ($package->getRepository() !== $platformRepo || !isset($provided[$package->getName()]) || !$provided[$package->getName()]->getConstraint()->matches(new Constraint('=', $package->getVersion()))) { $request->fixPackage($package); } } return $request; } private function requirePackagesForUpdate(Request $request, ?LockArrayRepository $lockedRepository = null, bool $includeDevRequires = \true) : void { // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata if ($this->updateMirrors) { $excludedPackages = []; if (!$includeDevRequires) { $excludedPackages = \array_flip($this->locker->getDevPackageNames()); } foreach ($lockedRepository->getPackages() as $lockedPackage) { // exclude alias packages here as for root aliases, both alias and aliased are // present in the lock repo and we only want to require the aliased version if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) { $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); } } } else { $links = $this->package->getRequires(); if ($includeDevRequires) { $links = \array_merge($links, $this->package->getDevRequires()); } foreach ($links as $link) { $request->requireName($link->getTarget(), $link->getConstraint()); } } } /** * @return array> * * @phpstan-return list */ private function getRootAliases(bool $forUpdate) : array { if ($forUpdate) { $aliases = $this->package->getAliases(); } else { $aliases = $this->locker->getAliases(); } return $aliases; } /** * @param Link[] $links * * @return array */ private function extractPlatformRequirements(array $links) : array { $platformReqs = []; foreach ($links as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { $platformReqs[$link->getTarget()] = $link->getPrettyConstraint(); } } return $platformReqs; } /** * Replace local repositories with InstalledArrayRepository instances * * This is to prevent any accidental modification of the existing repos on disk */ private function mockLocalRepositories(RepositoryManager $rm) : void { $packages = []; foreach ($rm->getLocalRepository()->getPackages() as $package) { $packages[(string) $package] = clone $package; } foreach ($packages as $key => $package) { if ($package instanceof AliasPackage) { $alias = (string) $package->getAliasOf(); $className = \get_class($package); $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); } } $rm->setLocalRepository(new InstalledArrayRepository($packages)); } private function createPoolOptimizer(PolicyInterface $policy) : ?PoolOptimizer { // Not the best architectural decision here, would need to be able // to configure from the outside of Installer but this is only // a debugging tool and should never be required in any other use case if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) { $this->io->write('Pool Optimizer was disabled for debugging purposes.', \true, IOInterface::DEBUG); return null; } return new PoolOptimizer($policy); } /** * Create Installer * * @return Installer */ public static function create(IOInterface $io, \Composer\Composer $composer) : self { return new static($io, $composer->getConfig(), $composer->getPackage(), $composer->getDownloadManager(), $composer->getRepositoryManager(), $composer->getLocker(), $composer->getInstallationManager(), $composer->getEventDispatcher(), $composer->getAutoloadGenerator()); } /** * @return $this */ public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository) : self { $this->additionalFixedRepository = $additionalFixedRepository; return $this; } /** * @param array $constraints * @return Installer */ public function setTemporaryConstraints(array $constraints) : self { $this->temporaryConstraints = $constraints; return $this; } /** * Whether to run in drymode or not * * @return Installer */ public function setDryRun(bool $dryRun = \true) : self { $this->dryRun = (bool) $dryRun; return $this; } /** * Checks, if this is a dry run (simulation mode). */ public function isDryRun() : bool { return $this->dryRun; } /** * Whether to download only or not. * * @return Installer */ public function setDownloadOnly(bool $downloadOnly = \true) : self { $this->downloadOnly = $downloadOnly; return $this; } /** * prefer source installation * * @return Installer */ public function setPreferSource(bool $preferSource = \true) : self { $this->preferSource = (bool) $preferSource; return $this; } /** * prefer dist installation * * @return Installer */ public function setPreferDist(bool $preferDist = \true) : self { $this->preferDist = (bool) $preferDist; return $this; } /** * Whether or not generated autoloader are optimized * * @return Installer */ public function setOptimizeAutoloader(bool $optimizeAutoloader) : self { $this->optimizeAutoloader = (bool) $optimizeAutoloader; if (!$this->optimizeAutoloader) { // Force classMapAuthoritative off when not optimizing the // autoloader $this->setClassMapAuthoritative(\false); } return $this; } /** * Whether or not generated autoloader considers the class map * authoritative. * * @return Installer */ public function setClassMapAuthoritative(bool $classMapAuthoritative) : self { $this->classMapAuthoritative = (bool) $classMapAuthoritative; if ($this->classMapAuthoritative) { // Force optimizeAutoloader when classmap is authoritative $this->setOptimizeAutoloader(\true); } return $this; } /** * Whether or not generated autoloader considers APCu caching. * * @return Installer */ public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderPrefix = null) : self { $this->apcuAutoloader = $apcuAutoloader; $this->apcuAutoloaderPrefix = $apcuAutoloaderPrefix; return $this; } /** * update packages * * @return Installer */ public function setUpdate(bool $update) : self { $this->update = (bool) $update; return $this; } /** * Allows disabling the install step after an update * * @return Installer */ public function setInstall(bool $install) : self { $this->install = (bool) $install; return $this; } /** * enables dev packages * * @return Installer */ public function setDevMode(bool $devMode = \true) : self { $this->devMode = (bool) $devMode; return $this; } /** * set whether to run autoloader or not * * This is disabled implicitly when enabling dryRun * * @return Installer */ public function setDumpAutoloader(bool $dumpAutoloader = \true) : self { $this->dumpAutoloader = (bool) $dumpAutoloader; return $this; } /** * set whether to run scripts or not * * This is disabled implicitly when enabling dryRun * * @return Installer * @deprecated Use setRunScripts(false) on the EventDispatcher instance being injected instead */ public function setRunScripts(bool $runScripts = \true) : self { $this->runScripts = (bool) $runScripts; return $this; } /** * set the config instance * * @return Installer */ public function setConfig(\Composer\Config $config) : self { $this->config = $config; return $this; } /** * run in verbose mode * * @return Installer */ public function setVerbose(bool $verbose = \true) : self { $this->verbose = (bool) $verbose; return $this; } /** * Checks, if running in verbose mode. */ public function isVerbose() : bool { return $this->verbose; } /** * set ignore Platform Package requirements * * If this is set to true, all platform requirements are ignored * If this is set to false, no platform requirements are ignored * If this is set to string[], those packages will be ignored * * @param bool|string[] $ignorePlatformReqs * * @return Installer * * @deprecated use setPlatformRequirementFilter instead */ public function setIgnorePlatformRequirements($ignorePlatformReqs) : self { \trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', \E_USER_DEPRECATED); return $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); } /** * @return Installer */ public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter) : self { $this->platformRequirementFilter = $platformRequirementFilter; return $this; } /** * Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info * * @return Installer */ public function setUpdateMirrors(bool $updateMirrors) : self { $this->updateMirrors = $updateMirrors; return $this; } /** * restrict the update operation to a few packages, all other packages * that are already installed will be kept at their current version * * @param string[] $packages * * @return Installer */ public function setUpdateAllowList(array $packages) : self { if (\count($packages) === 0) { $this->updateAllowList = null; } else { $this->updateAllowList = \array_values(\array_unique(\array_map('strtolower', $packages))); } return $this; } /** * Should dependencies of packages marked for update be updated? * * Depending on the chosen constant this will either only update the directly named packages, all transitive * dependencies which are not root requirement or all transitive dependencies including root requirements * * @param int $updateAllowTransitiveDependencies One of the UPDATE_ constants on the Request class * @return Installer */ public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveDependencies) : self { if (!\in_array($updateAllowTransitiveDependencies, [Request::UPDATE_ONLY_LISTED, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS], \true)) { throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied"); } $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; return $this; } /** * Should packages be preferred in a stable version when updating? * * @return Installer */ public function setPreferStable(bool $preferStable = \true) : self { $this->preferStable = $preferStable; return $this; } /** * Should packages be preferred in a lowest version when updating? * * @return Installer */ public function setPreferLowest(bool $preferLowest = \true) : self { $this->preferLowest = $preferLowest; return $this; } /** * Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist * * This reduces the update to * * @return Installer */ public function setMinimalUpdate(bool $minimalUpdate = \true) : self { $this->minimalUpdate = $minimalUpdate; return $this; } /** * Should the lock file be updated when updating? * * This is disabled implicitly when enabling dryRun * * @return Installer */ public function setWriteLock(bool $writeLock = \true) : self { $this->writeLock = $writeLock; return $this; } /** * Should the operations (package install, update and removal) be executed on disk? * * This is disabled implicitly when enabling dryRun * * @return Installer */ public function setExecuteOperations(bool $executeOperations = \true) : self { $this->executeOperations = $executeOperations; return $this; } /** * Should an audit be run after installation is complete? * * @return Installer */ public function setAudit(bool $audit) : self { $this->audit = $audit; return $this; } /** * Should exit with status code 5 on audit error * * @param bool $errorOnAudit * @return Installer */ public function setErrorOnAudit(bool $errorOnAudit) : self { $this->errorOnAudit = $errorOnAudit; return $this; } /** * What format should be used for audit output? * * @param Auditor::FORMAT_* $auditFormat * @return Installer */ public function setAuditFormat(string $auditFormat) : self { $this->auditFormat = $auditFormat; return $this; } /** * Disables plugins. * * Call this if you want to ensure that third-party code never gets * executed. The default is to automatically install, and execute * custom third-party installers. * * @return Installer */ public function disablePlugins() : self { $this->installationManager->disablePlugins(); return $this; } /** * @return Installer */ public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter) : self { $this->suggestedPackagesReporter = $suggestedPackagesReporter; return $this; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Filter\PlatformRequirementFilter; interface PlatformRequirementFilterInterface { public function isIgnored(string $req) : bool; public function isUpperBoundIgnored(string $req) : bool; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Filter\PlatformRequirementFilter; use Composer\Repository\PlatformRepository; final class IgnoreAllPlatformRequirementFilter implements \Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface { public function isIgnored(string $req) : bool { return PlatformRepository::isPlatformPackage($req); } public function isUpperBoundIgnored(string $req) : bool { return $this->isIgnored($req); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Filter\PlatformRequirementFilter; use Composer\Package\BasePackage; use Composer\Pcre\Preg; use Composer\Repository\PlatformRepository; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Interval; use Composer\Semver\Intervals; final class IgnoreListPlatformRequirementFilter implements \Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface { /** * @var non-empty-string */ private $ignoreRegex; /** * @var non-empty-string */ private $ignoreUpperBoundRegex; /** * @param string[] $reqList */ public function __construct(array $reqList) { $ignoreAll = $ignoreUpperBound = []; foreach ($reqList as $req) { if (\substr($req, -1) === '+') { $ignoreUpperBound[] = \substr($req, 0, -1); } else { $ignoreAll[] = $req; } } $this->ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll); $this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound); } public function isIgnored(string $req) : bool { if (!PlatformRepository::isPlatformPackage($req)) { return \false; } return Preg::isMatch($this->ignoreRegex, $req); } public function isUpperBoundIgnored(string $req) : bool { if (!PlatformRepository::isPlatformPackage($req)) { return \false; } return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req); } /** * @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped */ public function filterConstraint(string $req, ConstraintInterface $constraint, bool $allowUpperBoundOverride = \true) : ConstraintInterface { if (!PlatformRepository::isPlatformPackage($req)) { return $constraint; } if (!$allowUpperBoundOverride || !Preg::isMatch($this->ignoreUpperBoundRegex, $req)) { return $constraint; } if (Preg::isMatch($this->ignoreRegex, $req)) { return new MatchAllConstraint(); } $intervals = Intervals::get($constraint); $last = \end($intervals['numeric']); if ($last !== \false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) { $constraint = new MultiConstraint([$constraint, new Constraint('>=', $last->getEnd()->getVersion())], \false); } return $constraint; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Filter\PlatformRequirementFilter; final class IgnoreNothingPlatformRequirementFilter implements \Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface { /** * @return false */ public function isIgnored(string $req) : bool { return \false; } /** * @return false */ public function isUpperBoundIgnored(string $req) : bool { return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Filter\PlatformRequirementFilter; final class PlatformRequirementFilterFactory { /** * @param mixed $boolOrList */ public static function fromBoolOrList($boolOrList) : \Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface { if (\is_bool($boolOrList)) { return $boolOrList ? self::ignoreAll() : self::ignoreNothing(); } if (\is_array($boolOrList)) { return new \Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter($boolOrList); } throw new \InvalidArgumentException(\sprintf('PlatformRequirementFilter: Unknown $boolOrList parameter %s. Please report at https://github.com/composer/composer/issues/new.', \gettype($boolOrList))); } public static function ignoreAll() : \Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface { return new \Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter(); } public static function ignoreNothing() : \Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface { return new \Composer\Filter\PlatformRequirementFilter\IgnoreNothingPlatformRequirementFilter(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; /** * Package Events. * * @author Jordi Boggiano */ class PackageEvents { /** * The PRE_PACKAGE_INSTALL event occurs before a package is installed. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ public const PRE_PACKAGE_INSTALL = 'pre-package-install'; /** * The POST_PACKAGE_INSTALL event occurs after a package is installed. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ public const POST_PACKAGE_INSTALL = 'post-package-install'; /** * The PRE_PACKAGE_UPDATE event occurs before a package is updated. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ public const PRE_PACKAGE_UPDATE = 'pre-package-update'; /** * The POST_PACKAGE_UPDATE event occurs after a package is updated. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ public const POST_PACKAGE_UPDATE = 'post-package-update'; /** * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ public const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; /** * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. * * The event listener method receives a Composer\Installer\PackageEvent instance. * * @var string */ public const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Silencer; /** * Utility to handle installation of package "bin"/binaries * * @author Jordi Boggiano * @author Konstantin Kudryashov * @author Helmut Hummel */ class BinaryInstaller { /** @var string */ protected $binDir; /** @var string */ protected $binCompat; /** @var IOInterface */ protected $io; /** @var Filesystem */ protected $filesystem; /** @var string|null */ private $vendorDir; /** * @param Filesystem $filesystem */ public function __construct(IOInterface $io, string $binDir, string $binCompat, ?Filesystem $filesystem = null, ?string $vendorDir = null) { $this->binDir = $binDir; $this->binCompat = $binCompat; $this->io = $io; $this->filesystem = $filesystem ?: new Filesystem(); $this->vendorDir = $vendorDir; } public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = \true) : void { $binaries = $this->getBinaries($package); if (!$binaries) { return; } Platform::workaroundFilesystemIssues(); foreach ($binaries as $bin) { $binPath = $installPath . '/' . $bin; if (!\file_exists($binPath)) { $this->io->writeError(' Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': file not found in package'); continue; } if (\is_dir($binPath)) { $this->io->writeError(' Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': found a directory at that path'); continue; } if (!$this->filesystem->isAbsolutePath($binPath)) { // in case a custom installer returned a relative path for the // $package, we can now safely turn it into a absolute path (as we // already checked the binary's existence). The following helpers // will require absolute paths to work properly. $binPath = \realpath($binPath); } $this->initializeBinDir(); $link = $this->binDir . '/' . \basename($bin); if (\file_exists($link)) { if (!\is_link($link)) { if ($warnOnOverwrite) { $this->io->writeError(' Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': name conflicts with an existing file'); } continue; } if (\realpath($link) === \realpath($binPath)) { // It is a linked binary from a previous installation, which can be replaced with a proxy file $this->filesystem->unlink($link); } } $binCompat = $this->binCompat; if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) { $binCompat = 'full'; } if ($binCompat === "full") { $this->installFullBinaries($binPath, $link, $bin, $package); } else { $this->installUnixyProxyBinaries($binPath, $link); } Silencer::call('chmod', $binPath, 0777 & ~\umask()); } } public function removeBinaries(PackageInterface $package) : void { $this->initializeBinDir(); $binaries = $this->getBinaries($package); if (!$binaries) { return; } foreach ($binaries as $bin) { $link = $this->binDir . '/' . \basename($bin); if (\is_link($link) || \file_exists($link)) { // still checking for symlinks here for legacy support $this->filesystem->unlink($link); } if (\is_file($link . '.bat')) { $this->filesystem->unlink($link . '.bat'); } } // attempt removing the bin dir in case it is left empty if (\is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) { Silencer::call('rmdir', $this->binDir); } } public static function determineBinaryCaller(string $bin) : string { if ('.bat' === \substr($bin, -4) || '.exe' === \substr($bin, -4)) { return 'call'; } $handle = \fopen($bin, 'r'); $line = \fgets($handle); \fclose($handle); if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) { return \trim($match[1]); } return 'php'; } /** * @return string[] */ protected function getBinaries(PackageInterface $package) : array { return $package->getBinaries(); } protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package) : void { // add unixy support for cygwin and similar environments if ('.bat' !== \substr($binPath, -4)) { $this->installUnixyProxyBinaries($binPath, $link); $link .= '.bat'; if (\file_exists($link)) { $this->io->writeError(' Skipped installation of bin ' . $bin . '.bat proxy for package ' . $package->getName() . ': a .bat proxy was already installed'); } } if (!\file_exists($link)) { \file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); Silencer::call('chmod', $link, 0777 & ~\umask()); } } protected function installUnixyProxyBinaries(string $binPath, string $link) : void { \file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); Silencer::call('chmod', $link, 0777 & ~\umask()); } protected function initializeBinDir() : void { $this->filesystem->ensureDirectoryExists($this->binDir); $this->binDir = \realpath($this->binDir); } protected function generateWindowsProxyCode(string $bin, string $link) : string { $binPath = $this->filesystem->findShortestPath($link, $bin); $caller = self::determineBinaryCaller($bin); // if the target is a php file, we run the unixy proxy file // to ensure that _composer_autoload_path gets defined, instead // of running the binary directly if ($caller === 'php') { return "@ECHO OFF\r\n" . "setlocal DISABLEDELAYEDEXPANSION\r\n" . "SET BIN_TARGET=%~dp0/" . \trim(ProcessExecutor::escape(\basename($link, '.bat')), '"\'') . "\r\n" . "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n" . "{$caller} \"%BIN_TARGET%\" %*\r\n"; } return "@ECHO OFF\r\n" . "setlocal DISABLEDELAYEDEXPANSION\r\n" . "SET BIN_TARGET=%~dp0/" . \trim(ProcessExecutor::escape($binPath), '"\'') . "\r\n" . "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n" . "{$caller} \"%BIN_TARGET%\" %*\r\n"; } protected function generateUnixyProxyCode(string $bin, string $link) : string { $binPath = $this->filesystem->findShortestPath($link, $bin); $binDir = ProcessExecutor::escape(\dirname($binPath)); $binFile = \basename($binPath); $binContents = \file_get_contents($bin); // For php files, we generate a PHP proxy instead of a shell one, // which allows calling the proxy with a custom php process if (Preg::isMatch('{^(#!.*\\r?\\n)?[\\r\\n\\t ]*<\\?php}', $binContents, $match)) { // carry over the existing shebang if present, otherwise add our own $proxyCode = $match[1] === null ? '#!/usr/bin/env php' : \trim($match[1]); $binPathExported = $this->filesystem->findShortestPathCode($link, $bin, \false, \true); $streamProxyCode = $streamHint = ''; $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;' . "\n"; $phpunitHack1 = $phpunitHack2 = ''; // Don't expose autoload path when vendor dir was not set in custom installers if ($this->vendorDir) { $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $this->vendorDir . '/autoload.php', \false, \true) . ";\n"; } // Add workaround for PHPUnit process isolation if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir . '/phpunit/phpunit/phpunit')) { // workaround issue on PHPUnit 6.5+ running on PHP 8+ $globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath(' . $binPathExported . '));' . "\n"; // workaround issue on all PHPUnit versions running on PHP <8 $phpunitHack1 = "'phpvfscomposer://'."; $phpunitHack2 = ' $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data); $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);'; } if (\trim((string) $match[0]) !== 'realpath = realpath(\$opened_path) ?: \$opened_path; \$opened_path = {$phpunitHack1}\$this->realpath; \$this->handle = fopen(\$this->realpath, \$mode); \$this->position = 0; return (bool) \$this->handle; } public function stream_read(\$count) { \$data = fread(\$this->handle, \$count); if (\$this->position === 0) { \$data = preg_replace('{^#!.*\\r?\\n}', '', \$data); }{$phpunitHack2} \$this->position += strlen(\$data); return \$data; } public function stream_cast(\$castAs) { return \$this->handle; } public function stream_close() { fclose(\$this->handle); } public function stream_lock(\$operation) { return \$operation ? flock(\$this->handle, \$operation) : true; } public function stream_seek(\$offset, \$whence) { if (0 === fseek(\$this->handle, \$offset, \$whence)) { \$this->position = ftell(\$this->handle); return true; } return false; } public function stream_tell() { return \$this->position; } public function stream_eof() { return feof(\$this->handle); } public function stream_stat() { return array(); } public function stream_set_option(\$option, \$arg1, \$arg2) { return true; } public function url_stat(\$path, \$flags) { \$path = substr(\$path, 17); if (file_exists(\$path)) { return stat(\$path); } return false; } } } if ( (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper')) ) { return include("phpvfscomposer://" . {$binPathExported}); } } STREAMPROXY; } return $proxyCode . "\n" . << /dev/null) if [ -z "\$self" ]; then self="\$selfArg" fi dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd {$binDir} && pwd) if [ -d /proc/cygdrive ]; then case \$(which php) in \$(readlink -n /proc/cygdrive)/*) # We are in Cygwin using Windows php, so the path must be translated dir=\$(cygpath -m "\$dir"); ;; esac fi export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)" # If bash is sourcing this file, we have to source the target as well bashSource="\$BASH_SOURCE" if [ -n "\$bashSource" ]; then if [ "\$bashSource" != "\$0" ]; then source "\${dir}/{$binFile}" "\$@" return fi fi "\${dir}/{$binFile}" "\$@" PROXY; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\PartialComposer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Plugin\PluginManager; use Composer\Util\Filesystem; use Composer\Util\Platform; use React\Promise\PromiseInterface; /** * Installer for plugin packages * * @author Jordi Boggiano * @author Nils Adermann */ class PluginInstaller extends \Composer\Installer\LibraryInstaller { public function __construct(IOInterface $io, PartialComposer $composer, ?Filesystem $fs = null, ?\Composer\Installer\BinaryInstaller $binaryInstaller = null) { parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller); } /** * @inheritDoc */ public function supports(string $packageType) { return $packageType === 'composer-plugin' || $packageType === 'composer-installer'; } public function disablePlugins() : void { $this->getPluginManager()->disablePlugins(); } /** * @inheritDoc */ public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { // fail install process early if it is going to fail due to a plugin not being allowed if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) { $this->getPluginManager()->isPluginAllowed($package->getName(), \false, \true === ($package->getExtra()['plugin-optional'] ?? \false)); } return parent::prepare($type, $package, $prevPackage); } /** * @inheritDoc */ public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing ' . $package->getPrettyName() . ', composer-plugin packages should have a class defined in their extra key to be usable.'); } return parent::download($package, $prevPackage); } /** * @inheritDoc */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $promise = parent::install($repo, $package); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } return $promise->then(function () use($package, $repo) : void { try { Platform::workaroundFilesystemIssues(); $this->getPluginManager()->registerPackage($package, \true); } catch (\Exception $e) { $this->rollbackInstall($e, $repo, $package); } }); } /** * @inheritDoc */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { $promise = parent::update($repo, $initial, $target); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } return $promise->then(function () use($initial, $target, $repo) : void { try { Platform::workaroundFilesystemIssues(); $this->getPluginManager()->deactivatePackage($initial); $this->getPluginManager()->registerPackage($target, \true); } catch (\Exception $e) { $this->rollbackInstall($e, $repo, $target); } }); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->getPluginManager()->uninstallPackage($package); return parent::uninstall($repo, $package); } private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package) : void { $this->io->writeError('Plugin initialization failed (' . $e->getMessage() . '), uninstalling plugin'); parent::uninstall($repo, $package); throw $e; } protected function getPluginManager() : PluginManager { \assert($this->composer instanceof Composer, new \LogicException(self::class . ' should be initialized with a fully loaded Composer instance.')); $pluginManager = $this->composer->getPluginManager(); return $pluginManager; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Package\PackageInterface; /** * Interface for the package installation manager that handle binary installation. * * @author Jordi Boggiano */ interface BinaryPresenceInterface { /** * Make sure binaries are installed for a given package. * * @param PackageInterface $package package instance * * @return void */ public function ensureBinariesPresence(PackageInterface $package); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; class InstallerEvents { /** * The PRE_OPERATIONS_EXEC event occurs before the lock file gets * installed and operations are executed. * * The event listener method receives an Composer\Installer\InstallerEvent instance. * * @var string */ public const PRE_OPERATIONS_EXEC = 'pre-operations-exec'; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Repository\RepositoryInterface; use Composer\EventDispatcher\Event; /** * The Package Event. * * @author Jordi Boggiano */ class PackageEvent extends Event { /** * @var Composer */ private $composer; /** * @var IOInterface */ private $io; /** * @var bool */ private $devMode; /** * @var RepositoryInterface */ private $localRepo; /** * @var OperationInterface[] */ private $operations; /** * @var OperationInterface The operation instance which is being executed */ private $operation; /** * Constructor. * * @param OperationInterface[] $operations */ public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation) { parent::__construct($eventName); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; $this->localRepo = $localRepo; $this->operations = $operations; $this->operation = $operation; } public function getComposer() : Composer { return $this->composer; } public function getIO() : IOInterface { return $this->io; } public function isDevMode() : bool { return $this->devMode; } public function getLocalRepo() : RepositoryInterface { return $this->localRepo; } /** * @return OperationInterface[] */ public function getOperations() : array { return $this->operations; } /** * Returns the package instance. */ public function getOperation() : OperationInterface { return $this->operation; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\DependencyResolver\Transaction; use Composer\EventDispatcher\Event; use Composer\IO\IOInterface; class InstallerEvent extends Event { /** * @var Composer */ private $composer; /** * @var IOInterface */ private $io; /** * @var bool */ private $devMode; /** * @var bool */ private $executeOperations; /** * @var Transaction */ private $transaction; /** * Constructor. */ public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, bool $executeOperations, Transaction $transaction) { parent::__construct($eventName); $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; $this->executeOperations = $executeOperations; $this->transaction = $transaction; } public function getComposer() : Composer { return $this->composer; } public function getIO() : IOInterface { return $this->io; } public function isDevMode() : bool { return $this->devMode; } public function isExecutingOperations() : bool { return $this->executeOperations; } public function getTransaction() : ?Transaction { return $this->transaction; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Composer; use Composer\IO\IOInterface; use Composer\PartialComposer; use Composer\Pcre\Preg; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\Silencer; use Composer\Util\Platform; use React\Promise\PromiseInterface; use Composer\Downloader\DownloadManager; /** * Package installation manager. * * @author Jordi Boggiano * @author Konstantin Kudryashov */ class LibraryInstaller implements \Composer\Installer\InstallerInterface, \Composer\Installer\BinaryPresenceInterface { /** @var PartialComposer */ protected $composer; /** @var string */ protected $vendorDir; /** @var DownloadManager|null */ protected $downloadManager; /** @var IOInterface */ protected $io; /** @var string */ protected $type; /** @var Filesystem */ protected $filesystem; /** @var BinaryInstaller */ protected $binaryInstaller; /** * Initializes library installer. * * @param Filesystem $filesystem * @param BinaryInstaller $binaryInstaller */ public function __construct(IOInterface $io, PartialComposer $composer, ?string $type = 'library', ?Filesystem $filesystem = null, ?\Composer\Installer\BinaryInstaller $binaryInstaller = null) { $this->composer = $composer; $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null; $this->io = $io; $this->type = $type; $this->filesystem = $filesystem ?: new Filesystem(); $this->vendorDir = \rtrim($composer->getConfig()->get('vendor-dir'), '/'); $this->binaryInstaller = $binaryInstaller ?: new \Composer\Installer\BinaryInstaller($this->io, \rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem, $this->vendorDir); } /** * @inheritDoc */ public function supports(string $packageType) { return $packageType === $this->type || null === $this->type; } /** * @inheritDoc */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { return \false; } $installPath = $this->getInstallPath($package); if (Filesystem::isReadable($installPath)) { return \true; } if (Platform::isWindows() && $this->filesystem->isJunction($installPath)) { return \true; } if (\is_link($installPath)) { if (\realpath($installPath) === \false) { return \false; } return \true; } return \false; } /** * @inheritDoc */ public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage); } /** * @inheritDoc */ public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage); } /** * @inheritDoc */ public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage); } /** * @inheritDoc */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); // remove the binaries if it appears the package files are missing if (!Filesystem::isReadable($downloadPath) && $repo->hasPackage($package)) { $this->binaryInstaller->removeBinaries($package); } $promise = $this->installCode($package); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $binaryInstaller = $this->binaryInstaller; $installPath = $this->getInstallPath($package); return $promise->then(static function () use($binaryInstaller, $installPath, $package, $repo) : void { $binaryInstaller->installBinaries($package, $installPath); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } }); } /** * @inheritDoc */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: ' . $initial); } $this->initializeVendorDir(); $this->binaryInstaller->removeBinaries($initial); $promise = $this->updateCode($initial, $target); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $binaryInstaller = $this->binaryInstaller; $installPath = $this->getInstallPath($target); return $promise->then(static function () use($binaryInstaller, $installPath, $target, $initial, $repo) : void { $binaryInstaller->installBinaries($target, $installPath); $repo->removePackage($initial); if (!$repo->hasPackage($target)) { $repo->addPackage(clone $target); } }); } /** * @inheritDoc */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: ' . $package); } $promise = $this->removeCode($package); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $binaryInstaller = $this->binaryInstaller; $downloadPath = $this->getPackageBasePath($package); $filesystem = $this->filesystem; return $promise->then(static function () use($binaryInstaller, $filesystem, $downloadPath, $package, $repo) : void { $binaryInstaller->removeBinaries($package); $repo->removePackage($package); if (\strpos($package->getName(), '/')) { $packageVendorDir = \dirname($downloadPath); if (\is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) { Silencer::call('rmdir', $packageVendorDir); } } }); } /** * @inheritDoc * * @return string */ public function getInstallPath(PackageInterface $package) { $this->initializeVendorDir(); $basePath = ($this->vendorDir ? $this->vendorDir . '/' : '') . $package->getPrettyName(); $targetDir = $package->getTargetDir(); return $basePath . ($targetDir ? '/' . $targetDir : ''); } /** * Make sure binaries are installed for a given package. * * @param PackageInterface $package Package instance */ public function ensureBinariesPresence(PackageInterface $package) { $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), \false); } /** * Returns the base path of the package without target-dir path * * It is used for BC as getInstallPath tends to be overridden by * installer plugins but not getPackageBasePath * * @return string */ protected function getPackageBasePath(PackageInterface $package) { $installPath = $this->getInstallPath($package); $targetDir = $package->getTargetDir(); if ($targetDir) { return Preg::replace('{/*' . \str_replace('/', '/+', \preg_quote($targetDir)) . '/?$}', '', $installPath); } return $installPath; } /** * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ protected function installCode(PackageInterface $package) { $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->install($package, $downloadPath); } /** * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ protected function updateCode(PackageInterface $initial, PackageInterface $target) { $initialDownloadPath = $this->getInstallPath($initial); $targetDownloadPath = $this->getInstallPath($target); if ($targetDownloadPath !== $initialDownloadPath) { // if the target and initial dirs intersect, we force a remove + install // to avoid the rename wiping the target dir as part of the initial dir cleanup if (\strpos($initialDownloadPath, $targetDownloadPath) === 0 || \strpos($targetDownloadPath, $initialDownloadPath) === 0) { $promise = $this->removeCode($initial); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } return $promise->then(function () use($target) : PromiseInterface { $promise = $this->installCode($target); if ($promise instanceof PromiseInterface) { return $promise; } return \React\Promise\resolve(null); }); } $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); } return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath); } /** * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ protected function removeCode(PackageInterface $package) { $downloadPath = $this->getPackageBasePath($package); return $this->getDownloadManager()->remove($package, $downloadPath); } /** * @return void */ protected function initializeVendorDir() { $this->filesystem->ensureDirectoryExists($this->vendorDir); $this->vendorDir = \realpath($this->vendorDir); } protected function getDownloadManager() : DownloadManager { \assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class . ' should be initialized with a fully loaded Composer instance to be able to install/... packages')); return $this->downloadManager; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use InvalidArgumentException; use React\Promise\PromiseInterface; /** * Interface for the package installation manager. * * @author Konstantin Kudryashov * @author Jordi Boggiano */ interface InstallerInterface { /** * Decides if the installer supports the given type * * @return bool */ public function supports(string $packageType); /** * Checks that provided package is installed. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * * @return bool */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Downloads the files needed to later install the given package. * * @param PackageInterface $package package instance * @param PackageInterface $prevPackage previous package instance in case of an update * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ public function download(PackageInterface $package, ?PackageInterface $prevPackage = null); /** * Do anything that needs to be done between all downloads have been completed and the actual operation is executed * * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can * be undone as much as possible. * * @param string $type one of install/update/uninstall * @param PackageInterface $package package instance * @param PackageInterface $prevPackage previous package instance in case of an update * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ public function prepare(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null); /** * Installs specific package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Updates specific package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $initial already installed package version * @param PackageInterface $target updated version * @throws InvalidArgumentException if $initial package is not installed * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target); /** * Uninstalls specific package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps * * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give * all installers a change to cleanup things they did previously, so you need to keep track of changes * applied in the installer/downloader themselves. * * @param string $type one of install/update/uninstall * @param PackageInterface $package package instance * @param PackageInterface $prevPackage previous package instance in case of an update * @return PromiseInterface|null * @phpstan-return PromiseInterface|null */ public function cleanup(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null); /** * Returns the absolute installation path of a package. * * @return string|null absolute path to install to, which MUST not end with a slash, or null if the package does not have anything installed on disk */ public function getInstallPath(PackageInterface $package); } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\IO\IOInterface; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; /** * Metapackage installation manager. * * @author Martin Hasoň */ class MetapackageInstaller implements \Composer\Installer\InstallerInterface { /** @var IOInterface */ private $io; public function __construct(IOInterface $io) { $this->io = $io; } /** * @inheritDoc */ public function supports(string $packageType) { return $packageType === 'metapackage'; } /** * @inheritDoc */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package); } /** * @inheritDoc */ public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { // noop return \React\Promise\resolve(null); } /** * @inheritDoc */ public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { // noop return \React\Promise\resolve(null); } /** * @inheritDoc */ public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { // noop return \React\Promise\resolve(null); } /** * @inheritDoc */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->io->writeError(" - " . InstallOperation::format($package)); $repo->addPackage(clone $package); return \React\Promise\resolve(null); } /** * @inheritDoc */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: ' . $initial); } $this->io->writeError(" - " . UpdateOperation::format($initial, $target)); $repo->removePackage($initial); $repo->addPackage(clone $target); return \React\Promise\resolve(null); } /** * @inheritDoc */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: ' . $package); } $this->io->writeError(" - " . UninstallOperation::format($package)); $repo->removePackage($package); return \React\Promise\resolve(null); } /** * @inheritDoc * * @return null */ public function getInstallPath(PackageInterface $package) { return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use React\Promise\PromiseInterface; use Composer\Package\PackageInterface; use Composer\Downloader\DownloadManager; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; /** * Project Installer is used to install a single package into a directory as * root project. * * @author Benjamin Eberlei */ class ProjectInstaller implements \Composer\Installer\InstallerInterface { /** @var string */ private $installPath; /** @var DownloadManager */ private $downloadManager; /** @var Filesystem */ private $filesystem; public function __construct(string $installPath, DownloadManager $dm, Filesystem $fs) { $this->installPath = \rtrim(\strtr($installPath, '\\', '/'), '/') . '/'; $this->downloadManager = $dm; $this->filesystem = $fs; } /** * Decides if the installer supports the given type */ public function supports(string $packageType) : bool { return \true; } /** * @inheritDoc */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) : bool { return \false; } /** * @inheritDoc */ public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) : ?PromiseInterface { $installPath = $this->installPath; if (\file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { throw new \InvalidArgumentException("Project directory {$installPath} is not empty."); } if (!\is_dir($installPath)) { \mkdir($installPath, 0777, \true); } return $this->downloadManager->download($package, $installPath, $prevPackage); } /** * @inheritDoc */ public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) : ?PromiseInterface { return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); } /** * @inheritDoc */ public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) : ?PromiseInterface { return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); } /** * @inheritDoc */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) : ?PromiseInterface { return $this->downloadManager->install($package, $this->installPath); } /** * @inheritDoc */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) : ?PromiseInterface { throw new \InvalidArgumentException("not supported"); } /** * @inheritDoc */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) : ?PromiseInterface { throw new \InvalidArgumentException("not supported"); } /** * Returns the installation path of a package * * @return string configured install path */ public function getInstallPath(PackageInterface $package) : string { return $this->installPath; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Pcre\Preg; use Composer\Repository\InstalledRepository; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; /** * Add suggested packages from different places to output them in the end. * * @author Haralan Dobrev */ class SuggestedPackagesReporter { public const MODE_LIST = 1; public const MODE_BY_PACKAGE = 2; public const MODE_BY_SUGGESTION = 4; /** * @var array */ protected $suggestedPackages = []; /** * @var IOInterface */ private $io; public function __construct(IOInterface $io) { $this->io = $io; } /** * @return array Suggested packages with source, target and reason keys. */ public function getPackages() : array { return $this->suggestedPackages; } /** * Add suggested packages to be listed after install * * Could be used to add suggested packages both from the installer * or from CreateProjectCommand. * * @param string $source Source package which made the suggestion * @param string $target Target package to be suggested * @param string $reason Reason the target package to be suggested */ public function addPackage(string $source, string $target, string $reason) : \Composer\Installer\SuggestedPackagesReporter { $this->suggestedPackages[] = ['source' => $source, 'target' => $target, 'reason' => $reason]; return $this; } /** * Add all suggestions from a package. */ public function addSuggestionsFromPackage(PackageInterface $package) : \Composer\Installer\SuggestedPackagesReporter { $source = $package->getPrettyName(); foreach ($package->getSuggests() as $target => $reason) { $this->addPackage($source, $target, $reason); } return $this; } /** * Output suggested packages. * * Do not list the ones already installed if installed repository provided. * * @param int $mode One of the MODE_* constants from this class * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown */ public function output(int $mode, ?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null) : void { $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); $suggesters = []; $suggested = []; foreach ($suggestedPackages as $suggestion) { $suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason']; $suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason']; } \ksort($suggesters); \ksort($suggested); // Simple mode if ($mode & self::MODE_LIST) { foreach (\array_keys($suggested) as $name) { $this->io->write(\sprintf('%s', $name)); } return; } // Grouped by package if ($mode & self::MODE_BY_PACKAGE) { foreach ($suggesters as $suggester => $suggestions) { $this->io->write(\sprintf('%s suggests:', $suggester)); foreach ($suggestions as $suggestion => $reason) { $this->io->write(\sprintf(' - %s' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason))); } $this->io->write(''); } } // Grouped by suggestion if ($mode & self::MODE_BY_SUGGESTION) { // Improve readability in full mode if ($mode & self::MODE_BY_PACKAGE) { $this->io->write(\str_repeat('-', 78)); } foreach ($suggested as $suggestion => $suggesters) { $this->io->write(\sprintf('%s is suggested by:', $suggestion)); foreach ($suggesters as $suggester => $reason) { $this->io->write(\sprintf(' - %s' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason))); } $this->io->write(''); } } if ($onlyDependentsOf) { $allSuggestedPackages = $this->getFilteredSuggestions($installedRepo); $diff = \count($allSuggestedPackages) - \count($suggestedPackages); if ($diff) { $this->io->write('' . $diff . ' additional suggestions by transitive dependencies can be shown with --all'); } } } /** * Output number of new suggested packages and a hint to use suggest command. * * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown */ public function outputMinimalistic(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null) : void { $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); if ($suggestedPackages) { $this->io->writeError('' . \count($suggestedPackages) . ' package suggestions were added by new dependencies, use `composer suggest` to see details.'); } } /** * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown * @return mixed[] */ private function getFilteredSuggestions(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null) : array { $suggestedPackages = $this->getPackages(); $installedNames = []; if (null !== $installedRepo && !empty($suggestedPackages)) { foreach ($installedRepo->getPackages() as $package) { $installedNames = \array_merge($installedNames, $package->getNames()); } } $sourceFilter = []; if ($onlyDependentsOf) { $sourceFilter = \array_map(static function ($link) : string { return $link->getTarget(); }, \array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires())); $sourceFilter[] = $onlyDependentsOf->getName(); } $suggestions = []; foreach ($suggestedPackages as $suggestion) { if (\in_array($suggestion['target'], $installedNames) || $sourceFilter && !\in_array($suggestion['source'], $sourceFilter)) { continue; } $suggestions[] = $suggestion; } return $suggestions; } private function escapeOutput(string $string) : string { return OutputFormatter::escape($this->removeControlCharacters($string)); } private function removeControlCharacters(string $string) : string { return Preg::replace('/[[:cntrl:]]/', '', \str_replace("\n", ' ', $string)); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\IO\ConsoleIO; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\Downloader\FileDownloader; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Loop; use Composer\Util\Platform; use React\Promise\PromiseInterface; use _ContaoManager\Seld\Signal\SignalHandler; /** * Package operation manager. * * @author Konstantin Kudryashov * @author Jordi Boggiano * @author Nils Adermann */ class InstallationManager { /** @var array */ private $installers = []; /** @var array */ private $cache = []; /** @var array> */ private $notifiablePackages = []; /** @var Loop */ private $loop; /** @var IOInterface */ private $io; /** @var ?EventDispatcher */ private $eventDispatcher; /** @var bool */ private $outputProgress; public function __construct(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null) { $this->loop = $loop; $this->io = $io; $this->eventDispatcher = $eventDispatcher; } public function reset() : void { $this->notifiablePackages = []; FileDownloader::$downloadMetadata = []; } /** * Adds installer * * @param InstallerInterface $installer installer instance */ public function addInstaller(\Composer\Installer\InstallerInterface $installer) : void { \array_unshift($this->installers, $installer); $this->cache = []; } /** * Removes installer * * @param InstallerInterface $installer installer instance */ public function removeInstaller(\Composer\Installer\InstallerInterface $installer) : void { if (\false !== ($key = \array_search($installer, $this->installers, \true))) { \array_splice($this->installers, $key, 1); $this->cache = []; } } /** * Disables plugins. * * We prevent any plugins from being instantiated by * disabling the PluginManager. This ensures that no third-party * code is ever executed. */ public function disablePlugins() : void { foreach ($this->installers as $i => $installer) { if (!$installer instanceof \Composer\Installer\PluginInstaller) { continue; } $installer->disablePlugins(); } } /** * Returns installer for a specific package type. * * @param string $type package type * * @throws \InvalidArgumentException if installer for provided type is not registered */ public function getInstaller(string $type) : \Composer\Installer\InstallerInterface { $type = \strtolower($type); if (isset($this->cache[$type])) { return $this->cache[$type]; } foreach ($this->installers as $installer) { if ($installer->supports($type)) { return $this->cache[$type] = $installer; } } throw new \InvalidArgumentException('Unknown installer type: ' . $type); } /** * Checks whether provided package is installed in one of the registered installers. * * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance */ public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) : bool { if ($package instanceof AliasPackage) { return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf()); } return $this->getInstaller($package->getType())->isInstalled($repo, $package); } /** * Install binary for the given package. * If the installer associated to this package doesn't handle that function, it'll do nothing. * * @param PackageInterface $package Package instance */ public function ensureBinariesPresence(PackageInterface $package) : void { try { $installer = $this->getInstaller($package->getType()); } catch (\InvalidArgumentException $e) { // no installer found for the current package type (@see `getInstaller()`) return; } // if the given installer support installing binaries if ($installer instanceof \Composer\Installer\BinaryPresenceInterface) { $installer->ensureBinariesPresence($package); } } /** * Executes solver operation. * * @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages * @param OperationInterface[] $operations operations to execute * @param bool $devMode whether the install is being run in dev mode * @param bool $runScripts whether to dispatch script events * @param bool $downloadOnly whether to only download packages */ public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = \true, bool $runScripts = \true, bool $downloadOnly = \false) : void { /** @var array> */ $cleanupPromises = []; $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use(&$cleanupPromises) { $this->io->writeError('Received ' . $signal . ', aborting', \true, IOInterface::DEBUG); $this->runCleanup($cleanupPromises); $handler->exitWithLastSignal(); }); try { // execute operations in batches to make sure download-modifying-plugins are installed // before the other packages get downloaded $batches = []; $batch = []; foreach ($operations as $index => $operation) { if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) { $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); if ($package->getType() === 'composer-plugin' && ($extra = $package->getExtra()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === \true) { if ($batch) { $batches[] = $batch; } $batches[] = [$index => $operation]; $batch = []; continue; } } $batch[$index] = $operation; } if ($batch) { $batches[] = $batch; } foreach ($batches as $batch) { $this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations); } } catch (\Exception $e) { $this->runCleanup($cleanupPromises); throw $e; } finally { $signalHandler->unregister(); } if ($downloadOnly) { return; } // do a last write so that we write the repository even if nothing changed // as that can trigger an update of some files like InstalledVersions.php if // running a new composer version $repo->write($devMode, $this); } /** * @param OperationInterface[] $operations List of operations to execute in this batch * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners * @phpstan-param array> $cleanupPromises */ private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations) : void { $promises = []; foreach ($operations as $index => $operation) { $opType = $operation->getOperationType(); // ignoring alias ops as they don't need to execute anything at this stage if (!\in_array($opType, ['update', 'install', 'uninstall'])) { continue; } if ($opType === 'update') { /** @var UpdateOperation $operation */ $package = $operation->getTargetPackage(); $initialPackage = $operation->getInitialPackage(); } else { /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */ $package = $operation->getPackage(); $initialPackage = null; } $installer = $this->getInstaller($package->getType()); $cleanupPromises[$index] = static function () use($opType, $installer, $package, $initialPackage) : ?PromiseInterface { // avoid calling cleanup if the download was not even initialized for a package // as without installation source configured nothing will work if (!$package->getInstallationSource()) { return \React\Promise\resolve(null); } return $installer->cleanup($opType, $package, $initialPackage); }; if ($opType !== 'uninstall') { $promise = $installer->download($package, $initialPackage); if (null !== $promise) { $promises[] = $promise; } } } // execute all downloads first if (\count($promises)) { $this->waitOnPromises($promises); } if ($downloadOnly) { $this->runCleanup($cleanupPromises); return; } // execute operations in batches to make sure every plugin is installed in the // right order and activated before the packages depending on it are installed $batches = []; $batch = []; foreach ($operations as $index => $operation) { if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) { $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { if ($batch) { $batches[] = $batch; } $batches[] = [$index => $operation]; $batch = []; continue; } } $batch[$index] = $operation; } if ($batch) { $batches[] = $batch; } foreach ($batches as $batch) { $this->executeBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $allOperations); } } /** * @param OperationInterface[] $operations List of operations to execute in this batch * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners * @phpstan-param array> $cleanupPromises */ private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations) : void { $promises = []; $postExecCallbacks = []; foreach ($operations as $index => $operation) { $opType = $operation->getOperationType(); // ignoring alias ops as they don't need to execute anything if (!\in_array($opType, ['update', 'install', 'uninstall'])) { // output alias ops in debug verbosity as they have no output otherwise if ($this->io->isDebug()) { $this->io->writeError(' - ' . $operation->show(\false)); } $this->{$opType}($repo, $operation); continue; } if ($opType === 'update') { /** @var UpdateOperation $operation */ $package = $operation->getTargetPackage(); $initialPackage = $operation->getInitialPackage(); } else { /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */ $package = $operation->getPackage(); $initialPackage = null; } $installer = $this->getInstaller($package->getType()); $eventName = ['install' => \Composer\Installer\PackageEvents::PRE_PACKAGE_INSTALL, 'update' => \Composer\Installer\PackageEvents::PRE_PACKAGE_UPDATE, 'uninstall' => \Composer\Installer\PackageEvents::PRE_PACKAGE_UNINSTALL][$opType] ?? null; if (null !== $eventName && $runScripts && $this->eventDispatcher) { $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); } $dispatcher = $this->eventDispatcher; $io = $this->io; $promise = $installer->prepare($opType, $package, $initialPackage); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $promise = $promise->then(function () use($opType, $repo, $operation) { return $this->{$opType}($repo, $operation); })->then($cleanupPromises[$index])->then(function () use($devMode, $repo) : void { $repo->write($devMode, $this); }, static function ($e) use($opType, $package, $io) : void { $io->writeError(' ' . \ucfirst($opType) . ' of ' . $package->getPrettyName() . ' failed'); throw $e; }); $eventName = ['install' => \Composer\Installer\PackageEvents::POST_PACKAGE_INSTALL, 'update' => \Composer\Installer\PackageEvents::POST_PACKAGE_UPDATE, 'uninstall' => \Composer\Installer\PackageEvents::POST_PACKAGE_UNINSTALL][$opType] ?? null; if (null !== $eventName && $runScripts && $dispatcher) { $postExecCallbacks[] = static function () use($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation) : void { $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); }; } $promises[] = $promise; } // execute all prepare => installs/updates/removes => cleanup steps if (\count($promises)) { $this->waitOnPromises($promises); } Platform::workaroundFilesystemIssues(); foreach ($postExecCallbacks as $cb) { $cb(); } } /** * @param array> $promises */ private function waitOnPromises(array $promises) : void { $progress = null; if ($this->outputProgress && $this->io instanceof ConsoleIO && !Platform::getEnv('CI') && !$this->io->isDebug() && \count($promises) > 1) { $progress = $this->io->getProgressBar(); } $this->loop->wait($promises, $progress); if ($progress) { $progress->clear(); // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing if (!$this->io->isDecorated()) { $this->io->writeError(''); } } } /** * Executes download operation. * * @phpstan-return PromiseInterface|null */ public function download(PackageInterface $package) : ?PromiseInterface { $installer = $this->getInstaller($package->getType()); $promise = $installer->cleanup("install", $package); return $promise; } /** * Executes install operation. * * @param InstalledRepositoryInterface $repo repository in which to check * @param InstallOperation $operation operation instance * @phpstan-return PromiseInterface|null */ public function install(InstalledRepositoryInterface $repo, InstallOperation $operation) : ?PromiseInterface { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); $promise = $installer->install($repo, $package); $this->markForNotification($package); return $promise; } /** * Executes update operation. * * @param InstalledRepositoryInterface $repo repository in which to check * @param UpdateOperation $operation operation instance * @phpstan-return PromiseInterface|null */ public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation) : ?PromiseInterface { $initial = $operation->getInitialPackage(); $target = $operation->getTargetPackage(); $initialType = $initial->getType(); $targetType = $target->getType(); if ($initialType === $targetType) { $installer = $this->getInstaller($initialType); $promise = $installer->update($repo, $initial, $target); $this->markForNotification($target); } else { $promise = $this->getInstaller($initialType)->uninstall($repo, $initial); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $installer = $this->getInstaller($targetType); $promise = $promise->then(static function () use($installer, $repo, $target) : PromiseInterface { $promise = $installer->install($repo, $target); if ($promise instanceof PromiseInterface) { return $promise; } return \React\Promise\resolve(null); }); } return $promise; } /** * Uninstalls package. * * @param InstalledRepositoryInterface $repo repository in which to check * @param UninstallOperation $operation operation instance * @phpstan-return PromiseInterface|null */ public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation) : ?PromiseInterface { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); return $installer->uninstall($repo, $package); } /** * Executes markAliasInstalled operation. * * @param InstalledRepositoryInterface $repo repository in which to check * @param MarkAliasInstalledOperation $operation operation instance */ public function markAliasInstalled(InstalledRepositoryInterface $repo, MarkAliasInstalledOperation $operation) : void { $package = $operation->getPackage(); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } } /** * Executes markAlias operation. * * @param InstalledRepositoryInterface $repo repository in which to check * @param MarkAliasUninstalledOperation $operation operation instance */ public function markAliasUninstalled(InstalledRepositoryInterface $repo, MarkAliasUninstalledOperation $operation) : void { $package = $operation->getPackage(); $repo->removePackage($package); } /** * Returns the installation path of a package * * @return string|null absolute path to install to, which does not end with a slash, or null if the package does not have anything installed on disk */ public function getInstallPath(PackageInterface $package) : ?string { $installer = $this->getInstaller($package->getType()); return $installer->getInstallPath($package); } public function setOutputProgress(bool $outputProgress) : void { $this->outputProgress = $outputProgress; } public function notifyInstalls(IOInterface $io) : void { $promises = []; try { foreach ($this->notifiablePackages as $repoUrl => $packages) { // non-batch API, deprecated if (\strpos($repoUrl, '%package%')) { foreach ($packages as $package) { $url = \str_replace('%package%', $package->getPrettyName(), $repoUrl); $params = ['version' => $package->getPrettyVersion(), 'version_normalized' => $package->getVersion()]; $opts = ['retry-auth-failure' => \false, 'http' => ['method' => 'POST', 'header' => ['Content-type: application/x-www-form-urlencoded'], 'content' => \http_build_query($params, '', '&'), 'timeout' => 3]]; $promises[] = $this->loop->getHttpDownloader()->add($url, $opts); } continue; } $postData = ['downloads' => []]; foreach ($packages as $package) { $packageNotification = ['name' => $package->getPrettyName(), 'version' => $package->getVersion()]; if (\strpos($repoUrl, 'packagist.org/') !== \false) { if (isset(FileDownloader::$downloadMetadata[$package->getName()])) { $packageNotification['downloaded'] = FileDownloader::$downloadMetadata[$package->getName()]; } else { $packageNotification['downloaded'] = \false; } } $postData['downloads'][] = $packageNotification; } $opts = ['retry-auth-failure' => \false, 'http' => ['method' => 'POST', 'header' => ['Content-Type: application/json'], 'content' => \json_encode($postData), 'timeout' => 6]]; $promises[] = $this->loop->getHttpDownloader()->add($repoUrl, $opts); } $this->loop->wait($promises); } catch (\Exception $e) { } $this->reset(); } private function markForNotification(PackageInterface $package) : void { if ($package->getNotificationUrl()) { $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; } } /** * @return void * @phpstan-param array> $cleanupPromises */ private function runCleanup(array $cleanupPromises) : void { $promises = []; $this->loop->abortJobs(); foreach ($cleanupPromises as $cleanup) { $promises[] = new \React\Promise\Promise(static function ($resolve) use($cleanup) : void { $promise = $cleanup(); if (!$promise instanceof PromiseInterface) { $resolve(null); } else { $promise->then(static function () use($resolve) : void { $resolve(null); }); } }); } if (!empty($promises)) { $this->loop->wait($promises); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Installer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; /** * Does not install anything but marks packages installed in the repo * * Useful for dry runs * * @author Jordi Boggiano */ class NoopInstaller implements \Composer\Installer\InstallerInterface { /** * @inheritDoc */ public function supports(string $packageType) { return \true; } /** * @inheritDoc */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package); } /** * @inheritDoc */ public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } /** * @inheritDoc */ public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } /** * @inheritDoc */ public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } /** * @inheritDoc */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: ' . $initial); } $repo->removePackage($initial); if (!$repo->hasPackage($target)) { $repo->addPackage(clone $target); } return \React\Promise\resolve(null); } /** * @inheritDoc */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: ' . $package); } $repo->removePackage($package); return \React\Promise\resolve(null); } /** * @inheritDoc */ public function getInstallPath(PackageInterface $package) { $targetDir = $package->getTargetDir(); return $package->getPrettyName() . ($targetDir ? '/' . $targetDir : ''); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Advisory; use Composer\Semver\Constraint\ConstraintInterface; use DateTimeImmutable; class SecurityAdvisory extends \Composer\Advisory\PartialSecurityAdvisory { /** * @var string * @readonly */ public $title; /** * @var string|null * @readonly */ public $cve; /** * @var string|null * @readonly */ public $link; /** * @var DateTimeImmutable * @readonly */ public $reportedAt; /** * @var non-empty-array * @readonly */ public $sources; /** * @var string|null * @readonly */ public $severity; /** * @param non-empty-array $sources */ public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $severity = null) { parent::__construct($packageName, $advisoryId, $affectedVersions); $this->title = $title; $this->sources = $sources; $this->reportedAt = $reportedAt; $this->cve = $cve; $this->link = $link; $this->severity = $severity; } /** * @internal */ public function toIgnoredAdvisory(?string $ignoreReason) : \Composer\Advisory\IgnoredSecurityAdvisory { return new \Composer\Advisory\IgnoredSecurityAdvisory($this->packageName, $this->advisoryId, $this->affectedVersions, $this->title, $this->sources, $this->reportedAt, $this->cve, $this->link, $ignoreReason, $this->severity); } /** * @return mixed */ #[\ReturnTypeWillChange] public function jsonSerialize() { $data = parent::jsonSerialize(); $data['reportedAt'] = $data['reportedAt']->format(\DATE_RFC3339); return $data; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Advisory; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\VersionParser; use JsonSerializable; class PartialSecurityAdvisory implements JsonSerializable { /** * @var string * @readonly */ public $advisoryId; /** * @var string * @readonly */ public $packageName; /** * @var ConstraintInterface * @readonly */ public $affectedVersions; /** * @param array $data * @return SecurityAdvisory|PartialSecurityAdvisory */ public static function create(string $packageName, array $data, VersionParser $parser) : self { $constraint = $parser->parseConstraints($data['affectedVersions']); if (isset($data['title'], $data['sources'], $data['reportedAt'])) { return new \Composer\Advisory\SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null); } return new self($packageName, $data['advisoryId'], $constraint); } public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions) { $this->advisoryId = $advisoryId; $this->packageName = $packageName; $this->affectedVersions = $affectedVersions; } /** * @return mixed */ #[\ReturnTypeWillChange] public function jsonSerialize() { $data = (array) $this; $data['affectedVersions'] = $data['affectedVersions']->getPrettyString(); return $data; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Advisory; use Composer\IO\ConsoleIO; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\CompletePackageInterface; use Composer\Package\PackageInterface; use Composer\Repository\RepositorySet; use Composer\Util\PackageInfo; use Composer\Util\Platform; use InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; /** * @internal */ class Auditor { public const FORMAT_TABLE = 'table'; public const FORMAT_PLAIN = 'plain'; public const FORMAT_JSON = 'json'; public const FORMAT_SUMMARY = 'summary'; public const FORMATS = [self::FORMAT_TABLE, self::FORMAT_PLAIN, self::FORMAT_JSON, self::FORMAT_SUMMARY]; public const ABANDONED_IGNORE = 'ignore'; public const ABANDONED_REPORT = 'report'; public const ABANDONED_FAIL = 'fail'; /** * @param PackageInterface[] $packages * @param self::FORMAT_* $format The format that will be used to output audit results. * @param bool $warningOnly If true, outputs a warning. If false, outputs an error. * @param string[] $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities. * @param self::ABANDONED_* $abandoned * * @return int Amount of packages with vulnerabilities found * @throws InvalidArgumentException If no packages are passed in */ public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = \true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL) : int { $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY); // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above // and ignores are set then we need to query again the full data to make sure it can be filtered if (\count($allAdvisories) > 0 && $ignoreList !== [] && $format === self::FORMAT_SUMMARY) { $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, \false); } ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList); $abandonedCount = 0; $affectedPackagesCount = 0; if ($abandoned === self::ABANDONED_IGNORE) { $abandonedPackages = []; } else { $abandonedPackages = $this->filterAbandonedPackages($packages); if ($abandoned === self::ABANDONED_FAIL) { $abandonedCount = \count($abandonedPackages); } } if (self::FORMAT_JSON === $format) { $json = ['advisories' => $advisories]; if ($ignoredAdvisories !== []) { $json['ignored-advisories'] = $ignoredAdvisories; } $json['abandoned'] = \array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package) : array { $carry[$package->getPrettyName()] = $package->getReplacementPackage(); return $carry; }, []); $io->write(JsonFile::encode($json)); return \count($advisories) + $abandonedCount; } $errorOrWarn = $warningOnly ? 'warning' : 'error'; if (\count($advisories) > 0 || \count($ignoredAdvisories) > 0) { $passes = [ [$ignoredAdvisories, "Found %d ignored security vulnerability advisor%s affecting %d package%s%s"], // this has to run last to allow $affectedPackagesCount in the return statement to be correct [$advisories, "<{$errorOrWarn}>Found %d security vulnerability advisor%s affecting %d package%s%s"], ]; foreach ($passes as [$advisoriesToOutput, $message]) { [$affectedPackagesCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput); if ($affectedPackagesCount > 0) { $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies'; $pkgPlurality = $affectedPackagesCount === 1 ? '' : 's'; $punctuation = $format === 'summary' ? '.' : ':'; $io->writeError(\sprintf($message, $totalAdvisoryCount, $plurality, $affectedPackagesCount, $pkgPlurality, $punctuation)); $this->outputAdvisories($io, $advisoriesToOutput, $format); } } if ($format === self::FORMAT_SUMMARY) { $io->writeError('Run "composer audit" for a full list of advisories.'); } } else { $io->writeError('No security vulnerability advisories found.'); } if (\count($abandonedPackages) > 0 && $format !== self::FORMAT_SUMMARY) { $this->outputAbandonedPackages($io, $abandonedPackages, $format); } return $affectedPackagesCount + $abandonedCount; } /** * @param array $packages * @return array */ private function filterAbandonedPackages(array $packages) : array { return \array_filter($packages, static function (PackageInterface $pkg) { return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned(); }); } /** * @phpstan-param array> $allAdvisories * @param array|array $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities. * @phpstan-return array{advisories: array>, ignoredAdvisories: array>} */ private function processAdvisories(array $allAdvisories, array $ignoreList) : array { if ($ignoreList === []) { return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []]; } if (\count($ignoreList) > 0 && !\array_is_list($ignoreList)) { $ignoredIds = \array_keys($ignoreList); } else { $ignoredIds = $ignoreList; } $advisories = []; $ignored = []; $ignoreReason = null; foreach ($allAdvisories as $package => $pkgAdvisories) { foreach ($pkgAdvisories as $advisory) { $isActive = \true; if (\in_array($advisory->advisoryId, $ignoredIds, \true)) { $isActive = \false; $ignoreReason = $ignoreList[$advisory->advisoryId] ?? null; } if ($advisory instanceof \Composer\Advisory\SecurityAdvisory) { if (\in_array($advisory->cve, $ignoredIds, \true)) { $isActive = \false; $ignoreReason = $ignoreList[$advisory->cve] ?? null; } foreach ($advisory->sources as $source) { if (\in_array($source['remoteId'], $ignoredIds, \true)) { $isActive = \false; $ignoreReason = $ignoreList[$source['remoteId']] ?? null; break; } } } if ($isActive) { $advisories[$package][] = $advisory; continue; } // Partial security advisories only used in summary mode // and in that case we do not need to cast the object. if ($advisory instanceof \Composer\Advisory\SecurityAdvisory) { $advisory = $advisory->toIgnoredAdvisory($ignoreReason); } $ignored[$package][] = $advisory; } } return ['advisories' => $advisories, 'ignoredAdvisories' => $ignored]; } /** * @param array> $advisories * @return array{int, int} Count of affected packages and total count of advisories */ private function countAdvisories(array $advisories) : array { $count = 0; foreach ($advisories as $packageAdvisories) { $count += \count($packageAdvisories); } return [\count($advisories), $count]; } /** * @param array> $advisories * @param self::FORMAT_* $format The format that will be used to output audit results. */ private function outputAdvisories(IOInterface $io, array $advisories, string $format) : void { switch ($format) { case self::FORMAT_TABLE: if (!$io instanceof ConsoleIO) { throw new InvalidArgumentException('Cannot use table format with ' . \get_class($io)); } $this->outputAdvisoriesTable($io, $advisories); return; case self::FORMAT_PLAIN: $this->outputAdvisoriesPlain($io, $advisories); return; case self::FORMAT_SUMMARY: return; default: throw new InvalidArgumentException('Invalid format "' . $format . '".'); } } /** * @param array> $advisories */ private function outputAdvisoriesTable(ConsoleIO $io, array $advisories) : void { foreach ($advisories as $packageAdvisories) { foreach ($packageAdvisories as $advisory) { $headers = ['Package', 'Severity', 'CVE', 'Title', 'URL', 'Affected versions', 'Reported at']; $row = [$advisory->packageName, $this->getSeverity($advisory), $this->getCVE($advisory), $advisory->title, $this->getURL($advisory), $advisory->affectedVersions->getPrettyString(), $advisory->reportedAt->format(\DATE_ATOM)]; if ($advisory instanceof \Composer\Advisory\IgnoredSecurityAdvisory) { $headers[] = 'Ignore reason'; $row[] = $advisory->ignoreReason ?? 'None specified'; } $io->getTable()->setHorizontal()->setHeaders($headers)->addRow($row)->setColumnWidth(1, 80)->setColumnMaxWidth(1, 80)->render(); } } } /** * @param array> $advisories */ private function outputAdvisoriesPlain(IOInterface $io, array $advisories) : void { $error = []; $firstAdvisory = \true; foreach ($advisories as $packageAdvisories) { foreach ($packageAdvisories as $advisory) { if (!$firstAdvisory) { $error[] = '--------'; } $error[] = "Package: " . $advisory->packageName; $error[] = "Severity: " . $this->getSeverity($advisory); $error[] = "CVE: " . $this->getCVE($advisory); $error[] = "Title: " . OutputFormatter::escape($advisory->title); $error[] = "URL: " . $this->getURL($advisory); $error[] = "Affected versions: " . OutputFormatter::escape($advisory->affectedVersions->getPrettyString()); $error[] = "Reported at: " . $advisory->reportedAt->format(\DATE_ATOM); if ($advisory instanceof \Composer\Advisory\IgnoredSecurityAdvisory) { $error[] = "Ignore reason: " . ($advisory->ignoreReason ?? 'None specified'); } $firstAdvisory = \false; } } $io->writeError($error); } /** * @param array $packages * @param self::FORMAT_PLAIN|self::FORMAT_TABLE $format */ private function outputAbandonedPackages(IOInterface $io, array $packages, string $format) : void { $io->writeError(\sprintf('Found %d abandoned package%s:', \count($packages), \count($packages) > 1 ? 's' : '')); if ($format === self::FORMAT_PLAIN) { foreach ($packages as $pkg) { $replacement = $pkg->getReplacementPackage() !== null ? 'Use ' . $pkg->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $io->writeError(\sprintf('%s is abandoned. %s.', $this->getPackageNameWithLink($pkg), $replacement)); } return; } if (!$io instanceof ConsoleIO) { throw new InvalidArgumentException('Cannot use table format with ' . \get_class($io)); } $table = $io->getTable()->setHeaders(['Abandoned Package', 'Suggested Replacement'])->setColumnWidth(1, 80)->setColumnMaxWidth(1, 80); foreach ($packages as $pkg) { $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none'; $table->addRow([$this->getPackageNameWithLink($pkg), $replacement]); } $table->render(); } private function getPackageNameWithLink(PackageInterface $package) : string { $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); return $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); } private function getSeverity(\Composer\Advisory\SecurityAdvisory $advisory) : string { if ($advisory->severity === null) { return ''; } return $advisory->severity; } private function getCVE(\Composer\Advisory\SecurityAdvisory $advisory) : string { if ($advisory->cve === null) { return 'NO CVE'; } return '' . $advisory->cve . ''; } private function getURL(\Composer\Advisory\SecurityAdvisory $advisory) : string { if ($advisory->link === null) { return ''; } return 'link) . '>' . OutputFormatter::escape($advisory->link) . ''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Advisory; use Composer\Semver\Constraint\ConstraintInterface; use DateTimeImmutable; class IgnoredSecurityAdvisory extends \Composer\Advisory\SecurityAdvisory { /** * @var string|null * @readonly */ public $ignoreReason; /** * @param non-empty-array $sources */ public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null, ?string $severity = null) { parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link, $severity); $this->ignoreReason = $ignoreReason; } /** * @return mixed */ #[\ReturnTypeWillChange] public function jsonSerialize() { $data = parent::jsonSerialize(); if ($this->ignoreReason === NULL) { unset($data['ignoreReason']); } return $data; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Exception; /** * @author Jordi Boggiano */ class IrrecoverableDownloadException extends \RuntimeException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Exception; /** * Specific exception for Composer\Util\HttpDownloader creation. * * @author Jordi Boggiano */ class NoSslException extends \RuntimeException { } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Json\JsonFile; use Composer\CaBundle\CaBundle; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Finder\Finder; use _ContaoManager\Symfony\Component\Process\Process; use _ContaoManager\Seld\PharUtils\Timestamps; use _ContaoManager\Seld\PharUtils\Linter; /** * The Compiler class compiles composer into a phar * * @author Fabien Potencier * @author Jordi Boggiano */ class Compiler { /** @var string */ private $version; /** @var string */ private $branchAliasVersion = ''; /** @var \DateTime */ private $versionDate; /** * Compiles composer into a single phar file * * @param string $pharFile The full path to the file to create * * @throws \RuntimeException */ public function compile(string $pharFile = 'composer.phar') : void { if (\file_exists($pharFile)) { \unlink($pharFile); } $process = new Process(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], __DIR__); if ($process->run() !== 0) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->version = \trim($process->getOutput()); $process = new Process(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], __DIR__); if ($process->run() !== 0) { throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); } $this->versionDate = new \DateTime(\trim($process->getOutput())); $this->versionDate->setTimezone(new \DateTimeZone('UTC')); $process = new Process(['git', 'describe', '--tags', '--exact-match', 'HEAD'], __DIR__); if ($process->run() === 0) { $this->version = \trim($process->getOutput()); } else { // get branch-alias defined in composer.json for dev-main (if any) $localConfig = __DIR__ . '/../../composer.json'; $file = new JsonFile($localConfig); $localConfig = $file->read(); if (isset($localConfig['extra']['branch-alias']['dev-main'])) { $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-main']; } } $phar = new \Phar($pharFile, 0, 'composer.phar'); $phar->setSignatureAlgorithm(\Phar::SHA512); $phar->startBuffering(); $finderSort = static function ($a, $b) : int { return \strcmp(\strtr($a->getRealPath(), '\\', '/'), \strtr($b->getRealPath(), '\\', '/')); }; // Add Composer sources $finder = new Finder(); $finder->files()->ignoreVCS(\true)->name('*.php')->notName('Compiler.php')->notName('ClassLoader.php')->notName('InstalledVersions.php')->in(__DIR__ . '/..')->sort($finderSort); foreach ($finder as $file) { $this->addFile($phar, $file); } // Add runtime utilities separately to make sure they retains the docblocks as these will get copied into projects $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), \false); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/InstalledVersions.php'), \false); // Add Composer resources $finder = new Finder(); $finder->files()->in(__DIR__ . '/../../res')->sort($finderSort); foreach ($finder as $file) { $this->addFile($phar, $file, \false); } // Add vendor files $finder = new Finder(); $finder->files()->ignoreVCS(\true)->notPath('/\\/(composer\\.(json|lock)|[A-Z]+\\.md(?:own)?|\\.gitignore|appveyor.yml|phpunit\\.xml\\.dist|phpstan\\.neon\\.dist|phpstan-config\\.neon|phpstan-baseline\\.neon)$/')->notPath('/bin\\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\\.phar)(\\.bat)?$/')->notPath('justinrainbow/json-schema/demo/')->notPath('justinrainbow/json-schema/dist/')->notPath('composer/LICENSE')->exclude('Tests')->exclude('tests')->exclude('docs')->in(__DIR__ . '/../../vendor/')->sort($finderSort); $extraFiles = []; foreach ([__DIR__ . '/../../vendor/composer/installed.json', __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-exceptions.json', __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-licenses.json', CaBundle::getBundledCaBundlePath(), __DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe', __DIR__ . '/../../vendor/symfony/console/Resources/completion.bash'] as $file) { $extraFiles[$file] = \realpath($file); if (!\file_exists($file)) { throw new \RuntimeException('Extra file listed is missing from the filesystem: ' . $file); } } $unexpectedFiles = []; foreach ($finder as $file) { if (\false !== ($index = \array_search($file->getRealPath(), $extraFiles, \true))) { unset($extraFiles[$index]); } elseif (!Preg::isMatch('{(^LICENSE$|\\.php$)}', $file->getFilename())) { $unexpectedFiles[] = (string) $file; } if (Preg::isMatch('{\\.php[\\d.]*$}', $file->getFilename())) { $this->addFile($phar, $file); } else { $this->addFile($phar, $file, \false); } } if (\count($extraFiles) > 0) { throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:' . \PHP_EOL . \var_export($extraFiles, \true)); } if (\count($unexpectedFiles) > 0) { throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:' . \PHP_EOL . \var_export($unexpectedFiles, \true)); } // Add bin/composer $this->addComposerBin($phar); // Stubs $phar->setStub($this->getStub()); $phar->stopBuffering(); // disabled for interoperability with systems without gzip ext // $phar->compressFiles(\Phar::GZ); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../LICENSE'), \false); unset($phar); // re-sign the phar with reproducible timestamp / signature $util = new Timestamps($pharFile); $util->updateTimestamps($this->versionDate); $util->save($pharFile, \Phar::SHA512); Linter::lint($pharFile, ['vendor/symfony/console/Attribute/AsCommand.php', 'vendor/symfony/polyfill-intl-grapheme/bootstrap80.php', 'vendor/symfony/polyfill-intl-normalizer/bootstrap80.php', 'vendor/symfony/polyfill-mbstring/bootstrap80.php', 'vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'vendor/symfony/service-contracts/Attribute/SubscribedService.php']); } private function getRelativeFilePath(\SplFileInfo $file) : string { $realPath = $file->getRealPath(); $pathPrefix = \dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR; $pos = \strpos($realPath, $pathPrefix); $relativePath = $pos !== \false ? \substr_replace($realPath, '', $pos, \strlen($pathPrefix)) : $realPath; return \strtr($relativePath, '\\', '/'); } private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = \true) : void { $path = $this->getRelativeFilePath($file); $content = \file_get_contents((string) $file); if ($strip) { $content = $this->stripWhitespace($content); } elseif ('LICENSE' === $file->getFilename()) { $content = "\n" . $content . "\n"; } if ($path === 'src/Composer/Composer.php') { $content = \strtr($content, ['@package_version@' => $this->version, '@package_branch_alias_version@' => $this->branchAliasVersion, '@release_date@' => $this->versionDate->format('Y-m-d H:i:s')]); $content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content); } $phar->addFromString($path, $content); } private function addComposerBin(\Phar $phar) : void { $content = \file_get_contents(__DIR__ . '/../../bin/composer'); $content = Preg::replace('{^#!/usr/bin/env php\\s*}', '', $content); $phar->addFromString('bin/composer', $content); } /** * Removes whitespace from a PHP source string while preserving line numbers. * * @param string $source A PHP string * @return string The PHP string with the whitespace removed */ private function stripWhitespace(string $source) : string { if (!\function_exists('token_get_all')) { return $source; } $output = ''; foreach (\token_get_all($source) as $token) { if (\is_string($token)) { $output .= $token; } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { $output .= \str_repeat("\n", \substr_count($token[1], "\n")); } elseif (\T_WHITESPACE === $token[0]) { // reduce wide spaces $whitespace = Preg::replace('{[ \\t]+}', ' ', $token[1]); // normalize newlines to \n $whitespace = Preg::replace('{(?:\\r\\n|\\r|\\n)}', "\n", $whitespace); // trim leading spaces $whitespace = Preg::replace('{\\n +}', "\n", $whitespace); $output .= $whitespace; } else { $output .= $token[1]; } } return $output; } private function getStub() : string { $stub = <<<'EOF' #!/usr/bin/env php * Jordi Boggiano * * For the full copyright and license information, please view * the license that is located at the bottom of this file. */ // Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) { if (version_compare(phpversion('apc'), '3.0.12', '>=')) { ini_set('apc.cache_by_default', 0); } else { fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL); fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); } } if (!class_exists('Phar')) { echo 'PHP\'s phar extension is missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL; exit(1); } Phar::mapPhar('composer.phar'); EOF; // add warning once the phar is older than 60 days if (Preg::isMatch('{^[a-f0-9]+$}', $this->version)) { $warningTime = (int) $this->versionDate->format('U') + 60 * 86400; $stub .= "define('COMPOSER_DEV_WARNING_TIME', {$warningTime});\n"; } return $stub . <<<'EOF' require 'phar://composer.phar/bin/composer'; __HALT_COMPILER(); EOF; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Package\Version\VersionParser; use Composer\Pcre\Preg; use Composer\Util\ComposerMirror; /** * Core package definitions that are needed to resolve dependencies and install packages * * @author Nils Adermann * * @phpstan-import-type AutoloadRules from PackageInterface * @phpstan-import-type DevAutoloadRules from PackageInterface */ class Package extends \Composer\Package\BasePackage { /** @var string */ protected $type; /** @var ?string */ protected $targetDir; /** @var 'source'|'dist'|null */ protected $installationSource; /** @var ?string */ protected $sourceType; /** @var ?string */ protected $sourceUrl; /** @var ?string */ protected $sourceReference; /** @var ?list */ protected $sourceMirrors; /** @var ?non-empty-string */ protected $distType; /** @var ?non-empty-string */ protected $distUrl; /** @var ?string */ protected $distReference; /** @var ?string */ protected $distSha1Checksum; /** @var ?list */ protected $distMirrors; /** @var string */ protected $version; /** @var string */ protected $prettyVersion; /** @var ?\DateTimeInterface */ protected $releaseDate; /** @var mixed[] */ protected $extra = []; /** @var string[] */ protected $binaries = []; /** @var bool */ protected $dev; /** * @var string * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev' */ protected $stability; /** @var ?string */ protected $notificationUrl; /** @var array */ protected $requires = []; /** @var array */ protected $conflicts = []; /** @var array */ protected $provides = []; /** @var array */ protected $replaces = []; /** @var array */ protected $devRequires = []; /** @var array */ protected $suggests = []; /** * @var array * @phpstan-var AutoloadRules */ protected $autoload = []; /** * @var array * @phpstan-var DevAutoloadRules */ protected $devAutoload = []; /** @var string[] */ protected $includePaths = []; /** @var bool */ protected $isDefaultBranch = \false; /** @var mixed[] */ protected $transportOptions = []; /** * Creates a new in memory package. * * @param string $name The package's name * @param string $version The package's version * @param string $prettyVersion The package's non-normalized version */ public function __construct(string $name, string $version, string $prettyVersion) { parent::__construct($name); $this->version = $version; $this->prettyVersion = $prettyVersion; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; } /** * @inheritDoc */ public function isDev() : bool { return $this->dev; } public function setType(string $type) : void { $this->type = $type; } /** * @inheritDoc */ public function getType() : string { return $this->type ?: 'library'; } /** * @inheritDoc */ public function getStability() : string { return $this->stability; } public function setTargetDir(?string $targetDir) : void { $this->targetDir = $targetDir; } /** * @inheritDoc */ public function getTargetDir() : ?string { if (null === $this->targetDir) { return null; } return \ltrim(Preg::replace('{ (?:^|[\\\\/]+) \\.\\.? (?:[\\\\/]+|$) (?:\\.\\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/'); } /** * @param mixed[] $extra */ public function setExtra(array $extra) : void { $this->extra = $extra; } /** * @inheritDoc */ public function getExtra() : array { return $this->extra; } /** * @param string[] $binaries */ public function setBinaries(array $binaries) : void { $this->binaries = $binaries; } /** * @inheritDoc */ public function getBinaries() : array { return $this->binaries; } /** * @inheritDoc */ public function setInstallationSource(?string $type) : void { $this->installationSource = $type; } /** * @inheritDoc */ public function getInstallationSource() : ?string { return $this->installationSource; } public function setSourceType(?string $type) : void { $this->sourceType = $type; } /** * @inheritDoc */ public function getSourceType() : ?string { return $this->sourceType; } public function setSourceUrl(?string $url) : void { $this->sourceUrl = $url; } /** * @inheritDoc */ public function getSourceUrl() : ?string { return $this->sourceUrl; } public function setSourceReference(?string $reference) : void { $this->sourceReference = $reference; } /** * @inheritDoc */ public function getSourceReference() : ?string { return $this->sourceReference; } public function setSourceMirrors(?array $mirrors) : void { $this->sourceMirrors = $mirrors; } /** * @inheritDoc */ public function getSourceMirrors() : ?array { return $this->sourceMirrors; } /** * @inheritDoc */ public function getSourceUrls() : array { return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source'); } /** * @param string $type */ public function setDistType(?string $type) : void { $this->distType = $type === '' ? null : $type; } /** * @inheritDoc */ public function getDistType() : ?string { return $this->distType; } /** * @param string|null $url */ public function setDistUrl(?string $url) : void { $this->distUrl = $url === '' ? null : $url; } /** * @inheritDoc */ public function getDistUrl() : ?string { return $this->distUrl; } /** * @param string $reference */ public function setDistReference(?string $reference) : void { $this->distReference = $reference; } /** * @inheritDoc */ public function getDistReference() : ?string { return $this->distReference; } /** * @param string $sha1checksum */ public function setDistSha1Checksum(?string $sha1checksum) : void { $this->distSha1Checksum = $sha1checksum; } /** * @inheritDoc */ public function getDistSha1Checksum() : ?string { return $this->distSha1Checksum; } public function setDistMirrors(?array $mirrors) : void { $this->distMirrors = $mirrors; } /** * @inheritDoc */ public function getDistMirrors() : ?array { return $this->distMirrors; } /** * @inheritDoc */ public function getDistUrls() : array { return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist'); } /** * @inheritDoc */ public function getTransportOptions() : array { return $this->transportOptions; } /** * @inheritDoc */ public function setTransportOptions(array $options) : void { $this->transportOptions = $options; } /** * @inheritDoc */ public function getVersion() : string { return $this->version; } /** * @inheritDoc */ public function getPrettyVersion() : string { return $this->prettyVersion; } public function setReleaseDate(?\DateTimeInterface $releaseDate) : void { $this->releaseDate = $releaseDate; } /** * @inheritDoc */ public function getReleaseDate() : ?\DateTimeInterface { return $this->releaseDate; } /** * Set the required packages * * @param array $requires A set of package links */ public function setRequires(array $requires) : void { if (isset($requires[0])) { // @phpstan-ignore-line $requires = $this->convertLinksToMap($requires, 'setRequires'); } $this->requires = $requires; } /** * @inheritDoc */ public function getRequires() : array { return $this->requires; } /** * Set the conflicting packages * * @param array $conflicts A set of package links */ public function setConflicts(array $conflicts) : void { if (isset($conflicts[0])) { // @phpstan-ignore-line $conflicts = $this->convertLinksToMap($conflicts, 'setConflicts'); } $this->conflicts = $conflicts; } /** * @inheritDoc * @return array */ public function getConflicts() : array { return $this->conflicts; } /** * Set the provided virtual packages * * @param array $provides A set of package links */ public function setProvides(array $provides) : void { if (isset($provides[0])) { // @phpstan-ignore-line $provides = $this->convertLinksToMap($provides, 'setProvides'); } $this->provides = $provides; } /** * @inheritDoc * @return array */ public function getProvides() : array { return $this->provides; } /** * Set the packages this one replaces * * @param array $replaces A set of package links */ public function setReplaces(array $replaces) : void { if (isset($replaces[0])) { // @phpstan-ignore-line $replaces = $this->convertLinksToMap($replaces, 'setReplaces'); } $this->replaces = $replaces; } /** * @inheritDoc * @return array */ public function getReplaces() : array { return $this->replaces; } /** * Set the recommended packages * * @param array $devRequires A set of package links */ public function setDevRequires(array $devRequires) : void { if (isset($devRequires[0])) { // @phpstan-ignore-line $devRequires = $this->convertLinksToMap($devRequires, 'setDevRequires'); } $this->devRequires = $devRequires; } /** * @inheritDoc */ public function getDevRequires() : array { return $this->devRequires; } /** * Set the suggested packages * * @param array $suggests A set of package names/comments */ public function setSuggests(array $suggests) : void { $this->suggests = $suggests; } /** * @inheritDoc */ public function getSuggests() : array { return $this->suggests; } /** * Set the autoload mapping * * @param array $autoload Mapping of autoloading rules * * @phpstan-param AutoloadRules $autoload */ public function setAutoload(array $autoload) : void { $this->autoload = $autoload; } /** * @inheritDoc */ public function getAutoload() : array { return $this->autoload; } /** * Set the dev autoload mapping * * @param array $devAutoload Mapping of dev autoloading rules * * @phpstan-param DevAutoloadRules $devAutoload */ public function setDevAutoload(array $devAutoload) : void { $this->devAutoload = $devAutoload; } /** * @inheritDoc */ public function getDevAutoload() : array { return $this->devAutoload; } /** * Sets the list of paths added to PHP's include path. * * @param string[] $includePaths List of directories. */ public function setIncludePaths(array $includePaths) : void { $this->includePaths = $includePaths; } /** * @inheritDoc */ public function getIncludePaths() : array { return $this->includePaths; } /** * Sets the notification URL */ public function setNotificationUrl(string $notificationUrl) : void { $this->notificationUrl = $notificationUrl; } /** * @inheritDoc */ public function getNotificationUrl() : ?string { return $this->notificationUrl; } public function setIsDefaultBranch(bool $defaultBranch) : void { $this->isDefaultBranch = $defaultBranch; } /** * @inheritDoc */ public function isDefaultBranch() : bool { return $this->isDefaultBranch; } /** * @inheritDoc */ public function setSourceDistReferences(string $reference) : void { $this->setSourceReference($reference); // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this? if ($this->getDistUrl() !== null && Preg::isMatch('{^https?://(?:(?:www\\.)?bitbucket\\.org|(api\\.)?github\\.com|(?:www\\.)?gitlab\\.com)/}i', $this->getDistUrl())) { $this->setDistReference($reference); $this->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl())); } elseif ($this->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it $this->setDistReference($reference); } } /** * Replaces current version and pretty version with passed values. * It also sets stability. * * @param string $version The package's normalized version * @param string $prettyVersion The package's non-normalized version */ public function replaceVersion(string $version, string $prettyVersion) : void { $this->version = $version; $this->prettyVersion = $prettyVersion; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; } /** * @param mixed[]|null $mirrors * * @return list * * @phpstan-param list|null $mirrors */ protected function getUrls(?string $url, ?array $mirrors, ?string $ref, ?string $type, string $urlType) : array { if (!$url) { return []; } if ($urlType === 'dist' && \false !== \strpos($url, '%')) { $url = ComposerMirror::processUrl($url, $this->name, $this->version, $ref, $type, $this->prettyVersion); } $urls = [$url]; if ($mirrors) { foreach ($mirrors as $mirror) { if ($urlType === 'dist') { $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type, $this->prettyVersion); } elseif ($urlType === 'source' && $type === 'git') { $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type); } elseif ($urlType === 'source' && $type === 'hg') { $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type); } else { continue; } if (!\in_array($mirrorUrl, $urls)) { $func = $mirror['preferred'] ? 'array_unshift' : 'array_push'; $func($urls, $mirrorUrl); } } } return $urls; } /** * @param array $links * @return array */ private function convertLinksToMap(array $links, string $source) : array { \trigger_error('Package::' . $source . ' must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.'); $newLinks = []; foreach ($links as $link) { $newLinks[$link->getTarget()] = $link; } return $newLinks; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Semver\Constraint\ConstraintInterface; /** * Represents a link between two packages, represented by their names * * @author Nils Adermann */ class Link { public const TYPE_REQUIRE = 'requires'; public const TYPE_DEV_REQUIRE = 'devRequires'; public const TYPE_PROVIDE = 'provides'; public const TYPE_CONFLICT = 'conflicts'; public const TYPE_REPLACE = 'replaces'; /** * Special type * @internal */ public const TYPE_DOES_NOT_REQUIRE = 'does not require'; private const TYPE_UNKNOWN = 'relates to'; /** * Will be converted into a constant once the min PHP version allows this * * @internal * @var string[] * @phpstan-var array */ public static $TYPES = [self::TYPE_REQUIRE, self::TYPE_DEV_REQUIRE, self::TYPE_PROVIDE, self::TYPE_CONFLICT, self::TYPE_REPLACE]; /** * @var string */ protected $source; /** * @var string */ protected $target; /** * @var ConstraintInterface */ protected $constraint; /** * @var string * @phpstan-var string $description */ protected $description; /** * @var ?string */ protected $prettyConstraint; /** * Creates a new package link. * * @param ConstraintInterface $constraint Constraint applying to the target of this link * @param self::TYPE_* $description Used to create a descriptive string representation */ public function __construct(string $source, string $target, ConstraintInterface $constraint, $description = self::TYPE_UNKNOWN, ?string $prettyConstraint = null) { $this->source = \strtolower($source); $this->target = \strtolower($target); $this->constraint = $constraint; $this->description = self::TYPE_DEV_REQUIRE === $description ? 'requires (for development)' : $description; $this->prettyConstraint = $prettyConstraint; } public function getDescription() : string { return $this->description; } public function getSource() : string { return $this->source; } public function getTarget() : string { return $this->target; } public function getConstraint() : ConstraintInterface { return $this->constraint; } /** * @throws \UnexpectedValueException If no pretty constraint was provided */ public function getPrettyConstraint() : string { if (null === $this->prettyConstraint) { throw new \UnexpectedValueException(\sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this)); } return $this->prettyConstraint; } public function __toString() : string { return $this->source . ' ' . $this->description . ' ' . $this->target . ' (' . $this->constraint . ')'; } public function getPrettyString(\Composer\Package\PackageInterface $sourcePackage) : string { return $sourcePackage->getPrettyString() . ' ' . $this->description . ' ' . $this->target . ' ' . $this->constraint->getPrettyString(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * Defines package metadata that is not necessarily needed for solving and installing packages * * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. * * @author Nils Adermann */ interface CompletePackageInterface extends \Composer\Package\PackageInterface { /** * Returns the scripts of this package * * @return array Map of script name to array of handlers */ public function getScripts() : array; /** * @param array $scripts */ public function setScripts(array $scripts) : void; /** * Returns an array of repositories * * @return mixed[] Repositories */ public function getRepositories() : array; /** * Set the repositories * * @param mixed[] $repositories */ public function setRepositories(array $repositories) : void; /** * Returns the package license, e.g. MIT, BSD, GPL * * @return string[] The package licenses */ public function getLicense() : array; /** * Set the license * * @param string[] $license */ public function setLicense(array $license) : void; /** * Returns an array of keywords relating to the package * * @return string[] */ public function getKeywords() : array; /** * Set the keywords * * @param string[] $keywords */ public function setKeywords(array $keywords) : void; /** * Returns the package description * * @return ?string */ public function getDescription() : ?string; /** * Set the description */ public function setDescription(string $description) : void; /** * Returns the package homepage * * @return ?string */ public function getHomepage() : ?string; /** * Set the homepage */ public function setHomepage(string $homepage) : void; /** * Returns an array of authors of the package * * Each item can contain name/homepage/email keys * * @return array */ public function getAuthors() : array; /** * Set the authors * * @param array $authors */ public function setAuthors(array $authors) : void; /** * Returns the support information * * @return array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} */ public function getSupport() : array; /** * Set the support information * * @param array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} $support */ public function setSupport(array $support) : void; /** * Returns an array of funding options for the package * * Each item will contain type and url keys * * @return array */ public function getFunding() : array; /** * Set the funding * * @param array $funding */ public function setFunding(array $funding) : void; /** * Returns if the package is abandoned or not */ public function isAbandoned() : bool; /** * If the package is abandoned and has a suggested replacement, this method returns it */ public function getReplacementPackage() : ?string; /** * @param bool|string $abandoned */ public function setAbandoned($abandoned) : void; /** * Returns default base filename for archive * * @return ?string */ public function getArchiveName() : ?string; /** * Sets default base filename for archive */ public function setArchiveName(string $name) : void; /** * Returns a list of patterns to exclude from package archives * * @return string[] */ public function getArchiveExcludes() : array; /** * Sets a list of patterns to be excluded from archives * * @param string[] $excludes */ public function setArchiveExcludes(array $excludes) : void; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Composer\Downloader\DownloadManager; use Composer\Package\RootPackageInterface; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use Composer\Util\Loop; use Composer\Util\SyncHelper; use Composer\Json\JsonFile; use Composer\Package\CompletePackageInterface; /** * @author Matthieu Moquet * @author Till Klampaeckel */ class ArchiveManager { /** @var DownloadManager */ protected $downloadManager; /** @var Loop */ protected $loop; /** * @var ArchiverInterface[] */ protected $archivers = []; /** * @var bool */ protected $overwriteFiles = \true; /** * @param DownloadManager $downloadManager A manager used to download package sources */ public function __construct(DownloadManager $downloadManager, Loop $loop) { $this->downloadManager = $downloadManager; $this->loop = $loop; } public function addArchiver(\Composer\Package\Archiver\ArchiverInterface $archiver) : void { $this->archivers[] = $archiver; } /** * Set whether existing archives should be overwritten * * @param bool $overwriteFiles New setting * * @return $this */ public function setOverwriteFiles(bool $overwriteFiles) : self { $this->overwriteFiles = $overwriteFiles; return $this; } /** * @return array * @internal */ public function getPackageFilenameParts(CompletePackageInterface $package) : array { $baseName = $package->getArchiveName(); if (null === $baseName) { $baseName = Preg::replace('#[^a-z0-9-_]#i', '-', $package->getName()); } $parts = ['base' => $baseName]; $distReference = $package->getDistReference(); if (null !== $distReference && Preg::isMatch('{^[a-f0-9]{40}$}', $distReference)) { $parts['dist_reference'] = $distReference; $parts['dist_type'] = $package->getDistType(); } else { $parts['version'] = $package->getPrettyVersion(); $parts['dist_reference'] = $distReference; } $sourceReference = $package->getSourceReference(); if (null !== $sourceReference) { $parts['source_reference'] = \substr(\sha1($sourceReference), 0, 6); } $parts = \array_filter($parts); foreach ($parts as $key => $part) { $parts[$key] = \str_replace('/', '-', $part); } return $parts; } /** * @param array $parts * * @return string * @internal */ public function getPackageFilenameFromParts(array $parts) : string { return \implode('-', $parts); } /** * Generate a distinct filename for a particular version of a package. * * @param CompletePackageInterface $package The package to get a name for * * @return string A filename without an extension */ public function getPackageFilename(CompletePackageInterface $package) : string { return $this->getPackageFilenameFromParts($this->getPackageFilenameParts($package)); } /** * Create an archive of the specified package. * * @param CompletePackageInterface $package The package to archive * @param string $format The format of the archive (zip, tar, ...) * @param string $targetDir The directory where to build the archive * @param string|null $fileName The relative file name to use for the archive, or null to generate * the package name. Note that the format will be appended to this name * @param bool $ignoreFilters Ignore filters when looking for files in the package * @throws \InvalidArgumentException * @throws \RuntimeException * @return string The path of the created archive */ public function archive(CompletePackageInterface $package, string $format, string $targetDir, ?string $fileName = null, bool $ignoreFilters = \false) : string { if (empty($format)) { throw new \InvalidArgumentException('Format must be specified'); } // Search for the most appropriate archiver $usableArchiver = null; foreach ($this->archivers as $archiver) { if ($archiver->supports($format, $package->getSourceType())) { $usableArchiver = $archiver; break; } } // Checks the format/source type are supported before downloading the package if (null === $usableArchiver) { throw new \RuntimeException(\sprintf('No archiver found to support %s format', $format)); } $filesystem = new Filesystem(); if ($package instanceof RootPackageInterface) { $sourcePath = \realpath('.'); } else { // Directory used to download the sources $sourcePath = \sys_get_temp_dir() . '/composer_archive' . \uniqid(); $filesystem->ensureDirectoryExists($sourcePath); try { // Download sources $promise = $this->downloadManager->download($package, $sourcePath); SyncHelper::await($this->loop, $promise); $promise = $this->downloadManager->install($package, $sourcePath); SyncHelper::await($this->loop, $promise); } catch (\Exception $e) { $filesystem->removeDirectory($sourcePath); throw $e; } // Check exclude from downloaded composer.json if (\file_exists($composerJsonPath = $sourcePath . '/composer.json')) { $jsonFile = new JsonFile($composerJsonPath); $jsonData = $jsonFile->read(); if (!empty($jsonData['archive']['name'])) { $package->setArchiveName($jsonData['archive']['name']); } if (!empty($jsonData['archive']['exclude'])) { $package->setArchiveExcludes($jsonData['archive']['exclude']); } } } $supportedFormats = $this->getSupportedFormats(); $packageNameParts = null === $fileName ? $this->getPackageFilenameParts($package) : ['base' => $fileName]; $packageName = $this->getPackageFilenameFromParts($packageNameParts); $excludePatterns = $this->buildExcludePatterns($packageNameParts, $supportedFormats); // Archive filename $filesystem->ensureDirectoryExists($targetDir); $target = \realpath($targetDir) . '/' . $packageName . '.' . $format; $filesystem->ensureDirectoryExists(\dirname($target)); if (!$this->overwriteFiles && \file_exists($target)) { return $target; } // Create the archive $tempTarget = \sys_get_temp_dir() . '/composer_archive' . \uniqid() . '.' . $format; $filesystem->ensureDirectoryExists(\dirname($tempTarget)); $archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, \array_merge($excludePatterns, $package->getArchiveExcludes()), $ignoreFilters); $filesystem->rename($archivePath, $target); // cleanup temporary download if (!$package instanceof RootPackageInterface) { $filesystem->removeDirectory($sourcePath); } $filesystem->remove($tempTarget); return $target; } /** * @param string[] $parts * @param string[] $formats * * @return string[] */ private function buildExcludePatterns(array $parts, array $formats) : array { $base = $parts['base']; if (\count($parts) > 1) { $base .= '-*'; } $patterns = []; foreach ($formats as $format) { $patterns[] = "{$base}.{$format}"; } return $patterns; } /** * @return string[] */ private function getSupportedFormats() : array { // The problem is that the \Composer\Package\Archiver\ArchiverInterface // doesn't provide method to get the supported formats. // Supported formats are also hard-coded into the description of the // --format option. // See \Composer\Command\ArchiveCommand::configure(). $formats = []; foreach ($this->archivers as $archiver) { $items = []; switch (\get_class($archiver)) { case \Composer\Package\Archiver\ZipArchiver::class: $items = ['zip']; break; case \Composer\Package\Archiver\PharArchiver::class: $items = ['zip', 'tar', 'tar.gz', 'tar.bz2']; break; } $formats = \array_merge($formats, $items); } return \array_unique($formats); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Composer\Pcre\Preg; use Composer\Util\Filesystem; use FilesystemIterator; use FilterIterator; use Iterator; use _ContaoManager\Symfony\Component\Finder\Finder; use _ContaoManager\Symfony\Component\Finder\SplFileInfo; /** * A Symfony Finder wrapper which locates files that should go into archives * * Handles .gitignore, .gitattributes and .hgignore files as well as composer's * own exclude rules from composer.json * * @author Nils Adermann * @phpstan-extends FilterIterator> */ class ArchivableFilesFinder extends FilterIterator { /** * @var Finder */ protected $finder; /** * Initializes the internal Symfony Finder with appropriate filters * * @param string $sources Path to source files to be archived * @param string[] $excludes Composer's own exclude rules from composer.json * @param bool $ignoreFilters Ignore filters when looking for files */ public function __construct(string $sources, array $excludes, bool $ignoreFilters = \false) { $fs = new Filesystem(); $sources = $fs->normalizePath(\realpath($sources)); if ($ignoreFilters) { $filters = []; } else { $filters = [new \Composer\Package\Archiver\GitExcludeFilter($sources), new \Composer\Package\Archiver\ComposerExcludeFilter($sources, $excludes)]; } $this->finder = new Finder(); $filter = static function (\SplFileInfo $file) use($sources, $filters, $fs) : bool { if ($file->isLink() && ($file->getRealPath() === \false || \strpos($file->getRealPath(), $sources) !== 0)) { return \false; } $relativePath = Preg::replace('#^' . \preg_quote($sources, '#') . '#', '', $fs->normalizePath($file->getRealPath())); $exclude = \false; foreach ($filters as $filter) { $exclude = $filter->filter($relativePath, $exclude); } return !$exclude; }; if (\method_exists($filter, 'bindTo')) { $filter = $filter->bindTo(null); } $this->finder->in($sources)->filter($filter)->ignoreVCS(\true)->ignoreDotFiles(\false)->sortByName(); parent::__construct($this->finder->getIterator()); } public function accept() : bool { /** @var SplFileInfo $current */ $current = $this->getInnerIterator()->current(); if (!$current->isDir()) { return \true; } $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS); return !$iterator->valid(); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * @author Till Klampaeckel * @author Nils Adermann * @author Matthieu Moquet */ class PharArchiver implements \Composer\Package\Archiver\ArchiverInterface { /** @var array */ protected static $formats = ['zip' => \Phar::ZIP, 'tar' => \Phar::TAR, 'tar.gz' => \Phar::TAR, 'tar.bz2' => \Phar::TAR]; /** @var array */ protected static $compressFormats = ['tar.gz' => \Phar::GZ, 'tar.bz2' => \Phar::BZ2]; /** * @inheritDoc */ public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = \false) : string { $sources = \realpath($sources); // Phar would otherwise load the file which we don't want if (\file_exists($target)) { \unlink($target); } try { $filename = \substr($target, 0, \strrpos($target, $format) - 1); // Check if compress format if (isset(static::$compressFormats[$format])) { // Current compress format supported base on tar $target = $filename . '.tar'; } $phar = new \PharData($target, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO, '', static::$formats[$format]); $files = new \Composer\Package\Archiver\ArchivableFilesFinder($sources, $excludes, $ignoreFilters); $filesOnly = new \Composer\Package\Archiver\ArchivableFilesFilter($files); $phar->buildFromIterator($filesOnly, $sources); $filesOnly->addEmptyDir($phar, $sources); if (isset(static::$compressFormats[$format])) { // Check can be compressed? if (!$phar->canCompress(static::$compressFormats[$format])) { throw new \RuntimeException(\sprintf('Can not compress to %s format', $format)); } // Delete old tar \unlink($target); // Compress the new tar $phar->compress(static::$compressFormats[$format]); // Make the correct filename $target = $filename . '.' . $format; } return $target; } catch (\UnexpectedValueException $e) { $message = \sprintf("Could not create archive '%s' from '%s': %s", $target, $sources, $e->getMessage()); throw new \RuntimeException($message, $e->getCode(), $e); } } /** * @inheritDoc */ public function supports(string $format, ?string $sourceType) : bool { return isset(static::$formats[$format]); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Finder; /** * @author Nils Adermann */ abstract class BaseExcludeFilter { /** * @var string */ protected $sourcePath; /** * @var array array of [$pattern, $negate, $stripLeadingSlash] arrays */ protected $excludePatterns; /** * @param string $sourcePath Directory containing sources to be filtered */ public function __construct(string $sourcePath) { $this->sourcePath = $sourcePath; $this->excludePatterns = []; } /** * Checks the given path against all exclude patterns in this filter * * Negated patterns overwrite exclude decisions of previous filters. * * @param string $relativePath The file's path relative to the sourcePath * @param bool $exclude Whether a previous filter wants to exclude this file * * @return bool Whether the file should be excluded */ public function filter(string $relativePath, bool $exclude) : bool { foreach ($this->excludePatterns as $patternData) { [$pattern, $negate, $stripLeadingSlash] = $patternData; if ($stripLeadingSlash) { $path = \substr($relativePath, 1); } else { $path = $relativePath; } try { if (Preg::isMatch($pattern, $path)) { $exclude = !$negate; } } catch (\RuntimeException $e) { // suppressed } } return $exclude; } /** * Processes a file containing exclude rules of different formats per line * * @param string[] $lines A set of lines to be parsed * @param callable $lineParser The parser to be used on each line * * @return array Exclude patterns to be used in filter() */ protected function parseLines(array $lines, callable $lineParser) : array { return \array_filter(\array_map(static function ($line) use($lineParser) { $line = \trim($line); if (!$line || 0 === \strpos($line, '#')) { return null; } return $lineParser($line); }, $lines), static function ($pattern) : bool { return $pattern !== null; }); } /** * Generates a set of exclude patterns for filter() from gitignore rules * * @param string[] $rules A list of exclude rules in gitignore syntax * * @return array Exclude patterns */ protected function generatePatterns(array $rules) : array { $patterns = []; foreach ($rules as $rule) { $patterns[] = $this->generatePattern($rule); } return $patterns; } /** * Generates an exclude pattern for filter() from a gitignore rule * * @param string $rule An exclude rule in gitignore syntax * * @return array{0: non-empty-string, 1: bool, 2: bool} An exclude pattern */ protected function generatePattern(string $rule) : array { $negate = \false; $pattern = ''; if ($rule !== '' && $rule[0] === '!') { $negate = \true; $rule = \ltrim($rule, '!'); } $firstSlashPosition = \strpos($rule, '/'); if (0 === $firstSlashPosition) { $pattern = '^/'; } elseif (\false === $firstSlashPosition || \strlen($rule) - 1 === $firstSlashPosition) { $pattern = '/'; } $rule = \trim($rule, '/'); // remove delimiters as well as caret (^) and dollar sign ($) from the regex $rule = \substr(Finder\Glob::toRegex($rule), 2, -2); return ['{' . $pattern . $rule . '(?=$|/)}', $negate, \false]; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use FilterIterator; use Iterator; use PharData; use SplFileInfo; /** * @phpstan-extends FilterIterator> */ class ArchivableFilesFilter extends FilterIterator { /** @var string[] */ private $dirs = []; /** * @return bool true if the current element is acceptable, otherwise false. */ public function accept() : bool { $file = $this->getInnerIterator()->current(); if ($file->isDir()) { $this->dirs[] = (string) $file; return \false; } return \true; } public function addEmptyDir(PharData $phar, string $sources) : void { foreach ($this->dirs as $filepath) { $localname = \str_replace($sources . "/", '', $filepath); $phar->addEmptyDir($localname); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use Composer\Pcre\Preg; /** * An exclude filter that processes gitattributes * * It respects export-ignore git attributes * * @author Nils Adermann */ class GitExcludeFilter extends \Composer\Package\Archiver\BaseExcludeFilter { /** * Parses .gitattributes if it exists */ public function __construct(string $sourcePath) { parent::__construct($sourcePath); if (\file_exists($sourcePath . '/.gitattributes')) { $this->excludePatterns = \array_merge($this->excludePatterns, $this->parseLines(\file($sourcePath . '/.gitattributes'), [$this, 'parseGitAttributesLine'])); } } /** * Callback parser which finds export-ignore rules in git attribute lines * * @param string $line A line from .gitattributes * * @return array{0: string, 1: bool, 2: bool}|null An exclude pattern for filter() */ public function parseGitAttributesLine(string $line) : ?array { $parts = Preg::split('#\\s+#', $line); if (\count($parts) === 2 && $parts[1] === 'export-ignore') { return $this->generatePattern($parts[0]); } if (\count($parts) === 2 && $parts[1] === '-export-ignore') { return $this->generatePattern('!' . $parts[0]); } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * An exclude filter which processes composer's own exclude rules * * @author Nils Adermann */ class ComposerExcludeFilter extends \Composer\Package\Archiver\BaseExcludeFilter { /** * @param string $sourcePath Directory containing sources to be filtered * @param string[] $excludeRules An array of exclude rules from composer.json */ public function __construct(string $sourcePath, array $excludeRules) { parent::__construct($sourcePath); $this->excludePatterns = $this->generatePatterns($excludeRules); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; /** * @author Till Klampaeckel * @author Matthieu Moquet * @author Nils Adermann */ interface ArchiverInterface { /** * Create an archive from the sources. * * @param string $sources The sources directory * @param string $target The target file * @param string $format The format used for archive * @param string[] $excludes A list of patterns for files to exclude * @param bool $ignoreFilters Whether to ignore filters when looking for files * * @return string The path to the written archive file */ public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = \false) : string; /** * Format supported by the archiver. * * @param string $format The archive format * @param ?string $sourceType The source type (git, svn, hg, etc.) * * @return bool true if the format is supported by the archiver */ public function supports(string $format, ?string $sourceType) : bool; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Archiver; use ZipArchive; use Composer\Util\Filesystem; /** * @author Jan Prieser */ class ZipArchiver implements \Composer\Package\Archiver\ArchiverInterface { /** @var array */ protected static $formats = ['zip' => \true]; /** * @inheritDoc */ public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = \false) : string { $fs = new Filesystem(); $sourcesRealpath = \realpath($sources); if (\false !== $sourcesRealpath) { $sources = $sourcesRealpath; } unset($sourcesRealpath); $sources = $fs->normalizePath($sources); $zip = new ZipArchive(); $res = $zip->open($target, ZipArchive::CREATE); if ($res === \true) { $files = new \Composer\Package\Archiver\ArchivableFilesFinder($sources, $excludes, $ignoreFilters); foreach ($files as $file) { /** @var \Symfony\Component\Finder\SplFileInfo $file */ $filepath = \strtr($file->getPath() . "/" . $file->getFilename(), '\\', '/'); $localname = $filepath; if (\strpos($localname, $sources . '/') === 0) { $localname = \substr($localname, \strlen($sources . '/')); } if ($file->isDir()) { $zip->addEmptyDir($localname); } else { $zip->addFile($filepath, $localname); } /** * setExternalAttributesName() is only available with libzip 0.11.2 or above */ if (\method_exists($zip, 'setExternalAttributesName')) { $perms = \fileperms($filepath); /** * Ensure to preserve the permission umasks for the filepath in the archive. */ $zip->setExternalAttributesName($localname, ZipArchive::OPSYS_UNIX, $perms << 16); } } if ($zip->close()) { return $target; } } $message = \sprintf("Could not create archive '%s' from '%s': %s", $target, $sources, $zip->getStatusString()); throw new \RuntimeException($message); } /** * @inheritDoc */ public function supports(string $format, ?string $sourceType) : bool { return isset(static::$formats[$format]) && $this->compressionAvailable(); } private function compressionAvailable() : bool { return \class_exists('ZipArchive'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * @author Jordi Boggiano */ class CompleteAliasPackage extends \Composer\Package\AliasPackage implements \Composer\Package\CompletePackageInterface { /** @var CompletePackage */ protected $aliasOf; /** * All descendants' constructors should call this parent constructor * * @param CompletePackage $aliasOf The package this package is an alias of * @param string $version The version the alias must report * @param string $prettyVersion The alias's non-normalized version */ public function __construct(\Composer\Package\CompletePackage $aliasOf, string $version, string $prettyVersion) { parent::__construct($aliasOf, $version, $prettyVersion); } /** * @return CompletePackage */ public function getAliasOf() { return $this->aliasOf; } public function getScripts() : array { return $this->aliasOf->getScripts(); } public function setScripts(array $scripts) : void { $this->aliasOf->setScripts($scripts); } public function getRepositories() : array { return $this->aliasOf->getRepositories(); } public function setRepositories(array $repositories) : void { $this->aliasOf->setRepositories($repositories); } public function getLicense() : array { return $this->aliasOf->getLicense(); } public function setLicense(array $license) : void { $this->aliasOf->setLicense($license); } public function getKeywords() : array { return $this->aliasOf->getKeywords(); } public function setKeywords(array $keywords) : void { $this->aliasOf->setKeywords($keywords); } public function getDescription() : ?string { return $this->aliasOf->getDescription(); } public function setDescription(?string $description) : void { $this->aliasOf->setDescription($description); } public function getHomepage() : ?string { return $this->aliasOf->getHomepage(); } public function setHomepage(?string $homepage) : void { $this->aliasOf->setHomepage($homepage); } public function getAuthors() : array { return $this->aliasOf->getAuthors(); } public function setAuthors(array $authors) : void { $this->aliasOf->setAuthors($authors); } public function getSupport() : array { return $this->aliasOf->getSupport(); } public function setSupport(array $support) : void { $this->aliasOf->setSupport($support); } public function getFunding() : array { return $this->aliasOf->getFunding(); } public function setFunding(array $funding) : void { $this->aliasOf->setFunding($funding); } public function isAbandoned() : bool { return $this->aliasOf->isAbandoned(); } public function getReplacementPackage() : ?string { return $this->aliasOf->getReplacementPackage(); } public function setAbandoned($abandoned) : void { $this->aliasOf->setAbandoned($abandoned); } public function getArchiveName() : ?string { return $this->aliasOf->getArchiveName(); } public function setArchiveName(?string $name) : void { $this->aliasOf->setArchiveName($name); } public function getArchiveExcludes() : array { return $this->aliasOf->getArchiveExcludes(); } public function setArchiveExcludes(array $excludes) : void { $this->aliasOf->setArchiveExcludes($excludes); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; /** * @author Jordi Boggiano */ class AliasPackage extends \Composer\Package\BasePackage { /** @var string */ protected $version; /** @var string */ protected $prettyVersion; /** @var bool */ protected $dev; /** @var bool */ protected $rootPackageAlias = \false; /** * @var string * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev' */ protected $stability; /** @var bool */ protected $hasSelfVersionRequires = \false; /** @var BasePackage */ protected $aliasOf; /** @var Link[] */ protected $requires; /** @var Link[] */ protected $devRequires; /** @var Link[] */ protected $conflicts; /** @var Link[] */ protected $provides; /** @var Link[] */ protected $replaces; /** * All descendants' constructors should call this parent constructor * * @param BasePackage $aliasOf The package this package is an alias of * @param string $version The version the alias must report * @param string $prettyVersion The alias's non-normalized version */ public function __construct(\Composer\Package\BasePackage $aliasOf, string $version, string $prettyVersion) { parent::__construct($aliasOf->getName()); $this->version = $version; $this->prettyVersion = $prettyVersion; $this->aliasOf = $aliasOf; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; foreach (\Composer\Package\Link::$TYPES as $type) { $links = $aliasOf->{'get' . \ucfirst($type)}(); $this->{$type} = $this->replaceSelfVersionDependencies($links, $type); } } /** * @return BasePackage */ public function getAliasOf() { return $this->aliasOf; } /** * @inheritDoc */ public function getVersion() : string { return $this->version; } /** * @inheritDoc */ public function getStability() : string { return $this->stability; } /** * @inheritDoc */ public function getPrettyVersion() : string { return $this->prettyVersion; } /** * @inheritDoc */ public function isDev() : bool { return $this->dev; } /** * @inheritDoc */ public function getRequires() : array { return $this->requires; } /** * @inheritDoc * @return array */ public function getConflicts() : array { return $this->conflicts; } /** * @inheritDoc * @return array */ public function getProvides() : array { return $this->provides; } /** * @inheritDoc * @return array */ public function getReplaces() : array { return $this->replaces; } /** * @inheritDoc */ public function getDevRequires() : array { return $this->devRequires; } /** * Stores whether this is an alias created by an aliasing in the requirements of the root package or not * * Use by the policy for sorting manually aliased packages first, see #576 */ public function setRootPackageAlias(bool $value) : void { $this->rootPackageAlias = $value; } /** * @see setRootPackageAlias */ public function isRootPackageAlias() : bool { return $this->rootPackageAlias; } /** * @param Link[] $links * @param Link::TYPE_* $linkType * * @return Link[] */ protected function replaceSelfVersionDependencies(array $links, $linkType) : array { // for self.version requirements, we use the original package's branch name instead, to avoid leaking the magic dev-master-alias to users $prettyVersion = $this->prettyVersion; if ($prettyVersion === VersionParser::DEFAULT_BRANCH_ALIAS) { $prettyVersion = $this->aliasOf->getPrettyVersion(); } if (\in_array($linkType, [\Composer\Package\Link::TYPE_CONFLICT, \Composer\Package\Link::TYPE_PROVIDE, \Composer\Package\Link::TYPE_REPLACE], \true)) { $newLinks = []; foreach ($links as $link) { // link is self.version, but must be replacing also the replaced version if ('self.version' === $link->getPrettyConstraint()) { $newLinks[] = new \Composer\Package\Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); $constraint->setPrettyString($prettyVersion); } } $links = \array_merge($links, $newLinks); } else { foreach ($links as $index => $link) { if ('self.version' === $link->getPrettyConstraint()) { if ($linkType === \Composer\Package\Link::TYPE_REQUIRE) { $this->hasSelfVersionRequires = \true; } $links[$index] = new \Composer\Package\Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); $constraint->setPrettyString($prettyVersion); } } } return $links; } public function hasSelfVersionRequires() : bool { return $this->hasSelfVersionRequires; } public function __toString() : string { return parent::__toString() . ' (' . ($this->rootPackageAlias ? 'root ' : '') . 'alias of ' . $this->aliasOf->getVersion() . ')'; } /*************************************** * Wrappers around the aliased package * ***************************************/ public function getType() : string { return $this->aliasOf->getType(); } public function getTargetDir() : ?string { return $this->aliasOf->getTargetDir(); } public function getExtra() : array { return $this->aliasOf->getExtra(); } public function setInstallationSource(?string $type) : void { $this->aliasOf->setInstallationSource($type); } public function getInstallationSource() : ?string { return $this->aliasOf->getInstallationSource(); } public function getSourceType() : ?string { return $this->aliasOf->getSourceType(); } public function getSourceUrl() : ?string { return $this->aliasOf->getSourceUrl(); } public function getSourceUrls() : array { return $this->aliasOf->getSourceUrls(); } public function getSourceReference() : ?string { return $this->aliasOf->getSourceReference(); } public function setSourceReference(?string $reference) : void { $this->aliasOf->setSourceReference($reference); } public function setSourceMirrors(?array $mirrors) : void { $this->aliasOf->setSourceMirrors($mirrors); } public function getSourceMirrors() : ?array { return $this->aliasOf->getSourceMirrors(); } public function getDistType() : ?string { return $this->aliasOf->getDistType(); } public function getDistUrl() : ?string { return $this->aliasOf->getDistUrl(); } public function getDistUrls() : array { return $this->aliasOf->getDistUrls(); } public function getDistReference() : ?string { return $this->aliasOf->getDistReference(); } public function setDistReference(?string $reference) : void { $this->aliasOf->setDistReference($reference); } public function getDistSha1Checksum() : ?string { return $this->aliasOf->getDistSha1Checksum(); } public function setTransportOptions(array $options) : void { $this->aliasOf->setTransportOptions($options); } public function getTransportOptions() : array { return $this->aliasOf->getTransportOptions(); } public function setDistMirrors(?array $mirrors) : void { $this->aliasOf->setDistMirrors($mirrors); } public function getDistMirrors() : ?array { return $this->aliasOf->getDistMirrors(); } public function getAutoload() : array { return $this->aliasOf->getAutoload(); } public function getDevAutoload() : array { return $this->aliasOf->getDevAutoload(); } public function getIncludePaths() : array { return $this->aliasOf->getIncludePaths(); } public function getReleaseDate() : ?\DateTimeInterface { return $this->aliasOf->getReleaseDate(); } public function getBinaries() : array { return $this->aliasOf->getBinaries(); } public function getSuggests() : array { return $this->aliasOf->getSuggests(); } public function getNotificationUrl() : ?string { return $this->aliasOf->getNotificationUrl(); } public function isDefaultBranch() : bool { return $this->aliasOf->isDefaultBranch(); } public function setDistUrl(?string $url) : void { $this->aliasOf->setDistUrl($url); } public function setDistType(?string $type) : void { $this->aliasOf->setDistType($type); } public function setSourceDistReferences(string $reference) : void { $this->aliasOf->setSourceDistReferences($reference); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Json\JsonFile; use Composer\Installer\InstallationManager; use Composer\Pcre\Preg; use Composer\Repository\InstalledRepository; use Composer\Repository\LockArrayRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RootPackageRepository; use Composer\Util\ProcessExecutor; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginInterface; use Composer\Util\Git as GitUtil; use Composer\IO\IOInterface; use _ContaoManager\Seld\JsonLint\ParsingException; /** * Reads/writes project lockfile (composer.lock). * * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class Locker { /** @var JsonFile */ private $lockFile; /** @var InstallationManager */ private $installationManager; /** @var string */ private $hash; /** @var string */ private $contentHash; /** @var ArrayLoader */ private $loader; /** @var ArrayDumper */ private $dumper; /** @var ProcessExecutor */ private $process; /** @var mixed[]|null */ private $lockDataCache = null; /** @var bool */ private $virtualFileWritten = \false; /** * Initializes packages locker. * * @param JsonFile $lockFile lockfile loader * @param InstallationManager $installationManager installation manager instance * @param string $composerFileContents The contents of the composer file */ public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, string $composerFileContents, ?ProcessExecutor $process = null) { $this->lockFile = $lockFile; $this->installationManager = $installationManager; $this->hash = \md5($composerFileContents); $this->contentHash = self::getContentHash($composerFileContents); $this->loader = new ArrayLoader(null, \true); $this->dumper = new ArrayDumper(); $this->process = $process ?? new ProcessExecutor($io); } /** * Returns the md5 hash of the sorted content of the composer file. * * @param string $composerFileContents The contents of the composer file. */ public static function getContentHash(string $composerFileContents) : string { $content = JsonFile::parseJson($composerFileContents, 'composer.json'); $relevantKeys = ['name', 'version', 'require', 'require-dev', 'conflict', 'replace', 'provide', 'minimum-stability', 'prefer-stable', 'repositories', 'extra']; $relevantContent = []; foreach (\array_intersect($relevantKeys, \array_keys($content)) as $key) { $relevantContent[$key] = $content[$key]; } if (isset($content['config']['platform'])) { $relevantContent['config']['platform'] = $content['config']['platform']; } \ksort($relevantContent); return \md5(JsonFile::encode($relevantContent, 0)); } /** * Checks whether locker has been locked (lockfile found). */ public function isLocked() : bool { if (!$this->virtualFileWritten && !$this->lockFile->exists()) { return \false; } $data = $this->getLockData(); return isset($data['packages']); } /** * Checks whether the lock file is still up to date with the current hash */ public function isFresh() : bool { $lock = $this->lockFile->read(); if (!empty($lock['content-hash'])) { // There is a content hash key, use that instead of the file hash return $this->contentHash === $lock['content-hash']; } // BC support for old lock files without content-hash if (!empty($lock['hash'])) { return $this->hash === $lock['hash']; } // should not be reached unless the lock file is corrupted, so assume it's out of date return \false; } /** * Searches and returns an array of locked packages, retrieved from registered repositories. * * @param bool $withDevReqs true to retrieve the locked dev packages * @throws \RuntimeException */ public function getLockedRepository(bool $withDevReqs = \false) : LockArrayRepository { $lockData = $this->getLockData(); $packages = new LockArrayRepository(); $lockedPackages = $lockData['packages']; if ($withDevReqs) { if (isset($lockData['packages-dev'])) { $lockedPackages = \array_merge($lockedPackages, $lockData['packages-dev']); } else { throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.'); } } if (empty($lockedPackages)) { return $packages; } if (isset($lockedPackages[0]['name'])) { $packageByName = []; foreach ($lockedPackages as $info) { $package = $this->loader->load($info); $packages->addPackage($package); $packageByName[$package->getName()] = $package; if ($package instanceof \Composer\Package\AliasPackage) { $packageByName[$package->getAliasOf()->getName()] = $package->getAliasOf(); } } if (isset($lockData['aliases'])) { foreach ($lockData['aliases'] as $alias) { if (isset($packageByName[$alias['package']])) { $aliasPkg = new \Composer\Package\CompleteAliasPackage($packageByName[$alias['package']], $alias['alias_normalized'], $alias['alias']); $aliasPkg->setRootPackageAlias(\true); $packages->addPackage($aliasPkg); } } } return $packages; } throw new \RuntimeException('Your composer.lock is invalid. Run "composer update" to generate a new one.'); } /** * @return string[] Names of dependencies installed through require-dev */ public function getDevPackageNames() : array { $names = []; $lockData = $this->getLockData(); if (isset($lockData['packages-dev'])) { foreach ($lockData['packages-dev'] as $package) { $names[] = \strtolower($package['name']); } } return $names; } /** * Returns the platform requirements stored in the lock file * * @param bool $withDevReqs if true, the platform requirements from the require-dev block are also returned * @return \Composer\Package\Link[] */ public function getPlatformRequirements(bool $withDevReqs = \false) : array { $lockData = $this->getLockData(); $requirements = []; if (!empty($lockData['platform'])) { $requirements = $this->loader->parseLinks('__root__', '1.0.0', \Composer\Package\Link::TYPE_REQUIRE, $lockData['platform'] ?? []); } if ($withDevReqs && !empty($lockData['platform-dev'])) { $devRequirements = $this->loader->parseLinks('__root__', '1.0.0', \Composer\Package\Link::TYPE_REQUIRE, $lockData['platform-dev'] ?? []); $requirements = \array_merge($requirements, $devRequirements); } return $requirements; } public function getMinimumStability() : string { $lockData = $this->getLockData(); return $lockData['minimum-stability'] ?? 'stable'; } /** * @return array */ public function getStabilityFlags() : array { $lockData = $this->getLockData(); return $lockData['stability-flags'] ?? []; } public function getPreferStable() : ?bool { $lockData = $this->getLockData(); // return null if not set to allow caller logic to choose the // right behavior since old lock files have no prefer-stable return $lockData['prefer-stable'] ?? null; } public function getPreferLowest() : ?bool { $lockData = $this->getLockData(); // return null if not set to allow caller logic to choose the // right behavior since old lock files have no prefer-lowest return $lockData['prefer-lowest'] ?? null; } /** * @return array */ public function getPlatformOverrides() : array { $lockData = $this->getLockData(); return $lockData['platform-overrides'] ?? []; } /** * @return string[][] * * @phpstan-return list */ public function getAliases() : array { $lockData = $this->getLockData(); return $lockData['aliases'] ?? []; } /** * @return string */ public function getPluginApi() { $lockData = $this->getLockData(); return $lockData['plugin-api-version'] ?? '1.1.0'; } /** * @return array */ public function getLockData() : array { if (null !== $this->lockDataCache) { return $this->lockDataCache; } if (!$this->lockFile->exists()) { throw new \LogicException('No lockfile found. Unable to read locked packages'); } return $this->lockDataCache = $this->lockFile->read(); } /** * Locks provided data into lockfile. * * @param PackageInterface[] $packages array of packages * @param PackageInterface[]|null $devPackages array of dev packages or null if installed without --dev * @param array $platformReqs array of package name => constraint for required platform packages * @param array $platformDevReqs array of package name => constraint for dev-required platform packages * @param string[][] $aliases array of aliases * @param array $stabilityFlags * @param array $platformOverrides * @param bool $write Whether to actually write data to disk, useful in tests and for --dry-run * * @phpstan-param list $aliases */ public function setLockData(array $packages, ?array $devPackages, array $platformReqs, array $platformDevReqs, array $aliases, string $minimumStability, array $stabilityFlags, bool $preferStable, bool $preferLowest, array $platformOverrides, bool $write = \true) : bool { // keep old default branch names normalized to DEFAULT_BRANCH_ALIAS for BC as that is how Composer 1 outputs the lock file // when loading the lock file the version is anyway ignored in Composer 2, so it has no adverse effect $aliases = \array_map(static function ($alias) : array { if (\in_array($alias['version'], ['dev-master', 'dev-trunk', 'dev-default'], \true)) { $alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS; } return $alias; }, $aliases); $lock = ['_readme' => ['This file locks the dependencies of your project to a known state', 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', 'This file is @gener' . 'ated automatically'], 'content-hash' => $this->contentHash, 'packages' => null, 'packages-dev' => null, 'aliases' => $aliases, 'minimum-stability' => $minimumStability, 'stability-flags' => $stabilityFlags, 'prefer-stable' => $preferStable, 'prefer-lowest' => $preferLowest]; $lock['packages'] = $this->lockPackages($packages); if (null !== $devPackages) { $lock['packages-dev'] = $this->lockPackages($devPackages); } $lock['platform'] = $platformReqs; $lock['platform-dev'] = $platformDevReqs; if (\count($platformOverrides) > 0) { $lock['platform-overrides'] = $platformOverrides; } $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION; try { $isLocked = $this->isLocked(); } catch (ParsingException $e) { $isLocked = \false; } if (!$isLocked || $lock !== $this->getLockData()) { if ($write) { $this->lockFile->write($lock); $this->lockDataCache = null; $this->virtualFileWritten = \false; } else { $this->virtualFileWritten = \true; $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock)); } return \true; } return \false; } /** * @param PackageInterface[] $packages * * @return mixed[][] * * @phpstan-return list> */ private function lockPackages(array $packages) : array { $locked = []; foreach ($packages as $package) { if ($package instanceof \Composer\Package\AliasPackage) { continue; } $name = $package->getPrettyName(); $version = $package->getPrettyVersion(); if (!$name || !$version) { throw new \LogicException(\sprintf('Package "%s" has no version or name and can not be locked', $package)); } $spec = $this->dumper->dump($package); unset($spec['version_normalized']); // always move time to the end of the package definition $time = $spec['time'] ?? null; unset($spec['time']); if ($package->isDev() && $package->getInstallationSource() === 'source') { // use the exact commit time of the current reference if it's a dev package $time = $this->getPackageTime($package) ?: $time; } if (null !== $time) { $spec['time'] = $time; } unset($spec['installation-source']); $locked[] = $spec; } \usort($locked, static function ($a, $b) { $comparison = \strcmp($a['name'], $b['name']); if (0 !== $comparison) { return $comparison; } // If it is the same package, compare the versions to make the order deterministic return \strcmp($a['version'], $b['version']); }); return $locked; } /** * Returns the packages's datetime for its source reference. * * @param PackageInterface $package The package to scan. * @return string|null The formatted datetime or null if none was found. */ private function getPackageTime(\Composer\Package\PackageInterface $package) : ?string { if (!\function_exists('proc_open')) { return null; } $path = $this->installationManager->getInstallPath($package); if ($path === null) { return null; } $path = \realpath($path); $sourceType = $package->getSourceType(); $datetime = null; if ($path && \in_array($sourceType, ['git', 'hg'])) { $sourceRef = $package->getSourceReference() ?: $package->getDistReference(); switch ($sourceType) { case 'git': GitUtil::cleanEnv(); if (0 === $this->process->execute('git log -n1 --pretty=%ct ' . ProcessExecutor::escape($sourceRef) . GitUtil::getNoShowSignatureFlag($this->process), $output, $path) && Preg::isMatch('{^\\s*\\d+\\s*$}', $output)) { $datetime = new \DateTime('@' . \trim($output), new \DateTimeZone('UTC')); } break; case 'hg': if (0 === $this->process->execute('hg log --template "{date|hgdate}" -r ' . ProcessExecutor::escape($sourceRef), $output, $path) && Preg::isMatch('{^\\s*(\\d+)\\s*}', $output, $match)) { $datetime = new \DateTime('@' . $match[1], new \DateTimeZone('UTC')); } break; } } return $datetime ? $datetime->format(\DATE_RFC3339) : null; } /** * @return array */ public function getMissingRequirementInfo(\Composer\Package\RootPackageInterface $package, bool $includeDev) : array { $missingRequirementInfo = []; $missingRequirements = \false; $sets = [['repo' => $this->getLockedRepository(\false), 'method' => 'getRequires', 'description' => 'Required']]; if ($includeDev === \true) { $sets[] = ['repo' => $this->getLockedRepository(\true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)']; } $rootRepo = new RootPackageRepository(clone $package); foreach ($sets as $set) { $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]); foreach (\call_user_func([$package, $set['method']]) as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { continue; } if ($link->getPrettyConstraint() === 'self.version') { continue; } if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) { $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget()); if ($results !== []) { $provider = \reset($results); $description = $provider->getPrettyVersion(); if ($provider->getName() !== $link->getTarget()) { foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) { foreach (\call_user_func([$provider, $method]) as $providerLink) { if ($providerLink->getTarget() === $link->getTarget()) { $description = \sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName() . ' ' . $provider->getPrettyVersion()); break 2; } } } } $missingRequirementInfo[] = '- ' . $set['description'] . ' package "' . $link->getTarget() . '" is in the lock file as "' . $description . '" but that does not satisfy your constraint "' . $link->getPrettyConstraint() . '".'; } else { $missingRequirementInfo[] = '- ' . $set['description'] . ' package "' . $link->getTarget() . '" is not present in the lock file.'; } $missingRequirements = \true; } } } if ($missingRequirements) { $missingRequirementInfo[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.'; $missingRequirementInfo[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md'; $missingRequirementInfo[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r'; } return $missingRequirementInfo; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Repository\RepositoryInterface; /** * Defines the essential information a package has that is used during solving/installation * * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. * * @author Jordi Boggiano * * @phpstan-type AutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list, exclude-from-classmap?: list} * @phpstan-type DevAutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list} */ interface PackageInterface { public const DISPLAY_SOURCE_REF_IF_DEV = 0; public const DISPLAY_SOURCE_REF = 1; public const DISPLAY_DIST_REF = 2; /** * Returns the package's name without version info, thus not a unique identifier * * @return string package name */ public function getName() : string; /** * Returns the package's pretty (i.e. with proper case) name * * @return string package name */ public function getPrettyName() : string; /** * Returns a set of names that could refer to this package * * No version or release type information should be included in any of the * names. Provided or replaced package names need to be returned as well. * * @param bool $provides Whether provided names should be included * * @return string[] An array of strings referring to this package */ public function getNames(bool $provides = \true) : array; /** * Allows the solver to set an id for this package to refer to it. */ public function setId(int $id) : void; /** * Retrieves the package's id set through setId * * @return int The previously set package id */ public function getId() : int; /** * Returns whether the package is a development virtual package or a concrete one */ public function isDev() : bool; /** * Returns the package type, e.g. library * * @return string The package type */ public function getType() : string; /** * Returns the package targetDir property * * @return ?string The package targetDir */ public function getTargetDir() : ?string; /** * Returns the package extra data * * @return mixed[] The package extra data */ public function getExtra() : array; /** * Sets source from which this package was installed (source/dist). * * @param ?string $type source/dist * @phpstan-param 'source'|'dist'|null $type */ public function setInstallationSource(?string $type) : void; /** * Returns source from which this package was installed (source/dist). * * @return ?string source/dist * @phpstan-return 'source'|'dist'|null */ public function getInstallationSource() : ?string; /** * Returns the repository type of this package, e.g. git, svn * * @return ?string The repository type */ public function getSourceType() : ?string; /** * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git * * @return ?string The repository url */ public function getSourceUrl() : ?string; /** * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git * * @return list */ public function getSourceUrls() : array; /** * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git * * @return ?string The repository reference */ public function getSourceReference() : ?string; /** * Returns the source mirrors of this package * * @return ?list */ public function getSourceMirrors() : ?array; /** * @param null|list $mirrors */ public function setSourceMirrors(?array $mirrors) : void; /** * Returns the type of the distribution archive of this version, e.g. zip, tarball * * @return ?string The repository type */ public function getDistType() : ?string; /** * Returns the url of the distribution archive of this version * * @return ?non-empty-string */ public function getDistUrl() : ?string; /** * Returns the urls of the distribution archive of this version, including mirrors * * @return non-empty-string[] */ public function getDistUrls() : array; /** * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git * * @return ?string */ public function getDistReference() : ?string; /** * Returns the sha1 checksum for the distribution archive of this version * * @return ?string */ public function getDistSha1Checksum() : ?string; /** * Returns the dist mirrors of this package * * @return ?list */ public function getDistMirrors() : ?array; /** * @param null|list $mirrors */ public function setDistMirrors(?array $mirrors) : void; /** * Returns the version of this package * * @return string version */ public function getVersion() : string; /** * Returns the pretty (i.e. non-normalized) version string of this package * * @return string version */ public function getPrettyVersion() : string; /** * Returns the pretty version string plus a git or hg commit hash of this package * * @see getPrettyVersion * * @param bool $truncate If the source reference is a sha1 hash, truncate it * @param int $displayMode One of the DISPLAY_ constants on this interface determining display of references * @return string version * * @phpstan-param self::DISPLAY_SOURCE_REF_IF_DEV|self::DISPLAY_SOURCE_REF|self::DISPLAY_DIST_REF $displayMode */ public function getFullPrettyVersion(bool $truncate = \true, int $displayMode = self::DISPLAY_SOURCE_REF_IF_DEV) : string; /** * Returns the release date of the package * * @return ?\DateTimeInterface */ public function getReleaseDate() : ?\DateTimeInterface; /** * Returns the stability of this package: one of (dev, alpha, beta, RC, stable) * * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' */ public function getStability() : string; /** * Returns a set of links to packages which need to be installed before * this package can be installed * * @return array A map of package links defining required packages, indexed by the require package's name */ public function getRequires() : array; /** * Returns a set of links to packages which must not be installed at the * same time as this package * * @return Link[] An array of package links defining conflicting packages */ public function getConflicts() : array; /** * Returns a set of links to virtual packages that are provided through * this package * * @return Link[] An array of package links defining provided packages */ public function getProvides() : array; /** * Returns a set of links to packages which can alternatively be * satisfied by installing this package * * @return Link[] An array of package links defining replaced packages */ public function getReplaces() : array; /** * Returns a set of links to packages which are required to develop * this package. These are installed if in dev mode. * * @return array A map of package links defining packages required for development, indexed by the require package's name */ public function getDevRequires() : array; /** * Returns a set of package names and reasons why they are useful in * combination with this package. * * @return array An array of package suggestions with descriptions * @phpstan-return array */ public function getSuggests() : array; /** * Returns an associative array of autoloading rules * * {"": {""}} * * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to * directories for autoloading using the type specified. * * @return array Mapping of autoloading rules * @phpstan-return AutoloadRules */ public function getAutoload() : array; /** * Returns an associative array of dev autoloading rules * * {"": {""}} * * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to * directories for autoloading using the type specified. * * @return array Mapping of dev autoloading rules * @phpstan-return DevAutoloadRules */ public function getDevAutoload() : array; /** * Returns a list of directories which should get added to PHP's * include path. * * @return string[] */ public function getIncludePaths() : array; /** * Stores a reference to the repository that owns the package */ public function setRepository(RepositoryInterface $repository) : void; /** * Returns a reference to the repository that owns the package * * @return ?RepositoryInterface */ public function getRepository() : ?RepositoryInterface; /** * Returns the package binaries * * @return string[] */ public function getBinaries() : array; /** * Returns package unique name, constructed from name and version. */ public function getUniqueName() : string; /** * Returns the package notification url * * @return ?string */ public function getNotificationUrl() : ?string; /** * Converts the package into a readable and unique string */ public function __toString() : string; /** * Converts the package into a pretty readable string */ public function getPrettyString() : string; public function isDefaultBranch() : bool; /** * Returns a list of options to download package dist files * * @return mixed[] */ public function getTransportOptions() : array; /** * Configures the list of options to download package dist files * * @param mixed[] $options */ public function setTransportOptions(array $options) : void; public function setSourceReference(?string $reference) : void; public function setDistUrl(?string $url) : void; public function setDistType(?string $type) : void; public function setDistReference(?string $reference) : void; /** * Set dist and source references and update dist URL for ones that contain a reference */ public function setSourceDistReferences(string $reference) : void; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Package\PackageInterface; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; use Composer\Pcre\Preg; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Intervals; use Composer\Util\Platform; /** * @author Jordi Boggiano * @internal */ class VersionBumper { /** * Given a constraint, this returns a new constraint with * the lower bound bumped to match the given package's version. * * For example: * * ^1.0 + 1.2.1 -> ^1.2.1 * * ^1.2 + 1.2.0 -> ^1.2 * * ^1.2.0 + 1.3.0 -> ^1.3.0 * * ^1.2 || ^2.3 + 1.3.0 -> ^1.3 || ^2.3 * * ^1.2 || ^2.3 + 2.4.0 -> ^1.2 || ^2.4 * * ^3@dev + 3.2.99999-dev -> ^3.2@dev * * ~2 + 2.0-beta.1 -> ~2 * * dev-master + dev-master -> dev-master * * * + 1.2.3 -> >=1.2.3 */ public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package) : string { $parser = new \Composer\Package\Version\VersionParser(); $prettyConstraint = $constraint->getPrettyString(); if (\str_starts_with($constraint->getPrettyString(), 'dev-')) { return $prettyConstraint; } $version = $package->getVersion(); if (\str_starts_with($package->getVersion(), 'dev-')) { $loader = new ArrayLoader($parser); $dumper = new ArrayDumper(); $extra = $loader->getBranchAlias($dumper->dump($package)); // dev packages without branch alias cannot be processed if (null === $extra || $extra === \Composer\Package\Version\VersionParser::DEFAULT_BRANCH_ALIAS) { return $prettyConstraint; } $version = $extra; } $intervals = Intervals::get($constraint); // complex constraints with branch names are not bumped if (\count($intervals['branches']['names']) > 0) { return $prettyConstraint; } $major = Preg::replace('{^(\\d+).*}', '$1', $version); $versionWithoutSuffix = Preg::replace('{(?:\\.(?:0|9999999))+(-dev)?$}', '', $version); $newPrettyConstraint = '^' . $versionWithoutSuffix; // not a simple stable version, abort if (!Preg::isMatch('{^\\^\\d+(\\.\\d+)*$}', $newPrettyConstraint)) { return $prettyConstraint; } $pattern = '{ (?<=,|\\ |\\||^) # leading separator (?P \\^v?' . $major . '(?:\\.\\d+)* # e.g. ^2.anything | ~v?' . $major . '(?:\\.\\d+){0,2} # e.g. ~2 or ~2.2 or ~2.2.2 but no more | v?' . $major . '(?:\\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc | >=v?\\d(?:\\.\\d+)* # e.g. >=2 or >=1.2 etc | \\* # full wildcard ) (?=,|$|\\ |\\||@) # trailing separator }x'; if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) { $modified = $prettyConstraint; foreach (\array_reverse($matches['constraint']) as $match) { \assert(\is_string($match[0])); $suffix = ''; if (\substr_count($match[0], '.') === 2 && \substr_count($versionWithoutSuffix, '.') === 1) { $suffix = '.0'; } if (\str_starts_with($match[0], '~') && \substr_count($match[0], '.') === 2) { $replacement = '~' . $versionWithoutSuffix . $suffix; } elseif ($match[0] === '*' || \str_starts_with($match[0], '>=')) { $replacement = '>=' . $versionWithoutSuffix . $suffix; } else { $replacement = $newPrettyConstraint . $suffix; } $modified = \substr_replace($modified, $replacement, $match[1], Platform::strlen($match[0])); } // if it is strictly equal to the previous one then no need to change anything $newConstraint = $parser->parseConstraints($modified); if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) { return $prettyConstraint; } return $modified; } return $prettyConstraint; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Package\BasePackage; /** * @author Jordi Boggiano */ class StabilityFilter { /** * Checks if any of the provided package names in the given stability match the configured acceptable stability and flags * * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value * @phpstan-param array $acceptableStabilities * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @phpstan-param array $stabilityFlags * @param string[] $names The package name(s) to check for stability flags * @param string $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' * @return bool true if any package name is acceptable */ public static function isPackageAcceptable(array $acceptableStabilities, array $stabilityFlags, array $names, string $stability) : bool { foreach ($names as $name) { // allow if package matches the package-specific stability flag if (isset($stabilityFlags[$name])) { if (BasePackage::$stabilities[$stability] <= $stabilityFlags[$name]) { return \true; } } elseif (isset($acceptableStabilities[$stability])) { // allow if package matches the global stability requirement and has no exception return \true; } } return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Config; use Composer\Pcre\Preg; use Composer\Repository\Vcs\HgDriver; use Composer\IO\NullIO; use Composer\Semver\VersionParser as SemverVersionParser; use Composer\Util\Git as GitUtil; use Composer\Util\HttpDownloader; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Svn as SvnUtil; use React\Promise\CancellablePromiseInterface; use _ContaoManager\Symfony\Component\Process\Process; /** * Try to guess the current version number based on different VCS configuration. * * @author Jordi Boggiano * @author Samuel Roze * * @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null}|array{version: string, commit: string|null, pretty_version: string|null, feature_version: string|null, feature_pretty_version: string|null} */ class VersionGuesser { /** * @var Config */ private $config; /** * @var ProcessExecutor */ private $process; /** * @var SemverVersionParser */ private $versionParser; public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser) { $this->config = $config; $this->process = $process; $this->versionParser = $versionParser; } /** * @param array $packageConfig * @param string $path Path to guess into * * @phpstan-return Version|null */ public function guessVersion(array $packageConfig, string $path) : ?array { if (!\function_exists('proc_open')) { return null; } // bypass version guessing in bash completions as it takes time to create // new processes and the root version is usually not that important if (Platform::isInputCompletionProcess()) { return null; } $versionData = $this->guessGitVersion($packageConfig, $path); if (null !== $versionData['version']) { return $this->postprocess($versionData); } $versionData = $this->guessHgVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $this->postprocess($versionData); } $versionData = $this->guessFossilVersion($path); if (null !== $versionData['version']) { return $this->postprocess($versionData); } $versionData = $this->guessSvnVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $this->postprocess($versionData); } return null; } /** * @phpstan-param Version $versionData * * @phpstan-return Version */ private function postprocess(array $versionData) : array { if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) { unset($versionData['feature_version'], $versionData['feature_pretty_version']); } if ('-dev' === \substr($versionData['version'], -4) && Preg::isMatch('{\\.9{7}}', $versionData['version'])) { $versionData['pretty_version'] = Preg::replace('{(\\.9{7})+}', '.x', $versionData['version']); } if (!empty($versionData['feature_version']) && '-dev' === \substr($versionData['feature_version'], -4) && Preg::isMatch('{\\.9{7}}', $versionData['feature_version'])) { $versionData['feature_pretty_version'] = Preg::replace('{(\\.9{7})+}', '.x', $versionData['feature_version']); } return $versionData; } /** * @param array $packageConfig * * @return array{version: string|null, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null} */ private function guessGitVersion(array $packageConfig, string $path) : array { GitUtil::cleanEnv(); $commit = null; $version = null; $prettyVersion = null; $featureVersion = null; $featurePrettyVersion = null; $isDetached = \false; // try to fetch current version from git branch if (0 === $this->process->execute(['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], $output, $path)) { $branches = []; $isFeatureBranch = \false; // find current branch and collect all branch names foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('{^(?:\\* ) *(\\(no branch\\)|\\(detached from \\S+\\)|\\(HEAD detached at \\S+\\)|\\S+) *([a-f0-9]+) .*$}', $branch, $match)) { if ($match[1] === '(no branch)' || \strpos($match[1], '(detached ') === 0 || \strpos($match[1], '(HEAD detached at') === 0) { $version = 'dev-' . $match[2]; $prettyVersion = $version; $isFeatureBranch = \true; $isDetached = \true; } else { $version = $this->versionParser->normalizeBranch($match[1]); $prettyVersion = 'dev-' . $match[1]; $isFeatureBranch = $this->isFeatureBranch($packageConfig, $match[1]); } $commit = $match[2]; } if ($branch && !Preg::isMatchStrictGroups('{^ *.+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\\* )? *((?:remotes/(?:origin|upstream)/)?[^\\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) { $branches[] = $match[1]; } } } if ($isFeatureBranch) { $featureVersion = $version; $featurePrettyVersion = $prettyVersion; // try to find the best (nearest) version branch to assume this feature's version $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path); $version = $result['version']; $prettyVersion = $result['pretty_version']; } } if (!$version || $isDetached) { $result = $this->versionFromGitTags($path); if ($result) { $version = $result['version']; $prettyVersion = $result['pretty_version']; $featureVersion = null; $featurePrettyVersion = null; } } if (null === $commit) { $command = 'git log --pretty="%H" -n1 HEAD' . GitUtil::getNoShowSignatureFlag($this->process); if (0 === $this->process->execute($command, $output, $path)) { $commit = \trim($output) ?: null; } } if ($featureVersion) { return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion]; } return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion]; } /** * @return array{version: string, pretty_version: string}|null */ private function versionFromGitTags(string $path) : ?array { // try to fetch current version from git tags if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) { try { $version = $this->versionParser->normalize(\trim($output)); return ['version' => $version, 'pretty_version' => \trim($output)]; } catch (\Exception $e) { } } return null; } /** * @param array $packageConfig * * @return array{version: string|null, commit: ''|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}|null */ private function guessHgVersion(array $packageConfig, string $path) : ?array { // try to fetch current version from hg branch if (0 === $this->process->execute('hg branch', $output, $path)) { $branch = \trim($output); $version = $this->versionParser->normalizeBranch($branch); $isFeatureBranch = 0 === \strpos($version, 'dev-'); if (\Composer\Package\Version\VersionParser::DEFAULT_BRANCH_ALIAS === $version) { return ['version' => $version, 'commit' => null, 'pretty_version' => 'dev-' . $branch]; } if (!$isFeatureBranch) { return ['version' => $version, 'commit' => null, 'pretty_version' => $version]; } // re-use the HgDriver to fetch branches (this properly includes bookmarks) $io = new NullIO(); $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process); $branches = \array_map('strval', \array_keys($driver->getBranches())); // try to find the best (nearest) version branch to assume this feature's version $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path); $result['commit'] = ''; $result['feature_version'] = $version; $result['feature_pretty_version'] = $version; return $result; } return null; } /** * @param array $packageConfig * @param string[] $branches * * @phpstan-param non-empty-string $scmCmdline * * @return array{version: string|null, pretty_version: string|null} */ private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, string $scmCmdline, string $path) : array { $prettyVersion = $version; // ignore feature branches if they have no branch-alias or self.version is used // and find the branch they came from to use as a version instead if (!isset($packageConfig['extra']['branch-alias'][$version]) || \strpos(\json_encode($packageConfig), '"self.version"')) { $branch = Preg::replace('{^dev-}', '', $version); $length = \PHP_INT_MAX; // return directly, if branch is configured to be non-feature branch if (!$this->isFeatureBranch($packageConfig, $branch)) { return ['version' => $version, 'pretty_version' => $prettyVersion]; } // sort local branches first then remote ones // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, main is picked // and sort using natural sort so that 1.10 will appear before 1.9 \usort($branches, static function ($a, $b) : int { $aRemote = 0 === \strpos($a, 'remotes/'); $bRemote = 0 === \strpos($b, 'remotes/'); if ($aRemote !== $bRemote) { return $aRemote ? 1 : -1; } return \strnatcasecmp($b, $a); }); $promises = []; $this->process->setMaxJobs(30); try { foreach ($branches as $candidate) { $candidateVersion = Preg::replace('{^remotes/\\S+/}', '', $candidate); // do not compare against itself or other feature branches if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) { continue; } $cmdLine = \str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $scmCmdline); $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use(&$length, &$version, &$prettyVersion, $candidateVersion, &$promises) : void { if (!$process->isSuccessful()) { return; } $output = $process->getOutput(); if (\strlen($output) < $length) { $length = \strlen($output); $version = $this->versionParser->normalizeBranch($candidateVersion); $prettyVersion = 'dev-' . $candidateVersion; if ($length === 0) { foreach ($promises as $promise) { // to support react/promise 2.x we wrap the promise in a resolve() call for safety \React\Promise\resolve($promise)->cancel(); } } } }); } $this->process->wait(); } finally { $this->process->resetMaxJobs(); } } return ['version' => $version, 'pretty_version' => $prettyVersion]; } /** * @param array $packageConfig */ private function isFeatureBranch(array $packageConfig, ?string $branchName) : bool { $nonFeatureBranches = ''; if (!empty($packageConfig['non-feature-branches'])) { $nonFeatureBranches = \implode('|', $packageConfig['non-feature-branches']); } return !Preg::isMatch('{^(' . $nonFeatureBranches . '|master|main|latest|next|current|support|tip|trunk|default|develop|\\d+\\..+)$}', $branchName, $match); } /** * @return array{version: string|null, commit: '', pretty_version: string|null} */ private function guessFossilVersion(string $path) : array { $version = null; $prettyVersion = null; // try to fetch current version from fossil if (0 === $this->process->execute('fossil branch list', $output, $path)) { $branch = \trim($output); $version = $this->versionParser->normalizeBranch($branch); $prettyVersion = 'dev-' . $branch; } // try to fetch current version from fossil tags if (0 === $this->process->execute('fossil tag list', $output, $path)) { try { $version = $this->versionParser->normalize(\trim($output)); $prettyVersion = \trim($output); } catch (\Exception $e) { } } return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; } /** * @param array $packageConfig * * @return array{version: string, commit: '', pretty_version: string}|null */ private function guessSvnVersion(array $packageConfig, string $path) : ?array { SvnUtil::cleanEnv(); // try to fetch current version from svn if (0 === $this->process->execute('svn info --xml', $output, $path)) { $trunkPath = isset($packageConfig['trunk-path']) ? \preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; $branchesPath = isset($packageConfig['branches-path']) ? \preg_quote($packageConfig['branches-path'], '#') : 'branches'; $tagsPath = isset($packageConfig['tags-path']) ? \preg_quote($packageConfig['tags-path'], '#') : 'tags'; $urlPattern = '#.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))#'; if (Preg::isMatch($urlPattern, $output, $matches)) { if (isset($matches[2], $matches[3]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { // we are in a branches path $version = $this->versionParser->normalizeBranch($matches[3]); $prettyVersion = 'dev-' . $matches[3]; return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; } \assert(\is_string($matches[1])); $prettyVersion = \trim($matches[1]); if ($prettyVersion === 'trunk') { $version = 'dev-trunk'; } else { $version = $this->versionParser->normalize($prettyVersion); } return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; } } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\IO\IOInterface; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Composer; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; use Composer\Pcre\Preg; use Composer\Repository\RepositorySet; use Composer\Repository\PlatformRepository; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; /** * Selects the best possible version for a package * * @author Ryan Weaver * @author Jordi Boggiano */ class VersionSelector { /** @var RepositorySet */ private $repositorySet; /** @var array */ private $platformConstraints = []; /** @var VersionParser */ private $parser; /** * @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages */ public function __construct(RepositorySet $repositorySet, ?PlatformRepository $platformRepo = null) { $this->repositorySet = $repositorySet; if ($platformRepo) { foreach ($platformRepo->getPackages() as $package) { $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion()); } } } /** * Given a package name and optional version, returns the latest PackageInterface * that matches. * * @param string $targetPackageVersion * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter * @param IOInterface|null $io If passed, warnings will be output there in case versions cannot be selected due to platform requirements * @param callable(PackageInterface):bool|bool $showWarnings * @return PackageInterface|false */ public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = \true) { if (!isset(BasePackage::$stabilities[$preferredStability])) { // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got ' . $preferredStability); } if (null === $platformRequirementFilter) { $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); } elseif (!$platformRequirementFilter instanceof PlatformRequirementFilterInterface) { \trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', \E_USER_DEPRECATED); $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter); } $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; $candidates = $this->repositorySet->findPackages(\strtolower($packageName), $constraint, $repoSetFlags); $minPriority = BasePackage::$stabilities[$preferredStability]; \usort($candidates, static function (PackageInterface $a, PackageInterface $b) use($minPriority) { $aPriority = $a->getStabilityPriority(); $bPriority = $b->getStabilityPriority(); // A is less stable than our preferred stability, // and B is more stable than A, select B if ($minPriority < $aPriority && $bPriority < $aPriority) { return 1; } // A is less stable than our preferred stability, // and B is less stable than A, select A if ($minPriority < $aPriority && $aPriority < $bPriority) { return -1; } // A is more stable than our preferred stability, // and B is less stable than preferred stability, select A if ($minPriority >= $aPriority && $minPriority < $bPriority) { return -1; } // select highest version of the two return \version_compare($b->getVersion(), $a->getVersion()); }); if (\count($this->platformConstraints) > 0 && !$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) { /** @var array $alreadyWarnedNames */ $alreadyWarnedNames = []; /** @var array $alreadySeenNames */ $alreadySeenNames = []; foreach ($candidates as $pkg) { $reqs = $pkg->getRequires(); foreach ($reqs as $name => $link) { if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) { continue; } if (isset($this->platformConstraints[$name])) { foreach ($this->platformConstraints[$name] as $providedConstraint) { if ($link->getConstraint()->matches($providedConstraint)) { // constraint satisfied, go to next require continue 2; } if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) { $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint()); if ($filteredConstraint->matches($providedConstraint)) { // constraint satisfied with the upper bound ignored, go to next require continue 2; } } } // constraint not satisfied $reason = 'is not satisfied by your platform'; } else { // Package requires a platform package that is unknown on current platform. // It means that current platform cannot validate this constraint and so package is not installable. $reason = 'is missing from your platform'; } $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]); $alreadySeenNames[$pkg->getName()] = \true; if ($io !== null && ($showWarnings === \true || \is_callable($showWarnings) && $showWarnings($pkg))) { $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName()]); $alreadyWarnedNames[$pkg->getName()] = \true; $latest = $isLatestVersion ? "'s latest version" : ''; $io->writeError('Cannot use ' . $pkg->getPrettyName() . $latest . ' ' . $pkg->getPrettyVersion() . ' as it ' . $link->getDescription() . ' ' . $link->getTarget() . ' ' . $link->getPrettyConstraint() . ' which ' . $reason . '.', \true, $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE); } // skip candidate continue 2; } $package = $pkg; break; } } else { $package = \count($candidates) > 0 ? $candidates[0] : null; } if (!isset($package)) { return \false; } // if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias if ($package instanceof AliasPackage && $package->getVersion() === \Composer\Package\Version\VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } return $package; } /** * Given a concrete version, this returns a ^ constraint (when possible) * that should be used, for example, in composer.json. * * For example: * * 1.2.1 -> ^1.2 * * 1.2.1.2 -> ^1.2 * * 1.2 -> ^1.2 * * v3.2.1 -> ^3.2 * * 2.0-beta.1 -> ^2.0@beta * * dev-master -> ^2.1@dev (dev version with alias) * * dev-master -> dev-master (dev versions are untouched) */ public function findRecommendedRequireVersion(PackageInterface $package) : string { // Extensions which are versioned in sync with PHP should rather be required as "*" to simplify // the requires and have only one required version to change when bumping the php requirement if (0 === \strpos($package->getName(), 'ext-')) { $phpVersion = \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION . '.' . \PHP_RELEASE_VERSION; $extVersion = \implode('.', \array_slice(\explode('.', $package->getVersion()), 0, 3)); if ($phpVersion === $extVersion) { return '*'; } } $version = $package->getVersion(); if (!$package->isDev()) { return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability()); } $loader = new ArrayLoader($this->getParser()); $dumper = new ArrayDumper(); $extra = $loader->getBranchAlias($dumper->dump($package)); if ($extra && $extra !== \Composer\Package\Version\VersionParser::DEFAULT_BRANCH_ALIAS) { $extra = Preg::replace('{^(\\d+\\.\\d+\\.\\d+)(\\.9999999)-dev$}', '$1.0', $extra, -1, $count); if ($count > 0) { $extra = \str_replace('.9999999', '.0', $extra); return $this->transformVersion($extra, $extra, 'dev'); } } return $package->getPrettyVersion(); } private function transformVersion(string $version, string $prettyVersion, string $stability) : string { // attempt to transform 2.1.1 to 2.1 // this allows you to upgrade through minor versions $semanticVersionParts = \explode('.', $version); // check to see if we have a semver-looking version if (\count($semanticVersionParts) === 4 && Preg::isMatch('{^\\d+\\D?}', $semanticVersionParts[3])) { // remove the last parts (i.e. the patch version number and any extra) if ($semanticVersionParts[0] === '0') { unset($semanticVersionParts[3]); } else { unset($semanticVersionParts[2], $semanticVersionParts[3]); } $version = \implode('.', $semanticVersionParts); } else { return $prettyVersion; } // append stability flag if not default if ($stability !== 'stable') { $version .= '@' . $stability; } // 2.1 -> ^2.1 return '^' . $version; } private function getParser() : \Composer\Package\Version\VersionParser { if ($this->parser === null) { $this->parser = new \Composer\Package\Version\VersionParser(); } return $this->parser; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Version; use Composer\Pcre\Preg; use Composer\Repository\PlatformRepository; use Composer\Semver\VersionParser as SemverVersionParser; use Composer\Semver\Semver; use Composer\Semver\Constraint\ConstraintInterface; class VersionParser extends SemverVersionParser { public const DEFAULT_BRANCH_ALIAS = '9999999-dev'; /** @var array Constraint parsing cache */ private static $constraints = []; /** * @inheritDoc */ public function parseConstraints($constraints) : ConstraintInterface { if (!isset(self::$constraints[$constraints])) { self::$constraints[$constraints] = parent::parseConstraints($constraints); } return self::$constraints[$constraints]; } /** * Parses an array of strings representing package/version pairs. * * The parsing results in an array of arrays, each of which * contain a 'name' key with value and optionally a 'version' key with value. * * @param string[] $pairs a set of package/version pairs separated by ":", "=" or " " * * @return list */ public function parseNameVersionPairs(array $pairs) : array { $pairs = \array_values($pairs); $result = []; for ($i = 0, $count = \count($pairs); $i < $count; $i++) { $pair = Preg::replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', \trim($pairs[$i])); if (\false === \strpos($pair, ' ') && isset($pairs[$i + 1]) && \false === \strpos($pairs[$i + 1], '/') && !Preg::isMatch('{(?<=[a-z0-9_/-])\\*|\\*(?=[a-z0-9_/-])}i', $pairs[$i + 1]) && !PlatformRepository::isPlatformPackage($pairs[$i + 1])) { $pair .= ' ' . $pairs[$i + 1]; $i++; } if (\strpos($pair, ' ')) { [$name, $version] = \explode(' ', $pair, 2); $result[] = ['name' => $name, 'version' => $version]; } else { $result[] = ['name' => $pair]; } } return $result; } public static function isUpgrade(string $normalizedFrom, string $normalizedTo) : bool { if ($normalizedFrom === $normalizedTo) { return \true; } if (\in_array($normalizedFrom, ['dev-master', 'dev-trunk', 'dev-default'], \true)) { $normalizedFrom = \Composer\Package\Version\VersionParser::DEFAULT_BRANCH_ALIAS; } if (\in_array($normalizedTo, ['dev-master', 'dev-trunk', 'dev-default'], \true)) { $normalizedTo = \Composer\Package\Version\VersionParser::DEFAULT_BRANCH_ALIAS; } if (\strpos($normalizedFrom, 'dev-') === 0 || \strpos($normalizedTo, 'dev-') === 0) { return \true; } $sorted = Semver::sort([$normalizedTo, $normalizedFrom]); return $sorted[0] === $normalizedFrom; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package\BasePackage; use Composer\Pcre\Preg; use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; use Composer\Repository\PlatformRepository; use Composer\Semver\Constraint\MatchNoneConstraint; use Composer\Semver\Intervals; use Composer\Spdx\SpdxLicenses; /** * @author Jordi Boggiano */ class ValidatingArrayLoader implements \Composer\Package\Loader\LoaderInterface { public const CHECK_ALL = 3; public const CHECK_UNBOUND_CONSTRAINTS = 1; public const CHECK_STRICT_CONSTRAINTS = 2; /** @var LoaderInterface */ private $loader; /** @var VersionParser */ private $versionParser; /** @var list */ private $errors; /** @var list */ private $warnings; /** @var mixed[] */ private $config; /** @var int One or more of self::CHECK_* constants */ private $flags; /** * @param true $strictName */ public function __construct(\Composer\Package\Loader\LoaderInterface $loader, bool $strictName = \true, ?VersionParser $parser = null, int $flags = 0) { $this->loader = $loader; $this->versionParser = $parser ?? new VersionParser(); $this->flags = $flags; if ($strictName !== \true) { // @phpstan-ignore-line \trigger_error('$strictName must be set to true in ValidatingArrayLoader\'s constructor as of 2.2, and it will be removed in 3.0', \E_USER_DEPRECATED); } } /** * @inheritDoc */ public function load(array $config, string $class = 'Composer\\Package\\CompletePackage') : BasePackage { $this->errors = []; $this->warnings = []; $this->config = $config; $this->validateString('name', \true); if (isset($config['name']) && null !== ($err = self::hasPackageNamingError($config['name']))) { $this->errors[] = 'name : ' . $err; } if (isset($this->config['version'])) { if (!\is_scalar($this->config['version'])) { $this->validateString('version'); } else { if (!\is_string($this->config['version'])) { $this->config['version'] = (string) $this->config['version']; } try { $this->versionParser->normalize($this->config['version']); } catch (\Exception $e) { $this->errors[] = 'version : invalid value (' . $this->config['version'] . '): ' . $e->getMessage(); unset($this->config['version']); } } } if (isset($this->config['config']['platform'])) { foreach ((array) $this->config['config']['platform'] as $key => $platform) { if (\false === $platform) { continue; } if (!\is_string($platform)) { $this->errors[] = 'config.platform.' . $key . ' : invalid value (' . \gettype($platform) . ' ' . \var_export($platform, \true) . '): expected string or false'; continue; } try { $this->versionParser->normalize($platform); } catch (\Exception $e) { $this->errors[] = 'config.platform.' . $key . ' : invalid value (' . $platform . '): ' . $e->getMessage(); } } } $this->validateRegex('type', '[A-Za-z0-9-]+'); $this->validateString('target-dir'); $this->validateArray('extra'); if (isset($this->config['bin'])) { if (\is_string($this->config['bin'])) { $this->validateString('bin'); } else { $this->validateFlatArray('bin'); } } $this->validateArray('scripts'); // TODO validate event names & listener syntax $this->validateString('description'); $this->validateUrl('homepage'); $this->validateFlatArray('keywords', '[\\p{N}\\p{L} ._-]+'); $releaseDate = null; $this->validateString('time'); if (isset($this->config['time'])) { try { $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); } catch (\Exception $e) { $this->errors[] = 'time : invalid value (' . $this->config['time'] . '): ' . $e->getMessage(); unset($this->config['time']); } } // check for license validity on newly updated branches if (isset($this->config['license']) && (null === $releaseDate || $releaseDate->getTimestamp() >= \strtotime('-8days'))) { if (\is_array($this->config['license']) || \is_string($this->config['license'])) { $licenses = (array) $this->config['license']; $licenseValidator = new SpdxLicenses(); foreach ($licenses as $license) { // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer if ('proprietary' === $license) { continue; } $licenseToValidate = \str_replace('proprietary', 'MIT', $license); if (!$licenseValidator->validate($licenseToValidate)) { if ($licenseValidator->validate(\trim($licenseToValidate))) { $this->warnings[] = \sprintf('License %s must not contain extra spaces, make sure to trim it.', \json_encode($license)); } else { $this->warnings[] = \sprintf('License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . \PHP_EOL . 'If the software is closed-source, you may use "proprietary" as license.', \json_encode($license)); } } } } } if ($this->validateArray('authors')) { foreach ($this->config['authors'] as $key => $author) { if (!\is_array($author)) { $this->errors[] = 'authors.' . $key . ' : should be an array, ' . \gettype($author) . ' given'; unset($this->config['authors'][$key]); continue; } foreach (['homepage', 'email', 'name', 'role'] as $authorData) { if (isset($author[$authorData]) && !\is_string($author[$authorData])) { $this->errors[] = 'authors.' . $key . '.' . $authorData . ' : invalid value, must be a string'; unset($this->config['authors'][$key][$authorData]); } } if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) { $this->warnings[] = 'authors.' . $key . '.homepage : invalid value (' . $author['homepage'] . '), must be an http/https URL'; unset($this->config['authors'][$key]['homepage']); } if (isset($author['email']) && \false === \filter_var($author['email'], \FILTER_VALIDATE_EMAIL)) { $this->warnings[] = 'authors.' . $key . '.email : invalid value (' . $author['email'] . '), must be a valid email address'; unset($this->config['authors'][$key]['email']); } if (\count($this->config['authors'][$key]) === 0) { unset($this->config['authors'][$key]); } } if (\count($this->config['authors']) === 0) { unset($this->config['authors']); } } if ($this->validateArray('support') && !empty($this->config['support'])) { foreach (['issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat', 'security'] as $key) { if (isset($this->config['support'][$key]) && !\is_string($this->config['support'][$key])) { $this->errors[] = 'support.' . $key . ' : invalid value, must be a string'; unset($this->config['support'][$key]); } } if (isset($this->config['support']['email']) && !\filter_var($this->config['support']['email'], \FILTER_VALIDATE_EMAIL)) { $this->warnings[] = 'support.email : invalid value (' . $this->config['support']['email'] . '), must be a valid email address'; unset($this->config['support']['email']); } if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], ['irc', 'ircs'])) { $this->warnings[] = 'support.irc : invalid value (' . $this->config['support']['irc'] . '), must be a irc:/// or ircs:// URL'; unset($this->config['support']['irc']); } foreach (['issues', 'forum', 'wiki', 'source', 'docs', 'chat', 'security'] as $key) { if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) { $this->warnings[] = 'support.' . $key . ' : invalid value (' . $this->config['support'][$key] . '), must be an http/https URL'; unset($this->config['support'][$key]); } } if (empty($this->config['support'])) { unset($this->config['support']); } } if ($this->validateArray('funding') && !empty($this->config['funding'])) { foreach ($this->config['funding'] as $key => $fundingOption) { if (!\is_array($fundingOption)) { $this->errors[] = 'funding.' . $key . ' : should be an array, ' . \gettype($fundingOption) . ' given'; unset($this->config['funding'][$key]); continue; } foreach (['type', 'url'] as $fundingData) { if (isset($fundingOption[$fundingData]) && !\is_string($fundingOption[$fundingData])) { $this->errors[] = 'funding.' . $key . '.' . $fundingData . ' : invalid value, must be a string'; unset($this->config['funding'][$key][$fundingData]); } } if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) { $this->warnings[] = 'funding.' . $key . '.url : invalid value (' . $fundingOption['url'] . '), must be an http/https URL'; unset($this->config['funding'][$key]['url']); } if (empty($this->config['funding'][$key])) { unset($this->config['funding'][$key]); } } if (empty($this->config['funding'])) { unset($this->config['funding']); } } $unboundConstraint = new Constraint('=', '10000000-dev'); foreach (\array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if ($this->validateArray($linkType) && isset($this->config[$linkType])) { foreach ($this->config[$linkType] as $package => $constraint) { $package = (string) $package; if (isset($this->config['name']) && 0 === \strcasecmp($package, $this->config['name'])) { $this->errors[] = $linkType . '.' . $package . ' : a package cannot set a ' . $linkType . ' on itself'; unset($this->config[$linkType][$package]); continue; } if ($err = self::hasPackageNamingError($package, \true)) { $this->warnings[] = $linkType . '.' . $err; } elseif (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $package)) { $this->errors[] = $linkType . '.' . $package . ' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; } if (!\is_string($constraint)) { $this->errors[] = $linkType . '.' . $package . ' : invalid value, must be a string containing a version constraint'; unset($this->config[$linkType][$package]); } elseif ('self.version' !== $constraint) { try { $linkConstraint = $this->versionParser->parseConstraints($constraint); } catch (\Exception $e) { $this->errors[] = $linkType . '.' . $package . ' : invalid version constraint (' . $e->getMessage() . ')'; unset($this->config[$linkType][$package]); continue; } // check requires for unbound constraints on non-platform packages if ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS && 'require' === $linkType && $linkConstraint->matches($unboundConstraint) && !PlatformRepository::isPlatformPackage($package)) { $this->warnings[] = $linkType . '.' . $package . ' : unbound version constraints (' . $constraint . ') should be avoided'; } elseif ($this->flags & self::CHECK_STRICT_CONSTRAINTS && 'require' === $linkType && $linkConstraint instanceof Constraint && \in_array($linkConstraint->getOperator(), ['==', '='], \true) && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint)) { $this->warnings[] = $linkType . '.' . $package . ' : exact version constraints (' . $constraint . ') should be avoided if the package follows semantic versioning'; } $compacted = Intervals::compactConstraint($linkConstraint); if ($compacted instanceof MatchNoneConstraint) { $this->warnings[] = $linkType . '.' . $package . ' : this version constraint cannot possibly match anything (' . $constraint . ')'; } } if ($linkType === 'conflict' && isset($this->config['replace']) && ($keys = \array_intersect_key($this->config['replace'], $this->config['conflict']))) { $this->errors[] = $linkType . '.' . $package . ' : you cannot conflict with a package that is also replaced, as replace already creates an implicit conflict rule'; unset($this->config[$linkType][$package]); } } } } if ($this->validateArray('suggest') && isset($this->config['suggest'])) { foreach ($this->config['suggest'] as $package => $description) { if (!\is_string($description)) { $this->errors[] = 'suggest.' . $package . ' : invalid value, must be a string describing why the package is suggested'; unset($this->config['suggest'][$package]); } } } if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) { if (!isset(BasePackage::$stabilities[\strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') { $this->errors[] = 'minimum-stability : invalid value (' . $this->config['minimum-stability'] . '), must be one of ' . \implode(', ', \array_keys(BasePackage::$stabilities)); unset($this->config['minimum-stability']); } } if ($this->validateArray('autoload') && isset($this->config['autoload'])) { $types = ['psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap']; foreach ($this->config['autoload'] as $type => $typeConfig) { if (!\in_array($type, $types)) { $this->errors[] = 'autoload : invalid value (' . $type . '), must be one of ' . \implode(', ', $types); unset($this->config['autoload'][$type]); } if ($type === 'psr-4') { foreach ($typeConfig as $namespace => $dirs) { if ($namespace !== '' && '\\' !== \substr((string) $namespace, -1)) { $this->errors[] = 'autoload.psr-4 : invalid value (' . $namespace . '), namespaces must end with a namespace separator, should be ' . $namespace . '\\\\'; } } } } } if (isset($this->config['autoload']['psr-4']) && isset($this->config['target-dir'])) { $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4'; // Unset the psr-4 setting, since unsetting target-dir might // interfere with other settings. unset($this->config['autoload']['psr-4']); } foreach (['source', 'dist'] as $srcType) { if ($this->validateArray($srcType) && !empty($this->config[$srcType])) { if (!isset($this->config[$srcType]['type'])) { $this->errors[] = $srcType . '.type : must be present'; } if (!isset($this->config[$srcType]['url'])) { $this->errors[] = $srcType . '.url : must be present'; } if ($srcType === 'source' && !isset($this->config[$srcType]['reference'])) { $this->errors[] = $srcType . '.reference : must be present'; } if (isset($this->config[$srcType]['type']) && !\is_string($this->config[$srcType]['type'])) { $this->errors[] = $srcType . '.type : should be a string, ' . \gettype($this->config[$srcType]['type']) . ' given'; } if (isset($this->config[$srcType]['url']) && !\is_string($this->config[$srcType]['url'])) { $this->errors[] = $srcType . '.url : should be a string, ' . \gettype($this->config[$srcType]['url']) . ' given'; } if (isset($this->config[$srcType]['reference']) && !\is_string($this->config[$srcType]['reference']) && !\is_int($this->config[$srcType]['reference'])) { $this->errors[] = $srcType . '.reference : should be a string or int, ' . \gettype($this->config[$srcType]['reference']) . ' given'; } if (isset($this->config[$srcType]['reference']) && Preg::isMatch('{^\\s*-}', (string) $this->config[$srcType]['reference'])) { $this->errors[] = $srcType . '.reference : must not start with a "-", "' . $this->config[$srcType]['reference'] . '" given'; } if (isset($this->config[$srcType]['url']) && Preg::isMatch('{^\\s*-}', (string) $this->config[$srcType]['url'])) { $this->errors[] = $srcType . '.url : must not start with a "-", "' . $this->config[$srcType]['url'] . '" given'; } } } // TODO validate repositories // TODO validate package repositories' packages using this recursively $this->validateFlatArray('include-path'); $this->validateArray('transport-options'); // branch alias validation if (isset($this->config['extra']['branch-alias'])) { if (!\is_array($this->config['extra']['branch-alias'])) { $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases'; } else { foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { if (!\is_string($targetBranch)) { $this->warnings[] = 'extra.branch-alias.' . $sourceBranch . ' : the target branch (' . \json_encode($targetBranch) . ') must be a string, "' . \gettype($targetBranch) . '" received.'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } // ensure it is an alias to a -dev package if ('-dev' !== \substr($targetBranch, -4)) { $this->warnings[] = 'extra.branch-alias.' . $sourceBranch . ' : the target branch (' . $targetBranch . ') must end in -dev'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } // normalize without -dev and ensure it's a numeric branch that is parseable $validatedTargetBranch = $this->versionParser->normalizeBranch(\substr($targetBranch, 0, -4)); if ('-dev' !== \substr($validatedTargetBranch, -4)) { $this->warnings[] = 'extra.branch-alias.' . $sourceBranch . ' : the target branch (' . $targetBranch . ') must be a parseable number like 2.0-dev'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } // If using numeric aliases ensure the alias is a valid subversion if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && \stripos($targetPrefix, $sourcePrefix) !== 0) { $this->warnings[] = 'extra.branch-alias.' . $sourceBranch . ' : the target branch (' . $targetBranch . ') is not a valid numeric alias for this version'; unset($this->config['extra']['branch-alias'][$sourceBranch]); } } } } if ($this->errors) { throw new \Composer\Package\Loader\InvalidPackageException($this->errors, $this->warnings, $config); } $package = $this->loader->load($this->config, $class); $this->config = []; return $package; } /** * @return list */ public function getWarnings() : array { return $this->warnings; } /** * @return list */ public function getErrors() : array { return $this->errors; } public static function hasPackageNamingError(string $name, bool $isLink = \false) : ?string { if (PlatformRepository::isPlatformPackage($name)) { return null; } if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) { return $name . ' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".'; } $reservedNames = ['nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9']; $bits = \explode('/', \strtolower($name)); if (\in_array($bits[0], $reservedNames, \true) || \in_array($bits[1], $reservedNames, \true)) { return $name . ' is reserved, package and vendor names can not match any of: ' . \implode(', ', $reservedNames) . '.'; } if (Preg::isMatch('{\\.json$}', $name)) { return $name . ' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.'; } if (Preg::isMatch('{[A-Z]}', $name)) { if ($isLink) { return $name . ' is invalid, it should not contain uppercase characters. Please use ' . \strtolower($name) . ' instead.'; } $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '1\\3-\\2\\4', $name); $suggestName = \strtolower($suggestName); return $name . ' is invalid, it should not contain uppercase characters. We suggest using ' . $suggestName . ' instead.'; } return null; } /** * @phpstan-param non-empty-string $property * @phpstan-param non-empty-string $regex */ private function validateRegex(string $property, string $regex, bool $mandatory = \false) : bool { if (!$this->validateString($property, $mandatory)) { return \false; } if (!Preg::isMatch('{^' . $regex . '$}u', $this->config[$property])) { $message = $property . ' : invalid value (' . $this->config[$property] . '), must match ' . $regex; if ($mandatory) { $this->errors[] = $message; } else { $this->warnings[] = $message; } unset($this->config[$property]); return \false; } return \true; } /** * @phpstan-param non-empty-string $property */ private function validateString(string $property, bool $mandatory = \false) : bool { if (isset($this->config[$property]) && !\is_string($this->config[$property])) { $this->errors[] = $property . ' : should be a string, ' . \gettype($this->config[$property]) . ' given'; unset($this->config[$property]); return \false; } if (!isset($this->config[$property]) || \trim($this->config[$property]) === '') { if ($mandatory) { $this->errors[] = $property . ' : must be present'; } unset($this->config[$property]); return \false; } return \true; } /** * @phpstan-param non-empty-string $property */ private function validateArray(string $property, bool $mandatory = \false) : bool { if (isset($this->config[$property]) && !\is_array($this->config[$property])) { $this->errors[] = $property . ' : should be an array, ' . \gettype($this->config[$property]) . ' given'; unset($this->config[$property]); return \false; } if (!isset($this->config[$property]) || !\count($this->config[$property])) { if ($mandatory) { $this->errors[] = $property . ' : must be present and contain at least one element'; } unset($this->config[$property]); return \false; } return \true; } /** * @phpstan-param non-empty-string $property * @phpstan-param non-empty-string|null $regex */ private function validateFlatArray(string $property, ?string $regex = null, bool $mandatory = \false) : bool { if (!$this->validateArray($property, $mandatory)) { return \false; } $pass = \true; foreach ($this->config[$property] as $key => $value) { if (!\is_string($value) && !\is_numeric($value)) { $this->errors[] = $property . '.' . $key . ' : must be a string or int, ' . \gettype($value) . ' given'; unset($this->config[$property][$key]); $pass = \false; continue; } if ($regex && !Preg::isMatch('{^' . $regex . '$}u', (string) $value)) { $this->warnings[] = $property . '.' . $key . ' : invalid value (' . $value . '), must match ' . $regex; unset($this->config[$property][$key]); $pass = \false; } } return $pass; } /** * @phpstan-param non-empty-string $property */ private function validateUrl(string $property, bool $mandatory = \false) : bool { if (!$this->validateString($property, $mandatory)) { return \false; } if (!$this->filterUrl($this->config[$property])) { $this->warnings[] = $property . ' : invalid value (' . $this->config[$property] . '), must be an http/https URL'; unset($this->config[$property]); return \false; } return \true; } /** * @param mixed $value * @param string[] $schemes */ private function filterUrl($value, array $schemes = ['http', 'https']) : bool { if ($value === '') { return \true; } $bits = \parse_url($value); if (empty($bits['scheme']) || empty($bits['host'])) { return \false; } if (!\in_array($bits['scheme'], $schemes, \true)) { return \false; } return \true; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; /** * @author Jordi Boggiano */ class InvalidPackageException extends \Exception { /** @var list */ private $errors; /** @var list */ private $warnings; /** @var mixed[] package config */ private $data; /** * @param list $errors * @param list $warnings * @param mixed[] $data */ public function __construct(array $errors, array $warnings, array $data) { $this->errors = $errors; $this->warnings = $warnings; $this->data = $data; parent::__construct("Invalid package information: \n" . \implode("\n", \array_merge($errors, $warnings))); } /** * @return mixed[] */ public function getData() : array { return $this->data; } /** * @return list */ public function getErrors() : array { return $this->errors; } /** * @return list */ public function getWarnings() : array { return $this->warnings; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package\BasePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\CompletePackage; use Composer\Package\RootPackage; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\Link; use Composer\Package\RootAliasPackage; use Composer\Package\Version\VersionParser; use Composer\Pcre\Preg; /** * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class ArrayLoader implements \Composer\Package\Loader\LoaderInterface { /** @var VersionParser */ protected $versionParser; /** @var bool */ protected $loadOptions; public function __construct(?VersionParser $parser = null, bool $loadOptions = \false) { if (!$parser) { $parser = new VersionParser(); } $this->versionParser = $parser; $this->loadOptions = $loadOptions; } /** * @inheritDoc */ public function load(array $config, string $class = 'Composer\\Package\\CompletePackage') : BasePackage { if ($class !== 'Composer\\Package\\CompletePackage' && $class !== 'Composer\\Package\\RootPackage') { \trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', \E_USER_DEPRECATED); } $package = $this->createObject($config, $class); foreach (BasePackage::$supportedLinkTypes as $type => $opts) { if (!isset($config[$type]) || !\is_array($config[$type])) { continue; } $method = 'set' . \ucfirst($opts['method']); $package->{$method}($this->parseLinks($package->getName(), $package->getPrettyVersion(), $opts['method'], $config[$type])); } $package = $this->configureObject($package, $config); return $package; } /** * @param array> $versions * * @return list */ public function loadPackages(array $versions) : array { $packages = []; $linkCache = []; foreach ($versions as $version) { $package = $this->createObject($version, 'Composer\\Package\\CompletePackage'); $this->configureCachedLinks($linkCache, $package, $version); $package = $this->configureObject($package, $version); $packages[] = $package; } return $packages; } /** * @template PackageClass of CompletePackage * * @param mixed[] $config package data * @param string $class FQCN to be instantiated * * @return CompletePackage|RootPackage * * @phpstan-param class-string $class */ private function createObject(array $config, string $class) : CompletePackage { if (!isset($config['name'])) { throw new \UnexpectedValueException('Unknown package has no name defined (' . \json_encode($config) . ').'); } if (!isset($config['version']) || !\is_scalar($config['version'])) { throw new \UnexpectedValueException('Package ' . $config['name'] . ' has no version defined.'); } if (!\is_string($config['version'])) { $config['version'] = (string) $config['version']; } // handle already normalized versions if (isset($config['version_normalized']) && \is_string($config['version_normalized'])) { $version = $config['version_normalized']; // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it if ($version === VersionParser::DEFAULT_BRANCH_ALIAS) { $version = $this->versionParser->normalize($config['version']); } } else { $version = $this->versionParser->normalize($config['version']); } return new $class($config['name'], $version, $config['version']); } /** * @param CompletePackage $package * @param mixed[] $config package data * * @return RootPackage|RootAliasPackage|CompletePackage|CompleteAliasPackage */ private function configureObject(PackageInterface $package, array $config) : BasePackage { if (!$package instanceof CompletePackage) { throw new \LogicException('ArrayLoader expects instances of the Composer\\Package\\CompletePackage class to function correctly'); } $package->setType(isset($config['type']) ? \strtolower($config['type']) : 'library'); if (isset($config['target-dir'])) { $package->setTargetDir($config['target-dir']); } if (isset($config['extra']) && \is_array($config['extra'])) { $package->setExtra($config['extra']); } if (isset($config['bin'])) { if (!\is_array($config['bin'])) { $config['bin'] = [$config['bin']]; } foreach ($config['bin'] as $key => $bin) { $config['bin'][$key] = \ltrim($bin, '/'); } $package->setBinaries($config['bin']); } if (isset($config['installation-source'])) { $package->setInstallationSource($config['installation-source']); } if (isset($config['default-branch']) && $config['default-branch'] === \true) { $package->setIsDefaultBranch(\true); } if (isset($config['source'])) { if (!isset($config['source']['type'], $config['source']['url'], $config['source']['reference'])) { throw new \UnexpectedValueException(\sprintf("Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.", $config['name'], \json_encode($config['source']))); } $package->setSourceType($config['source']['type']); $package->setSourceUrl($config['source']['url']); $package->setSourceReference(isset($config['source']['reference']) ? (string) $config['source']['reference'] : null); if (isset($config['source']['mirrors'])) { $package->setSourceMirrors($config['source']['mirrors']); } } if (isset($config['dist'])) { if (!isset($config['dist']['type'], $config['dist']['url'])) { throw new \UnexpectedValueException(\sprintf("Package %s's dist key should be specified as " . "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.", $config['name'], \json_encode($config['dist']))); } $package->setDistType($config['dist']['type']); $package->setDistUrl($config['dist']['url']); $package->setDistReference(isset($config['dist']['reference']) ? (string) $config['dist']['reference'] : null); $package->setDistSha1Checksum($config['dist']['shasum'] ?? null); if (isset($config['dist']['mirrors'])) { $package->setDistMirrors($config['dist']['mirrors']); } } if (isset($config['suggest']) && \is_array($config['suggest'])) { foreach ($config['suggest'] as $target => $reason) { if ('self.version' === \trim($reason)) { $config['suggest'][$target] = $package->getPrettyVersion(); } } $package->setSuggests($config['suggest']); } if (isset($config['autoload'])) { $package->setAutoload($config['autoload']); } if (isset($config['autoload-dev'])) { $package->setDevAutoload($config['autoload-dev']); } if (isset($config['include-path'])) { $package->setIncludePaths($config['include-path']); } if (!empty($config['time'])) { $time = Preg::isMatch('/^\\d++$/D', $config['time']) ? '@' . $config['time'] : $config['time']; try { $date = new \DateTime($time, new \DateTimeZone('UTC')); $package->setReleaseDate($date); } catch (\Exception $e) { } } if (!empty($config['notification-url'])) { $package->setNotificationUrl($config['notification-url']); } if ($package instanceof CompletePackageInterface) { if (!empty($config['archive']['name'])) { $package->setArchiveName($config['archive']['name']); } if (!empty($config['archive']['exclude'])) { $package->setArchiveExcludes($config['archive']['exclude']); } if (isset($config['scripts']) && \is_array($config['scripts'])) { foreach ($config['scripts'] as $event => $listeners) { $config['scripts'][$event] = (array) $listeners; } foreach (['composer', 'php', 'putenv'] as $reserved) { if (isset($config['scripts'][$reserved])) { \trigger_error('The `' . $reserved . '` script name is reserved for internal use, please avoid defining it', \E_USER_DEPRECATED); } } $package->setScripts($config['scripts']); } if (!empty($config['description']) && \is_string($config['description'])) { $package->setDescription($config['description']); } if (!empty($config['homepage']) && \is_string($config['homepage'])) { $package->setHomepage($config['homepage']); } if (!empty($config['keywords']) && \is_array($config['keywords'])) { $package->setKeywords(\array_map('strval', $config['keywords'])); } if (!empty($config['license'])) { $package->setLicense(\is_array($config['license']) ? $config['license'] : [$config['license']]); } if (!empty($config['authors']) && \is_array($config['authors'])) { $package->setAuthors($config['authors']); } if (isset($config['support']) && \is_array($config['support'])) { $package->setSupport($config['support']); } if (!empty($config['funding']) && \is_array($config['funding'])) { $package->setFunding($config['funding']); } if (isset($config['abandoned'])) { $package->setAbandoned($config['abandoned']); } } if ($this->loadOptions && isset($config['transport-options'])) { $package->setTransportOptions($config['transport-options']); } if ($aliasNormalized = $this->getBranchAlias($config)) { $prettyAlias = Preg::replace('{(\\.9{7})+}', '.x', $aliasNormalized); if ($package instanceof RootPackage) { return new RootAliasPackage($package, $aliasNormalized, $prettyAlias); } return new CompleteAliasPackage($package, $aliasNormalized, $prettyAlias); } return $package; } /** * @param array>>> $linkCache * @param mixed[] $config */ private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config) : void { $name = $package->getName(); $prettyVersion = $package->getPrettyVersion(); foreach (BasePackage::$supportedLinkTypes as $type => $opts) { if (isset($config[$type])) { $method = 'set' . \ucfirst($opts['method']); $links = []; foreach ($config[$type] as $prettyTarget => $constraint) { $target = \strtolower($prettyTarget); // recursive links are not supported if ($target === $name) { continue; } if ($constraint === 'self.version') { $links[$target] = $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint); } else { if (!isset($linkCache[$name][$type][$target][$constraint])) { $linkCache[$name][$type][$target][$constraint] = [$target, $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint)]; } [$target, $link] = $linkCache[$name][$type][$target][$constraint]; $links[$target] = $link; } } $package->{$method}($links); } } } /** * @param string $source source package name * @param string $sourceVersion source package version (pretty version ideally) * @param string $description link description (e.g. requires, replaces, ..) * @param array $links array of package name => constraint mappings * * @return Link[] * * @phpstan-param Link::TYPE_* $description */ public function parseLinks(string $source, string $sourceVersion, string $description, array $links) : array { $res = []; foreach ($links as $target => $constraint) { if (!\is_string($constraint)) { continue; } $target = \strtolower((string) $target); $res[$target] = $this->createLink($source, $sourceVersion, $description, $target, $constraint); } return $res; } /** * @param string $source source package name * @param string $sourceVersion source package version (pretty version ideally) * @param Link::TYPE_* $description link description (e.g. requires, replaces, ..) * @param string $target target package name * @param string $prettyConstraint constraint string */ private function createLink(string $source, string $sourceVersion, string $description, string $target, string $prettyConstraint) : Link { if (!\is_string($prettyConstraint)) { throw new \UnexpectedValueException('Link constraint in ' . $source . ' ' . $description . ' > ' . $target . ' should be a string, got ' . \gettype($prettyConstraint) . ' (' . \var_export($prettyConstraint, \true) . ')'); } if ('self.version' === $prettyConstraint) { $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); } else { $parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint); } return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint); } /** * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists * * @param mixed[] $config the entire package config * * @return string|null normalized version of the branch alias or null if there is none */ public function getBranchAlias(array $config) : ?string { if (!isset($config['version']) || !\is_scalar($config['version'])) { throw new \UnexpectedValueException('no/invalid version defined'); } if (!\is_string($config['version'])) { $config['version'] = (string) $config['version']; } if (\strpos($config['version'], 'dev-') !== 0 && '-dev' !== \substr($config['version'], -4)) { return null; } if (isset($config['extra']['branch-alias']) && \is_array($config['extra']['branch-alias'])) { foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { $sourceBranch = (string) $sourceBranch; // ensure it is an alias to a -dev package if ('-dev' !== \substr($targetBranch, -4)) { continue; } // normalize without -dev and ensure it's a numeric branch that is parseable if ($targetBranch === VersionParser::DEFAULT_BRANCH_ALIAS) { $validatedTargetBranch = VersionParser::DEFAULT_BRANCH_ALIAS; } else { $validatedTargetBranch = $this->versionParser->normalizeBranch(\substr($targetBranch, 0, -4)); } if ('-dev' !== \substr($validatedTargetBranch, -4)) { continue; } // ensure that it is the current branch aliasing itself if (\strtolower($config['version']) !== \strtolower($sourceBranch)) { continue; } // If using numeric aliases ensure the alias is a valid subversion if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && \stripos($targetPrefix, $sourcePrefix) !== 0) { continue; } return $validatedTargetBranch; } } if (isset($config['default-branch']) && $config['default-branch'] === \true && \false === $this->versionParser->parseNumericAliasPrefix(Preg::replace('{^v}', '', $config['version']))) { return VersionParser::DEFAULT_BRANCH_ALIAS; } return null; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package\CompletePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\RootAliasPackage; use Composer\Package\RootPackage; use Composer\Package\BasePackage; /** * Defines a loader that takes an array to create package instances * * @author Jordi Boggiano */ interface LoaderInterface { /** * Converts a package from an array to a real instance * * @param mixed[] $config package data * @param string $class FQCN to be instantiated * * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage * * @phpstan-param class-string $class */ public function load(array $config, string $class = 'Composer\\Package\\CompletePackage') : BasePackage; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Package\BasePackage; use Composer\Config; use Composer\IO\IOInterface; use Composer\Package\RootAliasPackage; use Composer\Pcre\Preg; use Composer\Repository\RepositoryFactory; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Package\RootPackage; use Composer\Repository\RepositoryManager; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** * ArrayLoader built for the sole purpose of loading the root package * * Sets additional defaults and loads repositories * * @author Jordi Boggiano */ class RootPackageLoader extends \Composer\Package\Loader\ArrayLoader { /** * @var RepositoryManager */ private $manager; /** * @var Config */ private $config; /** * @var VersionGuesser */ private $versionGuesser; public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null) { parent::__construct($parser); $this->manager = $manager; $this->config = $config; $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser); } /** * @inheritDoc * * @return RootPackage|RootAliasPackage * * @phpstan-param class-string $class */ public function load(array $config, string $class = 'Composer\\Package\\RootPackage', ?string $cwd = null) : BasePackage { if ($class !== 'Composer\\Package\\RootPackage') { \trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', \E_USER_DEPRECATED); } if (!isset($config['name'])) { $config['name'] = '__root__'; } elseif ($err = \Composer\Package\Loader\ValidatingArrayLoader::hasPackageNamingError($config['name'])) { throw new \RuntimeException('Your package name ' . $err); } $autoVersioned = \false; if (!isset($config['version'])) { $commit = null; // override with env var if available if (Platform::getEnv('COMPOSER_ROOT_VERSION')) { $config['version'] = Platform::getEnv('COMPOSER_ROOT_VERSION'); } else { $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(\true)); if ($versionData) { $config['version'] = $versionData['pretty_version']; $config['version_normalized'] = $versionData['version']; $commit = $versionData['commit']; } } if (!isset($config['version'])) { $config['version'] = '1.0.0'; $autoVersioned = \true; } if ($commit) { $config['source'] = ['type' => '', 'url' => '', 'reference' => $commit]; $config['dist'] = ['type' => '', 'url' => '', 'reference' => $commit]; } } /** @var RootPackage|RootAliasPackage $package */ $package = parent::load($config, $class); if ($package instanceof RootAliasPackage) { $realPackage = $package->getAliasOf(); } else { $realPackage = $package; } if (!$realPackage instanceof RootPackage) { throw new \LogicException('Expecting a Composer\\Package\\RootPackage at this point'); } if ($autoVersioned) { $realPackage->replaceVersion($realPackage->getVersion(), RootPackage::DEFAULT_PRETTY_VERSION); } if (isset($config['minimum-stability'])) { $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); } $aliases = []; $stabilityFlags = []; $references = []; foreach (['require', 'require-dev'] as $linkType) { if (isset($config[$linkType])) { $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; $method = 'get' . \ucfirst($linkInfo['method']); $links = []; foreach ($realPackage->{$method}() as $link) { $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); } $aliases = $this->extractAliases($links, $aliases); $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags); $references = self::extractReferences($links, $references); if (isset($links[$config['name']])) { throw new \RuntimeException(\sprintf('Root package \'%s\' cannot require itself in its composer.json' . \PHP_EOL . 'Did you accidentally name your root package after an external package?', $config['name'])); } } } foreach (\array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if (isset($config[$linkType])) { foreach ($config[$linkType] as $linkName => $constraint) { if ($err = \Composer\Package\Loader\ValidatingArrayLoader::hasPackageNamingError($linkName, \true)) { throw new \RuntimeException($linkType . '.' . $err); } } } } $realPackage->setAliases($aliases); $realPackage->setStabilityFlags($stabilityFlags); $realPackage->setReferences($references); if (isset($config['prefer-stable'])) { $realPackage->setPreferStable((bool) $config['prefer-stable']); } if (isset($config['config'])) { $realPackage->setConfig($config['config']); } $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager); foreach ($repos as $repo) { $this->manager->addRepository($repo); } $realPackage->setRepositories($this->config->getRepositories()); return $package; } /** * @param array $requires * @param list $aliases * * @return list */ private function extractAliases(array $requires, array $aliases) : array { foreach ($requires as $reqName => $reqVersion) { if (Preg::isMatchStrictGroups('{(?:^|\\| *|, *)([^,\\s#|]+)(?:#[^ ]+)? +as +([^,\\s|]+)(?:$| *\\|| *,)}', $reqVersion, $match)) { $aliases[] = ['package' => \strtolower($reqName), 'version' => $this->versionParser->normalize($match[1], $reqVersion), 'alias' => $match[2], 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion)]; } elseif (\strpos($reqVersion, ' as ') !== \false) { throw new \UnexpectedValueException('Invalid alias definition in "' . $reqName . '": "' . $reqVersion . '". Aliases should be in the form "exact-version as other-exact-version".'); } } return $aliases; } /** * @internal * * @param array $requires * @param array $stabilityFlags * * @return array * * @phpstan-param array $stabilityFlags * @phpstan-return array */ public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags) : array { $stabilities = BasePackage::$stabilities; /** @var int $minimumStability */ $minimumStability = $stabilities[$minimumStability]; foreach ($requires as $reqName => $reqVersion) { $constraints = []; // extract all sub-constraints in case it is an OR/AND multi-constraint $orSplit = Preg::split('{\\s*\\|\\|?\\s*}', \trim($reqVersion)); foreach ($orSplit as $orConstraint) { $andSplit = Preg::split('{(?< ,]) *(? $stability) { continue; } $stabilityFlags[$name] = $stability; $matched = \true; } } if ($matched) { continue; } foreach ($constraints as $constraint) { // infer flags for requirements that have an explicit -dev or -beta version specified but only // for those that are more unstable than the minimumStability or existing flags $reqVersion = Preg::replace('{^([^,\\s@]+) as .+$}', '$1', $constraint); if (Preg::isMatch('{^[^,\\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) { $name = \strtolower($reqName); $stability = $stabilities[$stabilityName]; if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability || $minimumStability > $stability) { continue; } $stabilityFlags[$name] = $stability; } } } return $stabilityFlags; } /** * @internal * * @param array $requires * @param array $references * * @return array */ public static function extractReferences(array $requires, array $references) : array { foreach ($requires as $reqName => $reqVersion) { $reqVersion = Preg::replace('{^([^,\\s@]+) as .+$}', '$1', $reqVersion); if (Preg::isMatchStrictGroups('{^[^,\\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) { $name = \strtolower($reqName); $references[$name] = $match[1]; } } return $references; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Loader; use Composer\Json\JsonFile; use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\CompleteAliasPackage; use Composer\Package\RootPackage; use Composer\Package\RootAliasPackage; /** * @author Konstantin Kudryashiv */ class JsonLoader { /** @var LoaderInterface */ private $loader; public function __construct(\Composer\Package\Loader\LoaderInterface $loader) { $this->loader = $loader; } /** * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage */ public function load($json) : BasePackage { if ($json instanceof JsonFile) { $config = $json->read(); } elseif (\file_exists($json)) { $config = JsonFile::parseJson(\file_get_contents($json), $json); } elseif (\is_string($json)) { $config = JsonFile::parseJson($json); } else { throw new \InvalidArgumentException(\sprintf("JsonLoader: Unknown \$json parameter %s. Please report at https://github.com/composer/composer/issues/new.", \gettype($json))); } return $this->loader->load($config); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; use Composer\Repository\RepositoryInterface; use Composer\Repository\PlatformRepository; /** * Base class for packages providing name storage and default match implementation * * @author Nils Adermann */ abstract class BasePackage implements \Composer\Package\PackageInterface { /** * @phpstan-var array * @internal */ public static $supportedLinkTypes = ['require' => ['description' => 'requires', 'method' => \Composer\Package\Link::TYPE_REQUIRE], 'conflict' => ['description' => 'conflicts', 'method' => \Composer\Package\Link::TYPE_CONFLICT], 'provide' => ['description' => 'provides', 'method' => \Composer\Package\Link::TYPE_PROVIDE], 'replace' => ['description' => 'replaces', 'method' => \Composer\Package\Link::TYPE_REPLACE], 'require-dev' => ['description' => 'requires (for development)', 'method' => \Composer\Package\Link::TYPE_DEV_REQUIRE]]; public const STABILITY_STABLE = 0; public const STABILITY_RC = 5; public const STABILITY_BETA = 10; public const STABILITY_ALPHA = 15; public const STABILITY_DEV = 20; /** @var array */ public static $stabilities = ['stable' => self::STABILITY_STABLE, 'RC' => self::STABILITY_RC, 'beta' => self::STABILITY_BETA, 'alpha' => self::STABILITY_ALPHA, 'dev' => self::STABILITY_DEV]; /** * READ-ONLY: The package id, public for fast access in dependency solver * @var int * @internal */ public $id; /** @var string */ protected $name; /** @var string */ protected $prettyName; /** @var ?RepositoryInterface */ protected $repository = null; /** * All descendants' constructors should call this parent constructor * * @param string $name The package's name */ public function __construct(string $name) { $this->prettyName = $name; $this->name = \strtolower($name); $this->id = -1; } /** * @inheritDoc */ public function getName() : string { return $this->name; } /** * @inheritDoc */ public function getPrettyName() : string { return $this->prettyName; } /** * @inheritDoc */ public function getNames($provides = \true) : array { $names = [$this->getName() => \true]; if ($provides) { foreach ($this->getProvides() as $link) { $names[$link->getTarget()] = \true; } } foreach ($this->getReplaces() as $link) { $names[$link->getTarget()] = \true; } return \array_keys($names); } /** * @inheritDoc */ public function setId(int $id) : void { $this->id = $id; } /** * @inheritDoc */ public function getId() : int { return $this->id; } /** * @inheritDoc */ public function setRepository(RepositoryInterface $repository) : void { if ($this->repository && $repository !== $this->repository) { throw new \LogicException(\sprintf('Package "%s" cannot be added to repository "%s" as it is already in repository "%s".', $this->getPrettyName(), $repository->getRepoName(), $this->repository->getRepoName())); } $this->repository = $repository; } /** * @inheritDoc */ public function getRepository() : ?RepositoryInterface { return $this->repository; } /** * checks if this package is a platform package */ public function isPlatform() : bool { return $this->getRepository() instanceof PlatformRepository; } /** * Returns package unique name, constructed from name, version and release type. */ public function getUniqueName() : string { return $this->getName() . '-' . $this->getVersion(); } public function equals(\Composer\Package\PackageInterface $package) : bool { $self = $this; if ($this instanceof \Composer\Package\AliasPackage) { $self = $this->getAliasOf(); } if ($package instanceof \Composer\Package\AliasPackage) { $package = $package->getAliasOf(); } return $package === $self; } /** * Converts the package into a readable and unique string */ public function __toString() : string { return $this->getUniqueName(); } public function getPrettyString() : string { return $this->getPrettyName() . ' ' . $this->getPrettyVersion(); } /** * @inheritDoc */ public function getFullPrettyVersion(bool $truncate = \true, int $displayMode = \Composer\Package\PackageInterface::DISPLAY_SOURCE_REF_IF_DEV) : string { if ($displayMode === \Composer\Package\PackageInterface::DISPLAY_SOURCE_REF_IF_DEV && (!$this->isDev() || !\in_array($this->getSourceType(), ['hg', 'git']))) { return $this->getPrettyVersion(); } switch ($displayMode) { case \Composer\Package\PackageInterface::DISPLAY_SOURCE_REF_IF_DEV: case \Composer\Package\PackageInterface::DISPLAY_SOURCE_REF: $reference = $this->getSourceReference(); break; case \Composer\Package\PackageInterface::DISPLAY_DIST_REF: $reference = $this->getDistReference(); break; default: throw new \UnexpectedValueException('Display mode ' . $displayMode . ' is not supported'); } if (null === $reference) { return $this->getPrettyVersion(); } // if source reference is a sha1 hash -- truncate if ($truncate && \strlen($reference) === 40 && $this->getSourceType() !== 'svn') { return $this->getPrettyVersion() . ' ' . \substr($reference, 0, 7); } return $this->getPrettyVersion() . ' ' . $reference; } /** * @phpstan-return self::STABILITY_* */ public function getStabilityPriority() : int { return self::$stabilities[$this->getStability()]; } public function __clone() { $this->repository = null; $this->id = -1; } /** * Build a regexp from a package name, expanding * globs as required * * @param non-empty-string $wrap Wrap the cleaned string by the given string * @return non-empty-string */ public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i') : string { $cleanedAllowPattern = \str_replace('\\*', '.*', \preg_quote($allowPattern)); return \sprintf($wrap, $cleanedAllowPattern); } /** * Build a regexp from package names, expanding * globs as required * * @param string[] $packageNames * @param non-empty-string $wrap * @return non-empty-string */ public static function packageNamesToRegexp(array $packageNames, string $wrap = '{^(?:%s)$}iD') : string { $packageNames = \array_map(static function ($packageName) : string { return \Composer\Package\BasePackage::packageNameToRegexp($packageName, '%s'); }, $packageNames); return \sprintf($wrap, \implode('|', $packageNames)); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Comparer; use Composer\Util\Platform; /** * class Comparer * * @author Hector Prats */ class Comparer { /** @var string Source directory */ private $source; /** @var string Target directory */ private $update; /** @var array{changed?: string[], removed?: string[], added?: string[]} */ private $changed; public function setSource(string $source) : void { $this->source = $source; } public function setUpdate(string $update) : void { $this->update = $update; } /** * @return array{changed?: string[], removed?: string[], added?: string[]}|false false if no change */ public function getChanged(bool $explicated = \false) { $changed = $this->changed; if (!\count($changed)) { return \false; } if ($explicated) { foreach ($changed as $sectionKey => $itemSection) { foreach ($itemSection as $itemKey => $item) { $changed[$sectionKey][$itemKey] = $item . ' (' . $sectionKey . ')'; } } } return $changed; } /** * @return string empty string if no changes */ public function getChangedAsString(bool $toString = \false, bool $explicated = \false) : string { $changed = $this->getChanged($explicated); if (\false === $changed) { return ''; } $strings = []; foreach ($changed as $sectionKey => $itemSection) { foreach ($itemSection as $itemKey => $item) { $strings[] = $item . "\r\n"; } } return \trim(\implode("\r\n", $strings)); } public function doCompare() : void { $source = []; $destination = []; $this->changed = []; $currentDirectory = Platform::getCwd(); \chdir($this->source); $source = $this->doTree('.', $source); if (!\is_array($source)) { return; } \chdir($currentDirectory); \chdir($this->update); $destination = $this->doTree('.', $destination); if (!\is_array($destination)) { exit; } \chdir($currentDirectory); foreach ($source as $dir => $value) { foreach ($value as $file => $hash) { if (isset($destination[$dir][$file])) { if ($hash !== $destination[$dir][$file]) { $this->changed['changed'][] = $dir . '/' . $file; } } else { $this->changed['removed'][] = $dir . '/' . $file; } } } foreach ($destination as $dir => $value) { foreach ($value as $file => $hash) { if (!isset($source[$dir][$file])) { $this->changed['added'][] = $dir . '/' . $file; } } } } /** * @param mixed[] $array * * @return array>|false */ private function doTree(string $dir, array &$array) { if ($dh = \opendir($dir)) { while ($file = \readdir($dh)) { if ($file !== '.' && $file !== '..') { if (\is_link($dir . '/' . $file)) { $array[$dir][$file] = \readlink($dir . '/' . $file); } elseif (\is_dir($dir . '/' . $file)) { if (!\count($array)) { $array[0] = 'Temp'; } if (!$this->doTree($dir . '/' . $file, $array)) { return \false; } } elseif (\is_file($dir . '/' . $file) && \filesize($dir . '/' . $file)) { $array[$dir][$file] = \md5_file($dir . '/' . $file); } } } if (\count($array) > 1 && isset($array['0'])) { unset($array['0']); } return $array; } return \false; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * @author Jordi Boggiano */ class RootAliasPackage extends \Composer\Package\CompleteAliasPackage implements \Composer\Package\RootPackageInterface { /** @var RootPackage */ protected $aliasOf; /** * All descendants' constructors should call this parent constructor * * @param RootPackage $aliasOf The package this package is an alias of * @param string $version The version the alias must report * @param string $prettyVersion The alias's non-normalized version */ public function __construct(\Composer\Package\RootPackage $aliasOf, string $version, string $prettyVersion) { parent::__construct($aliasOf, $version, $prettyVersion); } /** * @return RootPackage */ public function getAliasOf() { return $this->aliasOf; } /** * @inheritDoc */ public function getAliases() : array { return $this->aliasOf->getAliases(); } /** * @inheritDoc */ public function getMinimumStability() : string { return $this->aliasOf->getMinimumStability(); } /** * @inheritDoc */ public function getStabilityFlags() : array { return $this->aliasOf->getStabilityFlags(); } /** * @inheritDoc */ public function getReferences() : array { return $this->aliasOf->getReferences(); } /** * @inheritDoc */ public function getPreferStable() : bool { return $this->aliasOf->getPreferStable(); } /** * @inheritDoc */ public function getConfig() : array { return $this->aliasOf->getConfig(); } /** * @inheritDoc */ public function setRequires(array $requires) : void { $this->requires = $this->replaceSelfVersionDependencies($requires, \Composer\Package\Link::TYPE_REQUIRE); $this->aliasOf->setRequires($requires); } /** * @inheritDoc */ public function setDevRequires(array $devRequires) : void { $this->devRequires = $this->replaceSelfVersionDependencies($devRequires, \Composer\Package\Link::TYPE_DEV_REQUIRE); $this->aliasOf->setDevRequires($devRequires); } /** * @inheritDoc */ public function setConflicts(array $conflicts) : void { $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, \Composer\Package\Link::TYPE_CONFLICT); $this->aliasOf->setConflicts($conflicts); } /** * @inheritDoc */ public function setProvides(array $provides) : void { $this->provides = $this->replaceSelfVersionDependencies($provides, \Composer\Package\Link::TYPE_PROVIDE); $this->aliasOf->setProvides($provides); } /** * @inheritDoc */ public function setReplaces(array $replaces) : void { $this->replaces = $this->replaceSelfVersionDependencies($replaces, \Composer\Package\Link::TYPE_REPLACE); $this->aliasOf->setReplaces($replaces); } /** * @inheritDoc */ public function setAutoload(array $autoload) : void { $this->aliasOf->setAutoload($autoload); } /** * @inheritDoc */ public function setDevAutoload(array $devAutoload) : void { $this->aliasOf->setDevAutoload($devAutoload); } /** * @inheritDoc */ public function setStabilityFlags(array $stabilityFlags) : void { $this->aliasOf->setStabilityFlags($stabilityFlags); } /** * @inheritDoc */ public function setMinimumStability(string $minimumStability) : void { $this->aliasOf->setMinimumStability($minimumStability); } /** * @inheritDoc */ public function setPreferStable(bool $preferStable) : void { $this->aliasOf->setPreferStable($preferStable); } /** * @inheritDoc */ public function setConfig(array $config) : void { $this->aliasOf->setConfig($config); } /** * @inheritDoc */ public function setReferences(array $references) : void { $this->aliasOf->setReferences($references); } /** * @inheritDoc */ public function setAliases(array $aliases) : void { $this->aliasOf->setAliases($aliases); } /** * @inheritDoc */ public function setSuggests(array $suggests) : void { $this->aliasOf->setSuggests($suggests); } /** * @inheritDoc */ public function setExtra(array $extra) : void { $this->aliasOf->setExtra($extra); } public function __clone() { parent::__clone(); $this->aliasOf = clone $this->aliasOf; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * Package containing additional metadata that is not used by the solver * * @author Nils Adermann */ class CompletePackage extends \Composer\Package\Package implements \Composer\Package\CompletePackageInterface { /** @var mixed[] */ protected $repositories = []; /** @var string[] */ protected $license = []; /** @var string[] */ protected $keywords = []; /** @var array */ protected $authors = []; /** @var ?string */ protected $description = null; /** @var ?string */ protected $homepage = null; /** @var array Map of script name to array of handlers */ protected $scripts = []; /** @var array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} */ protected $support = []; /** @var array */ protected $funding = []; /** @var bool|string */ protected $abandoned = \false; /** @var ?string */ protected $archiveName = null; /** @var string[] */ protected $archiveExcludes = []; /** * @inheritDoc */ public function setScripts(array $scripts) : void { $this->scripts = $scripts; } /** * @inheritDoc */ public function getScripts() : array { return $this->scripts; } /** * @inheritDoc */ public function setRepositories(array $repositories) : void { $this->repositories = $repositories; } /** * @inheritDoc */ public function getRepositories() : array { return $this->repositories; } /** * @inheritDoc */ public function setLicense(array $license) : void { $this->license = $license; } /** * @inheritDoc */ public function getLicense() : array { return $this->license; } /** * @inheritDoc */ public function setKeywords(array $keywords) : void { $this->keywords = $keywords; } /** * @inheritDoc */ public function getKeywords() : array { return $this->keywords; } /** * @inheritDoc */ public function setAuthors(array $authors) : void { $this->authors = $authors; } /** * @inheritDoc */ public function getAuthors() : array { return $this->authors; } /** * @inheritDoc */ public function setDescription(?string $description) : void { $this->description = $description; } /** * @inheritDoc */ public function getDescription() : ?string { return $this->description; } /** * @inheritDoc */ public function setHomepage(?string $homepage) : void { $this->homepage = $homepage; } /** * @inheritDoc */ public function getHomepage() : ?string { return $this->homepage; } /** * @inheritDoc */ public function setSupport(array $support) : void { $this->support = $support; } /** * @inheritDoc */ public function getSupport() : array { return $this->support; } /** * @inheritDoc */ public function setFunding(array $funding) : void { $this->funding = $funding; } /** * @inheritDoc */ public function getFunding() : array { return $this->funding; } /** * @inheritDoc */ public function isAbandoned() : bool { return (bool) $this->abandoned; } /** * @inheritDoc */ public function setAbandoned($abandoned) : void { $this->abandoned = $abandoned; } /** * @inheritDoc */ public function getReplacementPackage() : ?string { return \is_string($this->abandoned) ? $this->abandoned : null; } /** * @inheritDoc */ public function setArchiveName(?string $name) : void { $this->archiveName = $name; } /** * @inheritDoc */ public function getArchiveName() : ?string { return $this->archiveName; } /** * @inheritDoc */ public function setArchiveExcludes(array $excludes) : void { $this->archiveExcludes = $excludes; } /** * @inheritDoc */ public function getArchiveExcludes() : array { return $this->archiveExcludes; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * Defines additional fields that are only needed for the root package * * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. * * @author Jordi Boggiano * * @phpstan-import-type AutoloadRules from PackageInterface * @phpstan-import-type DevAutoloadRules from PackageInterface */ interface RootPackageInterface extends \Composer\Package\CompletePackageInterface { /** * Returns a set of package names and their aliases * * @return list */ public function getAliases() : array; /** * Returns the minimum stability of the package */ public function getMinimumStability() : string; /** * Returns the stability flags to apply to dependencies * * array('foo/bar' => 'dev') * * @return array */ public function getStabilityFlags() : array; /** * Returns a set of package names and source references that must be enforced on them * * array('foo/bar' => 'abcd1234') * * @return array */ public function getReferences() : array; /** * Returns true if the root package prefers picking stable packages over unstable ones */ public function getPreferStable() : bool; /** * Returns the root package's configuration * * @return mixed[] */ public function getConfig() : array; /** * Set the required packages * * @param Link[] $requires A set of package links */ public function setRequires(array $requires) : void; /** * Set the recommended packages * * @param Link[] $devRequires A set of package links */ public function setDevRequires(array $devRequires) : void; /** * Set the conflicting packages * * @param Link[] $conflicts A set of package links */ public function setConflicts(array $conflicts) : void; /** * Set the provided virtual packages * * @param Link[] $provides A set of package links */ public function setProvides(array $provides) : void; /** * Set the packages this one replaces * * @param Link[] $replaces A set of package links */ public function setReplaces(array $replaces) : void; /** * Set the autoload mapping * * @param array $autoload Mapping of autoloading rules * @phpstan-param AutoloadRules $autoload */ public function setAutoload(array $autoload) : void; /** * Set the dev autoload mapping * * @param array $devAutoload Mapping of dev autoloading rules * @phpstan-param DevAutoloadRules $devAutoload */ public function setDevAutoload(array $devAutoload) : void; /** * Set the stabilityFlags * * @param array $stabilityFlags */ public function setStabilityFlags(array $stabilityFlags) : void; /** * Set the minimumStability */ public function setMinimumStability(string $minimumStability) : void; /** * Set the preferStable */ public function setPreferStable(bool $preferStable) : void; /** * Set the config * * @param mixed[] $config */ public function setConfig(array $config) : void; /** * Set the references * * @param array $references */ public function setReferences(array $references) : void; /** * Set the aliases * * @param list $aliases */ public function setAliases(array $aliases) : void; /** * Set the suggested packages * * @param array $suggests A set of package names/comments */ public function setSuggests(array $suggests) : void; /** * @param mixed[] $extra */ public function setExtra(array $extra) : void; } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package; /** * The root package represents the project's composer.json and contains additional metadata * * @author Jordi Boggiano */ class RootPackage extends \Composer\Package\CompletePackage implements \Composer\Package\RootPackageInterface { public const DEFAULT_PRETTY_VERSION = '1.0.0+no-version-set'; /** @var string */ protected $minimumStability = 'stable'; /** @var bool */ protected $preferStable = \false; /** @var array Map of package name to stability constant */ protected $stabilityFlags = []; /** @var mixed[] */ protected $config = []; /** @var array Map of package name to reference/commit hash */ protected $references = []; /** @var list */ protected $aliases = []; /** * @inheritDoc */ public function setMinimumStability(string $minimumStability) : void { $this->minimumStability = $minimumStability; } /** * @inheritDoc */ public function getMinimumStability() : string { return $this->minimumStability; } /** * @inheritDoc */ public function setStabilityFlags(array $stabilityFlags) : void { $this->stabilityFlags = $stabilityFlags; } /** * @inheritDoc */ public function getStabilityFlags() : array { return $this->stabilityFlags; } /** * @inheritDoc */ public function setPreferStable(bool $preferStable) : void { $this->preferStable = $preferStable; } /** * @inheritDoc */ public function getPreferStable() : bool { return $this->preferStable; } /** * @inheritDoc */ public function setConfig(array $config) : void { $this->config = $config; } /** * @inheritDoc */ public function getConfig() : array { return $this->config; } /** * @inheritDoc */ public function setReferences(array $references) : void { $this->references = $references; } /** * @inheritDoc */ public function getReferences() : array { return $this->references; } /** * @inheritDoc */ public function setAliases(array $aliases) : void { $this->aliases = $aliases; } /** * @inheritDoc */ public function getAliases() : array { return $this->aliases; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Package\Dumper; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\RootPackageInterface; /** * @author Konstantin Kudryashiv * @author Jordi Boggiano */ class ArrayDumper { /** * @return array */ public function dump(PackageInterface $package) : array { $keys = ['binaries' => 'bin', 'type', 'extra', 'installationSource' => 'installation-source', 'autoload', 'devAutoload' => 'autoload-dev', 'notificationUrl' => 'notification-url', 'includePaths' => 'include-path']; $data = []; $data['name'] = $package->getPrettyName(); $data['version'] = $package->getPrettyVersion(); $data['version_normalized'] = $package->getVersion(); if ($package->getTargetDir()) { $data['target-dir'] = $package->getTargetDir(); } if ($package->getSourceType()) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); if (null !== ($value = $package->getSourceReference())) { $data['source']['reference'] = $value; } if ($mirrors = $package->getSourceMirrors()) { $data['source']['mirrors'] = $mirrors; } } if ($package->getDistType()) { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); if (null !== ($value = $package->getDistReference())) { $data['dist']['reference'] = $value; } if (null !== ($value = $package->getDistSha1Checksum())) { $data['dist']['shasum'] = $value; } if ($mirrors = $package->getDistMirrors()) { $data['dist']['mirrors'] = $mirrors; } } foreach (BasePackage::$supportedLinkTypes as $type => $opts) { if ($links = $package->{'get' . \ucfirst($opts['method'])}()) { foreach ($links as $link) { $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); } \ksort($data[$type]); } } if ($packages = $package->getSuggests()) { \ksort($packages); $data['suggest'] = $packages; } if ($package->getReleaseDate() instanceof \DateTimeInterface) { $data['time'] = $package->getReleaseDate()->format(\DATE_RFC3339); } if ($package->isDefaultBranch()) { $data['default-branch'] = \true; } $data = $this->dumpValues($package, $keys, $data); if ($package instanceof CompletePackageInterface) { if ($package->getArchiveName()) { $data['archive']['name'] = $package->getArchiveName(); } if ($package->getArchiveExcludes()) { $data['archive']['exclude'] = $package->getArchiveExcludes(); } $keys = ['scripts', 'license', 'authors', 'description', 'homepage', 'keywords', 'repositories', 'support', 'funding']; $data = $this->dumpValues($package, $keys, $data); if (isset($data['keywords']) && \is_array($data['keywords'])) { \sort($data['keywords']); } if ($package->isAbandoned()) { $data['abandoned'] = $package->getReplacementPackage() ?: \true; } } if ($package instanceof RootPackageInterface) { $minimumStability = $package->getMinimumStability(); if ($minimumStability) { $data['minimum-stability'] = $minimumStability; } } if (\count($package->getTransportOptions()) > 0) { $data['transport-options'] = $package->getTransportOptions(); } return $data; } /** * @param array $keys * @param array $data * * @return array */ private function dumpValues(PackageInterface $package, array $keys, array $data) : array { foreach ($keys as $method => $key) { if (\is_numeric($method)) { $method = $key; } $getter = 'get' . \ucfirst($method); $value = $package->{$getter}(); if (null !== $value && !(\is_array($value) && 0 === \count($value))) { $data[$key] = $value; } } return $data; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console; use Composer\IO\NullIO; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\Silencer; use LogicException; use RuntimeException; use _ContaoManager\Seld\Signal\SignalHandler; use _ContaoManager\Symfony\Component\Console\Application as BaseApplication; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; use _ContaoManager\Symfony\Component\Console\Helper\HelperSet; use _ContaoManager\Symfony\Component\Console\Helper\QuestionHelper; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Seld\JsonLint\ParsingException; use Composer\Command; use Composer\Composer; use Composer\Factory; use Composer\IO\IOInterface; use Composer\IO\ConsoleIO; use Composer\Json\JsonValidationException; use Composer\Util\ErrorHandler; use Composer\Util\HttpDownloader; use Composer\EventDispatcher\ScriptExecutionException; use Composer\Exception\NoSslException; use Composer\XdebugHandler\XdebugHandler; use _ContaoManager\Symfony\Component\Process\Exception\ProcessTimedOutException; /** * The console application that handles the commands * * @author Ryan Weaver * @author Jordi Boggiano * @author François Pluchino */ class Application extends BaseApplication { /** * @var ?Composer */ protected $composer; /** * @var IOInterface */ protected $io; /** @var string */ private static $logo = ' ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \\/ __ `__ \\/ __ \\/ __ \\/ ___/ _ \\/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \\____/\\____/_/ /_/ /_/ .___/\\____/____/\\___/_/ /_/ '; /** @var bool */ private $hasPluginCommands = \false; /** @var bool */ private $disablePluginsByDefault = \false; /** @var bool */ private $disableScriptsByDefault = \false; /** * @var string|false Store the initial working directory at startup time */ private $initialWorkingDirectory; /** @var SignalHandler */ private $signalHandler; public function __construct(string $name = 'Composer', string $version = '') { if (\method_exists($this, 'setCatchErrors')) { $this->setCatchErrors(\true); } static $shutdownRegistered = \false; if ($version === '') { $version = Composer::getVersion(); } if (\function_exists('ini_set') && \extension_loaded('xdebug')) { \ini_set('xdebug.show_exception_trace', '0'); \ini_set('xdebug.scream', '0'); } if (\function_exists('date_default_timezone_set') && \function_exists('date_default_timezone_get')) { \date_default_timezone_set(Silencer::call('date_default_timezone_get')); } $this->io = new NullIO(); $this->signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { $this->io->writeError('Received ' . $signal . ', aborting', \true, IOInterface::DEBUG); $handler->exitWithLastSignal(); }); if (!$shutdownRegistered) { $shutdownRegistered = \true; \register_shutdown_function(static function () : void { $lastError = \error_get_last(); if ($lastError && $lastError['message'] && (\strpos($lastError['message'], 'Allowed memory') !== \false || \strpos($lastError['message'], 'exceeded memory') !== \false)) { echo "\n" . 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.'; } }); } $this->initialWorkingDirectory = \getcwd(); parent::__construct($name, $version); } public function __destruct() { $this->signalHandler->unregister(); } public function run(?InputInterface $input = null, ?OutputInterface $output = null) : int { if (null === $output) { $output = Factory::createOutput(); } return parent::run($input, $output); } public function doRun(InputInterface $input, OutputInterface $output) : int { $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); $stdin = \defined('STDIN') ? \STDIN : \fopen('php://stdin', 'r'); if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === \false || !Platform::isTty($stdin))) { $input->setInteractive(\false); } $io = $this->io = new ConsoleIO($input, $output, new HelperSet([new QuestionHelper()])); // Register error handler again to pass it the IO instance ErrorHandler::register($io); if ($input->hasParameterOption('--no-cache')) { $io->writeError('Disabling cache usage', \true, IOInterface::DEBUG); Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null'); } // switch working dir $newWorkDir = $this->getNewWorkingDir($input); if (null !== $newWorkDir) { $oldWorkingDir = Platform::getCwd(\true); \chdir($newWorkDir); $this->initialWorkingDirectory = $newWorkDir; $cwd = Platform::getCwd(\true); $io->writeError('Changed CWD to ' . ($cwd !== '' ? $cwd : $newWorkDir), \true, IOInterface::DEBUG); } // determine command name to be executed without including plugin commands $commandName = ''; if ($name = $this->getCommandName($input)) { try { $commandName = $this->find($name)->getName(); } catch (CommandNotFoundException $e) { // we'll check command validity again later after plugins are loaded $commandName = \false; } catch (\InvalidArgumentException $e) { } } // prompt user for dir change if no composer.json is present in current dir if ($io->isInteractive() && null === $newWorkDir && !\in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], \true) && !\file_exists(Factory::getComposerFile()) && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== \false) { $dir = \dirname(Platform::getCwd(\true)); $home = \realpath((Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE')) ?: '/'); // abort when we reach the home dir or top of the filesystem while (\dirname($dir) !== $dir && $dir !== $home) { if (\file_exists($dir . '/' . Factory::getComposerFile())) { if ($useParentDirIfNoJsonAvailable === \true || $io->askConfirmation('No composer.json in current directory, do you want to use the one at ' . $dir . '? [Y,n]? ')) { if ($useParentDirIfNoJsonAvailable === \true) { $io->writeError('No composer.json in current directory, changing working directory to ' . $dir . ''); } else { $io->writeError('Always want to use the parent dir? Use "composer config --global use-parent-dir true" to change the default.'); } $oldWorkingDir = Platform::getCwd(\true); \chdir($dir); } break; } $dir = \dirname($dir); } } $needsSudoCheck = !Platform::isWindows() && \function_exists('exec') && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER') && (\ini_get('open_basedir') || !\file_exists('/.dockerenv')); $isNonAllowedRoot = \false; // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins if ($needsSudoCheck) { $isNonAllowedRoot = $this->isRunningAsRoot(); if ($isNonAllowedRoot) { if ($uid = (int) Platform::getEnv('SUDO_UID')) { // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on // ref. https://github.com/composer/composer/issues/5119 Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1"); } } // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations Silencer::call('exec', 'sudo -K > /dev/null 2>&1'); } // avoid loading plugins/initializing the Composer instance earlier than necessary if no plugin command is needed // if showing the version, we never need plugin commands $mayNeedPluginCommand = \false === $input->hasParameterOption(['--version', '-V']) && (\false === $commandName || \in_array($commandName, ['', 'list', 'help'], \true) || $commandName === '_complete' && !$isNonAllowedRoot); if ($mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) { // at this point plugins are needed, so if we are running as root and it is not allowed we need to prompt // if interactive, and abort otherwise if ($isNonAllowedRoot) { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); if ($io->isInteractive() && $io->askConfirmation('Continue as root/super user [yes]? ')) { // avoid a second prompt later $isNonAllowedRoot = \false; } else { $io->writeError('Aborting as no plugin should be loaded if running as super user is not explicitly allowed'); return 1; } } try { foreach ($this->getPluginCommands() as $command) { if ($this->has($command->getName())) { $io->writeError('Plugin command ' . $command->getName() . ' (' . \get_class($command) . ') would override a Composer command and has been skipped'); } else { $this->add($command); } } } catch (NoSslException $e) { // suppress these as they are not relevant at this point } catch (ParsingException $e) { $details = $e->getDetails(); $file = \realpath(Factory::getComposerFile()); $line = null; if ($details && isset($details['line'])) { $line = $details['line']; } $ghe = new \Composer\Console\GithubActionError($this->io); $ghe->emit($e->getMessage(), $file, $line); throw $e; } $this->hasPluginCommands = \true; } if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) { $io->writeError('Composer plugins have been disabled for safety in this non-interactive session. Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); $this->disablePluginsByDefault = \true; } // determine command name to be executed incl plugin commands, and check if it's a proxy command $isProxyCommand = \false; if ($name = $this->getCommandName($input)) { try { $command = $this->find($name); $commandName = $command->getName(); $isProxyCommand = $command instanceof Command\BaseCommand && $command->isProxyCommand(); } catch (\InvalidArgumentException $e) { } } if (!$isProxyCommand) { $io->writeError(\sprintf('Running %s (%s) with %s on %s', Composer::getVersion(), Composer::RELEASE_DATE, \defined('_ContaoManager\\HHVM_VERSION') ? 'HHVM ' . HHVM_VERSION : 'PHP ' . \PHP_VERSION, \function_exists('php_uname') ? \php_uname('s') . ' / ' . \php_uname('r') : 'Unknown OS'), \true, IOInterface::DEBUG); if (\PHP_VERSION_ID < 70205) { $io->writeError('Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP ' . \PHP_VERSION . '. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.'); } if (XdebugHandler::isXdebugActive() && !Platform::getEnv('COMPOSER_DISABLE_XDEBUG_WARN')) { $io->writeError('Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug'); } if (\defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && \time() > \COMPOSER_DEV_WARNING_TIME) { $io->writeError(\sprintf('Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); } if ($isNonAllowedRoot) { if ($commandName !== 'self-update' && $commandName !== 'selfupdate' && $commandName !== '_complete') { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); if ($io->isInteractive()) { if (!$io->askConfirmation('Continue as root/super user [yes]? ')) { return 1; } } } } // Check system temp folder for usability as it can cause weird runtime issues otherwise Silencer::call(static function () use($io) : void { $pid = \function_exists('getmypid') ? \getmypid() . '-' : ''; $tempfile = \sys_get_temp_dir() . '/temp-' . $pid . \md5(\microtime()); if (!(\file_put_contents($tempfile, __FILE__) && \file_get_contents($tempfile) === __FILE__ && \unlink($tempfile) && !\file_exists($tempfile))) { $io->writeError(\sprintf('PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini', \sys_get_temp_dir())); } }); // add non-standard scripts as own commands $file = Factory::getComposerFile(); if (\is_file($file) && Filesystem::isReadable($file) && \is_array($composer = \json_decode(\file_get_contents($file), \true))) { if (isset($composer['scripts']) && \is_array($composer['scripts'])) { foreach ($composer['scripts'] as $script => $dummy) { if (!\defined('Composer\\Script\\ScriptEvents::' . \str_replace('-', '_', \strtoupper($script)))) { if ($this->has($script)) { $io->writeError('A script named ' . $script . ' would override a Composer command and has been skipped'); } else { $description = null; if (isset($composer['scripts-descriptions'][$script])) { $description = $composer['scripts-descriptions'][$script]; } $aliases = $composer['scripts-aliases'][$script] ?? []; $this->add(new Command\ScriptAliasCommand($script, $description, $aliases)); } } } } } } try { if ($input->hasParameterOption('--profile')) { $startTime = \microtime(\true); $this->io->enableDebugging($startTime); } $result = parent::doRun($input, $output); // chdir back to $oldWorkingDir if set if (isset($oldWorkingDir) && '' !== $oldWorkingDir) { Silencer::call('chdir', $oldWorkingDir); } if (isset($startTime)) { $io->writeError('Memory usage: ' . \round(\memory_get_usage() / 1024 / 1024, 2) . 'MiB (peak: ' . \round(\memory_get_peak_usage() / 1024 / 1024, 2) . 'MiB), time: ' . \round(\microtime(\true) - $startTime, 2) . 's'); } return $result; } catch (ScriptExecutionException $e) { if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.', \true, IOInterface::QUIET); $io->writeError('See also https://getcomposer.org/root', \true, IOInterface::QUIET); } return $e->getCode(); } catch (\Throwable $e) { $ghe = new \Composer\Console\GithubActionError($this->io); $ghe->emit($e->getMessage()); $this->hintCommonErrors($e, $output); // symfony/console <6.4 does not handle \Error subtypes so we have to renderThrowable ourselves // instead of rethrowing those for consumption by the parent class // can be removed when Composer supports PHP 8.1+ if (!\method_exists($this, 'setCatchErrors') && !$e instanceof \Exception) { if ($output instanceof ConsoleOutputInterface) { $this->renderThrowable($e, $output->getErrorOutput()); } else { $this->renderThrowable($e, $output); } return \max(1, $e->getCode()); } throw $e; } finally { \restore_error_handler(); } } /** * @throws \RuntimeException * @return ?string */ private function getNewWorkingDir(InputInterface $input) : ?string { /** @var string|null $workingDir */ $workingDir = $input->getParameterOption(['--working-dir', '-d'], null, \true); if (null !== $workingDir && !\is_dir($workingDir)) { throw new \RuntimeException('Invalid working directory specified, ' . $workingDir . ' does not exist.'); } return $workingDir; } private function hintCommonErrors(\Throwable $exception, OutputInterface $output) : void { $io = $this->getIO(); if ((\get_class($exception) === LogicException::class || $exception instanceof \Error) && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } Silencer::suppress(); try { $composer = $this->getComposer(\false, \true); if (null !== $composer && \function_exists('disk_free_space')) { $config = $composer->getConfig(); $minSpaceFree = 100 * 1024 * 1024; if (($df = \disk_free_space($dir = $config->get('home'))) !== \false && $df < $minSpaceFree || ($df = \disk_free_space($dir = $config->get('vendor-dir'))) !== \false && $df < $minSpaceFree || ($df = \disk_free_space($dir = \sys_get_temp_dir())) !== \false && $df < $minSpaceFree) { $io->writeError('The disk hosting ' . $dir . ' has less than 100MiB of free space, this may be the cause of the following exception', \true, IOInterface::QUIET); } } } catch (\Exception $e) { } Silencer::restore(); if (Platform::isWindows() && \false !== \strpos($exception->getMessage(), 'The system cannot find the path specified')) { $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', \true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', \true, IOInterface::QUIET); } if (\false !== \strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { $io->writeError('The following exception is caused by a lack of memory or swap, or not having swap configured', \true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details', \true, IOInterface::QUIET); } if ($exception instanceof ProcessTimedOutException) { $io->writeError('The following exception is caused by a process timeout', \true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/06-config.md#process-timeout for details', \true, IOInterface::QUIET); } if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root', \true, IOInterface::QUIET); } elseif ($exception instanceof CommandNotFoundException && $this->getDisablePluginsByDefault()) { $io->writeError('Plugins have been disabled, which may be why some commands are missing, unless you made a typo', \true, IOInterface::QUIET); } $hints = HttpDownloader::getExceptionHints($exception); if (null !== $hints && \count($hints) > 0) { foreach ($hints as $hint) { $io->writeError($hint, \true, IOInterface::QUIET); } } } /** * @throws JsonValidationException * @throws \InvalidArgumentException * @return ?Composer If $required is true then the return value is guaranteed */ public function getComposer(bool $required = \true, ?bool $disablePlugins = null, ?bool $disableScripts = null) : ?Composer { if (null === $disablePlugins) { $disablePlugins = $this->disablePluginsByDefault; } if (null === $disableScripts) { $disableScripts = $this->disableScriptsByDefault; } if (null === $this->composer) { try { $this->composer = Factory::create(Platform::isInputCompletionProcess() ? new NullIO() : $this->io, null, $disablePlugins, $disableScripts); } catch (\InvalidArgumentException $e) { if ($required) { $this->io->writeError($e->getMessage()); if ($this->areExceptionsCaught()) { exit(1); } throw $e; } } catch (JsonValidationException $e) { if ($required) { throw $e; } } catch (RuntimeException $e) { if ($required) { throw $e; } } } return $this->composer; } /** * Removes the cached composer instance */ public function resetComposer() : void { $this->composer = null; if (\method_exists($this->getIO(), 'resetAuthentications')) { $this->getIO()->resetAuthentications(); } } public function getIO() : IOInterface { return $this->io; } public function getHelp() : string { return self::$logo . parent::getHelp(); } /** * Initializes all the composer commands. * @return \Symfony\Component\Console\Command\Command[] */ protected function getDefaultCommands() : array { $commands = \array_merge(parent::getDefaultCommands(), [new Command\AboutCommand(), new Command\ConfigCommand(), new Command\DependsCommand(), new Command\ProhibitsCommand(), new Command\InitCommand(), new Command\InstallCommand(), new Command\CreateProjectCommand(), new Command\UpdateCommand(), new Command\SearchCommand(), new Command\ValidateCommand(), new Command\AuditCommand(), new Command\ShowCommand(), new Command\SuggestsCommand(), new Command\RequireCommand(), new Command\DumpAutoloadCommand(), new Command\StatusCommand(), new Command\ArchiveCommand(), new Command\DiagnoseCommand(), new Command\RunScriptCommand(), new Command\LicensesCommand(), new Command\GlobalCommand(), new Command\ClearCacheCommand(), new Command\RemoveCommand(), new Command\HomeCommand(), new Command\ExecCommand(), new Command\OutdatedCommand(), new Command\CheckPlatformReqsCommand(), new Command\FundCommand(), new Command\ReinstallCommand(), new Command\BumpCommand()]); if (\strpos(__FILE__, 'phar:') === 0 || '1' === Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) { $commands[] = new Command\SelfUpdateCommand(); } return $commands; } public function getLongVersion() : string { $branchAliasString = ''; if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version' . '@') { $branchAliasString = \sprintf(' (%s)', Composer::BRANCH_ALIAS_VERSION); } return \sprintf('%s version %s%s %s', $this->getName(), $this->getVersion(), $branchAliasString, Composer::RELEASE_DATE); } protected function getDefaultInputDefinition() : InputDefinition { $definition = parent::getDefaultInputDefinition(); $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); return $definition; } /** * @return Command\BaseCommand[] */ private function getPluginCommands() : array { $commands = []; $composer = $this->getComposer(\false, \false); if (null === $composer) { $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault); } if (null !== $composer) { $pm = $composer->getPluginManager(); foreach ($pm->getPluginCapabilities('Composer\\Plugin\\Capability\\CommandProvider', ['composer' => $composer, 'io' => $this->io]) as $capability) { $newCommands = $capability->getCommands(); if (!\is_array($newCommands)) { throw new \UnexpectedValueException('Plugin capability ' . \get_class($capability) . ' failed to return an array from getCommands'); } foreach ($newCommands as $command) { if (!$command instanceof Command\BaseCommand) { throw new \UnexpectedValueException('Plugin capability ' . \get_class($capability) . ' returned an invalid value, we expected an array of Composer\\Command\\BaseCommand objects'); } } $commands = \array_merge($commands, $newCommands); } } return $commands; } /** * Get the working directory at startup time * * @return string|false */ public function getInitialWorkingDirectory() { return $this->initialWorkingDirectory; } public function getDisablePluginsByDefault() : bool { return $this->disablePluginsByDefault; } public function getDisableScriptsByDefault() : bool { return $this->disableScriptsByDefault; } /** * @return 'prompt'|bool */ private function getUseParentDirConfigValue() { $config = Factory::createConfig($this->io); return $config->get('use-parent-dir'); } private function isRunningAsRoot() : bool { return \function_exists('posix_getuid') && \posix_getuid() === 0; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console; use Composer\IO\IOInterface; use Composer\Util\Platform; final class GithubActionError { /** * @var IOInterface */ protected $io; public function __construct(IOInterface $io) { $this->io = $io; } public function emit(string $message, ?string $file = null, ?int $line = null) : void { if (Platform::getEnv('GITHUB_ACTIONS') && !Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) { $message = $this->escapeData($message); if ($file && $line) { $file = $this->escapeProperty($file); $this->io->write("::error file=" . $file . ",line=" . $line . "::" . $message); } elseif ($file) { $file = $this->escapeProperty($file); $this->io->write("::error file=" . $file . "::" . $message); } else { $this->io->write("::error ::" . $message); } } } private function escapeData(string $data) : string { // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L80-L85 $data = \str_replace("%", '%25', $data); $data = \str_replace("\r", '%0D', $data); $data = \str_replace("\n", '%0A', $data); return $data; } private function escapeProperty(string $property) : string { // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L87-L94 $property = \str_replace("%", '%25', $property); $property = \str_replace("\r", '%0D', $property); $property = \str_replace("\n", '%0A', $property); $property = \str_replace(":", '%3A', $property); $property = \str_replace(",", '%2C', $property); return $property; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console\Input; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Completion\Suggestion; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument as BaseInputArgument; /** * Backport suggested values definition from symfony/console 6.1+ * * @author Jérôme Tamarelle * * @internal * * TODO drop when PHP 8.1 / symfony 6.1+ can be required */ class InputArgument extends BaseInputArgument { /** * @var list|\Closure(CompletionInput,CompletionSuggestions):list */ private $suggestedValues; /** * @param string $name The argument name * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param string $description A description text * @param string|bool|int|float|string[]|null $default The default value (for self::OPTIONAL mode only) * @param list|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @throws InvalidArgumentException When argument mode is not valid */ public function __construct(string $name, ?int $mode = null, string $description = '', $default = null, $suggestedValues = []) { parent::__construct($name, $mode, $description, $default); $this->suggestedValues = $suggestedValues; } /** * Adds suggestions to $suggestions for the current completion input. * * @see Command::complete() */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { $values = $this->suggestedValues; if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore-line throw new LogicException(\sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), \get_debug_type($values))); } if ([] !== $values) { $suggestions->suggestValues($values); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console\Input; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Completion\Suggestion; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Input\InputOption as BaseInputOption; /** * Backport suggested values definition from symfony/console 6.1+ * * @author Jérôme Tamarelle * * @internal * * TODO drop when PHP 8.1 / symfony 6.1+ can be required */ class InputOption extends BaseInputOption { /** * @var list|\Closure(CompletionInput,CompletionSuggestions):list */ private $suggestedValues; /** * @param string|string[]|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int|null $mode The option mode: One of the VALUE_* constants * @param string|bool|int|float|string[]|null $default The default value (must be null for self::VALUE_NONE) * @param list|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completionnull for self::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible */ public function __construct(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null, $suggestedValues = []) { parent::__construct($name, $shortcut, $mode, $description, $default); $this->suggestedValues = $suggestedValues; if ([] !== $suggestedValues && !$this->acceptValue()) { throw new LogicException('Cannot set suggested values if the option does not accept a value.'); } } /** * Adds suggestions to $suggestions for the current completion input. * * @see Command::complete() */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { $values = $this->suggestedValues; if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore-line throw new LogicException(\sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), \get_debug_type($values))); } if ([] !== $values) { $suggestions->suggestValues($values); } } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Console; use Closure; use Composer\Pcre\Preg; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterStyle; /** * @author Jordi Boggiano */ class HtmlOutputFormatter extends OutputFormatter { /** @var array */ private static $availableForegroundColors = [30 => 'black', 31 => 'red', 32 => 'green', 33 => 'yellow', 34 => 'blue', 35 => 'magenta', 36 => 'cyan', 37 => 'white']; /** @var array */ private static $availableBackgroundColors = [40 => 'black', 41 => 'red', 42 => 'green', 43 => 'yellow', 44 => 'blue', 45 => 'magenta', 46 => 'cyan', 47 => 'white']; /** @var array */ private static $availableOptions = [1 => 'bold', 4 => 'underscore']; /** * @param array $styles Array of "name => FormatterStyle" instances */ public function __construct(array $styles = []) { parent::__construct(\true, $styles); } public function format(?string $message) : ?string { $formatted = parent::format($message); if ($formatted === null) { return null; } $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)'; return Preg::replaceCallback("{\x1b\\[([0-9;]+)m(.*?)\x1b\\[(?:" . $clearEscapeCodes . ";)*?" . $clearEscapeCodes . "m}s", Closure::fromCallable([$this, 'formatHtml']), $formatted); } /** * @param array $matches */ private function formatHtml(array $matches) : string { \assert(\is_string($matches[1])); $out = '' . $matches[2] . ''; } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Composer\Autoload\ClassLoader; function includeIfExists(string $file) : ?ClassLoader { return \file_exists($file) ? include $file : null; } if (!($loader = includeIfExists(__DIR__ . '/../vendor/autoload.php')) && !($loader = includeIfExists(__DIR__ . '/../../../autoload.php'))) { echo 'You must set up the project dependencies using `composer install`' . \PHP_EOL . 'See https://getcomposer.org/download/ for instructions on installing Composer' . \PHP_EOL; exit(1); } return $loader; ## ## Bundle of CA Root Certificates ## ## Certificate data from Mozilla as of: Tue Dec 12 04:12:04 2023 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: ## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. ## SHA256: 1970dd65858925d68498d2356aea6d03f764422523c5887deca8ce3ba9e1f845 ## GlobalSign Root CA ================== -----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- Entrust.net Premium 2048 Secure Server CA ========================================= -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= -----END CERTIFICATE----- Baltimore CyberTrust Root ========================= -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- Comodo AAA Services root ======================== -----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm 7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z 8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C 12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- QuoVadis Root CA 2 ================== -----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt 66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK +JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II 4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u -----END CERTIFICATE----- QuoVadis Root CA 3 ================== -----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp 8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc /Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz 8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= -----END CERTIFICATE----- Go Daddy Class 2 CA =================== -----BEGIN CERTIFICATE----- MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv 2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b vZ8= -----END CERTIFICATE----- Starfield Class 2 CA ==================== -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- DigiCert Assured ID Root CA =========================== -----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO 9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW /lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF 66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i 8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== -----END CERTIFICATE----- DigiCert Global Root CA ======================= -----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H 4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y 7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm 8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- DigiCert High Assurance EV Root CA ================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- SwissSign Gold CA - G2 ====================== -----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR 7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm 5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr 44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- SwissSign Silver CA - G2 ======================== -----BEGIN CERTIFICATE----- MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm +/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH 6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P 4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L 3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx /uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- SecureTrust CA ============== -----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b 01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR 3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= -----END CERTIFICATE----- Secure Global CA ================ -----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g 8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi 0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW -----END CERTIFICATE----- COMODO Certification Authority ============================== -----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH +7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV 4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA 1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN +8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== -----END CERTIFICATE----- COMODO ECC Certification Authority ================================== -----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X 4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- Certigna ======== -----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY 1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- ePKI Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX 12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- certSIGN ROOT CA ================ -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD 0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD -----END CERTIFICATE----- NetLock Arany (Class Gold) Főtanúsítvány ======================================== -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu 0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw /HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- SecureSign RootCA11 =================== -----BEGIN CERTIFICATE----- MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= -----END CERTIFICATE----- Microsec e-Szigno Root CA 2009 ============================== -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG 0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm 1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi LXpUq3DDfSJlgnCW -----END CERTIFICATE----- GlobalSign Root CA - R3 ======================= -----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ 0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r kpeDMdmztcpHWD9f -----END CERTIFICATE----- Izenpe.com ========== -----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ 03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU +zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK 0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ 0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- Go Daddy Root Certificate Authority - G2 ======================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq 9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD +qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r 5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 -----END CERTIFICATE----- Starfield Root Certificate Authority - G2 ========================================= -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx 4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- Starfield Services Root Certificate Authority - G2 ================================================== -----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 -----END CERTIFICATE----- AffirmTrust Commercial ====================== -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv 0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= -----END CERTIFICATE----- AffirmTrust Networking ====================== -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 /PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 /ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= -----END CERTIFICATE----- AffirmTrust Premium =================== -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV 5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs +7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 /bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo +Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB /wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC 6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK +4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== -----END CERTIFICATE----- AffirmTrust Premium ECC ======================= -----BEGIN CERTIFICATE----- MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X 57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM eQ== -----END CERTIFICATE----- Certum Trusted Network CA ========================= -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- TWCA Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP 4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG 9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== -----END CERTIFICATE----- Security Communication RootCA2 ============================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ +T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R 3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk 3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- Actalis Authentication Root CA ============================== -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC 4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo 2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- Buypass Class 2 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn 9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b /+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN rJgWVqA= -----END CERTIFICATE----- Buypass Class 3 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR 5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh 7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH 2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV /afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz 6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi Cp/HuZc= -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 3 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK 9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W 0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== -----END CERTIFICATE----- D-TRUST Root Class 3 CA 2 2009 ============================== -----BEGIN CERTIFICATE----- MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ 4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm 2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= -----END CERTIFICATE----- D-TRUST Root Class 3 CA 2 EV 2009 ================================= -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T 7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv w9y4AyHqnxbxLFS1 -----END CERTIFICATE----- CA Disig Root R2 ================ -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa 5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV 7+ZtsH8tZ/3zbBt1RqPlShfppNcL -----END CERTIFICATE----- ACCVRAIZ1 ========= -----BEGIN CERTIFICATE----- MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ 0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR 5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J 9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd 3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p EfbRD0tVNEYqi4Y7 -----END CERTIFICATE----- TWCA Global Root CA =================== -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M 8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg /eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= -----END CERTIFICATE----- TeliaSonera Root CA v1 ====================== -----BEGIN CERTIFICATE----- MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ 6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA 3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx 0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 2 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR 3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== -----END CERTIFICATE----- Atos TrustedRoot 2011 ===================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr 54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G 3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- QuoVadis Root CA 1 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV 7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX 9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP +V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh 3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV hMJKzRwuJIczYOXD -----END CERTIFICATE----- QuoVadis Root CA 2 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD 6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr O3jtZsSOeWmD3n+M -----END CERTIFICATE----- QuoVadis Root CA 3 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe 6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX 0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 PpxxVJkES/1Y+Zj0 -----END CERTIFICATE----- DigiCert Assured ID Root G2 =========================== -----BEGIN CERTIFICATE----- MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH 35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv 0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo IhNzbM8m9Yop5w== -----END CERTIFICATE----- DigiCert Assured ID Root G3 =========================== -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy 1vUhZscv6pZjamVFkpUBtA== -----END CERTIFICATE----- DigiCert Global Root G2 ======================= -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO 3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu 5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- DigiCert Global Root G3 ======================= -----BEGIN CERTIFICATE----- MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y 3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 VOKa5Vt8sycX -----END CERTIFICATE----- DigiCert Trusted Root G4 ======================== -----BEGIN CERTIFICATE----- MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy 7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN 5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb /UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa 5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP 82Z+ -----END CERTIFICATE----- COMODO RSA Certification Authority ================================== -----BEGIN CERTIFICATE----- MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ 5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX 2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I LaZRfyHBNVOFBkpdn627G190 -----END CERTIFICATE----- USERTrust RSA Certification Authority ===================================== -----BEGIN CERTIFICATE----- MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz 0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O +T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq /nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ 7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM 8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- USERTrust ECC Certification Authority ===================================== -----BEGIN CERTIFICATE----- MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu 9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= -----END CERTIFICATE----- GlobalSign ECC Root CA - R5 =========================== -----BEGIN CERTIFICATE----- MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- IdenTrust Commercial Root CA 1 ============================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi 1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl 3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH 6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe 2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R cGzM7vRX+Bi6hG6H -----END CERTIFICATE----- IdenTrust Public Sector Root CA 1 ================================= -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL 4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ 3Wl9af0AVqW3rLatt8o+Ae+c -----END CERTIFICATE----- Entrust Root Certification Authority - G2 ========================================= -----BEGIN CERTIFICATE----- MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP /vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO e4pIb4tF9g== -----END CERTIFICATE----- Entrust Root Certification Authority - EC1 ========================================== -----BEGIN CERTIFICATE----- MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef 9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G -----END CERTIFICATE----- CFCA EV ROOT ============ -----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD 7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB /wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua 4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- OISTE WISeKey Global Root GB CA =============================== -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk 9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= -----END CERTIFICATE----- SZAFIR ROOT CA2 =============== -----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE 2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul 4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 +/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== -----END CERTIFICATE----- Certum Trusted Network CA 2 =========================== -----BEGIN CERTIFICATE----- MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ 9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 zAYspsbiDrW5viSP -----END CERTIFICATE----- Hellenic Academic and Research Institutions RootCA 2015 ======================================================= -----BEGIN CERTIFICATE----- MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ 6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn 82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q p/UsQu0yrbYhnr68 -----END CERTIFICATE----- Hellenic Academic and Research Institutions ECC RootCA 2015 =========================================================== -----BEGIN CERTIFICATE----- MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR -----END CERTIFICATE----- ISRG Root X1 ============ -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ 4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf 1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY 9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV 0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ m+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- AC RAIZ FNMT-RCM ================ -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou 08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ 47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW +YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d 8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm 5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= -----END CERTIFICATE----- Amazon Root CA 1 ================ -----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy 8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa 2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- Amazon Root CA 2 ================ -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ 3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY +gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= -----END CERTIFICATE----- Amazon Root CA 3 ================ -----BEGIN CERTIFICATE----- MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== -----END CERTIFICATE----- Amazon Root CA 4 ================ -----BEGIN CERTIFICATE----- MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN /sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri 83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 ============================================= -----BEGIN CERTIFICATE----- MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= -----END CERTIFICATE----- GDCA TrustAUTH R5 ROOT ====================== -----BEGIN CERTIFICATE----- MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ 9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx 9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd +PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ 8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv /EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== -----END CERTIFICATE----- SSL.com Root Certification Authority RSA ======================================== -----BEGIN CERTIFICATE----- MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= -----END CERTIFICATE----- SSL.com Root Certification Authority ECC ======================================== -----BEGIN CERTIFICATE----- MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ 8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z 5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl -----END CERTIFICATE----- SSL.com EV Root Certification Authority RSA R2 ============================================== -----BEGIN CERTIFICATE----- MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim 9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 +qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 ++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX 9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== -----END CERTIFICATE----- SSL.com EV Root Certification Authority ECC =========================================== -----BEGIN CERTIFICATE----- MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy 3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe 5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== -----END CERTIFICATE----- GlobalSign Root CA - R6 ======================= -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE 3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP 0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr 3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= -----END CERTIFICATE----- OISTE WISeKey Global Root GC CA =============================== -----BEGIN CERTIFICATE----- MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 -----END CERTIFICATE----- UCA Global G2 Root ================== -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV 8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa 4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo 5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== -----END CERTIFICATE----- UCA Extended Validation Root ============================ -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR 59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH 0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS 3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb +7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr dhh2n1ax -----END CERTIFICATE----- Certigna Root CA ================ -----BEGIN CERTIFICATE----- MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq 4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ /TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of 1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq 7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd 8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS 6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= -----END CERTIFICATE----- emSign Root CA - G1 =================== -----BEGIN CERTIFICATE----- MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ 6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q +Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= -----END CERTIFICATE----- emSign ECC Root CA - G3 ======================= -----BEGIN CERTIFICATE----- MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc 58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj -----END CERTIFICATE----- emSign Root CA - C1 =================== -----BEGIN CERTIFICATE----- MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp /6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= -----END CERTIFICATE----- emSign ECC Root CA - C3 ======================= -----BEGIN CERTIFICATE----- MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd 6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== -----END CERTIFICATE----- Hongkong Post Root CA 3 ======================= -----BEGIN CERTIFICATE----- MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim 5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj 0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h +bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov +BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw 9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB 60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq dBb9HxEGmpv0 -----END CERTIFICATE----- Entrust Root Certification Authority - G4 ========================================= -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV 3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds 8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV 7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht 7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G +TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT kcpG2om3PVODLAgfi49T3f+sHw== -----END CERTIFICATE----- Microsoft ECC Root Certificate Authority 2017 ============================================= -----BEGIN CERTIFICATE----- MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM +Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= -----END CERTIFICATE----- Microsoft RSA Root Certificate Authority 2017 ============================================= -----BEGIN CERTIFICATE----- MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml 7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ 0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og 6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk +ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex /2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE 7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D 5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E -----END CERTIFICATE----- e-Szigno Root CA 2017 ===================== -----BEGIN CERTIFICATE----- MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO svxyqltZ+efcMQ== -----END CERTIFICATE----- certSIGN Root CA G2 =================== -----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf 95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB /AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N 0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= -----END CERTIFICATE----- Trustwave Global Certification Authority ======================================== -----BEGIN CERTIFICATE----- MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm +9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla 4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O 856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu 3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP 29FpHOTKyeC2nOnOcXHebD8WpHk= -----END CERTIFICATE----- Trustwave Global ECC P256 Certification Authority ================================================= -----BEGIN CERTIFICATE----- MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj 43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt 0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 -----END CERTIFICATE----- Trustwave Global ECC P384 Certification Authority ================================================= -----BEGIN CERTIFICATE----- MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr /TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== -----END CERTIFICATE----- NAVER Global Root Certification Authority ========================================= -----BEGIN CERTIFICATE----- MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW +j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK 21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg kpzNNIaRkPpkUZ3+/uul9XXeifdy -----END CERTIFICATE----- AC RAIZ FNMT-RCM SERVIDORES SEGUROS =================================== -----BEGIN CERTIFICATE----- MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= -----END CERTIFICATE----- GlobalSign Root R46 =================== -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje 2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 QEUxeCp6 -----END CERTIFICATE----- GlobalSign Root E46 =================== -----BEGIN CERTIFICATE----- MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ CAezNIm8BZ/3Hobui3A= -----END CERTIFICATE----- GLOBALTRUST 2020 ================ -----BEGIN CERTIFICATE----- MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw 4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS 8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== -----END CERTIFICATE----- ANF Secure Server Root CA ========================= -----BEGIN CERTIFICATE----- MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j 7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe 8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM 5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb 5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= -----END CERTIFICATE----- Certum EC-384 CA ================ -----BEGIN CERTIFICATE----- MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= -----END CERTIFICATE----- Certum Trusted Root CA ====================== -----BEGIN CERTIFICATE----- MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA 4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj 6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb -----END CERTIFICATE----- TunTrust Root CA ================ -----BEGIN CERTIFICATE----- MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz 2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI 04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl 0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= -----END CERTIFICATE----- HARICA TLS RSA Root CA 2021 =========================== -----BEGIN CERTIFICATE----- MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K 5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR 0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= -----END CERTIFICATE----- HARICA TLS ECC Root CA 2021 =========================== -----BEGIN CERTIFICATE----- MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW 0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps -----END CERTIFICATE----- Autoridad de Certificacion Firmaprofesional CIF A62634068 ========================================================= -----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY 7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL 4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH 9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE ZycPvEJdvSRUDewdcAZfpLz6IHxV -----END CERTIFICATE----- vTrus ECC Root CA ================= -----BEGIN CERTIFICATE----- MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL YgmRWAD5Tfs0aNoJrSEGGJTO -----END CERTIFICATE----- vTrus Root CA ============= -----BEGIN CERTIFICATE----- MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu /9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu 1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO 9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H l3s= -----END CERTIFICATE----- ISRG Root X2 ============ -----BEGIN CERTIFICATE----- MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn -----END CERTIFICATE----- HiPKI Root CA - G1 ================== -----BEGIN CERTIFICATE----- MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj 1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF 8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi 7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv 5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== -----END CERTIFICATE----- GlobalSign ECC Root CA - R4 =========================== -----BEGIN CERTIFICATE----- MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm -----END CERTIFICATE----- GTS Root R1 =========== -----BEGIN CERTIFICATE----- MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk 9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ 7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 gm3c -----END CERTIFICATE----- GTS Root R2 =========== -----BEGIN CERTIFICATE----- MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS +LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel /FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M 7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW HYbL -----END CERTIFICATE----- GTS Root R3 =========== -----BEGIN CERTIFICATE----- MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout 736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV 11RZt+cRLInUue4X -----END CERTIFICATE----- GTS Root R4 =========== -----BEGIN CERTIFICATE----- MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh 4rsUecrNIdSUtUlD -----END CERTIFICATE----- Telia Root CA v2 ================ -----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q 9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW 5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ 8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= -----END CERTIFICATE----- D-TRUST BR Root CA 1 2020 ========================= -----BEGIN CERTIFICATE----- MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 -----END CERTIFICATE----- D-TRUST EV Root CA 1 2020 ========================= -----BEGIN CERTIFICATE----- MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW -----END CERTIFICATE----- DigiCert TLS ECC P384 Root G5 ============================= -----BEGIN CERTIFICATE----- MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB /wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== -----END CERTIFICATE----- DigiCert TLS RSA4096 Root G5 ============================ -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh 47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP bEtoL8pU9ozaMv7Da4M/OMZ+ -----END CERTIFICATE----- Certainly Root R1 ================= -----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O 5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl 8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d 8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= -----END CERTIFICATE----- Certainly Root E1 ================= -----BEGIN CERTIFICATE----- MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- Security Communication RootCA3 ============================== -----BEGIN CERTIFICATE----- MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2 /D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC 3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB /wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml +LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9 dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm 6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg== -----END CERTIFICATE----- Security Communication ECC RootCA1 ================================== -----BEGIN CERTIFICATE----- MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo 5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e N9k= -----END CERTIFICATE----- BJCA Global Root CA1 ==================== -----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW 4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx 4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps 3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= -----END CERTIFICATE----- BJCA Global Root CA2 ==================== -----BEGIN CERTIFICATE----- MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK /eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI 1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== -----END CERTIFICATE----- Sectigo Public Server Authentication Root E46 ============================================= -----BEGIN CERTIFICATE----- MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U SAGKcw== -----END CERTIFICATE----- Sectigo Public Server Authentication Root R46 ============================================= -----BEGIN CERTIFICATE----- MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k 1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW 6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M 0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI 84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL -----END CERTIFICATE----- SSL.com TLS RSA Root CA 2022 ============================ -----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u 9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y 7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk 8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk 7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= -----END CERTIFICATE----- SSL.com TLS ECC Root CA 2022 ============================ -----BEGIN CERTIFICATE----- MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w 7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 Zn6g6g== -----END CERTIFICATE----- Atos TrustedRoot Root CA ECC TLS 2021 ===================================== -----BEGIN CERTIFICATE----- MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY a3cpetskz2VAv9LcjBHo9H1/IISpQuQo -----END CERTIFICATE----- Atos TrustedRoot Root CA RSA TLS 2021 ===================================== -----BEGIN CERTIFICATE----- MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt 0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS 4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj 1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W HYMfRsCbvUOZ58SWLs5fyQ== -----END CERTIFICATE----- TrustAsia Global Root CA G3 =========================== -----BEGIN CERTIFICATE----- MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm 9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj 7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys +TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli 2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH -----END CERTIFICATE----- TrustAsia Global Root CA G4 =========================== -----BEGIN CERTIFICATE----- MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== -----END CERTIFICATE----- CommScope Public Trust ECC Root-01 ================================== -----BEGIN CERTIFICATE----- MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot 6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2 Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE= -----END CERTIFICATE----- CommScope Public Trust ECC Root-02 ================================== -----BEGIN CERTIFICATE----- MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9 Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7 3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag== -----END CERTIFICATE----- CommScope Public Trust RSA Root-01 ================================== -----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6 uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9 lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH +VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4 5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM 3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+ o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/ oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc 1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM 6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw -----END CERTIFICATE----- CommScope Public Trust RSA Root-02 ================================== -----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx 7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF 1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN lM47ni3niAIi9G7oyOzWPPO5std3eqx7 -----END CERTIFICATE----- Copyright (C) 2016 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer/ca-bundle ================== Small utility library that lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. Installation ------------ Install the latest version with: ```bash $ composer require composer/ca-bundle ``` Requirements ------------ * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. Basic usage ----------- ### `Composer\CaBundle\CaBundle` - `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback - `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file - `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use - `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse() - `CaBundle::reset()`: Resets the static caches #### To use with curl ```php $curl = curl_init("https://example.org/"); $caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); if (is_dir($caPathOrFile)) { curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile); } else { curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile); } $result = curl_exec($curl); ``` #### To use with php streams ```php $opts = array( 'http' => array( 'method' => "GET" ) ); $caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); if (is_dir($caPathOrFile)) { $opts['ssl']['capath'] = $caPathOrFile; } else { $opts['ssl']['cafile'] = $caPathOrFile; } $context = stream_context_create($opts); $result = file_get_contents('https://example.com', false, $context); ``` #### To use with Guzzle ```php $client = new \GuzzleHttp\Client([ \GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath() ]); ``` License ------- composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details. { "name": "composer\/ca-bundle", "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", "type": "library", "license": "MIT", "keywords": [ "cabundle", "cacert", "certificate", "ssl", "tls" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http:\/\/seld.be" } ], "support": { "irc": "irc:\/\/irc.freenode.org\/composer", "issues": "https:\/\/github.com\/composer\/ca-bundle\/issues" }, "require": { "ext-openssl": "*", "ext-pcre": "*", "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "symfony\/phpunit-bridge": "^4.2 || ^5", "phpstan\/phpstan": "^0.12.55", "psr\/log": "^1.0", "symfony\/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "autoload": { "psr-4": { "Composer\\CaBundle\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\CaBundle\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "scripts": { "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit", "phpstan": "vendor\/bin\/phpstan analyse" } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\CaBundle; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Process\PhpProcess; /** * @author Chris Smith * @author Jordi Boggiano */ class CaBundle { /** @var string|null */ private static $caPath; /** @var array */ private static $caFileValidity = array(); /** @var bool|null */ private static $useOpensslParse; /** * Returns the system CA bundle path, or a path to the bundled one * * This method was adapted from Sslurp. * https://github.com/EvanDotPro/Sslurp * * (c) Evan Coury * * For the full copyright and license information, please see below: * * Copyright (c) 2013, Evan Coury * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @param LoggerInterface $logger optional logger for information about which CA files were loaded * @return string path to a CA bundle file or directory */ public static function getSystemCaRootBundlePath(LoggerInterface $logger = null) { if (self::$caPath !== null) { return self::$caPath; } $caBundlePaths = array(); // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE'); // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR'); $caBundlePaths[] = \ini_get('openssl.cafile'); $caBundlePaths[] = \ini_get('openssl.capath'); $otherLocations = array( '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) '/usr/ssl/certs/ca-bundle.crt', // Cygwin '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? '/etc/ssl/cert.pem', // OpenBSD '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package '/opt/homebrew/etc/openssl@1.1/cert.pem', ); foreach ($otherLocations as $location) { $otherLocations[] = \dirname($location); } $caBundlePaths = \array_merge($caBundlePaths, $otherLocations); foreach ($caBundlePaths as $caBundle) { if ($caBundle && self::caFileUsable($caBundle, $logger)) { return self::$caPath = $caBundle; } if ($caBundle && self::caDirUsable($caBundle, $logger)) { return self::$caPath = $caBundle; } } return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort } /** * Returns the path to the bundled CA file * * In case you don't want to trust the user or the system, you can use this directly * * @return string path to a CA bundle file */ public static function getBundledCaBundlePath() { $caBundleFile = __DIR__ . '/../res/cacert.pem'; // cURL does not understand 'phar://' paths // see https://github.com/composer/ca-bundle/issues/10 if (0 === \strpos($caBundleFile, 'phar://')) { $tempCaBundleFile = \tempnam(\sys_get_temp_dir(), 'openssl-ca-bundle-'); if (\false === $tempCaBundleFile) { throw new \RuntimeException('Could not create a temporary file to store the bundled CA file'); } \file_put_contents($tempCaBundleFile, \file_get_contents($caBundleFile)); \register_shutdown_function(function () use($tempCaBundleFile) { @\unlink($tempCaBundleFile); }); $caBundleFile = $tempCaBundleFile; } return $caBundleFile; } /** * Validates a CA file using opensl_x509_parse only if it is safe to use * * @param string $filename * @param LoggerInterface $logger optional logger for information about which CA files were loaded * * @return bool */ public static function validateCaFile($filename, LoggerInterface $logger = null) { static $warned = \false; if (isset(self::$caFileValidity[$filename])) { return self::$caFileValidity[$filename]; } $contents = \file_get_contents($filename); // assume the CA is valid if php is vulnerable to // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html if (!static::isOpensslParseSafe()) { if (!$warned && $logger) { $logger->warning(\sprintf('Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.', \PHP_VERSION)); $warned = \true; } $isValid = !empty($contents); } elseif (\is_string($contents) && \strlen($contents) > 0) { $contents = \preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents); if (null === $contents) { // regex extraction failed $isValid = \false; } else { $isValid = (bool) \openssl_x509_parse($contents); } } else { $isValid = \false; } if ($logger) { $logger->debug('Checked CA file ' . \realpath($filename) . ': ' . ($isValid ? 'valid' : 'invalid')); } return self::$caFileValidity[$filename] = $isValid; } /** * Test if it is safe to use the PHP function openssl_x509_parse(). * * This checks if OpenSSL extensions is vulnerable to remote code execution * via the exploit documented as CVE-2013-6420. * * @return bool */ public static function isOpensslParseSafe() { if (null !== self::$useOpensslParse) { return self::$useOpensslParse; } if (\PHP_VERSION_ID >= 50600) { return self::$useOpensslParse = \true; } // Vulnerable: // PHP 5.3.0 - PHP 5.3.27 // PHP 5.4.0 - PHP 5.4.22 // PHP 5.5.0 - PHP 5.5.6 if (\PHP_VERSION_ID < 50400 && \PHP_VERSION_ID >= 50328 || \PHP_VERSION_ID < 50500 && \PHP_VERSION_ID >= 50423 || \PHP_VERSION_ID >= 50507) { // This version of PHP has the fix for CVE-2013-6420 applied. return self::$useOpensslParse = \true; } if (\defined('PHP_WINDOWS_VERSION_BUILD')) { // Windows is probably insecure in this case. return self::$useOpensslParse = \false; } $compareDistroVersionPrefix = function ($prefix, $fixedVersion) { $regex = '{^' . \preg_quote($prefix) . '([0-9]+)$}'; if (\preg_match($regex, \PHP_VERSION, $m)) { return (int) $m[1] >= $fixedVersion; } return \false; }; // Hard coded list of PHP distributions with the fix backported. if ($compareDistroVersionPrefix('5.3.3-7+squeeze', 18) || $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) || $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9)) { return self::$useOpensslParse = \true; } // Symfony Process component is missing so we assume it is unsafe at this point if (!\class_exists('_ContaoManager\\Symfony\\Component\\Process\\PhpProcess')) { return self::$useOpensslParse = \false; } // This is where things get crazy, because distros backport security // fixes the chances are on NIX systems the fix has been applied but // it's not possible to verify that from the PHP version. // // To verify exec a new PHP process and run the issue testcase with // known safe input that replicates the bug. // Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415 // changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593 $cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K'; $script = <<<'EOT' error_reporting(-1); $info = openssl_x509_parse(base64_decode('%s')); var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']); EOT; $script = '<' . "?php\n" . \sprintf($script, $cert); try { $process = new PhpProcess($script); $process->mustRun(); } catch (\Exception $e) { // In the case of any exceptions just accept it is not possible to // determine the safety of openssl_x509_parse and bail out. return self::$useOpensslParse = \false; } $output = \preg_split('{\\r?\\n}', \trim($process->getOutput())); $errorOutput = \trim($process->getErrorOutput()); if (\is_array($output) && \count($output) === 3 && $output[0] === \sprintf('string(%d) "%s"', \strlen(\PHP_VERSION), \PHP_VERSION) && $output[1] === 'string(27) "stefan.esser@sektioneins.de"' && $output[2] === 'int(-1)' && \preg_match('{openssl_x509_parse\\(\\): illegal (?:ASN1 data type for|length in) timestamp in - on line \\d+}', $errorOutput)) { // This PHP has the fix backported probably by a distro security team. return self::$useOpensslParse = \true; } return self::$useOpensslParse = \false; } /** * Resets the static caches * @return void */ public static function reset() { self::$caFileValidity = array(); self::$caPath = null; self::$useOpensslParse = null; } /** * @param string $name * @return string|false */ private static function getEnvVariable($name) { if (isset($_SERVER[$name])) { return (string) $_SERVER[$name]; } if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== \false && $value !== null) { return (string) $value; } return \false; } /** * @param string|false $certFile * @param LoggerInterface|null $logger * @return bool */ private static function caFileUsable($certFile, LoggerInterface $logger = null) { return $certFile && static::isFile($certFile, $logger) && static::isReadable($certFile, $logger) && static::validateCaFile($certFile, $logger); } /** * @param string|false $certDir * @param LoggerInterface|null $logger * @return bool */ private static function caDirUsable($certDir, LoggerInterface $logger = null) { return $certDir && static::isDir($certDir, $logger) && static::isReadable($certDir, $logger) && static::glob($certDir . '/*', $logger); } /** * @param string $certFile * @param LoggerInterface|null $logger * @return bool */ private static function isFile($certFile, LoggerInterface $logger = null) { $isFile = @\is_file($certFile); if (!$isFile && $logger) { $logger->debug(\sprintf('Checked CA file %s does not exist or it is not a file.', $certFile)); } return $isFile; } /** * @param string $certDir * @param LoggerInterface|null $logger * @return bool */ private static function isDir($certDir, LoggerInterface $logger = null) { $isDir = @\is_dir($certDir); if (!$isDir && $logger) { $logger->debug(\sprintf('Checked directory %s does not exist or it is not a directory.', $certDir)); } return $isDir; } /** * @param string $certFileOrDir * @param LoggerInterface|null $logger * @return bool */ private static function isReadable($certFileOrDir, LoggerInterface $logger = null) { $isReadable = @\is_readable($certFileOrDir); if (!$isReadable && $logger) { $logger->debug(\sprintf('Checked file or directory %s is not readable.', $certFileOrDir)); } return $isReadable; } /** * @param string $pattern * @param LoggerInterface|null $logger * @return bool */ private static function glob($pattern, LoggerInterface $logger = null) { $certs = \glob($pattern); if ($certs === \false) { if ($logger) { $logger->debug(\sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern)); } return \false; } if (\count($certs) === 0) { if ($logger) { $logger->debug(\sprintf("No CA files found for pattern: %s", $pattern)); } return \false; } return \true; } } array($vendorDir . '/studio24/rotate/src'), '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\' => array($vendorDir . '/terminal42/service-annotation-bundle/src'), '_ContaoManager\\Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), '_ContaoManager\\Symfony\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), '_ContaoManager\\Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), '_ContaoManager\\Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), '_ContaoManager\\Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), '_ContaoManager\\Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), '_ContaoManager\\Symfony\\Component\\Security\\Http\\' => array($vendorDir . '/symfony/security-http'), '_ContaoManager\\Symfony\\Component\\Security\\Guard\\' => array($vendorDir . '/symfony/security-guard'), '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\' => array($vendorDir . '/symfony/security-csrf'), '_ContaoManager\\Symfony\\Component\\Security\\Core\\' => array($vendorDir . '/symfony/security-core'), '_ContaoManager\\Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), '_ContaoManager\\Symfony\\Component\\PropertyInfo\\' => array($vendorDir . '/symfony/property-info'), '_ContaoManager\\Symfony\\Component\\PropertyAccess\\' => array($vendorDir . '/symfony/property-access'), '_ContaoManager\\Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), '_ContaoManager\\Symfony\\Component\\PasswordHasher\\' => array($vendorDir . '/symfony/password-hasher'), '_ContaoManager\\Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), '_ContaoManager\\Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), '_ContaoManager\\Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), '_ContaoManager\\Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), '_ContaoManager\\Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), '_ContaoManager\\Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'), '_ContaoManager\\Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'), '_ContaoManager\\Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), '_ContaoManager\\Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), '_ContaoManager\\Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\' => array($vendorDir . '/symfony/security-bundle'), '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\' => array($vendorDir . '/symfony/monolog-bundle'), '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\' => array($vendorDir . '/symfony/framework-bundle'), '_ContaoManager\\Symfony\\Bridge\\Monolog\\' => array($vendorDir . '/symfony/monolog-bridge'), '_ContaoManager\\Seld\\Signal\\' => array($vendorDir . '/seld/signal-handler/src'), '_ContaoManager\\Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'), '_ContaoManager\\Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'), '_ContaoManager\\Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), '_ContaoManager\\Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), '_ContaoManager\\Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), '_ContaoManager\\Psr\\Container\\' => array($vendorDir . '/psr/container/src'), '_ContaoManager\\Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), '_ContaoManager\\Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), '_ContaoManager\\JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), '_ContaoManager\\Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), '_ContaoManager\\Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'), '_ContaoManager\\Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'), '_ContaoManager\\Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), '_ContaoManager\\Crell\\ApiProblem\\' => array($vendorDir . '/crell/api-problem/src'), '_ContaoManager\\Contao\\ManagerApi\\' => array($baseDir . '/api'), 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), 'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'), 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), 'Composer\\MetadataMinifier\\' => array($vendorDir . '/composer/metadata-minifier/src'), 'Composer\\ClassMapGenerator\\' => array($vendorDir . '/composer/class-map-generator/src'), 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), 'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'), ); $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\Advisory\\Auditor' => $vendorDir . '/composer/composer/src/Composer/Advisory/Auditor.php', 'Composer\\Advisory\\IgnoredSecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php', 'Composer\\Advisory\\PartialSecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php', 'Composer\\Advisory\\SecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/SecurityAdvisory.php', 'Composer\\Autoload\\AutoloadGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php', 'Composer\\Autoload\\ClassLoader' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassLoader.php', 'Composer\\Autoload\\ClassMapGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php', 'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php', 'Composer\\Cache' => $vendorDir . '/composer/composer/src/Composer/Cache.php', 'Composer\\ClassMapGenerator\\ClassMap' => $vendorDir . '/composer/class-map-generator/src/ClassMap.php', 'Composer\\ClassMapGenerator\\ClassMapGenerator' => $vendorDir . '/composer/class-map-generator/src/ClassMapGenerator.php', 'Composer\\ClassMapGenerator\\FileList' => $vendorDir . '/composer/class-map-generator/src/FileList.php', 'Composer\\ClassMapGenerator\\PhpFileCleaner' => $vendorDir . '/composer/class-map-generator/src/PhpFileCleaner.php', 'Composer\\ClassMapGenerator\\PhpFileParser' => $vendorDir . '/composer/class-map-generator/src/PhpFileParser.php', 'Composer\\Command\\AboutCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AboutCommand.php', 'Composer\\Command\\ArchiveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ArchiveCommand.php', 'Composer\\Command\\AuditCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AuditCommand.php', 'Composer\\Command\\BaseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseCommand.php', 'Composer\\Command\\BaseDependencyCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php', 'Composer\\Command\\BumpCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BumpCommand.php', 'Composer\\Command\\CheckPlatformReqsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php', 'Composer\\Command\\ClearCacheCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ClearCacheCommand.php', 'Composer\\Command\\CompletionTrait' => $vendorDir . '/composer/composer/src/Composer/Command/CompletionTrait.php', 'Composer\\Command\\ConfigCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ConfigCommand.php', 'Composer\\Command\\CreateProjectCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CreateProjectCommand.php', 'Composer\\Command\\DependsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DependsCommand.php', 'Composer\\Command\\DiagnoseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DiagnoseCommand.php', 'Composer\\Command\\DumpAutoloadCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php', 'Composer\\Command\\ExecCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ExecCommand.php', 'Composer\\Command\\FundCommand' => $vendorDir . '/composer/composer/src/Composer/Command/FundCommand.php', 'Composer\\Command\\GlobalCommand' => $vendorDir . '/composer/composer/src/Composer/Command/GlobalCommand.php', 'Composer\\Command\\HomeCommand' => $vendorDir . '/composer/composer/src/Composer/Command/HomeCommand.php', 'Composer\\Command\\InitCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InitCommand.php', 'Composer\\Command\\InstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InstallCommand.php', 'Composer\\Command\\LicensesCommand' => $vendorDir . '/composer/composer/src/Composer/Command/LicensesCommand.php', 'Composer\\Command\\OutdatedCommand' => $vendorDir . '/composer/composer/src/Composer/Command/OutdatedCommand.php', 'Composer\\Command\\PackageDiscoveryTrait' => $vendorDir . '/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php', 'Composer\\Command\\ProhibitsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ProhibitsCommand.php', 'Composer\\Command\\ReinstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ReinstallCommand.php', 'Composer\\Command\\RemoveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RemoveCommand.php', 'Composer\\Command\\RequireCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RequireCommand.php', 'Composer\\Command\\RunScriptCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RunScriptCommand.php', 'Composer\\Command\\ScriptAliasCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php', 'Composer\\Command\\SearchCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SearchCommand.php', 'Composer\\Command\\SelfUpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php', 'Composer\\Command\\ShowCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ShowCommand.php', 'Composer\\Command\\StatusCommand' => $vendorDir . '/composer/composer/src/Composer/Command/StatusCommand.php', 'Composer\\Command\\SuggestsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SuggestsCommand.php', 'Composer\\Command\\UpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/UpdateCommand.php', 'Composer\\Command\\ValidateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ValidateCommand.php', 'Composer\\Compiler' => $vendorDir . '/composer/composer/src/Composer/Compiler.php', 'Composer\\Composer' => $vendorDir . '/composer/composer/src/Composer/Composer.php', 'Composer\\Config' => $vendorDir . '/composer/composer/src/Composer/Config.php', 'Composer\\Config\\ConfigSourceInterface' => $vendorDir . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php', 'Composer\\Config\\JsonConfigSource' => $vendorDir . '/composer/composer/src/Composer/Config/JsonConfigSource.php', 'Composer\\Console\\Application' => $vendorDir . '/composer/composer/src/Composer/Console/Application.php', 'Composer\\Console\\GithubActionError' => $vendorDir . '/composer/composer/src/Composer/Console/GithubActionError.php', 'Composer\\Console\\HtmlOutputFormatter' => $vendorDir . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php', 'Composer\\Console\\Input\\InputArgument' => $vendorDir . '/composer/composer/src/Composer/Console/Input/InputArgument.php', 'Composer\\Console\\Input\\InputOption' => $vendorDir . '/composer/composer/src/Composer/Console/Input/InputOption.php', 'Composer\\DependencyResolver\\Decisions' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Decisions.php', 'Composer\\DependencyResolver\\DefaultPolicy' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php', 'Composer\\DependencyResolver\\GenericRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php', 'Composer\\DependencyResolver\\LocalRepoTransaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php', 'Composer\\DependencyResolver\\LockTransaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/LockTransaction.php', 'Composer\\DependencyResolver\\MultiConflictRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php', 'Composer\\DependencyResolver\\Operation\\InstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php', 'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php', 'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php', 'Composer\\DependencyResolver\\Operation\\OperationInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php', 'Composer\\DependencyResolver\\Operation\\SolverOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php', 'Composer\\DependencyResolver\\Operation\\UninstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php', 'Composer\\DependencyResolver\\Operation\\UpdateOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php', 'Composer\\DependencyResolver\\PolicyInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php', 'Composer\\DependencyResolver\\Pool' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Pool.php', 'Composer\\DependencyResolver\\PoolBuilder' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php', 'Composer\\DependencyResolver\\PoolOptimizer' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php', 'Composer\\DependencyResolver\\Problem' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Problem.php', 'Composer\\DependencyResolver\\Request' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Request.php', 'Composer\\DependencyResolver\\Rule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule.php', 'Composer\\DependencyResolver\\Rule2Literals' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php', 'Composer\\DependencyResolver\\RuleSet' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php', 'Composer\\DependencyResolver\\RuleSetGenerator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php', 'Composer\\DependencyResolver\\RuleSetIterator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php', 'Composer\\DependencyResolver\\RuleWatchChain' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php', 'Composer\\DependencyResolver\\RuleWatchGraph' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php', 'Composer\\DependencyResolver\\RuleWatchNode' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php', 'Composer\\DependencyResolver\\Solver' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Solver.php', 'Composer\\DependencyResolver\\SolverBugException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php', 'Composer\\DependencyResolver\\SolverProblemsException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php', 'Composer\\DependencyResolver\\Transaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Transaction.php', 'Composer\\Downloader\\ArchiveDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php', 'Composer\\Downloader\\ChangeReportInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php', 'Composer\\Downloader\\DownloadManager' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloadManager.php', 'Composer\\Downloader\\DownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php', 'Composer\\Downloader\\DvcsDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php', 'Composer\\Downloader\\FileDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FileDownloader.php', 'Composer\\Downloader\\FilesystemException' => $vendorDir . '/composer/composer/src/Composer/Downloader/FilesystemException.php', 'Composer\\Downloader\\FossilDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FossilDownloader.php', 'Composer\\Downloader\\GitDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GitDownloader.php', 'Composer\\Downloader\\GzipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GzipDownloader.php', 'Composer\\Downloader\\HgDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/HgDownloader.php', 'Composer\\Downloader\\MaxFileSizeExceededException' => $vendorDir . '/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php', 'Composer\\Downloader\\PathDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PathDownloader.php', 'Composer\\Downloader\\PerforceDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php', 'Composer\\Downloader\\PharDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PharDownloader.php', 'Composer\\Downloader\\RarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/RarDownloader.php', 'Composer\\Downloader\\SvnDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/SvnDownloader.php', 'Composer\\Downloader\\TarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/TarDownloader.php', 'Composer\\Downloader\\TransportException' => $vendorDir . '/composer/composer/src/Composer/Downloader/TransportException.php', 'Composer\\Downloader\\VcsCapableDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php', 'Composer\\Downloader\\VcsDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsDownloader.php', 'Composer\\Downloader\\XzDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/XzDownloader.php', 'Composer\\Downloader\\ZipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ZipDownloader.php', 'Composer\\EventDispatcher\\Event' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/Event.php', 'Composer\\EventDispatcher\\EventDispatcher' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php', 'Composer\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php', 'Composer\\EventDispatcher\\ScriptExecutionException' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php', 'Composer\\Exception\\IrrecoverableDownloadException' => $vendorDir . '/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php', 'Composer\\Exception\\NoSslException' => $vendorDir . '/composer/composer/src/Composer/Exception/NoSslException.php', 'Composer\\Factory' => $vendorDir . '/composer/composer/src/Composer/Factory.php', 'Composer\\Filter\\PlatformRequirementFilter\\IgnoreAllPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php', 'Composer\\Filter\\PlatformRequirementFilter\\IgnoreListPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php', 'Composer\\Filter\\PlatformRequirementFilter\\IgnoreNothingPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php', 'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterFactory' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php', 'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterInterface' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php', 'Composer\\IO\\BaseIO' => $vendorDir . '/composer/composer/src/Composer/IO/BaseIO.php', 'Composer\\IO\\BufferIO' => $vendorDir . '/composer/composer/src/Composer/IO/BufferIO.php', 'Composer\\IO\\ConsoleIO' => $vendorDir . '/composer/composer/src/Composer/IO/ConsoleIO.php', 'Composer\\IO\\IOInterface' => $vendorDir . '/composer/composer/src/Composer/IO/IOInterface.php', 'Composer\\IO\\NullIO' => $vendorDir . '/composer/composer/src/Composer/IO/NullIO.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\Installer' => $vendorDir . '/composer/composer/src/Composer/Installer.php', 'Composer\\Installer\\BinaryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryInstaller.php', 'Composer\\Installer\\BinaryPresenceInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php', 'Composer\\Installer\\InstallationManager' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallationManager.php', 'Composer\\Installer\\InstallerEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvent.php', 'Composer\\Installer\\InstallerEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvents.php', 'Composer\\Installer\\InstallerInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerInterface.php', 'Composer\\Installer\\LibraryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/LibraryInstaller.php', 'Composer\\Installer\\MetapackageInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php', 'Composer\\Installer\\NoopInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/NoopInstaller.php', 'Composer\\Installer\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvent.php', 'Composer\\Installer\\PackageEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvents.php', 'Composer\\Installer\\PluginInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PluginInstaller.php', 'Composer\\Installer\\ProjectInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/ProjectInstaller.php', 'Composer\\Installer\\SuggestedPackagesReporter' => $vendorDir . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php', 'Composer\\Json\\JsonFile' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFile.php', 'Composer\\Json\\JsonFormatter' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFormatter.php', 'Composer\\Json\\JsonManipulator' => $vendorDir . '/composer/composer/src/Composer/Json/JsonManipulator.php', 'Composer\\Json\\JsonValidationException' => $vendorDir . '/composer/composer/src/Composer/Json/JsonValidationException.php', 'Composer\\MetadataMinifier\\MetadataMinifier' => $vendorDir . '/composer/metadata-minifier/src/MetadataMinifier.php', 'Composer\\PHPStan\\ConfigReturnTypeExtension' => $vendorDir . '/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php', 'Composer\\PHPStan\\RuleReasonDataReturnTypeExtension' => $vendorDir . '/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php', 'Composer\\Package\\AliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/AliasPackage.php', 'Composer\\Package\\Archiver\\ArchivableFilesFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php', 'Composer\\Package\\Archiver\\ArchivableFilesFinder' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php', 'Composer\\Package\\Archiver\\ArchiveManager' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php', 'Composer\\Package\\Archiver\\ArchiverInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php', 'Composer\\Package\\Archiver\\BaseExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php', 'Composer\\Package\\Archiver\\ComposerExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php', 'Composer\\Package\\Archiver\\GitExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php', 'Composer\\Package\\Archiver\\PharArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php', 'Composer\\Package\\Archiver\\ZipArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php', 'Composer\\Package\\BasePackage' => $vendorDir . '/composer/composer/src/Composer/Package/BasePackage.php', 'Composer\\Package\\Comparer\\Comparer' => $vendorDir . '/composer/composer/src/Composer/Package/Comparer/Comparer.php', 'Composer\\Package\\CompleteAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompleteAliasPackage.php', 'Composer\\Package\\CompletePackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackage.php', 'Composer\\Package\\CompletePackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackageInterface.php', 'Composer\\Package\\Dumper\\ArrayDumper' => $vendorDir . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php', 'Composer\\Package\\Link' => $vendorDir . '/composer/composer/src/Composer/Package/Link.php', 'Composer\\Package\\Loader\\ArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php', 'Composer\\Package\\Loader\\InvalidPackageException' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php', 'Composer\\Package\\Loader\\JsonLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php', 'Composer\\Package\\Loader\\LoaderInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php', 'Composer\\Package\\Loader\\RootPackageLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php', 'Composer\\Package\\Loader\\ValidatingArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php', 'Composer\\Package\\Locker' => $vendorDir . '/composer/composer/src/Composer/Package/Locker.php', 'Composer\\Package\\Package' => $vendorDir . '/composer/composer/src/Composer/Package/Package.php', 'Composer\\Package\\PackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/PackageInterface.php', 'Composer\\Package\\RootAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootAliasPackage.php', 'Composer\\Package\\RootPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackage.php', 'Composer\\Package\\RootPackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackageInterface.php', 'Composer\\Package\\Version\\StabilityFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Version/StabilityFilter.php', 'Composer\\Package\\Version\\VersionBumper' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionBumper.php', 'Composer\\Package\\Version\\VersionGuesser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php', 'Composer\\Package\\Version\\VersionParser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionParser.php', 'Composer\\Package\\Version\\VersionSelector' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionSelector.php', 'Composer\\PartialComposer' => $vendorDir . '/composer/composer/src/Composer/PartialComposer.php', 'Composer\\Pcre\\MatchAllResult' => $vendorDir . '/composer/pcre/src/MatchAllResult.php', 'Composer\\Pcre\\MatchAllStrictGroupsResult' => $vendorDir . '/composer/pcre/src/MatchAllStrictGroupsResult.php', 'Composer\\Pcre\\MatchAllWithOffsetsResult' => $vendorDir . '/composer/pcre/src/MatchAllWithOffsetsResult.php', 'Composer\\Pcre\\MatchResult' => $vendorDir . '/composer/pcre/src/MatchResult.php', 'Composer\\Pcre\\MatchStrictGroupsResult' => $vendorDir . '/composer/pcre/src/MatchStrictGroupsResult.php', 'Composer\\Pcre\\MatchWithOffsetsResult' => $vendorDir . '/composer/pcre/src/MatchWithOffsetsResult.php', 'Composer\\Pcre\\PcreException' => $vendorDir . '/composer/pcre/src/PcreException.php', 'Composer\\Pcre\\Preg' => $vendorDir . '/composer/pcre/src/Preg.php', 'Composer\\Pcre\\Regex' => $vendorDir . '/composer/pcre/src/Regex.php', 'Composer\\Pcre\\ReplaceResult' => $vendorDir . '/composer/pcre/src/ReplaceResult.php', 'Composer\\Pcre\\UnexpectedNullMatchException' => $vendorDir . '/composer/pcre/src/UnexpectedNullMatchException.php', 'Composer\\Platform\\HhvmDetector' => $vendorDir . '/composer/composer/src/Composer/Platform/HhvmDetector.php', 'Composer\\Platform\\Runtime' => $vendorDir . '/composer/composer/src/Composer/Platform/Runtime.php', 'Composer\\Platform\\Version' => $vendorDir . '/composer/composer/src/Composer/Platform/Version.php', 'Composer\\Plugin\\Capability\\Capability' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/Capability.php', 'Composer\\Plugin\\Capability\\CommandProvider' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php', 'Composer\\Plugin\\Capable' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capable.php', 'Composer\\Plugin\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/CommandEvent.php', 'Composer\\Plugin\\PluginBlockedException' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginBlockedException.php', 'Composer\\Plugin\\PluginEvents' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginEvents.php', 'Composer\\Plugin\\PluginInterface' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginInterface.php', 'Composer\\Plugin\\PluginManager' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginManager.php', 'Composer\\Plugin\\PostFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php', 'Composer\\Plugin\\PreCommandRunEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php', 'Composer\\Plugin\\PreFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php', 'Composer\\Plugin\\PrePoolCreateEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php', 'Composer\\Question\\StrictConfirmationQuestion' => $vendorDir . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php', 'Composer\\Repository\\AdvisoryProviderInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php', 'Composer\\Repository\\ArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArrayRepository.php', 'Composer\\Repository\\ArtifactRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArtifactRepository.php', 'Composer\\Repository\\CanonicalPackagesTrait' => $vendorDir . '/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php', 'Composer\\Repository\\ComposerRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ComposerRepository.php', 'Composer\\Repository\\CompositeRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/CompositeRepository.php', 'Composer\\Repository\\ConfigurableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php', 'Composer\\Repository\\FilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilesystemRepository.php', 'Composer\\Repository\\FilterRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilterRepository.php', 'Composer\\Repository\\InstalledArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php', 'Composer\\Repository\\InstalledFilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php', 'Composer\\Repository\\InstalledRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepository.php', 'Composer\\Repository\\InstalledRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php', 'Composer\\Repository\\InvalidRepositoryException' => $vendorDir . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php', 'Composer\\Repository\\LockArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/LockArrayRepository.php', 'Composer\\Repository\\PackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PackageRepository.php', 'Composer\\Repository\\PathRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PathRepository.php', 'Composer\\Repository\\PearRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PearRepository.php', 'Composer\\Repository\\PlatformRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PlatformRepository.php', 'Composer\\Repository\\RepositoryFactory' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryFactory.php', 'Composer\\Repository\\RepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryInterface.php', 'Composer\\Repository\\RepositoryManager' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryManager.php', 'Composer\\Repository\\RepositorySecurityException' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php', 'Composer\\Repository\\RepositorySet' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySet.php', 'Composer\\Repository\\RepositoryUtils' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryUtils.php', 'Composer\\Repository\\RootPackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/RootPackageRepository.php', 'Composer\\Repository\\VcsRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/VcsRepository.php', 'Composer\\Repository\\Vcs\\FossilDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php', 'Composer\\Repository\\Vcs\\GitBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php', 'Composer\\Repository\\Vcs\\GitDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php', 'Composer\\Repository\\Vcs\\GitHubDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php', 'Composer\\Repository\\Vcs\\GitLabDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php', 'Composer\\Repository\\Vcs\\HgDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php', 'Composer\\Repository\\Vcs\\PerforceDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php', 'Composer\\Repository\\Vcs\\SvnDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php', 'Composer\\Repository\\Vcs\\VcsDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php', 'Composer\\Repository\\Vcs\\VcsDriverInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php', 'Composer\\Repository\\VersionCacheInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php', 'Composer\\Repository\\WritableArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php', 'Composer\\Repository\\WritableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php', 'Composer\\Script\\Event' => $vendorDir . '/composer/composer/src/Composer/Script/Event.php', 'Composer\\Script\\ScriptEvents' => $vendorDir . '/composer/composer/src/Composer/Script/ScriptEvents.php', 'Composer\\SelfUpdate\\Keys' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Keys.php', 'Composer\\SelfUpdate\\Versions' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Versions.php', 'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php', 'Composer\\Semver\\CompilingMatcher' => $vendorDir . '/composer/semver/src/CompilingMatcher.php', 'Composer\\Semver\\Constraint\\Bound' => $vendorDir . '/composer/semver/src/Constraint/Bound.php', 'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php', 'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php', 'Composer\\Semver\\Constraint\\MatchAllConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchAllConstraint.php', 'Composer\\Semver\\Constraint\\MatchNoneConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchNoneConstraint.php', 'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php', 'Composer\\Semver\\Interval' => $vendorDir . '/composer/semver/src/Interval.php', 'Composer\\Semver\\Intervals' => $vendorDir . '/composer/semver/src/Intervals.php', 'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php', 'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php', 'Composer\\Spdx\\SpdxLicenses' => $vendorDir . '/composer/spdx-licenses/src/SpdxLicenses.php', 'Composer\\Util\\AuthHelper' => $vendorDir . '/composer/composer/src/Composer/Util/AuthHelper.php', 'Composer\\Util\\Bitbucket' => $vendorDir . '/composer/composer/src/Composer/Util/Bitbucket.php', 'Composer\\Util\\ComposerMirror' => $vendorDir . '/composer/composer/src/Composer/Util/ComposerMirror.php', 'Composer\\Util\\ConfigValidator' => $vendorDir . '/composer/composer/src/Composer/Util/ConfigValidator.php', 'Composer\\Util\\ErrorHandler' => $vendorDir . '/composer/composer/src/Composer/Util/ErrorHandler.php', 'Composer\\Util\\Filesystem' => $vendorDir . '/composer/composer/src/Composer/Util/Filesystem.php', 'Composer\\Util\\Git' => $vendorDir . '/composer/composer/src/Composer/Util/Git.php', 'Composer\\Util\\GitHub' => $vendorDir . '/composer/composer/src/Composer/Util/GitHub.php', 'Composer\\Util\\GitLab' => $vendorDir . '/composer/composer/src/Composer/Util/GitLab.php', 'Composer\\Util\\Hg' => $vendorDir . '/composer/composer/src/Composer/Util/Hg.php', 'Composer\\Util\\HttpDownloader' => $vendorDir . '/composer/composer/src/Composer/Util/HttpDownloader.php', 'Composer\\Util\\Http\\CurlDownloader' => $vendorDir . '/composer/composer/src/Composer/Util/Http/CurlDownloader.php', 'Composer\\Util\\Http\\CurlResponse' => $vendorDir . '/composer/composer/src/Composer/Util/Http/CurlResponse.php', 'Composer\\Util\\Http\\ProxyHelper' => $vendorDir . '/composer/composer/src/Composer/Util/Http/ProxyHelper.php', 'Composer\\Util\\Http\\ProxyManager' => $vendorDir . '/composer/composer/src/Composer/Util/Http/ProxyManager.php', 'Composer\\Util\\Http\\RequestProxy' => $vendorDir . '/composer/composer/src/Composer/Util/Http/RequestProxy.php', 'Composer\\Util\\Http\\Response' => $vendorDir . '/composer/composer/src/Composer/Util/Http/Response.php', 'Composer\\Util\\IniHelper' => $vendorDir . '/composer/composer/src/Composer/Util/IniHelper.php', 'Composer\\Util\\Loop' => $vendorDir . '/composer/composer/src/Composer/Util/Loop.php', 'Composer\\Util\\MetadataMinifier' => $vendorDir . '/composer/composer/src/Composer/Util/MetadataMinifier.php', 'Composer\\Util\\NoProxyPattern' => $vendorDir . '/composer/composer/src/Composer/Util/NoProxyPattern.php', 'Composer\\Util\\PackageInfo' => $vendorDir . '/composer/composer/src/Composer/Util/PackageInfo.php', 'Composer\\Util\\PackageSorter' => $vendorDir . '/composer/composer/src/Composer/Util/PackageSorter.php', 'Composer\\Util\\Perforce' => $vendorDir . '/composer/composer/src/Composer/Util/Perforce.php', 'Composer\\Util\\Platform' => $vendorDir . '/composer/composer/src/Composer/Util/Platform.php', 'Composer\\Util\\ProcessExecutor' => $vendorDir . '/composer/composer/src/Composer/Util/ProcessExecutor.php', 'Composer\\Util\\RemoteFilesystem' => $vendorDir . '/composer/composer/src/Composer/Util/RemoteFilesystem.php', 'Composer\\Util\\Silencer' => $vendorDir . '/composer/composer/src/Composer/Util/Silencer.php', 'Composer\\Util\\StreamContextFactory' => $vendorDir . '/composer/composer/src/Composer/Util/StreamContextFactory.php', 'Composer\\Util\\Svn' => $vendorDir . '/composer/composer/src/Composer/Util/Svn.php', 'Composer\\Util\\SyncHelper' => $vendorDir . '/composer/composer/src/Composer/Util/SyncHelper.php', 'Composer\\Util\\Tar' => $vendorDir . '/composer/composer/src/Composer/Util/Tar.php', 'Composer\\Util\\TlsHelper' => $vendorDir . '/composer/composer/src/Composer/Util/TlsHelper.php', 'Composer\\Util\\Url' => $vendorDir . '/composer/composer/src/Composer/Util/Url.php', 'Composer\\Util\\Zip' => $vendorDir . '/composer/composer/src/Composer/Util/Zip.php', 'Composer\\XdebugHandler\\PhpConfig' => $vendorDir . '/composer/xdebug-handler/src/PhpConfig.php', 'Composer\\XdebugHandler\\Process' => $vendorDir . '/composer/xdebug-handler/src/Process.php', 'Composer\\XdebugHandler\\Status' => $vendorDir . '/composer/xdebug-handler/src/Status.php', 'Composer\\XdebugHandler\\XdebugHandler' => $vendorDir . '/composer/xdebug-handler/src/XdebugHandler.php', 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'React\\Promise\\Deferred' => $vendorDir . '/react/promise/src/Deferred.php', 'React\\Promise\\Exception\\CompositeException' => $vendorDir . '/react/promise/src/Exception/CompositeException.php', 'React\\Promise\\Exception\\LengthException' => $vendorDir . '/react/promise/src/Exception/LengthException.php', 'React\\Promise\\Internal\\CancellationQueue' => $vendorDir . '/react/promise/src/Internal/CancellationQueue.php', 'React\\Promise\\Internal\\FulfilledPromise' => $vendorDir . '/react/promise/src/Internal/FulfilledPromise.php', 'React\\Promise\\Internal\\RejectedPromise' => $vendorDir . '/react/promise/src/Internal/RejectedPromise.php', 'React\\Promise\\Promise' => $vendorDir . '/react/promise/src/Promise.php', 'React\\Promise\\PromiseInterface' => $vendorDir . '/react/promise/src/PromiseInterface.php', 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.php', 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', 'Symfony\\Polyfill\\Php81\\Php81' => $vendorDir . '/symfony/polyfill-php81/Php81.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', '_ContaoManager\\Contao\\ManagerApi\\ApiApplication' => $baseDir . '/api/ApiApplication.php', '_ContaoManager\\Contao\\ManagerApi\\ApiKernel' => $baseDir . '/api/ApiKernel.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\AboutCommand' => $baseDir . '/api/Command/AboutCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\IntegrityCheckCommand' => $baseDir . '/api/Command/IntegrityCheckCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\ProcessRunnerCommand' => $baseDir . '/api/Command/ProcessRunnerCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\TaskAbortCommand' => $baseDir . '/api/Command/TaskAbortCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\TaskDeleteCommand' => $baseDir . '/api/Command/TaskDeleteCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\TaskUpdateCommand' => $baseDir . '/api/Command/TaskUpdateCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\UpdateCommand' => $baseDir . '/api/Command/UpdateCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudChanges' => $baseDir . '/api/Composer/CloudChanges.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudException' => $baseDir . '/api/Composer/CloudException.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudJob' => $baseDir . '/api/Composer/CloudJob.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudResolver' => $baseDir . '/api/Composer/CloudResolver.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\Environment' => $baseDir . '/api/Composer/Environment.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\AbstractConfig' => $baseDir . '/api/Config/AbstractConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\AuthConfig' => $baseDir . '/api/Config/AuthConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\ComposerConfig' => $baseDir . '/api/Config/ComposerConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\ManagerConfig' => $baseDir . '/api/Config/ManagerConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\PartialConfig' => $baseDir . '/api/Config/PartialConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\UploadsConfig' => $baseDir . '/api/Config/UploadsConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\UserConfig' => $baseDir . '/api/Config/UserConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\AbstractConfigController' => $baseDir . '/api/Controller/Config/AbstractConfigController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\AuthController' => $baseDir . '/api/Controller/Config/AuthController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\ComposerController' => $baseDir . '/api/Controller/Config/ComposerController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\ManagerController' => $baseDir . '/api/Controller/Config/ManagerController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\ConstraintController' => $baseDir . '/api/Controller/ConstraintController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\AccessKeyController' => $baseDir . '/api/Controller/Contao/AccessKeyController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\BackupController' => $baseDir . '/api/Controller/Contao/BackupController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\DatabaseMigrationController' => $baseDir . '/api/Controller/Contao/DatabaseMigrationController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\InstallToolLockController' => $baseDir . '/api/Controller/Contao/InstallToolLockController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\JwtCookieController' => $baseDir . '/api/Controller/Contao/JwtCookieController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\MaintenanceModeController' => $baseDir . '/api/Controller/Contao/MaintenanceModeController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\FileController' => $baseDir . '/api/Controller/FileController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\LogController' => $baseDir . '/api/Controller/LogController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\CloudController' => $baseDir . '/api/Controller/Packages/CloudController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\LocalPackagesController' => $baseDir . '/api/Controller/Packages/LocalPackagesController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\MissingPackagesController' => $baseDir . '/api/Controller/Packages/MissingPackagesController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\RootPackageController' => $baseDir . '/api/Controller/Packages/RootPackageController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\UploadPackagesController' => $baseDir . '/api/Controller/Packages/UploadPackagesController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\AdminUserController' => $baseDir . '/api/Controller/Server/AdminUserController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\ComposerController' => $baseDir . '/api/Controller/Server/ComposerController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\ConfigController' => $baseDir . '/api/Controller/Server/ConfigController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\ContaoController' => $baseDir . '/api/Controller/Server/ContaoController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\DatabaseController' => $baseDir . '/api/Controller/Server/DatabaseController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\OpcacheController' => $baseDir . '/api/Controller/Server/OpcacheController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\PhpCliController' => $baseDir . '/api/Controller/Server/PhpCliController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\PhpWebController' => $baseDir . '/api/Controller/Server/PhpWebController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\PhpinfoController' => $baseDir . '/api/Controller/Server/PhpinfoController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\SelfUpdateController' => $baseDir . '/api/Controller/Server/SelfUpdateController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\SessionController' => $baseDir . '/api/Controller/SessionController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\TaskController' => $baseDir . '/api/Controller/TaskController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\UserController' => $baseDir . '/api/Controller/UserController.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\ExceptionListener' => $baseDir . '/api/EventListener/ExceptionListener.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\JsonRequestListener' => $baseDir . '/api/EventListener/JsonRequestListener.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\LocaleListener' => $baseDir . '/api/EventListener/LocaleListener.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\SecurityListener' => $baseDir . '/api/EventListener/SecurityListener.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\ApiProblemException' => $baseDir . '/api/Exception/ApiProblemException.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\InvalidJsonException' => $baseDir . '/api/Exception/InvalidJsonException.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\ProcessOutputException' => $baseDir . '/api/Exception/ProcessOutputException.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\RequestException' => $baseDir . '/api/Exception/RequestException.php', '_ContaoManager\\Contao\\ManagerApi\\HttpKernel\\ApiProblemResponse' => $baseDir . '/api/HttpKernel/ApiProblemResponse.php', '_ContaoManager\\Contao\\ManagerApi\\I18n\\Translator' => $baseDir . '/api/I18n/Translator.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\AbstractIntegrityCheck' => $baseDir . '/api/IntegrityCheck/AbstractIntegrityCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\AllowUrlFopenCheck' => $baseDir . '/api/IntegrityCheck/AllowUrlFopenCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\GraphicsLibCheck' => $baseDir . '/api/IntegrityCheck/GraphicsLibCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\IntegrityCheckFactory' => $baseDir . '/api/IntegrityCheck/IntegrityCheckFactory.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\IntegrityCheckInterface' => $baseDir . '/api/IntegrityCheck/IntegrityCheckInterface.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\MemoryLimitCheck' => $baseDir . '/api/IntegrityCheck/MemoryLimitCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\PhpExtensionsCheck' => $baseDir . '/api/IntegrityCheck/PhpExtensionsCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\ProcessCheck' => $baseDir . '/api/IntegrityCheck/ProcessCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\SessionCheck' => $baseDir . '/api/IntegrityCheck/SessionCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\SymlinkCheck' => $baseDir . '/api/IntegrityCheck/SymlinkCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\SysTempDirCheck' => $baseDir . '/api/IntegrityCheck/SysTempDirCheck.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\AbstractProcess' => $baseDir . '/api/Process/AbstractProcess.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ConsoleProcessFactory' => $baseDir . '/api/Process/ConsoleProcessFactory.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ContaoApi' => $baseDir . '/api/Process/ContaoApi.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ContaoConsole' => $baseDir . '/api/Process/ContaoConsole.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\AbstractForker' => $baseDir . '/api/Process/Forker/AbstractForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\DisownForker' => $baseDir . '/api/Process/Forker/DisownForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\ForkerInterface' => $baseDir . '/api/Process/Forker/ForkerInterface.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\InlineForker' => $baseDir . '/api/Process/Forker/InlineForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\NohupForker' => $baseDir . '/api/Process/Forker/NohupForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\WindowsStartForker' => $baseDir . '/api/Process/Forker/WindowsStartForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\PhpExecutableFinder' => $baseDir . '/api/Process/PhpExecutableFinder.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ProcessController' => $baseDir . '/api/Process/ProcessController.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ProcessRunner' => $baseDir . '/api/Process/ProcessRunner.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Utf8Process' => $baseDir . '/api/Process/Utf8Process.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\AbstractBrowserAuthenticator' => $baseDir . '/api/Security/AbstractBrowserAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\JwtAuthenticator' => $baseDir . '/api/Security/JwtAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\JwtManager' => $baseDir . '/api/Security/JwtManager.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\LoginAuthenticator' => $baseDir . '/api/Security/LoginAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\PasswordlessAuthenticator' => $baseDir . '/api/Security/PasswordlessAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\TokenAuthenticator' => $baseDir . '/api/Security/TokenAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\User' => $baseDir . '/api/Security/User.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\UserProvider' => $baseDir . '/api/Security/UserProvider.php', '_ContaoManager\\Contao\\ManagerApi\\System\\Request' => $baseDir . '/api/System/Request.php', '_ContaoManager\\Contao\\ManagerApi\\System\\SelfUpdate' => $baseDir . '/api/System/SelfUpdate.php', '_ContaoManager\\Contao\\ManagerApi\\System\\ServerInfo' => $baseDir . '/api/System/ServerInfo.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\AbstractInlineOperation' => $baseDir . '/api/TaskOperation/AbstractInlineOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\AbstractProcessOperation' => $baseDir . '/api/TaskOperation/AbstractProcessOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\ClearCacheOperation' => $baseDir . '/api/TaskOperation/Composer/ClearCacheOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\CloudOperation' => $baseDir . '/api/TaskOperation/Composer/CloudOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\CreateProjectOperation' => $baseDir . '/api/TaskOperation/Composer/CreateProjectOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\DumpAutoloadOperation' => $baseDir . '/api/TaskOperation/Composer/DumpAutoloadOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\InstallOperation' => $baseDir . '/api/TaskOperation/Composer/InstallOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\RemoveOperation' => $baseDir . '/api/TaskOperation/Composer/RemoveOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\RequireOperation' => $baseDir . '/api/TaskOperation/Composer/RequireOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\UpdateOperation' => $baseDir . '/api/TaskOperation/Composer/UpdateOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\ConsoleOutput' => $baseDir . '/api/TaskOperation/ConsoleOutput.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\BackupCreateOperation' => $baseDir . '/api/TaskOperation/Contao/BackupCreateOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\BackupRestoreOperation' => $baseDir . '/api/TaskOperation/Contao/BackupRestoreOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\CacheClearOperation' => $baseDir . '/api/TaskOperation/Contao/CacheClearOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\CacheWarmupOperation' => $baseDir . '/api/TaskOperation/Contao/CacheWarmupOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\CreateContaoOperation' => $baseDir . '/api/TaskOperation/Contao/CreateContaoOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\MaintenanceModeOperation' => $baseDir . '/api/TaskOperation/Contao/MaintenanceModeOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\InstallUploadsOperation' => $baseDir . '/api/TaskOperation/Filesystem/InstallUploadsOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\RemoveCacheOperation' => $baseDir . '/api/TaskOperation/Filesystem/RemoveCacheOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\RemoveUploadsOperation' => $baseDir . '/api/TaskOperation/Filesystem/RemoveUploadsOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\RemoveVendorOperation' => $baseDir . '/api/TaskOperation/Filesystem/RemoveVendorOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Manager\\SelfUpdateOperation' => $baseDir . '/api/TaskOperation/Manager/SelfUpdateOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\SponsoredOperationInterface' => $baseDir . '/api/TaskOperation/SponsoredOperationInterface.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\TaskOperationInterface' => $baseDir . '/api/TaskOperation/TaskOperationInterface.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\AbstractTask' => $baseDir . '/api/Task/AbstractTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Composer\\ClearCacheTask' => $baseDir . '/api/Task/Composer/ClearCacheTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Composer\\DumpAutoloadTask' => $baseDir . '/api/Task/Composer/DumpAutoloadTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Contao\\BackupCreateTask' => $baseDir . '/api/Task/Contao/BackupCreateTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Contao\\BackupRestoreTask' => $baseDir . '/api/Task/Contao/BackupRestoreTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Contao\\RebuildCacheTask' => $baseDir . '/api/Task/Contao/RebuildCacheTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Manager\\SelfUpdateTask' => $baseDir . '/api/Task/Manager/SelfUpdateTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\AbstractPackagesTask' => $baseDir . '/api/Task/Packages/AbstractPackagesTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\InstallTask' => $baseDir . '/api/Task/Packages/InstallTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\SetupTask' => $baseDir . '/api/Task/Packages/SetupTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\UpdateTask' => $baseDir . '/api/Task/Packages/UpdateTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskConfig' => $baseDir . '/api/Task/TaskConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskInterface' => $baseDir . '/api/Task/TaskInterface.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskManager' => $baseDir . '/api/Task/TaskManager.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskStatus' => $baseDir . '/api/Task/TaskStatus.php', '_ContaoManager\\Contao\\ManagerApi\\Tests\\Composer\\CloudJobTest' => $baseDir . '/api/Tests/Composer/CloudJobTest.php', '_ContaoManager\\Crell\\ApiProblem\\ApiProblem' => $vendorDir . '/crell/api-problem/src/ApiProblem.php', '_ContaoManager\\Crell\\ApiProblem\\HttpConverter' => $vendorDir . '/crell/api-problem/src/HttpConverter.php', '_ContaoManager\\Crell\\ApiProblem\\JsonEncodeException' => $vendorDir . '/crell/api-problem/src/JsonEncodeException.php', '_ContaoManager\\Crell\\ApiProblem\\JsonException' => $vendorDir . '/crell/api-problem/src/JsonException.php', '_ContaoManager\\Crell\\ApiProblem\\JsonParseException' => $vendorDir . '/crell/api-problem/src/JsonParseException.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\AnnotationException' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\AnnotationReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\AnnotationRegistry' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Attribute' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Attributes' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Enum' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\IgnoreAnnotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\NamedArgumentConstructor' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Required' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Target' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\CachedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\DocLexer' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\DocParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\FileCacheReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\ImplicitlyIgnoredAnnotationNames' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\IndexedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\NamedArgumentConstructorAnnotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\PhpParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\PsrCachedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Reader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\SimpleAnnotationReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\TokenParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php', '_ContaoManager\\Doctrine\\Common\\Lexer\\AbstractLexer' => $vendorDir . '/doctrine/lexer/src/AbstractLexer.php', '_ContaoManager\\Doctrine\\Common\\Lexer\\Token' => $vendorDir . '/doctrine/lexer/src/Token.php', '_ContaoManager\\Doctrine\\Deprecations\\Deprecation' => $vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php', '_ContaoManager\\Doctrine\\Deprecations\\PHPUnit\\VerifyDeprecations' => $vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php', '_ContaoManager\\Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php', '_ContaoManager\\Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php', '_ContaoManager\\Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php', '_ContaoManager\\Firebase\\JWT\\SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/src/SignatureInvalidException.php', '_ContaoManager\\JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php', '_ContaoManager\\JsonSchema\\Constraints\\ConstraintInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php', '_ContaoManager\\JsonSchema\\Constraints\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php', '_ContaoManager\\JsonSchema\\Constraints\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\NumberConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\ObjectConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\SchemaConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\StringConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\UndefinedConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php', '_ContaoManager\\JsonSchema\\Entity\\JsonPointer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php', '_ContaoManager\\JsonSchema\\Exception\\ExceptionInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidArgumentException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidConfigException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidSchemaException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidSourceUriException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php', '_ContaoManager\\JsonSchema\\Exception\\JsonDecodingException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php', '_ContaoManager\\JsonSchema\\Exception\\ResourceNotFoundException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php', '_ContaoManager\\JsonSchema\\Exception\\RuntimeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php', '_ContaoManager\\JsonSchema\\Exception\\UnresolvableJsonPointerException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php', '_ContaoManager\\JsonSchema\\Exception\\UriResolverException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php', '_ContaoManager\\JsonSchema\\Exception\\ValidationException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php', '_ContaoManager\\JsonSchema\\Iterator\\ObjectIterator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php', '_ContaoManager\\JsonSchema\\Rfc3339' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php', '_ContaoManager\\JsonSchema\\SchemaStorage' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php', '_ContaoManager\\JsonSchema\\SchemaStorageInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php', '_ContaoManager\\JsonSchema\\UriResolverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php', '_ContaoManager\\JsonSchema\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\Curl' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\FileGetContents' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\PredefinedArray' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php', '_ContaoManager\\JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php', '_ContaoManager\\JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php', '_ContaoManager\\JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php', '_ContaoManager\\Monolog\\Attribute\\AsMonologProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php', '_ContaoManager\\Monolog\\DateTimeImmutable' => $vendorDir . '/monolog/monolog/src/Monolog/DateTimeImmutable.php', '_ContaoManager\\Monolog\\ErrorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/ErrorHandler.php', '_ContaoManager\\Monolog\\Formatter\\ChromePHPFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', '_ContaoManager\\Monolog\\Formatter\\ElasticaFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php', '_ContaoManager\\Monolog\\Formatter\\ElasticsearchFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php', '_ContaoManager\\Monolog\\Formatter\\FlowdockFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php', '_ContaoManager\\Monolog\\Formatter\\FluentdFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php', '_ContaoManager\\Monolog\\Formatter\\FormatterInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', '_ContaoManager\\Monolog\\Formatter\\GelfMessageFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', '_ContaoManager\\Monolog\\Formatter\\GoogleCloudLoggingFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php', '_ContaoManager\\Monolog\\Formatter\\HtmlFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php', '_ContaoManager\\Monolog\\Formatter\\JsonFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LineFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LogglyFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LogmaticFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LogstashFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', '_ContaoManager\\Monolog\\Formatter\\MongoDBFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php', '_ContaoManager\\Monolog\\Formatter\\NormalizerFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', '_ContaoManager\\Monolog\\Formatter\\ScalarFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php', '_ContaoManager\\Monolog\\Formatter\\WildfireFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', '_ContaoManager\\Monolog\\Handler\\AbstractHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', '_ContaoManager\\Monolog\\Handler\\AbstractProcessingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', '_ContaoManager\\Monolog\\Handler\\AbstractSyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php', '_ContaoManager\\Monolog\\Handler\\AmqpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', '_ContaoManager\\Monolog\\Handler\\BrowserConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php', '_ContaoManager\\Monolog\\Handler\\BufferHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', '_ContaoManager\\Monolog\\Handler\\ChromePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', '_ContaoManager\\Monolog\\Handler\\CouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', '_ContaoManager\\Monolog\\Handler\\CubeHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', '_ContaoManager\\Monolog\\Handler\\Curl\\Util' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php', '_ContaoManager\\Monolog\\Handler\\DeduplicationHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php', '_ContaoManager\\Monolog\\Handler\\DoctrineCouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', '_ContaoManager\\Monolog\\Handler\\DynamoDbHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php', '_ContaoManager\\Monolog\\Handler\\ElasticaHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php', '_ContaoManager\\Monolog\\Handler\\ElasticsearchHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php', '_ContaoManager\\Monolog\\Handler\\ErrorLogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', '_ContaoManager\\Monolog\\Handler\\FallbackGroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php', '_ContaoManager\\Monolog\\Handler\\FilterHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossedHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', '_ContaoManager\\Monolog\\Handler\\FirePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', '_ContaoManager\\Monolog\\Handler\\FleepHookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php', '_ContaoManager\\Monolog\\Handler\\FlowdockHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php', '_ContaoManager\\Monolog\\Handler\\FormattableHandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php', '_ContaoManager\\Monolog\\Handler\\FormattableHandlerTrait' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php', '_ContaoManager\\Monolog\\Handler\\GelfHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', '_ContaoManager\\Monolog\\Handler\\GroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', '_ContaoManager\\Monolog\\Handler\\Handler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Handler.php', '_ContaoManager\\Monolog\\Handler\\HandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', '_ContaoManager\\Monolog\\Handler\\HandlerWrapper' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php', '_ContaoManager\\Monolog\\Handler\\IFTTTHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php', '_ContaoManager\\Monolog\\Handler\\InsightOpsHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php', '_ContaoManager\\Monolog\\Handler\\LogEntriesHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php', '_ContaoManager\\Monolog\\Handler\\LogglyHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php', '_ContaoManager\\Monolog\\Handler\\LogmaticHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php', '_ContaoManager\\Monolog\\Handler\\MailHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', '_ContaoManager\\Monolog\\Handler\\MandrillHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php', '_ContaoManager\\Monolog\\Handler\\MissingExtensionException' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', '_ContaoManager\\Monolog\\Handler\\MongoDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', '_ContaoManager\\Monolog\\Handler\\NativeMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', '_ContaoManager\\Monolog\\Handler\\NewRelicHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', '_ContaoManager\\Monolog\\Handler\\NoopHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NoopHandler.php', '_ContaoManager\\Monolog\\Handler\\NullHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', '_ContaoManager\\Monolog\\Handler\\OverflowHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/OverflowHandler.php', '_ContaoManager\\Monolog\\Handler\\PHPConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php', '_ContaoManager\\Monolog\\Handler\\ProcessHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ProcessHandler.php', '_ContaoManager\\Monolog\\Handler\\ProcessableHandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php', '_ContaoManager\\Monolog\\Handler\\ProcessableHandlerTrait' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php', '_ContaoManager\\Monolog\\Handler\\PsrHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php', '_ContaoManager\\Monolog\\Handler\\PushoverHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', '_ContaoManager\\Monolog\\Handler\\RedisHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', '_ContaoManager\\Monolog\\Handler\\RedisPubSubHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php', '_ContaoManager\\Monolog\\Handler\\RollbarHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php', '_ContaoManager\\Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', '_ContaoManager\\Monolog\\Handler\\SamplingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', '_ContaoManager\\Monolog\\Handler\\SendGridHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SendGridHandler.php', '_ContaoManager\\Monolog\\Handler\\SlackHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', '_ContaoManager\\Monolog\\Handler\\SlackWebhookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', '_ContaoManager\\Monolog\\Handler\\Slack\\SlackRecord' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', '_ContaoManager\\Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', '_ContaoManager\\Monolog\\Handler\\SqsHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SqsHandler.php', '_ContaoManager\\Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', '_ContaoManager\\Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', '_ContaoManager\\Monolog\\Handler\\SymfonyMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php', '_ContaoManager\\Monolog\\Handler\\SyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', '_ContaoManager\\Monolog\\Handler\\SyslogUdpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php', '_ContaoManager\\Monolog\\Handler\\SyslogUdp\\UdpSocket' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php', '_ContaoManager\\Monolog\\Handler\\TelegramBotHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php', '_ContaoManager\\Monolog\\Handler\\TestHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', '_ContaoManager\\Monolog\\Handler\\WebRequestRecognizerTrait' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php', '_ContaoManager\\Monolog\\Handler\\WhatFailureGroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php', '_ContaoManager\\Monolog\\Handler\\ZendMonitorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', '_ContaoManager\\Monolog\\LogRecord' => $vendorDir . '/monolog/monolog/src/Monolog/LogRecord.php', '_ContaoManager\\Monolog\\Logger' => $vendorDir . '/monolog/monolog/src/Monolog/Logger.php', '_ContaoManager\\Monolog\\Processor\\GitProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php', '_ContaoManager\\Monolog\\Processor\\HostnameProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php', '_ContaoManager\\Monolog\\Processor\\IntrospectionProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', '_ContaoManager\\Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', '_ContaoManager\\Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', '_ContaoManager\\Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', '_ContaoManager\\Monolog\\Processor\\MercurialProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', '_ContaoManager\\Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', '_ContaoManager\\Monolog\\Processor\\ProcessorInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php', '_ContaoManager\\Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', '_ContaoManager\\Monolog\\Processor\\TagProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', '_ContaoManager\\Monolog\\Processor\\UidProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', '_ContaoManager\\Monolog\\Processor\\WebProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', '_ContaoManager\\Monolog\\Registry' => $vendorDir . '/monolog/monolog/src/Monolog/Registry.php', '_ContaoManager\\Monolog\\ResettableInterface' => $vendorDir . '/monolog/monolog/src/Monolog/ResettableInterface.php', '_ContaoManager\\Monolog\\SignalHandler' => $vendorDir . '/monolog/monolog/src/Monolog/SignalHandler.php', '_ContaoManager\\Monolog\\Test\\TestCase' => $vendorDir . '/monolog/monolog/src/Monolog/Test/TestCase.php', '_ContaoManager\\Monolog\\Utils' => $vendorDir . '/monolog/monolog/src/Monolog/Utils.php', '_ContaoManager\\Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php', '_ContaoManager\\Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php', '_ContaoManager\\Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php', '_ContaoManager\\Psr\\Cache\\InvalidArgumentException' => $vendorDir . '/psr/cache/src/InvalidArgumentException.php', '_ContaoManager\\Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', '_ContaoManager\\Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', '_ContaoManager\\Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', '_ContaoManager\\Psr\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/psr/event-dispatcher/src/EventDispatcherInterface.php', '_ContaoManager\\Psr\\EventDispatcher\\ListenerProviderInterface' => $vendorDir . '/psr/event-dispatcher/src/ListenerProviderInterface.php', '_ContaoManager\\Psr\\EventDispatcher\\StoppableEventInterface' => $vendorDir . '/psr/event-dispatcher/src/StoppableEventInterface.php', '_ContaoManager\\Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', '_ContaoManager\\Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', '_ContaoManager\\Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', '_ContaoManager\\Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', '_ContaoManager\\Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', '_ContaoManager\\Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', '_ContaoManager\\Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', '_ContaoManager\\Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', '_ContaoManager\\Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/DummyTest.php', '_ContaoManager\\Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', '_ContaoManager\\Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php', '_ContaoManager\\Ramsey\\Uuid\\BinaryUtils' => $vendorDir . '/ramsey/uuid/src/BinaryUtils.php', '_ContaoManager\\Ramsey\\Uuid\\Builder\\DefaultUuidBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/DefaultUuidBuilder.php', '_ContaoManager\\Ramsey\\Uuid\\Builder\\DegradedUuidBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/DegradedUuidBuilder.php', '_ContaoManager\\Ramsey\\Uuid\\Builder\\UuidBuilderInterface' => $vendorDir . '/ramsey/uuid/src/Builder/UuidBuilderInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\CodecInterface' => $vendorDir . '/ramsey/uuid/src/Codec/CodecInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\GuidStringCodec' => $vendorDir . '/ramsey/uuid/src/Codec/GuidStringCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\OrderedTimeCodec' => $vendorDir . '/ramsey/uuid/src/Codec/OrderedTimeCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\StringCodec' => $vendorDir . '/ramsey/uuid/src/Codec/StringCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\TimestampFirstCombCodec' => $vendorDir . '/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\TimestampLastCombCodec' => $vendorDir . '/ramsey/uuid/src/Codec/TimestampLastCombCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\NumberConverterInterface' => $vendorDir . '/ramsey/uuid/src/Converter/NumberConverterInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Number\\BigNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/BigNumberConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Number\\DegradedNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\TimeConverterInterface' => $vendorDir . '/ramsey/uuid/src/Converter/TimeConverterInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Time\\BigNumberTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Time\\DegradedTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Time\\PhpTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php', '_ContaoManager\\Ramsey\\Uuid\\DegradedUuid' => $vendorDir . '/ramsey/uuid/src/DegradedUuid.php', '_ContaoManager\\Ramsey\\Uuid\\Exception\\InvalidUuidStringException' => $vendorDir . '/ramsey/uuid/src/Exception/InvalidUuidStringException.php', '_ContaoManager\\Ramsey\\Uuid\\Exception\\UnsatisfiedDependencyException' => $vendorDir . '/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php', '_ContaoManager\\Ramsey\\Uuid\\Exception\\UnsupportedOperationException' => $vendorDir . '/ramsey/uuid/src/Exception/UnsupportedOperationException.php', '_ContaoManager\\Ramsey\\Uuid\\FeatureSet' => $vendorDir . '/ramsey/uuid/src/FeatureSet.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\CombGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/CombGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\DefaultTimeGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/DefaultTimeGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\MtRandGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/MtRandGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\OpenSslGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/OpenSslGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\PeclUuidRandomGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\PeclUuidTimeGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomBytesGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/RandomBytesGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/RandomGeneratorFactory.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/RandomGeneratorInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomLibAdapter' => $vendorDir . '/ramsey/uuid/src/Generator/RandomLibAdapter.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\SodiumRandomGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/SodiumRandomGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\TimeGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/TimeGeneratorFactory.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\TimeGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/TimeGeneratorInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\NodeProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/NodeProviderInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Node\\FallbackNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Node\\RandomNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Node\\SystemNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\TimeProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/TimeProviderInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Time\\FixedTimeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Time\\SystemTimeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Uuid' => $vendorDir . '/ramsey/uuid/src/Uuid.php', '_ContaoManager\\Ramsey\\Uuid\\UuidFactory' => $vendorDir . '/ramsey/uuid/src/UuidFactory.php', '_ContaoManager\\Ramsey\\Uuid\\UuidFactoryInterface' => $vendorDir . '/ramsey/uuid/src/UuidFactoryInterface.php', '_ContaoManager\\Ramsey\\Uuid\\UuidInterface' => $vendorDir . '/ramsey/uuid/src/UuidInterface.php', '_ContaoManager\\Seld\\JsonLint\\DuplicateKeyException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php', '_ContaoManager\\Seld\\JsonLint\\JsonParser' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php', '_ContaoManager\\Seld\\JsonLint\\Lexer' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php', '_ContaoManager\\Seld\\JsonLint\\ParsingException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php', '_ContaoManager\\Seld\\JsonLint\\Undefined' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php', '_ContaoManager\\Seld\\PharUtils\\Linter' => $vendorDir . '/seld/phar-utils/src/Linter.php', '_ContaoManager\\Seld\\PharUtils\\Timestamps' => $vendorDir . '/seld/phar-utils/src/Timestamps.php', '_ContaoManager\\Seld\\Signal\\SignalHandler' => $vendorDir . '/seld/signal-handler/src/SignalHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Command\\ServerLogCommand' => $vendorDir . '/symfony/monolog-bridge/Command/ServerLogCommand.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Formatter\\ConsoleFormatter' => $vendorDir . '/symfony/monolog-bridge/Formatter/ConsoleFormatter.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Formatter\\VarDumperFormatter' => $vendorDir . '/symfony/monolog-bridge/Formatter/VarDumperFormatter.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ChromePhpHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/ChromePhpHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ConsoleHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/ConsoleHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ElasticsearchLogstashHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/ElasticsearchLogstashHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FingersCrossed\\HttpCodeActivationStrategy' => $vendorDir . '/symfony/monolog-bridge/Handler/FingersCrossed/HttpCodeActivationStrategy.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FingersCrossed\\NotFoundActivationStrategy' => $vendorDir . '/symfony/monolog-bridge/Handler/FingersCrossed/NotFoundActivationStrategy.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FirePHPHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/FirePHPHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\MailerHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/MailerHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\NotifierHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/NotifierHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ServerLogHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/ServerLogHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/symfony/monolog-bridge/Handler/SwiftMailerHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Logger' => $vendorDir . '/symfony/monolog-bridge/Logger.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Messenger\\ResetLoggersWorkerSubscriber' => $vendorDir . '/symfony/monolog-bridge/Messenger/ResetLoggersWorkerSubscriber.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\AbstractTokenProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/AbstractTokenProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\ConsoleCommandProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/ConsoleCommandProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\DebugProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/DebugProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\RouteProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/RouteProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\SwitchUserTokenProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/SwitchUserTokenProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\TokenProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/TokenProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\WebProcessor' => $vendorDir . '/symfony/monolog-bridge/Processor/WebProcessor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AbstractPhpFileCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AnnotationsCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\CachePoolClearerCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ConfigBuilderCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\RouterCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\SerializerCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TranslationsCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ValidatorCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\AboutCommand' => $vendorDir . '/symfony/framework-bundle/Command/AboutCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\AbstractConfigCommand' => $vendorDir . '/symfony/framework-bundle/Command/AbstractConfigCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\AssetsInstallCommand' => $vendorDir . '/symfony/framework-bundle/Command/AssetsInstallCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\BuildDebugContainerTrait' => $vendorDir . '/symfony/framework-bundle/Command/BuildDebugContainerTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand' => $vendorDir . '/symfony/framework-bundle/Command/CacheClearCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolClearCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolDeleteCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolListCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolPruneCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand' => $vendorDir . '/symfony/framework-bundle/Command/CacheWarmupCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/ConfigDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDumpReferenceCommand' => $vendorDir . '/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/ContainerDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerLintCommand' => $vendorDir . '/symfony/framework-bundle/Command/ContainerLintCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\DebugAutowiringCommand' => $vendorDir . '/symfony/framework-bundle/Command/DebugAutowiringCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\EventDispatcherDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\RouterDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/RouterDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\RouterMatchCommand' => $vendorDir . '/symfony/framework-bundle/Command/RouterMatchCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsDecryptToLocalCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsEncryptFromLocalCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsGenerateKeysCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsListCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsListCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsRemoveCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsRemoveCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsSetCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsSetCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/TranslationDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationUpdateCommand' => $vendorDir . '/symfony/framework-bundle/Command/TranslationUpdateCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\WorkflowDumpCommand' => $vendorDir . '/symfony/framework-bundle/Command/WorkflowDumpCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\XliffLintCommand' => $vendorDir . '/symfony/framework-bundle/Command/XliffLintCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\YamlLintCommand' => $vendorDir . '/symfony/framework-bundle/Command/YamlLintCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Application' => $vendorDir . '/symfony/framework-bundle/Console/Application.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/framework-bundle/Console/Descriptor/Descriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/framework-bundle/Console/Helper/DescriptorHelper.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController' => $vendorDir . '/symfony/framework-bundle/Controller/AbstractController.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver' => $vendorDir . '/symfony/framework-bundle/Controller/ControllerResolver.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController' => $vendorDir . '/symfony/framework-bundle/Controller/RedirectController.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController' => $vendorDir . '/symfony/framework-bundle/Controller/TemplateController.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DataCollector\\AbstractDataCollector' => $vendorDir . '/symfony/framework-bundle/DataCollector/AbstractDataCollector.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/framework-bundle/DataCollector/RouterDataCollector.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DataCollector\\TemplateAwareDataCollectorInterface' => $vendorDir . '/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddAnnotationsCachedReaderPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddDebugLogProcessorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddExpressionLanguageProvidersPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AssetsContextPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ContainerBuilderDebugDumpPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\DataCollectorTranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ErrorLoggerCompilerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\LoggingTranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ProfilerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RemoveUnusedSessionMarshallingHandlerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SessionPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerRealRefPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerWeakRefPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\UnusedTagsPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\WorkflowGuardListenerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Configuration' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Configuration.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\FrameworkExtension' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber' => $vendorDir . '/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle' => $vendorDir . '/symfony/framework-bundle/FrameworkBundle.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache' => $vendorDir . '/symfony/framework-bundle/HttpCache/HttpCache.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\KernelBrowser' => $vendorDir . '/symfony/framework-bundle/KernelBrowser.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait' => $vendorDir . '/symfony/framework-bundle/Kernel/MicroKernelTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\AnnotatedRouteControllerLoader' => $vendorDir . '/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader' => $vendorDir . '/symfony/framework-bundle/Routing/DelegatingLoader.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher' => $vendorDir . '/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface' => $vendorDir . '/symfony/framework-bundle/Routing/RouteLoaderInterface.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\Router' => $vendorDir . '/symfony/framework-bundle/Routing/Router.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => $vendorDir . '/symfony/framework-bundle/Secrets/AbstractVault.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => $vendorDir . '/symfony/framework-bundle/Secrets/DotenvVault.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => $vendorDir . '/symfony/framework-bundle/Secrets/SodiumVault.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Session\\DeprecatedSessionFactory' => $vendorDir . '/symfony/framework-bundle/Session/DeprecatedSessionFactory.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Session\\ServiceSessionFactory' => $vendorDir . '/symfony/framework-bundle/Session/ServiceSessionFactory.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\DomCrawlerAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase' => $vendorDir . '/symfony/framework-bundle/Test/KernelTestCase.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/MailerAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\TestBrowserToken' => $vendorDir . '/symfony/framework-bundle/Test/TestBrowserToken.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\TestContainer' => $vendorDir . '/symfony/framework-bundle/Test/TestContainer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestAssertionsTrait' => $vendorDir . '/symfony/framework-bundle/Test/WebTestAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase' => $vendorDir . '/symfony/framework-bundle/Test/WebTestCase.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => $vendorDir . '/symfony/framework-bundle/Translation/Translator.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\AddProcessorsPass' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/Compiler/AddProcessorsPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\AddSwiftMailerTransportPass' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/Compiler/AddSwiftMailerTransportPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\DebugHandlerPass' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/Compiler/DebugHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\FixEmptyLoggerPass' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/Compiler/FixEmptyLoggerPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\LoggerChannelPass' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/Compiler/LoggerChannelPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Configuration' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/Configuration.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\MonologExtension' => $vendorDir . '/symfony/monolog-bundle/DependencyInjection/MonologExtension.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\MonologBundle' => $vendorDir . '/symfony/monolog-bundle/MonologBundle.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\SwiftMailer\\MessageFactory' => $vendorDir . '/symfony/monolog-bundle/SwiftMailer/MessageFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\CacheWarmer\\ExpressionCacheWarmer' => $vendorDir . '/symfony/security-bundle/CacheWarmer/ExpressionCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Command\\DebugFirewallCommand' => $vendorDir . '/symfony/security-bundle/Command/DebugFirewallCommand.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Command\\UserPasswordEncoderCommand' => $vendorDir . '/symfony/security-bundle/Command/UserPasswordEncoderCommand.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DataCollector\\SecurityDataCollector' => $vendorDir . '/symfony/security-bundle/DataCollector/SecurityDataCollector.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableFirewallListener' => $vendorDir . '/symfony/security-bundle/Debug/TraceableFirewallListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableListenerTrait' => $vendorDir . '/symfony/security-bundle/Debug/TraceableListenerTrait.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\WrappedLazyListener' => $vendorDir . '/symfony/security-bundle/Debug/WrappedLazyListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\WrappedListener' => $vendorDir . '/symfony/security-bundle/Debug/WrappedListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\AddExpressionLanguageProvidersPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\AddSecurityVotersPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/AddSecurityVotersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\AddSessionDomainConstraintPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\CleanRememberMeVerifierPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterCsrfFeaturesPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterCsrfTokenClearingLogoutHandlerPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterEntryPointPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterEntryPointPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterGlobalSecurityEventListenersPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterLdapLocatorPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterTokenUsageTrackingPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\ReplaceDecoratedRememberMeHandlerPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\SortFirewallListenersPass' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Compiler/SortFirewallListenersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\MainConfiguration' => $vendorDir . '/symfony/security-bundle/DependencyInjection/MainConfiguration.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\SecurityExtension' => $vendorDir . '/symfony/security-bundle/DependencyInjection/SecurityExtension.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\AbstractFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/AbstractFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\AnonymousFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/AnonymousFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\AuthenticatorFactoryInterface' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\CustomAuthenticatorFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\FirewallListenerFactoryInterface' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\FormLoginFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\FormLoginLdapFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\GuardAuthenticationFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\HttpBasicFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\HttpBasicLdapFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\JsonLoginFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\JsonLoginLdapFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\LdapFactoryTrait' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/LdapFactoryTrait.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\LoginLinkFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/LoginLinkFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\LoginThrottlingFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\RememberMeFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/RememberMeFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\RemoteUserFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/RemoteUserFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\SecurityFactoryInterface' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\X509Factory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/Factory/X509Factory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\UserProvider\\InMemoryFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\UserProvider\\LdapFactory' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/UserProvider/LdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\UserProvider\\UserProviderFactoryInterface' => $vendorDir . '/symfony/security-bundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\EventListener\\FirewallListener' => $vendorDir . '/symfony/security-bundle/EventListener/FirewallListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\EventListener\\VoteListener' => $vendorDir . '/symfony/security-bundle/EventListener/VoteListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\LoginLink\\FirewallAwareLoginLinkHandler' => $vendorDir . '/symfony/security-bundle/LoginLink/FirewallAwareLoginLinkHandler.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\RememberMe\\DecoratedRememberMeHandler' => $vendorDir . '/symfony/security-bundle/RememberMe/DecoratedRememberMeHandler.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\RememberMe\\FirewallAwareRememberMeHandler' => $vendorDir . '/symfony/security-bundle/RememberMe/FirewallAwareRememberMeHandler.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\SecurityBundle' => $vendorDir . '/symfony/security-bundle/SecurityBundle.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallAwareTrait' => $vendorDir . '/symfony/security-bundle/Security/FirewallAwareTrait.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallConfig' => $vendorDir . '/symfony/security-bundle/Security/FirewallConfig.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext' => $vendorDir . '/symfony/security-bundle/Security/FirewallContext.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap' => $vendorDir . '/symfony/security-bundle/Security/FirewallMap.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\LazyFirewallContext' => $vendorDir . '/symfony/security-bundle/Security/LazyFirewallContext.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\LegacyLogoutHandlerListener' => $vendorDir . '/symfony/security-bundle/Security/LegacyLogoutHandlerListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\UserAuthenticator' => $vendorDir . '/symfony/security-bundle/Security/UserAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/cache/Adapter/AdapterInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => $vendorDir . '/symfony/cache/Adapter/ApcuAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/ArrayAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => $vendorDir . '/symfony/cache/Adapter/ChainAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter' => $vendorDir . '/symfony/cache/Adapter/CouchbaseBucketAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter' => $vendorDir . '/symfony/cache/Adapter/CouchbaseCollectionAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineDbalAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => $vendorDir . '/symfony/cache/Adapter/MemcachedAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\NullAdapter' => $vendorDir . '/symfony/cache/Adapter/NullAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ParameterNormalizer' => $vendorDir . '/symfony/cache/Adapter/ParameterNormalizer.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => $vendorDir . '/symfony/cache/Adapter/PdoAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpArrayAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpFilesAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => $vendorDir . '/symfony/cache/Adapter/ProxyAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => $vendorDir . '/symfony/cache/Adapter/Psr16Adapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapterInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TraceableAdapter' => $vendorDir . '/symfony/cache/Adapter/TraceableAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\CacheItem' => $vendorDir . '/symfony/cache/CacheItem.php', '_ContaoManager\\Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => $vendorDir . '/symfony/cache/DataCollector/CacheDataCollector.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => $vendorDir . '/symfony/cache/DependencyInjection/CacheCollectorPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DoctrineProvider' => $vendorDir . '/symfony/cache/DoctrineProvider.php', '_ContaoManager\\Symfony\\Component\\Cache\\Exception\\CacheException' => $vendorDir . '/symfony/cache/Exception/CacheException.php', '_ContaoManager\\Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/cache/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Cache\\Exception\\LogicException' => $vendorDir . '/symfony/cache/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Cache\\LockRegistry' => $vendorDir . '/symfony/cache/LockRegistry.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DefaultMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DeflateMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => $vendorDir . '/symfony/cache/Marshaller/MarshallerInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\SodiumMarshaller' => $vendorDir . '/symfony/cache/Marshaller/SodiumMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => $vendorDir . '/symfony/cache/Marshaller/TagAwareMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Messenger\\EarlyExpirationDispatcher' => $vendorDir . '/symfony/cache/Messenger/EarlyExpirationDispatcher.php', '_ContaoManager\\Symfony\\Component\\Cache\\Messenger\\EarlyExpirationHandler' => $vendorDir . '/symfony/cache/Messenger/EarlyExpirationHandler.php', '_ContaoManager\\Symfony\\Component\\Cache\\Messenger\\EarlyExpirationMessage' => $vendorDir . '/symfony/cache/Messenger/EarlyExpirationMessage.php', '_ContaoManager\\Symfony\\Component\\Cache\\PruneableInterface' => $vendorDir . '/symfony/cache/PruneableInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Psr16Cache' => $vendorDir . '/symfony/cache/Psr16Cache.php', '_ContaoManager\\Symfony\\Component\\Cache\\ResettableInterface' => $vendorDir . '/symfony/cache/ResettableInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => $vendorDir . '/symfony/cache/Traits/AbstractAdapterTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\ContractsTrait' => $vendorDir . '/symfony/cache/Traits/ContractsTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemCommonTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\ProxyTrait' => $vendorDir . '/symfony/cache/Traits/ProxyTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterNodeProxy.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterProxy.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisProxy' => $vendorDir . '/symfony/cache/Traits/RedisProxy.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisTrait' => $vendorDir . '/symfony/cache/Traits/RedisTrait.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ClassBuilder' => $vendorDir . '/symfony/config/Builder/ClassBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGenerator.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ConfigBuilderInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\Method' => $vendorDir . '/symfony/config/Builder/Method.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\Property' => $vendorDir . '/symfony/config/Builder/Property.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCache' => $vendorDir . '/symfony/config/ConfigCache.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCacheFactory' => $vendorDir . '/symfony/config/ConfigCacheFactory.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => $vendorDir . '/symfony/config/ConfigCacheFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCacheInterface' => $vendorDir . '/symfony/config/ConfigCacheInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\ArrayNode' => $vendorDir . '/symfony/config/Definition/ArrayNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\BaseNode' => $vendorDir . '/symfony/config/Definition/BaseNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => $vendorDir . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/MergeBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NodeBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => $vendorDir . '/symfony/config/Definition/Builder/NodeParentInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NormalizationBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NumericNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => $vendorDir . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/TreeBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ValidationBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => $vendorDir . '/symfony/config/Definition/ConfigurationInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\EnumNode' => $vendorDir . '/symfony/config/Definition/EnumNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => $vendorDir . '/symfony/config/Definition/Exception/DuplicateKeyException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\Exception' => $vendorDir . '/symfony/config/Definition/Exception/Exception.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => $vendorDir . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidTypeException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\NodeInterface' => $vendorDir . '/symfony/config/Definition/NodeInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\NumericNode' => $vendorDir . '/symfony/config/Definition/NumericNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Processor' => $vendorDir . '/symfony/config/Definition/Processor.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => $vendorDir . '/symfony/config/Definition/PrototypeNodeInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => $vendorDir . '/symfony/config/Definition/PrototypedArrayNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\ScalarNode' => $vendorDir . '/symfony/config/Definition/ScalarNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\VariableNode' => $vendorDir . '/symfony/config/Definition/VariableNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => $vendorDir . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Config\\Exception\\LoaderLoadException' => $vendorDir . '/symfony/config/Exception/LoaderLoadException.php', '_ContaoManager\\Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php', '_ContaoManager\\Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\FileLoader' => $vendorDir . '/symfony/config/Loader/FileLoader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/config/Loader/GlobFileLoader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\Loader' => $vendorDir . '/symfony/config/Loader/Loader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\LoaderInterface' => $vendorDir . '/symfony/config/Loader/LoaderInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\LoaderResolver' => $vendorDir . '/symfony/config/Loader/LoaderResolver.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => $vendorDir . '/symfony/config/Loader/LoaderResolverInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\ParamConfigurator' => $vendorDir . '/symfony/config/Loader/ParamConfigurator.php', '_ContaoManager\\Symfony\\Component\\Config\\ResourceCheckerConfigCache' => $vendorDir . '/symfony/config/ResourceCheckerConfigCache.php', '_ContaoManager\\Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => $vendorDir . '/symfony/config/ResourceCheckerConfigCacheFactory.php', '_ContaoManager\\Symfony\\Component\\Config\\ResourceCheckerInterface' => $vendorDir . '/symfony/config/ResourceCheckerInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => $vendorDir . '/symfony/config/Resource/ClassExistenceResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ComposerResource' => $vendorDir . '/symfony/config/Resource/ComposerResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\DirectoryResource' => $vendorDir . '/symfony/config/Resource/DirectoryResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\FileExistenceResource' => $vendorDir . '/symfony/config/Resource/FileExistenceResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\FileResource' => $vendorDir . '/symfony/config/Resource/FileResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\GlobResource' => $vendorDir . '/symfony/config/Resource/GlobResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => $vendorDir . '/symfony/config/Resource/ReflectionClassResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ResourceInterface' => $vendorDir . '/symfony/config/Resource/ResourceInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceChecker.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => $vendorDir . '/symfony/config/Util/Exception/InvalidXmlException.php', '_ContaoManager\\Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => $vendorDir . '/symfony/config/Util/Exception/XmlParsingException.php', '_ContaoManager\\Symfony\\Component\\Config\\Util\\XmlUtils' => $vendorDir . '/symfony/config/Util/XmlUtils.php', '_ContaoManager\\Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', '_ContaoManager\\Symfony\\Component\\Console\\Attribute\\AsCommand' => $vendorDir . '/symfony/console/Attribute/AsCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\CI\\GithubActionReporter' => $vendorDir . '/symfony/console/CI/GithubActionReporter.php', '_ContaoManager\\Symfony\\Component\\Console\\Color' => $vendorDir . '/symfony/console/Color.php', '_ContaoManager\\Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php', '_ContaoManager\\Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\CompleteCommand' => $vendorDir . '/symfony/console/Command/CompleteCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => $vendorDir . '/symfony/console/Command/DumpCompletionCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\LazyCommand' => $vendorDir . '/symfony/console/Command/LazyCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => $vendorDir . '/symfony/console/Command/SignalableCommandInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\CompletionInput' => $vendorDir . '/symfony/console/Completion/CompletionInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => $vendorDir . '/symfony/console/Completion/CompletionSuggestions.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/BashCompletionOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => $vendorDir . '/symfony/console/Completion/Output/CompletionOutputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\Suggestion' => $vendorDir . '/symfony/console/Completion/Suggestion.php', '_ContaoManager\\Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', '_ContaoManager\\Symfony\\Component\\Console\\Cursor' => $vendorDir . '/symfony/console/Cursor.php', '_ContaoManager\\Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => $vendorDir . '/symfony/console/Event/ConsoleSignalEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\MissingInputException' => $vendorDir . '/symfony/console/Exception/MissingInputException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatter.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatterStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableCellStyle' => $vendorDir . '/symfony/console/Helper/TableCellStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => $vendorDir . '/symfony/console/Output/TrimmedBufferOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', '_ContaoManager\\Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', '_ContaoManager\\Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', '_ContaoManager\\Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => $vendorDir . '/symfony/console/SignalRegistry/SignalRegistry.php', '_ContaoManager\\Symfony\\Component\\Console\\SingleCommandApplication' => $vendorDir . '/symfony/console/SingleCommandApplication.php', '_ContaoManager\\Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => $vendorDir . '/symfony/console/Tester/CommandCompletionTester.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => $vendorDir . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Alias' => $vendorDir . '/symfony/dependency-injection/Alias.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\AbstractArgument' => $vendorDir . '/symfony/dependency-injection/Argument/AbstractArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => $vendorDir . '/symfony/dependency-injection/Argument/ArgumentInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => $vendorDir . '/symfony/dependency-injection/Argument/BoundArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/IteratorArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => $vendorDir . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => $vendorDir . '/symfony/dependency-injection/Argument/RewindableGenerator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\AsTaggedItem' => $vendorDir . '/symfony/dependency-injection/Attribute/AsTaggedItem.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure' => $vendorDir . '/symfony/dependency-injection/Attribute/Autoconfigure.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\AutoconfigureTag' => $vendorDir . '/symfony/dependency-injection/Attribute/AutoconfigureTag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator' => $vendorDir . '/symfony/dependency-injection/Attribute/TaggedIterator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator' => $vendorDir . '/symfony/dependency-injection/Attribute/TaggedLocator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\Target' => $vendorDir . '/symfony/dependency-injection/Attribute/Target.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\When' => $vendorDir . '/symfony/dependency-injection/Attribute/When.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ChildDefinition' => $vendorDir . '/symfony/dependency-injection/ChildDefinition.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AbstractRecursivePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AbstractRecursivePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AliasDeprecatedPublicServicesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AttributeAutoconfigurationPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowirePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredMethodsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredPropertiesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckArgumentsValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckCircularReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckTypeDeclarationsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => $vendorDir . '/symfony/dependency-injection/Compiler/Compiler.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => $vendorDir . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\DefinitionErrorExceptionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ExtensionCompilerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\InlineServiceDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => $vendorDir . '/symfony/dependency-injection/Compiler/PassConfig.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\PriorityTaggedServiceTrait' => $vendorDir . '/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterAutoconfigureAttributesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterEnvVarProcessorsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveBindingsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveBindingsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveChildDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveClassPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveClassPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDecoratorStackPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveEnvPlaceholdersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveFactoryClassPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveHotPathPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveHotPathPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInstanceofConditionalsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInvalidReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNamedArgumentsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNoPreloadPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolvePrivatesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveServiceSubscribersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveTaggedIteratorArgumentPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceLocatorTagPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ValidateEnvPlaceholdersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResource' => $vendorDir . '/symfony/dependency-injection/Config/ContainerParametersResource.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResourceChecker' => $vendorDir . '/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Container' => $vendorDir . '/symfony/dependency-injection/Container.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerAwareInterface' => $vendorDir . '/symfony/dependency-injection/ContainerAwareInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerAwareTrait' => $vendorDir . '/symfony/dependency-injection/ContainerAwareTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerBuilder' => $vendorDir . '/symfony/dependency-injection/ContainerBuilder.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => $vendorDir . '/symfony/dependency-injection/ContainerInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Definition' => $vendorDir . '/symfony/dependency-injection/Definition.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\Dumper' => $vendorDir . '/symfony/dependency-injection/Dumper/Dumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/Dumper/DumperInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/PhpDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\Preloader' => $vendorDir . '/symfony/dependency-injection/Dumper/Preloader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/XmlDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/YamlDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\EnvVarLoaderInterface' => $vendorDir . '/symfony/dependency-injection/EnvVarLoaderInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\EnvVarProcessor' => $vendorDir . '/symfony/dependency-injection/EnvVarProcessor.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface' => $vendorDir . '/symfony/dependency-injection/EnvVarProcessorInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\AutowiringFailedException' => $vendorDir . '/symfony/dependency-injection/Exception/AutowiringFailedException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\BadMethodCallException' => $vendorDir . '/symfony/dependency-injection/Exception/BadMethodCallException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\EnvNotFoundException' => $vendorDir . '/symfony/dependency-injection/Exception/EnvNotFoundException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\EnvParameterException' => $vendorDir . '/symfony/dependency-injection/Exception/EnvParameterException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/dependency-injection/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\InvalidParameterTypeException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidParameterTypeException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => $vendorDir . '/symfony/dependency-injection/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => $vendorDir . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException' => $vendorDir . '/symfony/dependency-injection/Exception/ParameterNotFoundException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\RuntimeException' => $vendorDir . '/symfony/dependency-injection/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ServiceCircularReferenceException' => $vendorDir . '/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException' => $vendorDir . '/symfony/dependency-injection/Exception/ServiceNotFoundException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ExpressionLanguage' => $vendorDir . '/symfony/dependency-injection/ExpressionLanguage.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ExpressionLanguageProvider' => $vendorDir . '/symfony/dependency-injection/ExpressionLanguageProvider.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\ConfigurationExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\Extension' => $vendorDir . '/symfony/dependency-injection/Extension/Extension.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ExtensionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/PrependExtensionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\InstantiatorInterface' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\RealServiceInstantiator' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\NullDumper' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\ProxyHelper' => $vendorDir . '/symfony/dependency-injection/LazyProxy/ProxyHelper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\ClosureLoader' => $vendorDir . '/symfony/dependency-injection/Loader/ClosureLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractServiceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AliasConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ClosureReferenceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\DefaultsConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\EnvConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InlineServiceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InstanceofConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ParametersConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\PrototypeConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ReferenceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServiceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServicesConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\AbstractTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ArgumentTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\AutoconfigureTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\AutowireTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\BindTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\CallTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ClassTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ConfiguratorTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DecorateTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DeprecateTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FactoryTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FileTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\LazyTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ParentTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\PropertyTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\PublicTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ShareTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\SyntheticTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\TagTrait' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\DirectoryLoader' => $vendorDir . '/symfony/dependency-injection/Loader/DirectoryLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\FileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/FileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/GlobFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\IniFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/IniFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/PhpFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/XmlFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/YamlFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Parameter' => $vendorDir . '/symfony/dependency-injection/Parameter.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ContainerBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\EnvPlaceholderParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Reference' => $vendorDir . '/symfony/dependency-injection/Reference.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ReverseContainer' => $vendorDir . '/symfony/dependency-injection/ReverseContainer.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/ServiceLocator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => $vendorDir . '/symfony/dependency-injection/TaggedContainerInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\TypedReference' => $vendorDir . '/symfony/dependency-injection/TypedReference.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Variable' => $vendorDir . '/symfony/dependency-injection/Variable.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\BufferingLogger' => $vendorDir . '/symfony/error-handler/BufferingLogger.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Debug' => $vendorDir . '/symfony/error-handler/Debug.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\DebugClassLoader' => $vendorDir . '/symfony/error-handler/DebugClassLoader.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ClassNotFoundErrorEnhancer' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ErrorEnhancerInterface' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedFunctionErrorEnhancer' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedMethodErrorEnhancer' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorHandler' => $vendorDir . '/symfony/error-handler/ErrorHandler.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\CliErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\ErrorRendererInterface' => $vendorDir . '/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\HtmlErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\SerializerErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError' => $vendorDir . '/symfony/error-handler/Error/ClassNotFoundError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\FatalError' => $vendorDir . '/symfony/error-handler/Error/FatalError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\OutOfMemoryError' => $vendorDir . '/symfony/error-handler/Error/OutOfMemoryError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\UndefinedFunctionError' => $vendorDir . '/symfony/error-handler/Error/UndefinedFunctionError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\UndefinedMethodError' => $vendorDir . '/symfony/error-handler/Error/UndefinedMethodError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Exception\\FlattenException' => $vendorDir . '/symfony/error-handler/Exception/FlattenException.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Exception\\SilencedErrorContext' => $vendorDir . '/symfony/error-handler/Exception/SilencedErrorContext.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Internal\\TentativeTypes' => $vendorDir . '/symfony/error-handler/Internal/TentativeTypes.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ThrowableUtils' => $vendorDir . '/symfony/error-handler/ThrowableUtils.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener' => $vendorDir . '/symfony/event-dispatcher/Attribute/AsEventListener.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => $vendorDir . '/symfony/event-dispatcher/Debug/WrappedListener.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/EventDispatcher.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/EventDispatcherInterface.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => $vendorDir . '/symfony/filesystem/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Path' => $vendorDir . '/symfony/filesystem/Path.php', '_ContaoManager\\Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', '_ContaoManager\\Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', '_ContaoManager\\Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php', '_ContaoManager\\Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\LazyIterator' => $vendorDir . '/symfony/finder/Iterator/LazyIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\VcsIgnoredFilterIterator' => $vendorDir . '/symfony/finder/Iterator/VcsIgnoredFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\AcceptHeader' => $vendorDir . '/symfony/http-foundation/AcceptHeader.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => $vendorDir . '/symfony/http-foundation/AcceptHeaderItem.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => $vendorDir . '/symfony/http-foundation/BinaryFileResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Cookie' => $vendorDir . '/symfony/http-foundation/Cookie.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\BadRequestException' => $vendorDir . '/symfony/http-foundation/Exception/BadRequestException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => $vendorDir . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\JsonException' => $vendorDir . '/symfony/http-foundation/Exception/JsonException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface' => $vendorDir . '/symfony/http-foundation/Exception/RequestExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\SessionNotFoundException' => $vendorDir . '/symfony/http-foundation/Exception/SessionNotFoundException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\SuspiciousOperationException' => $vendorDir . '/symfony/http-foundation/Exception/SuspiciousOperationException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => $vendorDir . '/symfony/http-foundation/ExpressionRequestMatcher.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\FileBag' => $vendorDir . '/symfony/http-foundation/FileBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\CannotWriteFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/CannotWriteFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\ExtensionFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/ExtensionFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\FormSizeFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FormSizeFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\IniSizeFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/IniSizeFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\NoFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/NoFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/NoTmpDirFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/PartialFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => $vendorDir . '/symfony/http-foundation/File/Exception/UploadException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\File' => $vendorDir . '/symfony/http-foundation/File/File.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Stream' => $vendorDir . '/symfony/http-foundation/File/Stream.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => $vendorDir . '/symfony/http-foundation/File/UploadedFile.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\HeaderBag' => $vendorDir . '/symfony/http-foundation/HeaderBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\HeaderUtils' => $vendorDir . '/symfony/http-foundation/HeaderUtils.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\InputBag' => $vendorDir . '/symfony/http-foundation/InputBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\IpUtils' => $vendorDir . '/symfony/http-foundation/IpUtils.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\JsonResponse' => $vendorDir . '/symfony/http-foundation/JsonResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ParameterBag' => $vendorDir . '/symfony/http-foundation/ParameterBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RateLimiter\\AbstractRequestRateLimiter' => $vendorDir . '/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RateLimiter\\RequestRateLimiterInterface' => $vendorDir . '/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RedirectResponse' => $vendorDir . '/symfony/http-foundation/RedirectResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Request' => $vendorDir . '/symfony/http-foundation/Request.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => $vendorDir . '/symfony/http-foundation/RequestMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestStack' => $vendorDir . '/symfony/http-foundation/RequestStack.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Response' => $vendorDir . '/symfony/http-foundation/Response.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => $vendorDir . '/symfony/http-foundation/ResponseHeaderBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ServerBag' => $vendorDir . '/symfony/http-foundation/ServerBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => $vendorDir . '/symfony/http-foundation/Session/Attribute/AttributeBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => $vendorDir . '/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => $vendorDir . '/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => $vendorDir . '/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => $vendorDir . '/symfony/http-foundation/Session/Flash/FlashBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => $vendorDir . '/symfony/http-foundation/Session/Flash/FlashBagInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Session' => $vendorDir . '/symfony/http-foundation/Session/Session.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionBagInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionBagProxy' => $vendorDir . '/symfony/http-foundation/Session/SessionBagProxy.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionFactory' => $vendorDir . '/symfony/http-foundation/Session/SessionFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionFactoryInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionUtils' => $vendorDir . '/symfony/http-foundation/Session/SessionUtils.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\IdentityMarshaller' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MarshallingSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\StrictSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => $vendorDir . '/symfony/http-foundation/Session/Storage/MetadataBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/NativeSessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\ServiceSessionFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/StreamedResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\RequestAttributeValueSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseCookieValueSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseFormatSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasCookie' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasHeader' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsRedirected' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsSuccessful' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsUnprocessable' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseStatusCodeSame' => $vendorDir . '/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\UrlHelper' => $vendorDir . '/symfony/http-foundation/UrlHelper.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Attribute\\ArgumentInterface' => $vendorDir . '/symfony/http-kernel/Attribute/ArgumentInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Attribute\\AsController' => $vendorDir . '/symfony/http-kernel/Attribute/AsController.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => $vendorDir . '/symfony/http-kernel/Bundle/Bundle.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => $vendorDir . '/symfony/http-kernel/Bundle/BundleInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => $vendorDir . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => $vendorDir . '/symfony/http-kernel/CacheClearer/ChainCacheClearer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheClearer\\Psr6CacheClearer' => $vendorDir . '/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmer' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => $vendorDir . '/symfony/http-kernel/CacheWarmer/WarmableInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Config\\FileLocator' => $vendorDir . '/symfony/http-kernel/Config/FileLocator.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata' => $vendorDir . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory' => $vendorDir . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactoryInterface' => $vendorDir . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => $vendorDir . '/symfony/http-kernel/Controller/ControllerReference.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => $vendorDir . '/symfony/http-kernel/Controller/ErrorController.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/ConfigDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/DataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface' => $vendorDir . '/symfony/http-kernel/DataCollector/DataCollectorInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\DumpDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/DumpDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\EventDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/EventDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\ExceptionDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/ExceptionDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\LateDataCollectorInterface' => $vendorDir . '/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\LoggerDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/LoggerDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\MemoryDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/MemoryDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => $vendorDir . '/symfony/http-kernel/Debug/FileLinkFormatter.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\AddAnnotatedClassesToCachePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ControllerArgumentValueResolverPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/Extension.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\FragmentRendererPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\LazyLoadingFragmentHandler' => $vendorDir . '/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\LoggerPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/LoggerPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterControllerArgumentLocatorsPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterLocaleAwareServicesPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\RemoveEmptyControllerArgumentLocatorsPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\AbstractSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/AbstractSessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => $vendorDir . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => $vendorDir . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => $vendorDir . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => $vendorDir . '/symfony/http-kernel/EventListener/DumpListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener' => $vendorDir . '/symfony/http-kernel/EventListener/ErrorListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => $vendorDir . '/symfony/http-kernel/EventListener/FragmentListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleAwareListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => $vendorDir . '/symfony/http-kernel/EventListener/ProfilerListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/ResponseListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => $vendorDir . '/symfony/http-kernel/EventListener/RouterListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/SessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => $vendorDir . '/symfony/http-kernel/EventListener/SurrogateListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/TestSessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => $vendorDir . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => $vendorDir . '/symfony/http-kernel/Event/ExceptionEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => $vendorDir . '/symfony/http-kernel/Event/FinishRequestEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => $vendorDir . '/symfony/http-kernel/Event/KernelEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => $vendorDir . '/symfony/http-kernel/Event/RequestEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/ResponseEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => $vendorDir . '/symfony/http-kernel/Event/TerminateEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => $vendorDir . '/symfony/http-kernel/Event/ViewEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => $vendorDir . '/symfony/http-kernel/Exception/BadRequestHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ConflictHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\ControllerDoesNotReturnResponseException' => $vendorDir . '/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => $vendorDir . '/symfony/http-kernel/Exception/GoneHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\HttpException' => $vendorDir . '/symfony/http-kernel/Exception/HttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\InvalidMetadataException' => $vendorDir . '/symfony/http-kernel/Exception/InvalidMetadataException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotFoundHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/PreconditionFailedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => $vendorDir . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnexpectedSessionUsageException' => $vendorDir . '/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\AbstractSurrogateFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/EsiFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentHandler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentRendererInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGenerator' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentUriGenerator.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGeneratorInterface' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/InlineFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\SsiFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/SsiFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\AbstractSurrogate' => $vendorDir . '/symfony/http-kernel/HttpCache/AbstractSurrogate.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\Esi' => $vendorDir . '/symfony/http-kernel/HttpCache/Esi.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache' => $vendorDir . '/symfony/http-kernel/HttpCache/HttpCache.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategy' => $vendorDir . '/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategyInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\Ssi' => $vendorDir . '/symfony/http-kernel/HttpCache/Ssi.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\Store' => $vendorDir . '/symfony/http-kernel/HttpCache/Store.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/StoreInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => $vendorDir . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpClientKernel' => $vendorDir . '/symfony/http-kernel/HttpClientKernel.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpKernel' => $vendorDir . '/symfony/http-kernel/HttpKernel.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpKernelBrowser' => $vendorDir . '/symfony/http-kernel/HttpKernelBrowser.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpKernelInterface' => $vendorDir . '/symfony/http-kernel/HttpKernelInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Kernel' => $vendorDir . '/symfony/http-kernel/Kernel.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\KernelEvents' => $vendorDir . '/symfony/http-kernel/KernelEvents.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\KernelInterface' => $vendorDir . '/symfony/http-kernel/KernelInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => $vendorDir . '/symfony/http-kernel/Log/DebugLoggerInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Log\\Logger' => $vendorDir . '/symfony/http-kernel/Log/Logger.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/FileProfilerStorage.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\Profile' => $vendorDir . '/symfony/http-kernel/Profiler/Profile.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\Profiler' => $vendorDir . '/symfony/http-kernel/Profiler/Profiler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface' => $vendorDir . '/symfony/http-kernel/Profiler/ProfilerStorageInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\RebootableInterface' => $vendorDir . '/symfony/http-kernel/RebootableInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\TerminableInterface' => $vendorDir . '/symfony/http-kernel/TerminableInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\UriSigner' => $vendorDir . '/symfony/http-kernel/UriSigner.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Command\\UserPasswordHashCommand' => $vendorDir . '/symfony/password-hasher/Command/UserPasswordHashCommand.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/password-hasher/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Exception\\InvalidPasswordException' => $vendorDir . '/symfony/password-hasher/Exception/InvalidPasswordException.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Exception\\LogicException' => $vendorDir . '/symfony/password-hasher/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\CheckPasswordLengthTrait' => $vendorDir . '/symfony/password-hasher/Hasher/CheckPasswordLengthTrait.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\MessageDigestPasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/MessageDigestPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\MigratingPasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/MigratingPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\NativePasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/NativePasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherAwareInterface' => $vendorDir . '/symfony/password-hasher/Hasher/PasswordHasherAwareInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactory' => $vendorDir . '/symfony/password-hasher/Hasher/PasswordHasherFactory.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactoryInterface' => $vendorDir . '/symfony/password-hasher/Hasher/PasswordHasherFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\Pbkdf2PasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/Pbkdf2PasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PlaintextPasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/PlaintextPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\SodiumPasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/SodiumPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasher' => $vendorDir . '/symfony/password-hasher/Hasher/UserPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface' => $vendorDir . '/symfony/password-hasher/Hasher/UserPasswordHasherInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\LegacyPasswordHasherInterface' => $vendorDir . '/symfony/password-hasher/LegacyPasswordHasherInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\PasswordHasherInterface' => $vendorDir . '/symfony/password-hasher/PasswordHasherInterface.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php', '_ContaoManager\\Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php', '_ContaoManager\\Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php', '_ContaoManager\\Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php', '_ContaoManager\\Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php', '_ContaoManager\\Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\AccessException' => $vendorDir . '/symfony/property-access/Exception/AccessException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/property-access/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/property-access/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\InvalidPropertyPathException' => $vendorDir . '/symfony/property-access/Exception/InvalidPropertyPathException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\NoSuchIndexException' => $vendorDir . '/symfony/property-access/Exception/NoSuchIndexException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\NoSuchPropertyException' => $vendorDir . '/symfony/property-access/Exception/NoSuchPropertyException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/property-access/Exception/OutOfBoundsException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\RuntimeException' => $vendorDir . '/symfony/property-access/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/property-access/Exception/UnexpectedTypeException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\UninitializedPropertyException' => $vendorDir . '/symfony/property-access/Exception/UninitializedPropertyException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccess' => $vendorDir . '/symfony/property-access/PropertyAccess.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccessor' => $vendorDir . '/symfony/property-access/PropertyAccessor.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder' => $vendorDir . '/symfony/property-access/PropertyAccessorBuilder.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccessorInterface' => $vendorDir . '/symfony/property-access/PropertyAccessorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPath' => $vendorDir . '/symfony/property-access/PropertyPath.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathBuilder' => $vendorDir . '/symfony/property-access/PropertyPathBuilder.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathInterface' => $vendorDir . '/symfony/property-access/PropertyPathInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathIterator' => $vendorDir . '/symfony/property-access/PropertyPathIterator.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathIteratorInterface' => $vendorDir . '/symfony/property-access/PropertyPathIteratorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\DependencyInjection\\PropertyInfoConstructorPass' => $vendorDir . '/symfony/property-info/DependencyInjection/PropertyInfoConstructorPass.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\DependencyInjection\\PropertyInfoPass' => $vendorDir . '/symfony/property-info/DependencyInjection/PropertyInfoPass.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorArgumentTypeExtractorInterface' => $vendorDir . '/symfony/property-info/Extractor/ConstructorArgumentTypeExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorExtractor' => $vendorDir . '/symfony/property-info/Extractor/ConstructorExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor' => $vendorDir . '/symfony/property-info/Extractor/PhpDocExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor' => $vendorDir . '/symfony/property-info/Extractor/PhpStanExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor' => $vendorDir . '/symfony/property-info/Extractor/ReflectionExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor' => $vendorDir . '/symfony/property-info/Extractor/SerializerExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PhpStan\\NameScope' => $vendorDir . '/symfony/property-info/PhpStan/NameScope.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PhpStan\\NameScopeFactory' => $vendorDir . '/symfony/property-info/PhpStan/NameScopeFactory.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyAccessExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyDescriptionExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInfoCacheExtractor' => $vendorDir . '/symfony/property-info/PropertyInfoCacheExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor' => $vendorDir . '/symfony/property-info/PropertyInfoExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInfoExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyInfoExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyInitializableExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyListExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyReadInfo' => $vendorDir . '/symfony/property-info/PropertyReadInfo.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyReadInfoExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyReadInfoExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyTypeExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyWriteInfo' => $vendorDir . '/symfony/property-info/PropertyWriteInfo.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyWriteInfoExtractorInterface' => $vendorDir . '/symfony/property-info/PropertyWriteInfoExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Type' => $vendorDir . '/symfony/property-info/Type.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Util\\PhpDocTypeHelper' => $vendorDir . '/symfony/property-info/Util/PhpDocTypeHelper.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Util\\PhpStanTypeHelper' => $vendorDir . '/symfony/property-info/Util/PhpStanTypeHelper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Alias' => $vendorDir . '/symfony/routing/Alias.php', '_ContaoManager\\Symfony\\Component\\Routing\\Annotation\\Route' => $vendorDir . '/symfony/routing/Annotation/Route.php', '_ContaoManager\\Symfony\\Component\\Routing\\CompiledRoute' => $vendorDir . '/symfony/routing/CompiledRoute.php', '_ContaoManager\\Symfony\\Component\\Routing\\DependencyInjection\\RoutingResolverPass' => $vendorDir . '/symfony/routing/DependencyInjection/RoutingResolverPass.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/routing/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/routing/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => $vendorDir . '/symfony/routing/Exception/InvalidParameterException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => $vendorDir . '/symfony/routing/Exception/MethodNotAllowedException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => $vendorDir . '/symfony/routing/Exception/MissingMandatoryParametersException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\NoConfigurationException' => $vendorDir . '/symfony/routing/Exception/NoConfigurationException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => $vendorDir . '/symfony/routing/Exception/ResourceNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\RouteCircularReferenceException' => $vendorDir . '/symfony/routing/Exception/RouteCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => $vendorDir . '/symfony/routing/Exception/RouteNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\RuntimeException' => $vendorDir . '/symfony/routing/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator' => $vendorDir . '/symfony/routing/Generator/CompiledUrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => $vendorDir . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\UrlGenerator' => $vendorDir . '/symfony/routing/Generator/UrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => $vendorDir . '/symfony/routing/Generator/UrlGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationClassLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\ClosureLoader' => $vendorDir . '/symfony/routing/Loader/ClosureLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\AliasConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/AliasConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\CollectionConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/CollectionConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\ImportConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/ImportConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\RouteConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/RouteConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/RoutingConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\AddTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/AddTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\HostTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/HostTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\LocalizedRouteTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\PrefixTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/RouteTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\ContainerLoader' => $vendorDir . '/symfony/routing/Loader/ContainerLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => $vendorDir . '/symfony/routing/Loader/DirectoryLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/routing/Loader/GlobFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\ObjectLoader' => $vendorDir . '/symfony/routing/Loader/ObjectLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/routing/Loader/PhpFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/routing/Loader/XmlFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/routing/Loader/YamlFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/CompiledUrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherTrait' => $vendorDir . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\StaticPrefixCollection' => $vendorDir . '/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\ExpressionLanguageProvider' => $vendorDir . '/symfony/routing/Matcher/ExpressionLanguageProvider.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/RedirectableUrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/RequestMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\TraceableUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/TraceableUrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\UrlMatcher' => $vendorDir . '/symfony/routing/Matcher/UrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/UrlMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\RequestContext' => $vendorDir . '/symfony/routing/RequestContext.php', '_ContaoManager\\Symfony\\Component\\Routing\\RequestContextAwareInterface' => $vendorDir . '/symfony/routing/RequestContextAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Route' => $vendorDir . '/symfony/routing/Route.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCollection' => $vendorDir . '/symfony/routing/RouteCollection.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCollectionBuilder' => $vendorDir . '/symfony/routing/RouteCollectionBuilder.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCompiler' => $vendorDir . '/symfony/routing/RouteCompiler.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCompilerInterface' => $vendorDir . '/symfony/routing/RouteCompilerInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Router' => $vendorDir . '/symfony/routing/Router.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouterInterface' => $vendorDir . '/symfony/routing/RouterInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\AuthenticationEvents' => $vendorDir . '/symfony/security-core/AuthenticationEvents.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface' => $vendorDir . '/symfony/security-core/Authentication/AuthenticationManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager' => $vendorDir . '/symfony/security-core/Authentication/AuthenticationProviderManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationTrustResolver' => $vendorDir . '/symfony/security-core/Authentication/AuthenticationTrustResolver.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationTrustResolverInterface' => $vendorDir . '/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AnonymousAuthenticationProvider' => $vendorDir . '/symfony/security-core/Authentication/Provider/AnonymousAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface' => $vendorDir . '/symfony/security-core/Authentication/Provider/AuthenticationProviderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider' => $vendorDir . '/symfony/security-core/Authentication/Provider/DaoAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\LdapBindAuthenticationProvider' => $vendorDir . '/symfony/security-core/Authentication/Provider/LdapBindAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\PreAuthenticatedAuthenticationProvider' => $vendorDir . '/symfony/security-core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\RememberMeAuthenticationProvider' => $vendorDir . '/symfony/security-core/Authentication/Provider/RememberMeAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\UserAuthenticationProvider' => $vendorDir . '/symfony/security-core/Authentication/Provider/UserAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\CacheTokenVerifier' => $vendorDir . '/symfony/security-core/Authentication/RememberMe/CacheTokenVerifier.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\InMemoryTokenProvider' => $vendorDir . '/symfony/security-core/Authentication/RememberMe/InMemoryTokenProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\PersistentToken' => $vendorDir . '/symfony/security-core/Authentication/RememberMe/PersistentToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\PersistentTokenInterface' => $vendorDir . '/symfony/security-core/Authentication/RememberMe/PersistentTokenInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\TokenProviderInterface' => $vendorDir . '/symfony/security-core/Authentication/RememberMe/TokenProviderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\TokenVerifierInterface' => $vendorDir . '/symfony/security-core/Authentication/RememberMe/TokenVerifierInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken' => $vendorDir . '/symfony/security-core/Authentication/Token/AbstractToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken' => $vendorDir . '/symfony/security-core/Authentication/Token/AnonymousToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\NullToken' => $vendorDir . '/symfony/security-core/Authentication/Token/NullToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\PreAuthenticatedToken' => $vendorDir . '/symfony/security-core/Authentication/Token/PreAuthenticatedToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\RememberMeToken' => $vendorDir . '/symfony/security-core/Authentication/Token/RememberMeToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorage' => $vendorDir . '/symfony/security-core/Authentication/Token/Storage/TokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface' => $vendorDir . '/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\UsageTrackingTokenStorage' => $vendorDir . '/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\SwitchUserToken' => $vendorDir . '/symfony/security-core/Authentication/Token/SwitchUserToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface' => $vendorDir . '/symfony/security-core/Authentication/Token/TokenInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\UsernamePasswordToken' => $vendorDir . '/symfony/security-core/Authentication/Token/UsernamePasswordToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager' => $vendorDir . '/symfony/security-core/Authorization/AccessDecisionManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface' => $vendorDir . '/symfony/security-core/Authorization/AccessDecisionManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker' => $vendorDir . '/symfony/security-core/Authorization/AuthorizationChecker.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface' => $vendorDir . '/symfony/security-core/Authorization/AuthorizationCheckerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\ExpressionLanguage' => $vendorDir . '/symfony/security-core/Authorization/ExpressionLanguage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\ExpressionLanguageProvider' => $vendorDir . '/symfony/security-core/Authorization/ExpressionLanguageProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AccessDecisionStrategyInterface' => $vendorDir . '/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AffirmativeStrategy' => $vendorDir . '/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\ConsensusStrategy' => $vendorDir . '/symfony/security-core/Authorization/Strategy/ConsensusStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\PriorityStrategy' => $vendorDir . '/symfony/security-core/Authorization/Strategy/PriorityStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\UnanimousStrategy' => $vendorDir . '/symfony/security-core/Authorization/Strategy/UnanimousStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\TraceableAccessDecisionManager' => $vendorDir . '/symfony/security-core/Authorization/TraceableAccessDecisionManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter' => $vendorDir . '/symfony/security-core/Authorization/Voter/AuthenticatedVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface' => $vendorDir . '/symfony/security-core/Authorization/Voter/CacheableVoterInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter' => $vendorDir . '/symfony/security-core/Authorization/Voter/ExpressionVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter' => $vendorDir . '/symfony/security-core/Authorization/Voter/RoleHierarchyVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter' => $vendorDir . '/symfony/security-core/Authorization/Voter/RoleVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\TraceableVoter' => $vendorDir . '/symfony/security-core/Authorization/Voter/TraceableVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Voter' => $vendorDir . '/symfony/security-core/Authorization/Voter/Voter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface' => $vendorDir . '/symfony/security-core/Authorization/Voter/VoterInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/BasePasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\EncoderAwareInterface' => $vendorDir . '/symfony/security-core/Encoder/EncoderAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory' => $vendorDir . '/symfony/security-core/Encoder/EncoderFactory.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactoryInterface' => $vendorDir . '/symfony/security-core/Encoder/EncoderFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\LegacyEncoderTrait' => $vendorDir . '/symfony/security-core/Encoder/LegacyEncoderTrait.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\LegacyPasswordHasherEncoder' => $vendorDir . '/symfony/security-core/Encoder/LegacyPasswordHasherEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\MessageDigestPasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/MessageDigestPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\MigratingPasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/MigratingPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\NativePasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/NativePasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface' => $vendorDir . '/symfony/security-core/Encoder/PasswordEncoderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PasswordHasherAdapter' => $vendorDir . '/symfony/security-core/Encoder/PasswordHasherAdapter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PasswordHasherEncoder' => $vendorDir . '/symfony/security-core/Encoder/PasswordHasherEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\Pbkdf2PasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/Pbkdf2PasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PlaintextPasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/PlaintextPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\SelfSaltingEncoderInterface' => $vendorDir . '/symfony/security-core/Encoder/SelfSaltingEncoderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\SodiumPasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/SodiumPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoder' => $vendorDir . '/symfony/security-core/Encoder/UserPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoderInterface' => $vendorDir . '/symfony/security-core/Encoder/UserPasswordEncoderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\AuthenticationEvent' => $vendorDir . '/symfony/security-core/Event/AuthenticationEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent' => $vendorDir . '/symfony/security-core/Event/AuthenticationFailureEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent' => $vendorDir . '/symfony/security-core/Event/AuthenticationSuccessEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\VoteEvent' => $vendorDir . '/symfony/security-core/Event/VoteEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/security-core/Exception/AccessDeniedException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AccountExpiredException' => $vendorDir . '/symfony/security-core/Exception/AccountExpiredException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AccountStatusException' => $vendorDir . '/symfony/security-core/Exception/AccountStatusException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException' => $vendorDir . '/symfony/security-core/Exception/AuthenticationCredentialsNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException' => $vendorDir . '/symfony/security-core/Exception/AuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException' => $vendorDir . '/symfony/security-core/Exception/AuthenticationExpiredException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationServiceException' => $vendorDir . '/symfony/security-core/Exception/AuthenticationServiceException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\BadCredentialsException' => $vendorDir . '/symfony/security-core/Exception/BadCredentialsException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CookieTheftException' => $vendorDir . '/symfony/security-core/Exception/CookieTheftException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CredentialsExpiredException' => $vendorDir . '/symfony/security-core/Exception/CredentialsExpiredException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CustomUserMessageAccountStatusException' => $vendorDir . '/symfony/security-core/Exception/CustomUserMessageAccountStatusException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CustomUserMessageAuthenticationException' => $vendorDir . '/symfony/security-core/Exception/CustomUserMessageAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\DisabledException' => $vendorDir . '/symfony/security-core/Exception/DisabledException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/security-core/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\InsufficientAuthenticationException' => $vendorDir . '/symfony/security-core/Exception/InsufficientAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/security-core/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\InvalidCsrfTokenException' => $vendorDir . '/symfony/security-core/Exception/InvalidCsrfTokenException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LazyResponseException' => $vendorDir . '/symfony/security-core/Exception/LazyResponseException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LockedException' => $vendorDir . '/symfony/security-core/Exception/LockedException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LogicException' => $vendorDir . '/symfony/security-core/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LogoutException' => $vendorDir . '/symfony/security-core/Exception/LogoutException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\ProviderNotFoundException' => $vendorDir . '/symfony/security-core/Exception/ProviderNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\RuntimeException' => $vendorDir . '/symfony/security-core/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\SessionUnavailableException' => $vendorDir . '/symfony/security-core/Exception/SessionUnavailableException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\TokenNotFoundException' => $vendorDir . '/symfony/security-core/Exception/TokenNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\TooManyLoginAttemptsAuthenticationException' => $vendorDir . '/symfony/security-core/Exception/TooManyLoginAttemptsAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\UnsupportedUserException' => $vendorDir . '/symfony/security-core/Exception/UnsupportedUserException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\UserNotFoundException' => $vendorDir . '/symfony/security-core/Exception/UserNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\UsernameNotFoundException' => $vendorDir . '/symfony/security-core/Exception/UsernameNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\Role' => $vendorDir . '/symfony/security-core/Role/Role.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\RoleHierarchy' => $vendorDir . '/symfony/security-core/Role/RoleHierarchy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\RoleHierarchyInterface' => $vendorDir . '/symfony/security-core/Role/RoleHierarchyInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\SwitchUserRole' => $vendorDir . '/symfony/security-core/Role/SwitchUserRole.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Security' => $vendorDir . '/symfony/security-core/Security.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\Exception\\ExpiredSignatureException' => $vendorDir . '/symfony/security-core/Signature/Exception/ExpiredSignatureException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\Exception\\InvalidSignatureException' => $vendorDir . '/symfony/security-core/Signature/Exception/InvalidSignatureException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\ExpiredSignatureStorage' => $vendorDir . '/symfony/security-core/Signature/ExpiredSignatureStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\SignatureHasher' => $vendorDir . '/symfony/security-core/Signature/SignatureHasher.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Test\\AccessDecisionStrategyTestCase' => $vendorDir . '/symfony/security-core/Test/AccessDecisionStrategyTestCase.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\ChainUserProvider' => $vendorDir . '/symfony/security-core/User/ChainUserProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\EquatableInterface' => $vendorDir . '/symfony/security-core/User/EquatableInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\InMemoryUser' => $vendorDir . '/symfony/security-core/User/InMemoryUser.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\InMemoryUserChecker' => $vendorDir . '/symfony/security-core/User/InMemoryUserChecker.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\InMemoryUserProvider' => $vendorDir . '/symfony/security-core/User/InMemoryUserProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\LegacyPasswordAuthenticatedUserInterface' => $vendorDir . '/symfony/security-core/User/LegacyPasswordAuthenticatedUserInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\MissingUserProvider' => $vendorDir . '/symfony/security-core/User/MissingUserProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface' => $vendorDir . '/symfony/security-core/User/PasswordAuthenticatedUserInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\PasswordUpgraderInterface' => $vendorDir . '/symfony/security-core/User/PasswordUpgraderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\User' => $vendorDir . '/symfony/security-core/User/User.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserChecker' => $vendorDir . '/symfony/security-core/User/UserChecker.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface' => $vendorDir . '/symfony/security-core/User/UserCheckerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserInterface' => $vendorDir . '/symfony/security-core/User/UserInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserProviderInterface' => $vendorDir . '/symfony/security-core/User/UserProviderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Validator\\Constraints\\UserPassword' => $vendorDir . '/symfony/security-core/Validator/Constraints/UserPassword.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Validator\\Constraints\\UserPasswordValidator' => $vendorDir . '/symfony/security-core/Validator/Constraints/UserPasswordValidator.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\CsrfToken' => $vendorDir . '/symfony/security-csrf/CsrfToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\CsrfTokenManager' => $vendorDir . '/symfony/security-csrf/CsrfTokenManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface' => $vendorDir . '/symfony/security-csrf/CsrfTokenManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\Exception\\TokenNotFoundException' => $vendorDir . '/symfony/security-csrf/Exception/TokenNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenGenerator\\TokenGeneratorInterface' => $vendorDir . '/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenGenerator\\UriSafeTokenGenerator' => $vendorDir . '/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\ClearableTokenStorageInterface' => $vendorDir . '/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\NativeSessionTokenStorage' => $vendorDir . '/symfony/security-csrf/TokenStorage/NativeSessionTokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\SessionTokenStorage' => $vendorDir . '/symfony/security-csrf/TokenStorage/SessionTokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\TokenStorageInterface' => $vendorDir . '/symfony/security-csrf/TokenStorage/TokenStorageInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator' => $vendorDir . '/symfony/security-guard/AbstractGuardAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\AuthenticatorInterface' => $vendorDir . '/symfony/security-guard/AuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Authenticator\\AbstractFormLoginAuthenticator' => $vendorDir . '/symfony/security-guard/Authenticator/AbstractFormLoginAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Authenticator\\GuardBridgeAuthenticator' => $vendorDir . '/symfony/security-guard/Authenticator/GuardBridgeAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Firewall\\GuardAuthenticationListener' => $vendorDir . '/symfony/security-guard/Firewall/GuardAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\GuardAuthenticatorHandler' => $vendorDir . '/symfony/security-guard/GuardAuthenticatorHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\PasswordAuthenticatedInterface' => $vendorDir . '/symfony/security-guard/PasswordAuthenticatedInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Provider\\GuardAuthenticationProvider' => $vendorDir . '/symfony/security-guard/Provider/GuardAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Token\\GuardTokenInterface' => $vendorDir . '/symfony/security-guard/Token/GuardTokenInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken' => $vendorDir . '/symfony/security-guard/Token/PostAuthenticationGuardToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Token\\PreAuthenticationGuardToken' => $vendorDir . '/symfony/security-guard/Token/PreAuthenticationGuardToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\AccessMap' => $vendorDir . '/symfony/security-http/AccessMap.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\AccessMapInterface' => $vendorDir . '/symfony/security-http/AccessMapInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Attribute\\CurrentUser' => $vendorDir . '/symfony/security-http/Attribute/CurrentUser.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationFailureHandlerInterface' => $vendorDir . '/symfony/security-http/Authentication/AuthenticationFailureHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationSuccessHandlerInterface' => $vendorDir . '/symfony/security-http/Authentication/AuthenticationSuccessHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationUtils' => $vendorDir . '/symfony/security-http/Authentication/AuthenticationUtils.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticatorManager' => $vendorDir . '/symfony/security-http/Authentication/AuthenticatorManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticatorManagerInterface' => $vendorDir . '/symfony/security-http/Authentication/AuthenticatorManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\CustomAuthenticationFailureHandler' => $vendorDir . '/symfony/security-http/Authentication/CustomAuthenticationFailureHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\CustomAuthenticationSuccessHandler' => $vendorDir . '/symfony/security-http/Authentication/CustomAuthenticationSuccessHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\DefaultAuthenticationFailureHandler' => $vendorDir . '/symfony/security-http/Authentication/DefaultAuthenticationFailureHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\DefaultAuthenticationSuccessHandler' => $vendorDir . '/symfony/security-http/Authentication/DefaultAuthenticationSuccessHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\NoopAuthenticationManager' => $vendorDir . '/symfony/security-http/Authentication/NoopAuthenticationManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\UserAuthenticatorInterface' => $vendorDir . '/symfony/security-http/Authentication/UserAuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AbstractAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/AbstractAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AbstractLoginFormAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/AbstractLoginFormAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AbstractPreAuthenticatedAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/AbstractPreAuthenticatedAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface' => $vendorDir . '/symfony/security-http/Authenticator/AuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Debug\\TraceableAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/Debug/TraceableAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Debug\\TraceableAuthenticatorManagerListener' => $vendorDir . '/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\FormLoginAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/FormLoginAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\HttpBasicAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/HttpBasicAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\InteractiveAuthenticatorInterface' => $vendorDir . '/symfony/security-http/Authenticator/InteractiveAuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\JsonLoginAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/JsonLoginAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\LoginLinkAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/LoginLinkAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\BadgeInterface' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Badge/BadgeInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\CsrfTokenBadge' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Badge/CsrfTokenBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PasswordUpgradeBadge' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PreAuthenticatedUserBadge' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\RememberMeBadge' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Badge/RememberMeBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\UserBadge' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\CredentialsInterface' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Credentials/CredentialsInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\CustomCredentials' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Credentials/CustomCredentials.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Credentials/PasswordCredentials.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport' => $vendorDir . '/symfony/security-http/Authenticator/Passport/Passport.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\PassportInterface' => $vendorDir . '/symfony/security-http/Authenticator/Passport/PassportInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\PassportTrait' => $vendorDir . '/symfony/security-http/Authenticator/Passport/PassportTrait.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\SelfValidatingPassport' => $vendorDir . '/symfony/security-http/Authenticator/Passport/SelfValidatingPassport.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\UserPassportInterface' => $vendorDir . '/symfony/security-http/Authenticator/Passport/UserPassportInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\RememberMeAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/RememberMeAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\RemoteUserAuthenticator' => $vendorDir . '/symfony/security-http/Authenticator/RemoteUserAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Token\\PostAuthenticationToken' => $vendorDir . '/symfony/security-http/Authenticator/Token/PostAuthenticationToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\X509Authenticator' => $vendorDir . '/symfony/security-http/Authenticator/X509Authenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authorization\\AccessDeniedHandlerInterface' => $vendorDir . '/symfony/security-http/Authorization/AccessDeniedHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver' => $vendorDir . '/symfony/security-http/Controller/UserValueResolver.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface' => $vendorDir . '/symfony/security-http/EntryPoint/AuthenticationEntryPointInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\BasicAuthenticationEntryPoint' => $vendorDir . '/symfony/security-http/EntryPoint/BasicAuthenticationEntryPoint.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\Exception\\NotAnEntryPointException' => $vendorDir . '/symfony/security-http/EntryPoint/Exception/NotAnEntryPointException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\FormAuthenticationEntryPoint' => $vendorDir . '/symfony/security-http/EntryPoint/FormAuthenticationEntryPoint.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\RetryAuthenticationEntryPoint' => $vendorDir . '/symfony/security-http/EntryPoint/RetryAuthenticationEntryPoint.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CheckCredentialsListener' => $vendorDir . '/symfony/security-http/EventListener/CheckCredentialsListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CheckRememberMeConditionsListener' => $vendorDir . '/symfony/security-http/EventListener/CheckRememberMeConditionsListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CookieClearingLogoutListener' => $vendorDir . '/symfony/security-http/EventListener/CookieClearingLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CsrfProtectionListener' => $vendorDir . '/symfony/security-http/EventListener/CsrfProtectionListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CsrfTokenClearingLogoutListener' => $vendorDir . '/symfony/security-http/EventListener/CsrfTokenClearingLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\DefaultLogoutListener' => $vendorDir . '/symfony/security-http/EventListener/DefaultLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\LoginThrottlingListener' => $vendorDir . '/symfony/security-http/EventListener/LoginThrottlingListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\PasswordMigratingListener' => $vendorDir . '/symfony/security-http/EventListener/PasswordMigratingListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\RememberMeListener' => $vendorDir . '/symfony/security-http/EventListener/RememberMeListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\RememberMeLogoutListener' => $vendorDir . '/symfony/security-http/EventListener/RememberMeLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\SessionLogoutListener' => $vendorDir . '/symfony/security-http/EventListener/SessionLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\SessionStrategyListener' => $vendorDir . '/symfony/security-http/EventListener/SessionStrategyListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\UserCheckerListener' => $vendorDir . '/symfony/security-http/EventListener/UserCheckerListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\UserProviderListener' => $vendorDir . '/symfony/security-http/EventListener/UserProviderListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\AuthenticationTokenCreatedEvent' => $vendorDir . '/symfony/security-http/Event/AuthenticationTokenCreatedEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent' => $vendorDir . '/symfony/security-http/Event/CheckPassportEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent' => $vendorDir . '/symfony/security-http/Event/DeauthenticatedEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent' => $vendorDir . '/symfony/security-http/Event/InteractiveLoginEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LazyResponseEvent' => $vendorDir . '/symfony/security-http/Event/LazyResponseEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LoginFailureEvent' => $vendorDir . '/symfony/security-http/Event/LoginFailureEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LoginSuccessEvent' => $vendorDir . '/symfony/security-http/Event/LoginSuccessEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LogoutEvent' => $vendorDir . '/symfony/security-http/Event/LogoutEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent' => $vendorDir . '/symfony/security-http/Event/SwitchUserEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\TokenDeauthenticatedEvent' => $vendorDir . '/symfony/security-http/Event/TokenDeauthenticatedEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall' => $vendorDir . '/symfony/security-http/Firewall.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\FirewallMap' => $vendorDir . '/symfony/security-http/FirewallMap.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\FirewallMapInterface' => $vendorDir . '/symfony/security-http/FirewallMapInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AbstractAuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/AbstractAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AbstractListener' => $vendorDir . '/symfony/security-http/Firewall/AbstractListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AbstractPreAuthenticatedListener' => $vendorDir . '/symfony/security-http/Firewall/AbstractPreAuthenticatedListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AccessListener' => $vendorDir . '/symfony/security-http/Firewall/AccessListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AnonymousAuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/AnonymousAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AuthenticatorManagerListener' => $vendorDir . '/symfony/security-http/Firewall/AuthenticatorManagerListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\BasicAuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/BasicAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\ChannelListener' => $vendorDir . '/symfony/security-http/Firewall/ChannelListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\ContextListener' => $vendorDir . '/symfony/security-http/Firewall/ContextListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener' => $vendorDir . '/symfony/security-http/Firewall/ExceptionListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\FirewallListenerInterface' => $vendorDir . '/symfony/security-http/Firewall/FirewallListenerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\LogoutListener' => $vendorDir . '/symfony/security-http/Firewall/LogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\RememberMeListener' => $vendorDir . '/symfony/security-http/Firewall/RememberMeListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\RemoteUserAuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/RemoteUserAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\SwitchUserListener' => $vendorDir . '/symfony/security-http/Firewall/SwitchUserListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/UsernamePasswordFormAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordJsonAuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/UsernamePasswordJsonAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\X509AuthenticationListener' => $vendorDir . '/symfony/security-http/Firewall/X509AuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\HttpUtils' => $vendorDir . '/symfony/security-http/HttpUtils.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Impersonate\\ImpersonateUrlGenerator' => $vendorDir . '/symfony/security-http/Impersonate/ImpersonateUrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\ExpiredLoginLinkException' => $vendorDir . '/symfony/security-http/LoginLink/Exception/ExpiredLoginLinkException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\InvalidLoginLinkAuthenticationException' => $vendorDir . '/symfony/security-http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\InvalidLoginLinkException' => $vendorDir . '/symfony/security-http/LoginLink/Exception/InvalidLoginLinkException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\InvalidLoginLinkExceptionInterface' => $vendorDir . '/symfony/security-http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkDetails' => $vendorDir . '/symfony/security-http/LoginLink/LoginLinkDetails.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandler' => $vendorDir . '/symfony/security-http/LoginLink/LoginLinkHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandlerInterface' => $vendorDir . '/symfony/security-http/LoginLink/LoginLinkHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkNotification' => $vendorDir . '/symfony/security-http/LoginLink/LoginLinkNotification.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\CookieClearingLogoutHandler' => $vendorDir . '/symfony/security-http/Logout/CookieClearingLogoutHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\CsrfTokenClearingLogoutHandler' => $vendorDir . '/symfony/security-http/Logout/CsrfTokenClearingLogoutHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\DefaultLogoutSuccessHandler' => $vendorDir . '/symfony/security-http/Logout/DefaultLogoutSuccessHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\LogoutHandlerInterface' => $vendorDir . '/symfony/security-http/Logout/LogoutHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\LogoutSuccessHandlerInterface' => $vendorDir . '/symfony/security-http/Logout/LogoutSuccessHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\LogoutUrlGenerator' => $vendorDir . '/symfony/security-http/Logout/LogoutUrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\SessionLogoutHandler' => $vendorDir . '/symfony/security-http/Logout/SessionLogoutHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\ParameterBagUtils' => $vendorDir . '/symfony/security-http/ParameterBagUtils.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RateLimiter\\DefaultLoginRateLimiter' => $vendorDir . '/symfony/security-http/RateLimiter/DefaultLoginRateLimiter.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\AbstractRememberMeHandler' => $vendorDir . '/symfony/security-http/RememberMe/AbstractRememberMeHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\AbstractRememberMeServices' => $vendorDir . '/symfony/security-http/RememberMe/AbstractRememberMeServices.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\PersistentRememberMeHandler' => $vendorDir . '/symfony/security-http/RememberMe/PersistentRememberMeHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\PersistentTokenBasedRememberMeServices' => $vendorDir . '/symfony/security-http/RememberMe/PersistentTokenBasedRememberMeServices.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\RememberMeDetails' => $vendorDir . '/symfony/security-http/RememberMe/RememberMeDetails.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\RememberMeHandlerInterface' => $vendorDir . '/symfony/security-http/RememberMe/RememberMeHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\RememberMeServicesInterface' => $vendorDir . '/symfony/security-http/RememberMe/RememberMeServicesInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\ResponseListener' => $vendorDir . '/symfony/security-http/RememberMe/ResponseListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\SignatureRememberMeHandler' => $vendorDir . '/symfony/security-http/RememberMe/SignatureRememberMeHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\TokenBasedRememberMeServices' => $vendorDir . '/symfony/security-http/RememberMe/TokenBasedRememberMeServices.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\SecurityEvents' => $vendorDir . '/symfony/security-http/SecurityEvents.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Session\\SessionAuthenticationStrategy' => $vendorDir . '/symfony/security-http/Session/SessionAuthenticationStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Session\\SessionAuthenticationStrategyInterface' => $vendorDir . '/symfony/security-http/Session/SessionAuthenticationStrategyInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Util\\TargetPathTrait' => $vendorDir . '/symfony/security-http/Util/TargetPathTrait.php', '_ContaoManager\\Symfony\\Component\\String\\AbstractString' => $vendorDir . '/symfony/string/AbstractString.php', '_ContaoManager\\Symfony\\Component\\String\\AbstractUnicodeString' => $vendorDir . '/symfony/string/AbstractUnicodeString.php', '_ContaoManager\\Symfony\\Component\\String\\ByteString' => $vendorDir . '/symfony/string/ByteString.php', '_ContaoManager\\Symfony\\Component\\String\\CodePointString' => $vendorDir . '/symfony/string/CodePointString.php', '_ContaoManager\\Symfony\\Component\\String\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/string/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\String\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/string/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\String\\Exception\\RuntimeException' => $vendorDir . '/symfony/string/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\String\\Inflector\\EnglishInflector' => $vendorDir . '/symfony/string/Inflector/EnglishInflector.php', '_ContaoManager\\Symfony\\Component\\String\\Inflector\\FrenchInflector' => $vendorDir . '/symfony/string/Inflector/FrenchInflector.php', '_ContaoManager\\Symfony\\Component\\String\\Inflector\\InflectorInterface' => $vendorDir . '/symfony/string/Inflector/InflectorInterface.php', '_ContaoManager\\Symfony\\Component\\String\\LazyString' => $vendorDir . '/symfony/string/LazyString.php', '_ContaoManager\\Symfony\\Component\\String\\Slugger\\AsciiSlugger' => $vendorDir . '/symfony/string/Slugger/AsciiSlugger.php', '_ContaoManager\\Symfony\\Component\\String\\Slugger\\SluggerInterface' => $vendorDir . '/symfony/string/Slugger/SluggerInterface.php', '_ContaoManager\\Symfony\\Component\\String\\UnicodeString' => $vendorDir . '/symfony/string/UnicodeString.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => $vendorDir . '/symfony/var-dumper/Caster/AmqpCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => $vendorDir . '/symfony/var-dumper/Caster/ArgsStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\Caster' => $vendorDir . '/symfony/var-dumper/Caster/Caster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ClassStub' => $vendorDir . '/symfony/var-dumper/Caster/ClassStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ConstStub' => $vendorDir . '/symfony/var-dumper/Caster/ConstStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => $vendorDir . '/symfony/var-dumper/Caster/CutArrayStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\CutStub' => $vendorDir . '/symfony/var-dumper/Caster/CutStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => $vendorDir . '/symfony/var-dumper/Caster/DOMCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DateCaster' => $vendorDir . '/symfony/var-dumper/Caster/DateCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => $vendorDir . '/symfony/var-dumper/Caster/DoctrineCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsCaster' => $vendorDir . '/symfony/var-dumper/Caster/DsCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => $vendorDir . '/symfony/var-dumper/Caster/DsPairStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\EnumStub' => $vendorDir . '/symfony/var-dumper/Caster/EnumStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ExceptionCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\FiberCaster' => $vendorDir . '/symfony/var-dumper/Caster/FiberCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\FrameStub' => $vendorDir . '/symfony/var-dumper/Caster/FrameStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => $vendorDir . '/symfony/var-dumper/Caster/GmpCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => $vendorDir . '/symfony/var-dumper/Caster/ImagineCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ImgStub' => $vendorDir . '/symfony/var-dumper/Caster/ImgStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => $vendorDir . '/symfony/var-dumper/Caster/IntlCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\LinkStub' => $vendorDir . '/symfony/var-dumper/Caster/LinkStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => $vendorDir . '/symfony/var-dumper/Caster/MemcachedCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\MysqliCaster' => $vendorDir . '/symfony/var-dumper/Caster/MysqliCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => $vendorDir . '/symfony/var-dumper/Caster/PdoCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => $vendorDir . '/symfony/var-dumper/Caster/PgSqlCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => $vendorDir . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster' => $vendorDir . '/symfony/var-dumper/Caster/RdKafkaCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => $vendorDir . '/symfony/var-dumper/Caster/RedisCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ReflectionCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/ResourceCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster' => $vendorDir . '/symfony/var-dumper/Caster/SplCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster' => $vendorDir . '/symfony/var-dumper/Caster/StubCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => $vendorDir . '/symfony/var-dumper/Caster/SymfonyCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\TraceStub' => $vendorDir . '/symfony/var-dumper/Caster/TraceStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => $vendorDir . '/symfony/var-dumper/Caster/UuidCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlReaderCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlResourceCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => $vendorDir . '/symfony/var-dumper/Cloner/AbstractCloner.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\ClonerInterface' => $vendorDir . '/symfony/var-dumper/Cloner/ClonerInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\Cursor' => $vendorDir . '/symfony/var-dumper/Cloner/Cursor.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\Data' => $vendorDir . '/symfony/var-dumper/Cloner/Data.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => $vendorDir . '/symfony/var-dumper/Cloner/DumperInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\Stub' => $vendorDir . '/symfony/var-dumper/Cloner/Stub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => $vendorDir . '/symfony/var-dumper/Cloner/VarCloner.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => $vendorDir . '/symfony/var-dumper/Command/ServerDumpCommand.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => $vendorDir . '/symfony/var-dumper/Dumper/AbstractDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => $vendorDir . '/symfony/var-dumper/Dumper/CliDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => $vendorDir . '/symfony/var-dumper/Dumper/DataDumperInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => $vendorDir . '/symfony/var-dumper/Dumper/HtmlDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ServerDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => $vendorDir . '/symfony/var-dumper/Exception/ThrowingCasterException.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Server\\Connection' => $vendorDir . '/symfony/var-dumper/Server/Connection.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Server\\DumpServer' => $vendorDir . '/symfony/var-dumper/Server/DumpServer.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => $vendorDir . '/symfony/var-dumper/Test/VarDumperTestTrait.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/var-exporter/Exception/ClassNotFoundException.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/var-exporter/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => $vendorDir . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Instantiator' => $vendorDir . '/symfony/var-exporter/Instantiator.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Exporter' => $vendorDir . '/symfony/var-exporter/Internal/Exporter.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Hydrator' => $vendorDir . '/symfony/var-exporter/Internal/Hydrator.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Reference' => $vendorDir . '/symfony/var-exporter/Internal/Reference.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Registry' => $vendorDir . '/symfony/var-exporter/Internal/Registry.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Values' => $vendorDir . '/symfony/var-exporter/Internal/Values.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\VarExporter' => $vendorDir . '/symfony/var-exporter/VarExporter.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/yaml/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\ParseException' => $vendorDir . '/symfony/yaml/Exception/ParseException.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\CacheInterface' => $vendorDir . '/symfony/cache-contracts/CacheInterface.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\CacheTrait' => $vendorDir . '/symfony/cache-contracts/CacheTrait.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\CallbackInterface' => $vendorDir . '/symfony/cache-contracts/CallbackInterface.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\ItemInterface' => $vendorDir . '/symfony/cache-contracts/ItemInterface.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => $vendorDir . '/symfony/cache-contracts/TagAwareCacheInterface.php', '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher-contracts/Event.php', '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php', '_ContaoManager\\Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => $vendorDir . '/symfony/service-contracts/Attribute/SubscribedService.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php', '_ContaoManager\\Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\Annotation\\ServiceTag' => $vendorDir . '/terminal42/service-annotation-bundle/src/Annotation/ServiceTag.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\Annotation\\ServiceTagInterface' => $vendorDir . '/terminal42/service-annotation-bundle/src/Annotation/ServiceTagInterface.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\DependencyInjection\\Compiler\\ServiceAnnotationPass' => $vendorDir . '/terminal42/service-annotation-bundle/src/DependencyInjection/Compiler/ServiceAnnotationPass.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\ServiceAnnotationInterface' => $vendorDir . '/terminal42/service-annotation-bundle/src/ServiceAnnotationInterface.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\Terminal42ServiceAnnotationBundle' => $vendorDir . '/terminal42/service-annotation-bundle/src/Terminal42ServiceAnnotationBundle.php', '_ContaoManager\\studio24\\Rotate\\Delete' => $vendorDir . '/studio24/rotate/src/Delete.php', '_ContaoManager\\studio24\\Rotate\\DirectoryIterator' => $vendorDir . '/studio24/rotate/src/DirectoryIterator.php', '_ContaoManager\\studio24\\Rotate\\FilenameFormat' => $vendorDir . '/studio24/rotate/src/FilenameFormat.php', '_ContaoManager\\studio24\\Rotate\\FilenameFormatException' => $vendorDir . '/studio24/rotate/src/FilenameFormatException.php', '_ContaoManager\\studio24\\Rotate\\Rotate' => $vendorDir . '/studio24/rotate/src/Rotate.php', '_ContaoManager\\studio24\\Rotate\\RotateAbstract' => $vendorDir . '/studio24/rotate/src/RotateAbstract.php', '_ContaoManager\\studio24\\Rotate\\RotateException' => $vendorDir . '/studio24/rotate/src/RotateException.php', ); = 70205)) { $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.'; } if ($issues) { if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } if (!ini_get('display_errors')) { if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); } elseif (!headers_sent()) { echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; } } trigger_error( 'Composer detected issues in your platform: ' . implode(' ', $issues), E_USER_ERROR ); } __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', 'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php', ); public static $prefixLengthsPsr4 = array ( '_' => array ( '_ContaoManager\\studio24\\Rotate\\' => 31, '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\' => 50, '_ContaoManager\\Symfony\\Contracts\\Service\\' => 41, '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\' => 49, '_ContaoManager\\Symfony\\Contracts\\Cache\\' => 39, '_ContaoManager\\Symfony\\Component\\Yaml\\' => 38, '_ContaoManager\\Symfony\\Component\\VarExporter\\' => 45, '_ContaoManager\\Symfony\\Component\\VarDumper\\' => 43, '_ContaoManager\\Symfony\\Component\\String\\' => 40, '_ContaoManager\\Symfony\\Component\\Security\\Http\\' => 47, '_ContaoManager\\Symfony\\Component\\Security\\Guard\\' => 48, '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\' => 47, '_ContaoManager\\Symfony\\Component\\Security\\Core\\' => 47, '_ContaoManager\\Symfony\\Component\\Routing\\' => 41, '_ContaoManager\\Symfony\\Component\\PropertyInfo\\' => 46, '_ContaoManager\\Symfony\\Component\\PropertyAccess\\' => 48, '_ContaoManager\\Symfony\\Component\\Process\\' => 41, '_ContaoManager\\Symfony\\Component\\PasswordHasher\\' => 48, '_ContaoManager\\Symfony\\Component\\HttpKernel\\' => 44, '_ContaoManager\\Symfony\\Component\\HttpFoundation\\' => 48, '_ContaoManager\\Symfony\\Component\\Finder\\' => 40, '_ContaoManager\\Symfony\\Component\\Filesystem\\' => 44, '_ContaoManager\\Symfony\\Component\\EventDispatcher\\' => 49, '_ContaoManager\\Symfony\\Component\\ErrorHandler\\' => 46, '_ContaoManager\\Symfony\\Component\\DependencyInjection\\' => 53, '_ContaoManager\\Symfony\\Component\\Console\\' => 41, '_ContaoManager\\Symfony\\Component\\Config\\' => 40, '_ContaoManager\\Symfony\\Component\\Cache\\' => 39, '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\' => 45, '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\' => 44, '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\' => 46, '_ContaoManager\\Symfony\\Bridge\\Monolog\\' => 38, '_ContaoManager\\Seld\\Signal\\' => 27, '_ContaoManager\\Seld\\PharUtils\\' => 30, '_ContaoManager\\Seld\\JsonLint\\' => 29, '_ContaoManager\\Ramsey\\Uuid\\' => 27, '_ContaoManager\\Psr\\Log\\' => 23, '_ContaoManager\\Psr\\EventDispatcher\\' => 35, '_ContaoManager\\Psr\\Container\\' => 29, '_ContaoManager\\Psr\\Cache\\' => 25, '_ContaoManager\\Monolog\\' => 23, '_ContaoManager\\JsonSchema\\' => 26, '_ContaoManager\\Firebase\\JWT\\' => 28, '_ContaoManager\\Doctrine\\Deprecations\\' => 37, '_ContaoManager\\Doctrine\\Common\\Lexer\\' => 37, '_ContaoManager\\Doctrine\\Common\\Annotations\\' => 43, '_ContaoManager\\Crell\\ApiProblem\\' => 32, '_ContaoManager\\Contao\\ManagerApi\\' => 33, ), 'S' => array ( 'Symfony\\Polyfill\\Php81\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, 'Symfony\\Polyfill\\Ctype\\' => 23, ), 'R' => array ( 'React\\Promise\\' => 14, ), 'C' => array ( 'Composer\\XdebugHandler\\' => 23, 'Composer\\Spdx\\' => 14, 'Composer\\Semver\\' => 16, 'Composer\\Pcre\\' => 14, 'Composer\\MetadataMinifier\\' => 26, 'Composer\\ClassMapGenerator\\' => 27, 'Composer\\CaBundle\\' => 18, 'Composer\\' => 9, ), ); public static $prefixDirsPsr4 = array ( '_ContaoManager\\studio24\\Rotate\\' => array ( 0 => __DIR__ . '/..' . '/studio24/rotate/src', ), '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\' => array ( 0 => __DIR__ . '/..' . '/terminal42/service-annotation-bundle/src', ), '_ContaoManager\\Symfony\\Contracts\\Service\\' => array ( 0 => __DIR__ . '/..' . '/symfony/service-contracts', ), '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', ), '_ContaoManager\\Symfony\\Contracts\\Cache\\' => array ( 0 => __DIR__ . '/..' . '/symfony/cache-contracts', ), '_ContaoManager\\Symfony\\Component\\Yaml\\' => array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), '_ContaoManager\\Symfony\\Component\\VarExporter\\' => array ( 0 => __DIR__ . '/..' . '/symfony/var-exporter', ), '_ContaoManager\\Symfony\\Component\\VarDumper\\' => array ( 0 => __DIR__ . '/..' . '/symfony/var-dumper', ), '_ContaoManager\\Symfony\\Component\\String\\' => array ( 0 => __DIR__ . '/..' . '/symfony/string', ), '_ContaoManager\\Symfony\\Component\\Security\\Http\\' => array ( 0 => __DIR__ . '/..' . '/symfony/security-http', ), '_ContaoManager\\Symfony\\Component\\Security\\Guard\\' => array ( 0 => __DIR__ . '/..' . '/symfony/security-guard', ), '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\' => array ( 0 => __DIR__ . '/..' . '/symfony/security-csrf', ), '_ContaoManager\\Symfony\\Component\\Security\\Core\\' => array ( 0 => __DIR__ . '/..' . '/symfony/security-core', ), '_ContaoManager\\Symfony\\Component\\Routing\\' => array ( 0 => __DIR__ . '/..' . '/symfony/routing', ), '_ContaoManager\\Symfony\\Component\\PropertyInfo\\' => array ( 0 => __DIR__ . '/..' . '/symfony/property-info', ), '_ContaoManager\\Symfony\\Component\\PropertyAccess\\' => array ( 0 => __DIR__ . '/..' . '/symfony/property-access', ), '_ContaoManager\\Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), '_ContaoManager\\Symfony\\Component\\PasswordHasher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/password-hasher', ), '_ContaoManager\\Symfony\\Component\\HttpKernel\\' => array ( 0 => __DIR__ . '/..' . '/symfony/http-kernel', ), '_ContaoManager\\Symfony\\Component\\HttpFoundation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/http-foundation', ), '_ContaoManager\\Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', ), '_ContaoManager\\Symfony\\Component\\Filesystem\\' => array ( 0 => __DIR__ . '/..' . '/symfony/filesystem', ), '_ContaoManager\\Symfony\\Component\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', ), '_ContaoManager\\Symfony\\Component\\ErrorHandler\\' => array ( 0 => __DIR__ . '/..' . '/symfony/error-handler', ), '_ContaoManager\\Symfony\\Component\\DependencyInjection\\' => array ( 0 => __DIR__ . '/..' . '/symfony/dependency-injection', ), '_ContaoManager\\Symfony\\Component\\Console\\' => array ( 0 => __DIR__ . '/..' . '/symfony/console', ), '_ContaoManager\\Symfony\\Component\\Config\\' => array ( 0 => __DIR__ . '/..' . '/symfony/config', ), '_ContaoManager\\Symfony\\Component\\Cache\\' => array ( 0 => __DIR__ . '/..' . '/symfony/cache', ), '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\' => array ( 0 => __DIR__ . '/..' . '/symfony/security-bundle', ), '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\' => array ( 0 => __DIR__ . '/..' . '/symfony/monolog-bundle', ), '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\' => array ( 0 => __DIR__ . '/..' . '/symfony/framework-bundle', ), '_ContaoManager\\Symfony\\Bridge\\Monolog\\' => array ( 0 => __DIR__ . '/..' . '/symfony/monolog-bridge', ), '_ContaoManager\\Seld\\Signal\\' => array ( 0 => __DIR__ . '/..' . '/seld/signal-handler/src', ), '_ContaoManager\\Seld\\PharUtils\\' => array ( 0 => __DIR__ . '/..' . '/seld/phar-utils/src', ), '_ContaoManager\\Seld\\JsonLint\\' => array ( 0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint', ), '_ContaoManager\\Ramsey\\Uuid\\' => array ( 0 => __DIR__ . '/..' . '/ramsey/uuid/src', ), '_ContaoManager\\Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), '_ContaoManager\\Psr\\EventDispatcher\\' => array ( 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', ), '_ContaoManager\\Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), '_ContaoManager\\Psr\\Cache\\' => array ( 0 => __DIR__ . '/..' . '/psr/cache/src', ), '_ContaoManager\\Monolog\\' => array ( 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', ), '_ContaoManager\\JsonSchema\\' => array ( 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', ), '_ContaoManager\\Firebase\\JWT\\' => array ( 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', ), '_ContaoManager\\Doctrine\\Deprecations\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations', ), '_ContaoManager\\Doctrine\\Common\\Lexer\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/lexer/src', ), '_ContaoManager\\Doctrine\\Common\\Annotations\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', ), '_ContaoManager\\Crell\\ApiProblem\\' => array ( 0 => __DIR__ . '/..' . '/crell/api-problem/src', ), '_ContaoManager\\Contao\\ManagerApi\\' => array ( 0 => __DIR__ . '/../..' . '/api', ), 'Symfony\\Polyfill\\Php81\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', ), 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), 'Symfony\\Polyfill\\Php73\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', ), 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), 'React\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/react/promise/src', ), 'Composer\\XdebugHandler\\' => array ( 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', ), 'Composer\\Spdx\\' => array ( 0 => __DIR__ . '/..' . '/composer/spdx-licenses/src', ), 'Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), 'Composer\\Pcre\\' => array ( 0 => __DIR__ . '/..' . '/composer/pcre/src', ), 'Composer\\MetadataMinifier\\' => array ( 0 => __DIR__ . '/..' . '/composer/metadata-minifier/src', ), 'Composer\\ClassMapGenerator\\' => array ( 0 => __DIR__ . '/..' . '/composer/class-map-generator/src', ), 'Composer\\CaBundle\\' => array ( 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', ), 'Composer\\' => array ( 0 => __DIR__ . '/..' . '/composer/composer/src/Composer', ), ); public static $classMap = array ( 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\Advisory\\Auditor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/Auditor.php', 'Composer\\Advisory\\IgnoredSecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php', 'Composer\\Advisory\\PartialSecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php', 'Composer\\Advisory\\SecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/SecurityAdvisory.php', 'Composer\\Autoload\\AutoloadGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php', 'Composer\\Autoload\\ClassLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassLoader.php', 'Composer\\Autoload\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php', 'Composer\\CaBundle\\CaBundle' => __DIR__ . '/..' . '/composer/ca-bundle/src/CaBundle.php', 'Composer\\Cache' => __DIR__ . '/..' . '/composer/composer/src/Composer/Cache.php', 'Composer\\ClassMapGenerator\\ClassMap' => __DIR__ . '/..' . '/composer/class-map-generator/src/ClassMap.php', 'Composer\\ClassMapGenerator\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/class-map-generator/src/ClassMapGenerator.php', 'Composer\\ClassMapGenerator\\FileList' => __DIR__ . '/..' . '/composer/class-map-generator/src/FileList.php', 'Composer\\ClassMapGenerator\\PhpFileCleaner' => __DIR__ . '/..' . '/composer/class-map-generator/src/PhpFileCleaner.php', 'Composer\\ClassMapGenerator\\PhpFileParser' => __DIR__ . '/..' . '/composer/class-map-generator/src/PhpFileParser.php', 'Composer\\Command\\AboutCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AboutCommand.php', 'Composer\\Command\\ArchiveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ArchiveCommand.php', 'Composer\\Command\\AuditCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AuditCommand.php', 'Composer\\Command\\BaseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseCommand.php', 'Composer\\Command\\BaseDependencyCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php', 'Composer\\Command\\BumpCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BumpCommand.php', 'Composer\\Command\\CheckPlatformReqsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php', 'Composer\\Command\\ClearCacheCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ClearCacheCommand.php', 'Composer\\Command\\CompletionTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CompletionTrait.php', 'Composer\\Command\\ConfigCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ConfigCommand.php', 'Composer\\Command\\CreateProjectCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CreateProjectCommand.php', 'Composer\\Command\\DependsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DependsCommand.php', 'Composer\\Command\\DiagnoseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DiagnoseCommand.php', 'Composer\\Command\\DumpAutoloadCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php', 'Composer\\Command\\ExecCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ExecCommand.php', 'Composer\\Command\\FundCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/FundCommand.php', 'Composer\\Command\\GlobalCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/GlobalCommand.php', 'Composer\\Command\\HomeCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/HomeCommand.php', 'Composer\\Command\\InitCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InitCommand.php', 'Composer\\Command\\InstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InstallCommand.php', 'Composer\\Command\\LicensesCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/LicensesCommand.php', 'Composer\\Command\\OutdatedCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/OutdatedCommand.php', 'Composer\\Command\\PackageDiscoveryTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php', 'Composer\\Command\\ProhibitsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ProhibitsCommand.php', 'Composer\\Command\\ReinstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ReinstallCommand.php', 'Composer\\Command\\RemoveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RemoveCommand.php', 'Composer\\Command\\RequireCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RequireCommand.php', 'Composer\\Command\\RunScriptCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RunScriptCommand.php', 'Composer\\Command\\ScriptAliasCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php', 'Composer\\Command\\SearchCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SearchCommand.php', 'Composer\\Command\\SelfUpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php', 'Composer\\Command\\ShowCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ShowCommand.php', 'Composer\\Command\\StatusCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/StatusCommand.php', 'Composer\\Command\\SuggestsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SuggestsCommand.php', 'Composer\\Command\\UpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/UpdateCommand.php', 'Composer\\Command\\ValidateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ValidateCommand.php', 'Composer\\Compiler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Compiler.php', 'Composer\\Composer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Composer.php', 'Composer\\Config' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config.php', 'Composer\\Config\\ConfigSourceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php', 'Composer\\Config\\JsonConfigSource' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/JsonConfigSource.php', 'Composer\\Console\\Application' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Application.php', 'Composer\\Console\\GithubActionError' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/GithubActionError.php', 'Composer\\Console\\HtmlOutputFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php', 'Composer\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Input/InputArgument.php', 'Composer\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Input/InputOption.php', 'Composer\\DependencyResolver\\Decisions' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Decisions.php', 'Composer\\DependencyResolver\\DefaultPolicy' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php', 'Composer\\DependencyResolver\\GenericRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php', 'Composer\\DependencyResolver\\LocalRepoTransaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php', 'Composer\\DependencyResolver\\LockTransaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/LockTransaction.php', 'Composer\\DependencyResolver\\MultiConflictRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php', 'Composer\\DependencyResolver\\Operation\\InstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php', 'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php', 'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php', 'Composer\\DependencyResolver\\Operation\\OperationInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php', 'Composer\\DependencyResolver\\Operation\\SolverOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php', 'Composer\\DependencyResolver\\Operation\\UninstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php', 'Composer\\DependencyResolver\\Operation\\UpdateOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php', 'Composer\\DependencyResolver\\PolicyInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php', 'Composer\\DependencyResolver\\Pool' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Pool.php', 'Composer\\DependencyResolver\\PoolBuilder' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php', 'Composer\\DependencyResolver\\PoolOptimizer' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php', 'Composer\\DependencyResolver\\Problem' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Problem.php', 'Composer\\DependencyResolver\\Request' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Request.php', 'Composer\\DependencyResolver\\Rule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule.php', 'Composer\\DependencyResolver\\Rule2Literals' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php', 'Composer\\DependencyResolver\\RuleSet' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php', 'Composer\\DependencyResolver\\RuleSetGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php', 'Composer\\DependencyResolver\\RuleSetIterator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php', 'Composer\\DependencyResolver\\RuleWatchChain' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php', 'Composer\\DependencyResolver\\RuleWatchGraph' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php', 'Composer\\DependencyResolver\\RuleWatchNode' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php', 'Composer\\DependencyResolver\\Solver' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Solver.php', 'Composer\\DependencyResolver\\SolverBugException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php', 'Composer\\DependencyResolver\\SolverProblemsException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php', 'Composer\\DependencyResolver\\Transaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Transaction.php', 'Composer\\Downloader\\ArchiveDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php', 'Composer\\Downloader\\ChangeReportInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php', 'Composer\\Downloader\\DownloadManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloadManager.php', 'Composer\\Downloader\\DownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php', 'Composer\\Downloader\\DvcsDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php', 'Composer\\Downloader\\FileDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FileDownloader.php', 'Composer\\Downloader\\FilesystemException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FilesystemException.php', 'Composer\\Downloader\\FossilDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FossilDownloader.php', 'Composer\\Downloader\\GitDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GitDownloader.php', 'Composer\\Downloader\\GzipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GzipDownloader.php', 'Composer\\Downloader\\HgDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/HgDownloader.php', 'Composer\\Downloader\\MaxFileSizeExceededException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php', 'Composer\\Downloader\\PathDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PathDownloader.php', 'Composer\\Downloader\\PerforceDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php', 'Composer\\Downloader\\PharDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PharDownloader.php', 'Composer\\Downloader\\RarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/RarDownloader.php', 'Composer\\Downloader\\SvnDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/SvnDownloader.php', 'Composer\\Downloader\\TarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TarDownloader.php', 'Composer\\Downloader\\TransportException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TransportException.php', 'Composer\\Downloader\\VcsCapableDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php', 'Composer\\Downloader\\VcsDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsDownloader.php', 'Composer\\Downloader\\XzDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/XzDownloader.php', 'Composer\\Downloader\\ZipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ZipDownloader.php', 'Composer\\EventDispatcher\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/Event.php', 'Composer\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php', 'Composer\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php', 'Composer\\EventDispatcher\\ScriptExecutionException' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php', 'Composer\\Exception\\IrrecoverableDownloadException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php', 'Composer\\Exception\\NoSslException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/NoSslException.php', 'Composer\\Factory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Factory.php', 'Composer\\Filter\\PlatformRequirementFilter\\IgnoreAllPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php', 'Composer\\Filter\\PlatformRequirementFilter\\IgnoreListPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php', 'Composer\\Filter\\PlatformRequirementFilter\\IgnoreNothingPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php', 'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php', 'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php', 'Composer\\IO\\BaseIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BaseIO.php', 'Composer\\IO\\BufferIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BufferIO.php', 'Composer\\IO\\ConsoleIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/ConsoleIO.php', 'Composer\\IO\\IOInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/IOInterface.php', 'Composer\\IO\\NullIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/NullIO.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\Installer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer.php', 'Composer\\Installer\\BinaryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryInstaller.php', 'Composer\\Installer\\BinaryPresenceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php', 'Composer\\Installer\\InstallationManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallationManager.php', 'Composer\\Installer\\InstallerEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvent.php', 'Composer\\Installer\\InstallerEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvents.php', 'Composer\\Installer\\InstallerInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerInterface.php', 'Composer\\Installer\\LibraryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/LibraryInstaller.php', 'Composer\\Installer\\MetapackageInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php', 'Composer\\Installer\\NoopInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/NoopInstaller.php', 'Composer\\Installer\\PackageEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvent.php', 'Composer\\Installer\\PackageEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvents.php', 'Composer\\Installer\\PluginInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PluginInstaller.php', 'Composer\\Installer\\ProjectInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/ProjectInstaller.php', 'Composer\\Installer\\SuggestedPackagesReporter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php', 'Composer\\Json\\JsonFile' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFile.php', 'Composer\\Json\\JsonFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFormatter.php', 'Composer\\Json\\JsonManipulator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonManipulator.php', 'Composer\\Json\\JsonValidationException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonValidationException.php', 'Composer\\MetadataMinifier\\MetadataMinifier' => __DIR__ . '/..' . '/composer/metadata-minifier/src/MetadataMinifier.php', 'Composer\\PHPStan\\ConfigReturnTypeExtension' => __DIR__ . '/..' . '/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php', 'Composer\\PHPStan\\RuleReasonDataReturnTypeExtension' => __DIR__ . '/..' . '/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php', 'Composer\\Package\\AliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/AliasPackage.php', 'Composer\\Package\\Archiver\\ArchivableFilesFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php', 'Composer\\Package\\Archiver\\ArchivableFilesFinder' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php', 'Composer\\Package\\Archiver\\ArchiveManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php', 'Composer\\Package\\Archiver\\ArchiverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php', 'Composer\\Package\\Archiver\\BaseExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php', 'Composer\\Package\\Archiver\\ComposerExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php', 'Composer\\Package\\Archiver\\GitExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php', 'Composer\\Package\\Archiver\\PharArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php', 'Composer\\Package\\Archiver\\ZipArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php', 'Composer\\Package\\BasePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/BasePackage.php', 'Composer\\Package\\Comparer\\Comparer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Comparer/Comparer.php', 'Composer\\Package\\CompleteAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompleteAliasPackage.php', 'Composer\\Package\\CompletePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackage.php', 'Composer\\Package\\CompletePackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackageInterface.php', 'Composer\\Package\\Dumper\\ArrayDumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php', 'Composer\\Package\\Link' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Link.php', 'Composer\\Package\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php', 'Composer\\Package\\Loader\\InvalidPackageException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php', 'Composer\\Package\\Loader\\JsonLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php', 'Composer\\Package\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php', 'Composer\\Package\\Loader\\RootPackageLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php', 'Composer\\Package\\Loader\\ValidatingArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php', 'Composer\\Package\\Locker' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Locker.php', 'Composer\\Package\\Package' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Package.php', 'Composer\\Package\\PackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/PackageInterface.php', 'Composer\\Package\\RootAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootAliasPackage.php', 'Composer\\Package\\RootPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackage.php', 'Composer\\Package\\RootPackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackageInterface.php', 'Composer\\Package\\Version\\StabilityFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/StabilityFilter.php', 'Composer\\Package\\Version\\VersionBumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionBumper.php', 'Composer\\Package\\Version\\VersionGuesser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php', 'Composer\\Package\\Version\\VersionParser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionParser.php', 'Composer\\Package\\Version\\VersionSelector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionSelector.php', 'Composer\\PartialComposer' => __DIR__ . '/..' . '/composer/composer/src/Composer/PartialComposer.php', 'Composer\\Pcre\\MatchAllResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllResult.php', 'Composer\\Pcre\\MatchAllStrictGroupsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllStrictGroupsResult.php', 'Composer\\Pcre\\MatchAllWithOffsetsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllWithOffsetsResult.php', 'Composer\\Pcre\\MatchResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchResult.php', 'Composer\\Pcre\\MatchStrictGroupsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchStrictGroupsResult.php', 'Composer\\Pcre\\MatchWithOffsetsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchWithOffsetsResult.php', 'Composer\\Pcre\\PcreException' => __DIR__ . '/..' . '/composer/pcre/src/PcreException.php', 'Composer\\Pcre\\Preg' => __DIR__ . '/..' . '/composer/pcre/src/Preg.php', 'Composer\\Pcre\\Regex' => __DIR__ . '/..' . '/composer/pcre/src/Regex.php', 'Composer\\Pcre\\ReplaceResult' => __DIR__ . '/..' . '/composer/pcre/src/ReplaceResult.php', 'Composer\\Pcre\\UnexpectedNullMatchException' => __DIR__ . '/..' . '/composer/pcre/src/UnexpectedNullMatchException.php', 'Composer\\Platform\\HhvmDetector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/HhvmDetector.php', 'Composer\\Platform\\Runtime' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/Runtime.php', 'Composer\\Platform\\Version' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/Version.php', 'Composer\\Plugin\\Capability\\Capability' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/Capability.php', 'Composer\\Plugin\\Capability\\CommandProvider' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php', 'Composer\\Plugin\\Capable' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capable.php', 'Composer\\Plugin\\CommandEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/CommandEvent.php', 'Composer\\Plugin\\PluginBlockedException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginBlockedException.php', 'Composer\\Plugin\\PluginEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginEvents.php', 'Composer\\Plugin\\PluginInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginInterface.php', 'Composer\\Plugin\\PluginManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginManager.php', 'Composer\\Plugin\\PostFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php', 'Composer\\Plugin\\PreCommandRunEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php', 'Composer\\Plugin\\PreFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php', 'Composer\\Plugin\\PrePoolCreateEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php', 'Composer\\Question\\StrictConfirmationQuestion' => __DIR__ . '/..' . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php', 'Composer\\Repository\\AdvisoryProviderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php', 'Composer\\Repository\\ArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArrayRepository.php', 'Composer\\Repository\\ArtifactRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArtifactRepository.php', 'Composer\\Repository\\CanonicalPackagesTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php', 'Composer\\Repository\\ComposerRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ComposerRepository.php', 'Composer\\Repository\\CompositeRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CompositeRepository.php', 'Composer\\Repository\\ConfigurableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php', 'Composer\\Repository\\FilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilesystemRepository.php', 'Composer\\Repository\\FilterRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilterRepository.php', 'Composer\\Repository\\InstalledArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php', 'Composer\\Repository\\InstalledFilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php', 'Composer\\Repository\\InstalledRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepository.php', 'Composer\\Repository\\InstalledRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php', 'Composer\\Repository\\InvalidRepositoryException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php', 'Composer\\Repository\\LockArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/LockArrayRepository.php', 'Composer\\Repository\\PackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PackageRepository.php', 'Composer\\Repository\\PathRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PathRepository.php', 'Composer\\Repository\\PearRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PearRepository.php', 'Composer\\Repository\\PlatformRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PlatformRepository.php', 'Composer\\Repository\\RepositoryFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryFactory.php', 'Composer\\Repository\\RepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryInterface.php', 'Composer\\Repository\\RepositoryManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryManager.php', 'Composer\\Repository\\RepositorySecurityException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php', 'Composer\\Repository\\RepositorySet' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySet.php', 'Composer\\Repository\\RepositoryUtils' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryUtils.php', 'Composer\\Repository\\RootPackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RootPackageRepository.php', 'Composer\\Repository\\VcsRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VcsRepository.php', 'Composer\\Repository\\Vcs\\FossilDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php', 'Composer\\Repository\\Vcs\\GitBitbucketDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php', 'Composer\\Repository\\Vcs\\GitDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php', 'Composer\\Repository\\Vcs\\GitHubDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php', 'Composer\\Repository\\Vcs\\GitLabDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php', 'Composer\\Repository\\Vcs\\HgDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php', 'Composer\\Repository\\Vcs\\PerforceDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php', 'Composer\\Repository\\Vcs\\SvnDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php', 'Composer\\Repository\\Vcs\\VcsDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php', 'Composer\\Repository\\Vcs\\VcsDriverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php', 'Composer\\Repository\\VersionCacheInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php', 'Composer\\Repository\\WritableArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php', 'Composer\\Repository\\WritableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php', 'Composer\\Script\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/Event.php', 'Composer\\Script\\ScriptEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/ScriptEvents.php', 'Composer\\SelfUpdate\\Keys' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Keys.php', 'Composer\\SelfUpdate\\Versions' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Versions.php', 'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php', 'Composer\\Semver\\CompilingMatcher' => __DIR__ . '/..' . '/composer/semver/src/CompilingMatcher.php', 'Composer\\Semver\\Constraint\\Bound' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Bound.php', 'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php', 'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php', 'Composer\\Semver\\Constraint\\MatchAllConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchAllConstraint.php', 'Composer\\Semver\\Constraint\\MatchNoneConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchNoneConstraint.php', 'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php', 'Composer\\Semver\\Interval' => __DIR__ . '/..' . '/composer/semver/src/Interval.php', 'Composer\\Semver\\Intervals' => __DIR__ . '/..' . '/composer/semver/src/Intervals.php', 'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php', 'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php', 'Composer\\Spdx\\SpdxLicenses' => __DIR__ . '/..' . '/composer/spdx-licenses/src/SpdxLicenses.php', 'Composer\\Util\\AuthHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/AuthHelper.php', 'Composer\\Util\\Bitbucket' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Bitbucket.php', 'Composer\\Util\\ComposerMirror' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ComposerMirror.php', 'Composer\\Util\\ConfigValidator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ConfigValidator.php', 'Composer\\Util\\ErrorHandler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ErrorHandler.php', 'Composer\\Util\\Filesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Filesystem.php', 'Composer\\Util\\Git' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Git.php', 'Composer\\Util\\GitHub' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitHub.php', 'Composer\\Util\\GitLab' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitLab.php', 'Composer\\Util\\Hg' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Hg.php', 'Composer\\Util\\HttpDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/HttpDownloader.php', 'Composer\\Util\\Http\\CurlDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/CurlDownloader.php', 'Composer\\Util\\Http\\CurlResponse' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/CurlResponse.php', 'Composer\\Util\\Http\\ProxyHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/ProxyHelper.php', 'Composer\\Util\\Http\\ProxyManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/ProxyManager.php', 'Composer\\Util\\Http\\RequestProxy' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/RequestProxy.php', 'Composer\\Util\\Http\\Response' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/Response.php', 'Composer\\Util\\IniHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/IniHelper.php', 'Composer\\Util\\Loop' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Loop.php', 'Composer\\Util\\MetadataMinifier' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/MetadataMinifier.php', 'Composer\\Util\\NoProxyPattern' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/NoProxyPattern.php', 'Composer\\Util\\PackageInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageInfo.php', 'Composer\\Util\\PackageSorter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageSorter.php', 'Composer\\Util\\Perforce' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Perforce.php', 'Composer\\Util\\Platform' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Platform.php', 'Composer\\Util\\ProcessExecutor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ProcessExecutor.php', 'Composer\\Util\\RemoteFilesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/RemoteFilesystem.php', 'Composer\\Util\\Silencer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Silencer.php', 'Composer\\Util\\StreamContextFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/StreamContextFactory.php', 'Composer\\Util\\Svn' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Svn.php', 'Composer\\Util\\SyncHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/SyncHelper.php', 'Composer\\Util\\Tar' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Tar.php', 'Composer\\Util\\TlsHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/TlsHelper.php', 'Composer\\Util\\Url' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Url.php', 'Composer\\Util\\Zip' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Zip.php', 'Composer\\XdebugHandler\\PhpConfig' => __DIR__ . '/..' . '/composer/xdebug-handler/src/PhpConfig.php', 'Composer\\XdebugHandler\\Process' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Process.php', 'Composer\\XdebugHandler\\Status' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Status.php', 'Composer\\XdebugHandler\\XdebugHandler' => __DIR__ . '/..' . '/composer/xdebug-handler/src/XdebugHandler.php', 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'React\\Promise\\Deferred' => __DIR__ . '/..' . '/react/promise/src/Deferred.php', 'React\\Promise\\Exception\\CompositeException' => __DIR__ . '/..' . '/react/promise/src/Exception/CompositeException.php', 'React\\Promise\\Exception\\LengthException' => __DIR__ . '/..' . '/react/promise/src/Exception/LengthException.php', 'React\\Promise\\Internal\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/Internal/CancellationQueue.php', 'React\\Promise\\Internal\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/FulfilledPromise.php', 'React\\Promise\\Internal\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/RejectedPromise.php', 'React\\Promise\\Promise' => __DIR__ . '/..' . '/react/promise/src/Promise.php', 'React\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/react/promise/src/PromiseInterface.php', 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.php', 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', 'Symfony\\Polyfill\\Php81\\Php81' => __DIR__ . '/..' . '/symfony/polyfill-php81/Php81.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', '_ContaoManager\\Contao\\ManagerApi\\ApiApplication' => __DIR__ . '/../..' . '/api/ApiApplication.php', '_ContaoManager\\Contao\\ManagerApi\\ApiKernel' => __DIR__ . '/../..' . '/api/ApiKernel.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\AboutCommand' => __DIR__ . '/../..' . '/api/Command/AboutCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\IntegrityCheckCommand' => __DIR__ . '/../..' . '/api/Command/IntegrityCheckCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\ProcessRunnerCommand' => __DIR__ . '/../..' . '/api/Command/ProcessRunnerCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\TaskAbortCommand' => __DIR__ . '/../..' . '/api/Command/TaskAbortCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\TaskDeleteCommand' => __DIR__ . '/../..' . '/api/Command/TaskDeleteCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\TaskUpdateCommand' => __DIR__ . '/../..' . '/api/Command/TaskUpdateCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Command\\UpdateCommand' => __DIR__ . '/../..' . '/api/Command/UpdateCommand.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudChanges' => __DIR__ . '/../..' . '/api/Composer/CloudChanges.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudException' => __DIR__ . '/../..' . '/api/Composer/CloudException.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudJob' => __DIR__ . '/../..' . '/api/Composer/CloudJob.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\CloudResolver' => __DIR__ . '/../..' . '/api/Composer/CloudResolver.php', '_ContaoManager\\Contao\\ManagerApi\\Composer\\Environment' => __DIR__ . '/../..' . '/api/Composer/Environment.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\AbstractConfig' => __DIR__ . '/../..' . '/api/Config/AbstractConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\AuthConfig' => __DIR__ . '/../..' . '/api/Config/AuthConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\ComposerConfig' => __DIR__ . '/../..' . '/api/Config/ComposerConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\ManagerConfig' => __DIR__ . '/../..' . '/api/Config/ManagerConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\PartialConfig' => __DIR__ . '/../..' . '/api/Config/PartialConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\UploadsConfig' => __DIR__ . '/../..' . '/api/Config/UploadsConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Config\\UserConfig' => __DIR__ . '/../..' . '/api/Config/UserConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\AbstractConfigController' => __DIR__ . '/../..' . '/api/Controller/Config/AbstractConfigController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\AuthController' => __DIR__ . '/../..' . '/api/Controller/Config/AuthController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\ComposerController' => __DIR__ . '/../..' . '/api/Controller/Config/ComposerController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Config\\ManagerController' => __DIR__ . '/../..' . '/api/Controller/Config/ManagerController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\ConstraintController' => __DIR__ . '/../..' . '/api/Controller/ConstraintController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\AccessKeyController' => __DIR__ . '/../..' . '/api/Controller/Contao/AccessKeyController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\BackupController' => __DIR__ . '/../..' . '/api/Controller/Contao/BackupController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\DatabaseMigrationController' => __DIR__ . '/../..' . '/api/Controller/Contao/DatabaseMigrationController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\InstallToolLockController' => __DIR__ . '/../..' . '/api/Controller/Contao/InstallToolLockController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\JwtCookieController' => __DIR__ . '/../..' . '/api/Controller/Contao/JwtCookieController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Contao\\MaintenanceModeController' => __DIR__ . '/../..' . '/api/Controller/Contao/MaintenanceModeController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\FileController' => __DIR__ . '/../..' . '/api/Controller/FileController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\LogController' => __DIR__ . '/../..' . '/api/Controller/LogController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\CloudController' => __DIR__ . '/../..' . '/api/Controller/Packages/CloudController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\LocalPackagesController' => __DIR__ . '/../..' . '/api/Controller/Packages/LocalPackagesController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\MissingPackagesController' => __DIR__ . '/../..' . '/api/Controller/Packages/MissingPackagesController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\RootPackageController' => __DIR__ . '/../..' . '/api/Controller/Packages/RootPackageController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Packages\\UploadPackagesController' => __DIR__ . '/../..' . '/api/Controller/Packages/UploadPackagesController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\AdminUserController' => __DIR__ . '/../..' . '/api/Controller/Server/AdminUserController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\ComposerController' => __DIR__ . '/../..' . '/api/Controller/Server/ComposerController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\ConfigController' => __DIR__ . '/../..' . '/api/Controller/Server/ConfigController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\ContaoController' => __DIR__ . '/../..' . '/api/Controller/Server/ContaoController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\DatabaseController' => __DIR__ . '/../..' . '/api/Controller/Server/DatabaseController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\OpcacheController' => __DIR__ . '/../..' . '/api/Controller/Server/OpcacheController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\PhpCliController' => __DIR__ . '/../..' . '/api/Controller/Server/PhpCliController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\PhpWebController' => __DIR__ . '/../..' . '/api/Controller/Server/PhpWebController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\PhpinfoController' => __DIR__ . '/../..' . '/api/Controller/Server/PhpinfoController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\Server\\SelfUpdateController' => __DIR__ . '/../..' . '/api/Controller/Server/SelfUpdateController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\SessionController' => __DIR__ . '/../..' . '/api/Controller/SessionController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\TaskController' => __DIR__ . '/../..' . '/api/Controller/TaskController.php', '_ContaoManager\\Contao\\ManagerApi\\Controller\\UserController' => __DIR__ . '/../..' . '/api/Controller/UserController.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\ExceptionListener' => __DIR__ . '/../..' . '/api/EventListener/ExceptionListener.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\JsonRequestListener' => __DIR__ . '/../..' . '/api/EventListener/JsonRequestListener.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\LocaleListener' => __DIR__ . '/../..' . '/api/EventListener/LocaleListener.php', '_ContaoManager\\Contao\\ManagerApi\\EventListener\\SecurityListener' => __DIR__ . '/../..' . '/api/EventListener/SecurityListener.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\ApiProblemException' => __DIR__ . '/../..' . '/api/Exception/ApiProblemException.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\InvalidJsonException' => __DIR__ . '/../..' . '/api/Exception/InvalidJsonException.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\ProcessOutputException' => __DIR__ . '/../..' . '/api/Exception/ProcessOutputException.php', '_ContaoManager\\Contao\\ManagerApi\\Exception\\RequestException' => __DIR__ . '/../..' . '/api/Exception/RequestException.php', '_ContaoManager\\Contao\\ManagerApi\\HttpKernel\\ApiProblemResponse' => __DIR__ . '/../..' . '/api/HttpKernel/ApiProblemResponse.php', '_ContaoManager\\Contao\\ManagerApi\\I18n\\Translator' => __DIR__ . '/../..' . '/api/I18n/Translator.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\AbstractIntegrityCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/AbstractIntegrityCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\AllowUrlFopenCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/AllowUrlFopenCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\GraphicsLibCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/GraphicsLibCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\IntegrityCheckFactory' => __DIR__ . '/../..' . '/api/IntegrityCheck/IntegrityCheckFactory.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\IntegrityCheckInterface' => __DIR__ . '/../..' . '/api/IntegrityCheck/IntegrityCheckInterface.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\MemoryLimitCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/MemoryLimitCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\PhpExtensionsCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/PhpExtensionsCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\ProcessCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/ProcessCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\SessionCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/SessionCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\SymlinkCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/SymlinkCheck.php', '_ContaoManager\\Contao\\ManagerApi\\IntegrityCheck\\SysTempDirCheck' => __DIR__ . '/../..' . '/api/IntegrityCheck/SysTempDirCheck.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\AbstractProcess' => __DIR__ . '/../..' . '/api/Process/AbstractProcess.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ConsoleProcessFactory' => __DIR__ . '/../..' . '/api/Process/ConsoleProcessFactory.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ContaoApi' => __DIR__ . '/../..' . '/api/Process/ContaoApi.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ContaoConsole' => __DIR__ . '/../..' . '/api/Process/ContaoConsole.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\AbstractForker' => __DIR__ . '/../..' . '/api/Process/Forker/AbstractForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\DisownForker' => __DIR__ . '/../..' . '/api/Process/Forker/DisownForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\ForkerInterface' => __DIR__ . '/../..' . '/api/Process/Forker/ForkerInterface.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\InlineForker' => __DIR__ . '/../..' . '/api/Process/Forker/InlineForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\NohupForker' => __DIR__ . '/../..' . '/api/Process/Forker/NohupForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Forker\\WindowsStartForker' => __DIR__ . '/../..' . '/api/Process/Forker/WindowsStartForker.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\PhpExecutableFinder' => __DIR__ . '/../..' . '/api/Process/PhpExecutableFinder.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ProcessController' => __DIR__ . '/../..' . '/api/Process/ProcessController.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\ProcessRunner' => __DIR__ . '/../..' . '/api/Process/ProcessRunner.php', '_ContaoManager\\Contao\\ManagerApi\\Process\\Utf8Process' => __DIR__ . '/../..' . '/api/Process/Utf8Process.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\AbstractBrowserAuthenticator' => __DIR__ . '/../..' . '/api/Security/AbstractBrowserAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\JwtAuthenticator' => __DIR__ . '/../..' . '/api/Security/JwtAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\JwtManager' => __DIR__ . '/../..' . '/api/Security/JwtManager.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\LoginAuthenticator' => __DIR__ . '/../..' . '/api/Security/LoginAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\PasswordlessAuthenticator' => __DIR__ . '/../..' . '/api/Security/PasswordlessAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\TokenAuthenticator' => __DIR__ . '/../..' . '/api/Security/TokenAuthenticator.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\User' => __DIR__ . '/../..' . '/api/Security/User.php', '_ContaoManager\\Contao\\ManagerApi\\Security\\UserProvider' => __DIR__ . '/../..' . '/api/Security/UserProvider.php', '_ContaoManager\\Contao\\ManagerApi\\System\\Request' => __DIR__ . '/../..' . '/api/System/Request.php', '_ContaoManager\\Contao\\ManagerApi\\System\\SelfUpdate' => __DIR__ . '/../..' . '/api/System/SelfUpdate.php', '_ContaoManager\\Contao\\ManagerApi\\System\\ServerInfo' => __DIR__ . '/../..' . '/api/System/ServerInfo.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\AbstractInlineOperation' => __DIR__ . '/../..' . '/api/TaskOperation/AbstractInlineOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\AbstractProcessOperation' => __DIR__ . '/../..' . '/api/TaskOperation/AbstractProcessOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\ClearCacheOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/ClearCacheOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\CloudOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/CloudOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\CreateProjectOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/CreateProjectOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\DumpAutoloadOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/DumpAutoloadOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\InstallOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/InstallOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\RemoveOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/RemoveOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\RequireOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/RequireOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Composer\\UpdateOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Composer/UpdateOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\ConsoleOutput' => __DIR__ . '/../..' . '/api/TaskOperation/ConsoleOutput.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\BackupCreateOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Contao/BackupCreateOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\BackupRestoreOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Contao/BackupRestoreOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\CacheClearOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Contao/CacheClearOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\CacheWarmupOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Contao/CacheWarmupOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\CreateContaoOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Contao/CreateContaoOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Contao\\MaintenanceModeOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Contao/MaintenanceModeOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\InstallUploadsOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Filesystem/InstallUploadsOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\RemoveCacheOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Filesystem/RemoveCacheOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\RemoveUploadsOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Filesystem/RemoveUploadsOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Filesystem\\RemoveVendorOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Filesystem/RemoveVendorOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\Manager\\SelfUpdateOperation' => __DIR__ . '/../..' . '/api/TaskOperation/Manager/SelfUpdateOperation.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\SponsoredOperationInterface' => __DIR__ . '/../..' . '/api/TaskOperation/SponsoredOperationInterface.php', '_ContaoManager\\Contao\\ManagerApi\\TaskOperation\\TaskOperationInterface' => __DIR__ . '/../..' . '/api/TaskOperation/TaskOperationInterface.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\AbstractTask' => __DIR__ . '/../..' . '/api/Task/AbstractTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Composer\\ClearCacheTask' => __DIR__ . '/../..' . '/api/Task/Composer/ClearCacheTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Composer\\DumpAutoloadTask' => __DIR__ . '/../..' . '/api/Task/Composer/DumpAutoloadTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Contao\\BackupCreateTask' => __DIR__ . '/../..' . '/api/Task/Contao/BackupCreateTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Contao\\BackupRestoreTask' => __DIR__ . '/../..' . '/api/Task/Contao/BackupRestoreTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Contao\\RebuildCacheTask' => __DIR__ . '/../..' . '/api/Task/Contao/RebuildCacheTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Manager\\SelfUpdateTask' => __DIR__ . '/../..' . '/api/Task/Manager/SelfUpdateTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\AbstractPackagesTask' => __DIR__ . '/../..' . '/api/Task/Packages/AbstractPackagesTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\InstallTask' => __DIR__ . '/../..' . '/api/Task/Packages/InstallTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\SetupTask' => __DIR__ . '/../..' . '/api/Task/Packages/SetupTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\Packages\\UpdateTask' => __DIR__ . '/../..' . '/api/Task/Packages/UpdateTask.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskConfig' => __DIR__ . '/../..' . '/api/Task/TaskConfig.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskInterface' => __DIR__ . '/../..' . '/api/Task/TaskInterface.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskManager' => __DIR__ . '/../..' . '/api/Task/TaskManager.php', '_ContaoManager\\Contao\\ManagerApi\\Task\\TaskStatus' => __DIR__ . '/../..' . '/api/Task/TaskStatus.php', '_ContaoManager\\Contao\\ManagerApi\\Tests\\Composer\\CloudJobTest' => __DIR__ . '/../..' . '/api/Tests/Composer/CloudJobTest.php', '_ContaoManager\\Crell\\ApiProblem\\ApiProblem' => __DIR__ . '/..' . '/crell/api-problem/src/ApiProblem.php', '_ContaoManager\\Crell\\ApiProblem\\HttpConverter' => __DIR__ . '/..' . '/crell/api-problem/src/HttpConverter.php', '_ContaoManager\\Crell\\ApiProblem\\JsonEncodeException' => __DIR__ . '/..' . '/crell/api-problem/src/JsonEncodeException.php', '_ContaoManager\\Crell\\ApiProblem\\JsonException' => __DIR__ . '/..' . '/crell/api-problem/src/JsonException.php', '_ContaoManager\\Crell\\ApiProblem\\JsonParseException' => __DIR__ . '/..' . '/crell/api-problem/src/JsonParseException.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\AnnotationException' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\AnnotationReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\AnnotationRegistry' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Attribute' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Attributes' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Enum' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\IgnoreAnnotation' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\NamedArgumentConstructor' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Required' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Annotation\\Target' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\CachedReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\DocLexer' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\DocParser' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\FileCacheReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\ImplicitlyIgnoredAnnotationNames' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\IndexedReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\NamedArgumentConstructorAnnotation' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\PhpParser' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\PsrCachedReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\Reader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\SimpleAnnotationReader' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php', '_ContaoManager\\Doctrine\\Common\\Annotations\\TokenParser' => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php', '_ContaoManager\\Doctrine\\Common\\Lexer\\AbstractLexer' => __DIR__ . '/..' . '/doctrine/lexer/src/AbstractLexer.php', '_ContaoManager\\Doctrine\\Common\\Lexer\\Token' => __DIR__ . '/..' . '/doctrine/lexer/src/Token.php', '_ContaoManager\\Doctrine\\Deprecations\\Deprecation' => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php', '_ContaoManager\\Doctrine\\Deprecations\\PHPUnit\\VerifyDeprecations' => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php', '_ContaoManager\\Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php', '_ContaoManager\\Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php', '_ContaoManager\\Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php', '_ContaoManager\\Firebase\\JWT\\SignatureInvalidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/SignatureInvalidException.php', '_ContaoManager\\JsonSchema\\Constraints\\BaseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\CollectionConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php', '_ContaoManager\\JsonSchema\\Constraints\\ConstraintInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php', '_ContaoManager\\JsonSchema\\Constraints\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php', '_ContaoManager\\JsonSchema\\Constraints\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\NumberConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\ObjectConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\SchemaConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\StringConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php', '_ContaoManager\\JsonSchema\\Constraints\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php', '_ContaoManager\\JsonSchema\\Constraints\\UndefinedConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php', '_ContaoManager\\JsonSchema\\Entity\\JsonPointer' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php', '_ContaoManager\\JsonSchema\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidConfigException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidSchemaException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php', '_ContaoManager\\JsonSchema\\Exception\\InvalidSourceUriException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php', '_ContaoManager\\JsonSchema\\Exception\\JsonDecodingException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php', '_ContaoManager\\JsonSchema\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php', '_ContaoManager\\JsonSchema\\Exception\\RuntimeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php', '_ContaoManager\\JsonSchema\\Exception\\UnresolvableJsonPointerException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php', '_ContaoManager\\JsonSchema\\Exception\\UriResolverException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php', '_ContaoManager\\JsonSchema\\Exception\\ValidationException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php', '_ContaoManager\\JsonSchema\\Iterator\\ObjectIterator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php', '_ContaoManager\\JsonSchema\\Rfc3339' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php', '_ContaoManager\\JsonSchema\\SchemaStorage' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php', '_ContaoManager\\JsonSchema\\SchemaStorageInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php', '_ContaoManager\\JsonSchema\\UriResolverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php', '_ContaoManager\\JsonSchema\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\Curl' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\FileGetContents' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\PredefinedArray' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php', '_ContaoManager\\JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php', '_ContaoManager\\JsonSchema\\Uri\\UriResolver' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php', '_ContaoManager\\JsonSchema\\Uri\\UriRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php', '_ContaoManager\\JsonSchema\\Validator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Validator.php', '_ContaoManager\\Monolog\\Attribute\\AsMonologProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php', '_ContaoManager\\Monolog\\DateTimeImmutable' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/DateTimeImmutable.php', '_ContaoManager\\Monolog\\ErrorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ErrorHandler.php', '_ContaoManager\\Monolog\\Formatter\\ChromePHPFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', '_ContaoManager\\Monolog\\Formatter\\ElasticaFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php', '_ContaoManager\\Monolog\\Formatter\\ElasticsearchFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php', '_ContaoManager\\Monolog\\Formatter\\FlowdockFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php', '_ContaoManager\\Monolog\\Formatter\\FluentdFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php', '_ContaoManager\\Monolog\\Formatter\\FormatterInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', '_ContaoManager\\Monolog\\Formatter\\GelfMessageFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', '_ContaoManager\\Monolog\\Formatter\\GoogleCloudLoggingFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php', '_ContaoManager\\Monolog\\Formatter\\HtmlFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php', '_ContaoManager\\Monolog\\Formatter\\JsonFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LineFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LogglyFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LogmaticFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php', '_ContaoManager\\Monolog\\Formatter\\LogstashFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', '_ContaoManager\\Monolog\\Formatter\\MongoDBFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php', '_ContaoManager\\Monolog\\Formatter\\NormalizerFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', '_ContaoManager\\Monolog\\Formatter\\ScalarFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php', '_ContaoManager\\Monolog\\Formatter\\WildfireFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', '_ContaoManager\\Monolog\\Handler\\AbstractHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', '_ContaoManager\\Monolog\\Handler\\AbstractProcessingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', '_ContaoManager\\Monolog\\Handler\\AbstractSyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php', '_ContaoManager\\Monolog\\Handler\\AmqpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', '_ContaoManager\\Monolog\\Handler\\BrowserConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php', '_ContaoManager\\Monolog\\Handler\\BufferHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', '_ContaoManager\\Monolog\\Handler\\ChromePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', '_ContaoManager\\Monolog\\Handler\\CouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', '_ContaoManager\\Monolog\\Handler\\CubeHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', '_ContaoManager\\Monolog\\Handler\\Curl\\Util' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php', '_ContaoManager\\Monolog\\Handler\\DeduplicationHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php', '_ContaoManager\\Monolog\\Handler\\DoctrineCouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', '_ContaoManager\\Monolog\\Handler\\DynamoDbHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php', '_ContaoManager\\Monolog\\Handler\\ElasticaHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php', '_ContaoManager\\Monolog\\Handler\\ElasticsearchHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php', '_ContaoManager\\Monolog\\Handler\\ErrorLogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', '_ContaoManager\\Monolog\\Handler\\FallbackGroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php', '_ContaoManager\\Monolog\\Handler\\FilterHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossedHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', '_ContaoManager\\Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', '_ContaoManager\\Monolog\\Handler\\FirePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', '_ContaoManager\\Monolog\\Handler\\FleepHookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php', '_ContaoManager\\Monolog\\Handler\\FlowdockHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php', '_ContaoManager\\Monolog\\Handler\\FormattableHandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php', '_ContaoManager\\Monolog\\Handler\\FormattableHandlerTrait' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php', '_ContaoManager\\Monolog\\Handler\\GelfHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', '_ContaoManager\\Monolog\\Handler\\GroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', '_ContaoManager\\Monolog\\Handler\\Handler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Handler.php', '_ContaoManager\\Monolog\\Handler\\HandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', '_ContaoManager\\Monolog\\Handler\\HandlerWrapper' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php', '_ContaoManager\\Monolog\\Handler\\IFTTTHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php', '_ContaoManager\\Monolog\\Handler\\InsightOpsHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php', '_ContaoManager\\Monolog\\Handler\\LogEntriesHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php', '_ContaoManager\\Monolog\\Handler\\LogglyHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php', '_ContaoManager\\Monolog\\Handler\\LogmaticHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php', '_ContaoManager\\Monolog\\Handler\\MailHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', '_ContaoManager\\Monolog\\Handler\\MandrillHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php', '_ContaoManager\\Monolog\\Handler\\MissingExtensionException' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', '_ContaoManager\\Monolog\\Handler\\MongoDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', '_ContaoManager\\Monolog\\Handler\\NativeMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', '_ContaoManager\\Monolog\\Handler\\NewRelicHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', '_ContaoManager\\Monolog\\Handler\\NoopHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NoopHandler.php', '_ContaoManager\\Monolog\\Handler\\NullHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', '_ContaoManager\\Monolog\\Handler\\OverflowHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/OverflowHandler.php', '_ContaoManager\\Monolog\\Handler\\PHPConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php', '_ContaoManager\\Monolog\\Handler\\ProcessHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ProcessHandler.php', '_ContaoManager\\Monolog\\Handler\\ProcessableHandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php', '_ContaoManager\\Monolog\\Handler\\ProcessableHandlerTrait' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php', '_ContaoManager\\Monolog\\Handler\\PsrHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php', '_ContaoManager\\Monolog\\Handler\\PushoverHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', '_ContaoManager\\Monolog\\Handler\\RedisHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', '_ContaoManager\\Monolog\\Handler\\RedisPubSubHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php', '_ContaoManager\\Monolog\\Handler\\RollbarHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php', '_ContaoManager\\Monolog\\Handler\\RotatingFileHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', '_ContaoManager\\Monolog\\Handler\\SamplingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', '_ContaoManager\\Monolog\\Handler\\SendGridHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SendGridHandler.php', '_ContaoManager\\Monolog\\Handler\\SlackHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', '_ContaoManager\\Monolog\\Handler\\SlackWebhookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', '_ContaoManager\\Monolog\\Handler\\Slack\\SlackRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', '_ContaoManager\\Monolog\\Handler\\SocketHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', '_ContaoManager\\Monolog\\Handler\\SqsHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SqsHandler.php', '_ContaoManager\\Monolog\\Handler\\StreamHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', '_ContaoManager\\Monolog\\Handler\\SwiftMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', '_ContaoManager\\Monolog\\Handler\\SymfonyMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php', '_ContaoManager\\Monolog\\Handler\\SyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', '_ContaoManager\\Monolog\\Handler\\SyslogUdpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php', '_ContaoManager\\Monolog\\Handler\\SyslogUdp\\UdpSocket' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php', '_ContaoManager\\Monolog\\Handler\\TelegramBotHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php', '_ContaoManager\\Monolog\\Handler\\TestHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', '_ContaoManager\\Monolog\\Handler\\WebRequestRecognizerTrait' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php', '_ContaoManager\\Monolog\\Handler\\WhatFailureGroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php', '_ContaoManager\\Monolog\\Handler\\ZendMonitorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', '_ContaoManager\\Monolog\\LogRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/LogRecord.php', '_ContaoManager\\Monolog\\Logger' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Logger.php', '_ContaoManager\\Monolog\\Processor\\GitProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php', '_ContaoManager\\Monolog\\Processor\\HostnameProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php', '_ContaoManager\\Monolog\\Processor\\IntrospectionProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', '_ContaoManager\\Monolog\\Processor\\MemoryPeakUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', '_ContaoManager\\Monolog\\Processor\\MemoryProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', '_ContaoManager\\Monolog\\Processor\\MemoryUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', '_ContaoManager\\Monolog\\Processor\\MercurialProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', '_ContaoManager\\Monolog\\Processor\\ProcessIdProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', '_ContaoManager\\Monolog\\Processor\\ProcessorInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php', '_ContaoManager\\Monolog\\Processor\\PsrLogMessageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', '_ContaoManager\\Monolog\\Processor\\TagProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', '_ContaoManager\\Monolog\\Processor\\UidProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', '_ContaoManager\\Monolog\\Processor\\WebProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', '_ContaoManager\\Monolog\\Registry' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Registry.php', '_ContaoManager\\Monolog\\ResettableInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ResettableInterface.php', '_ContaoManager\\Monolog\\SignalHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/SignalHandler.php', '_ContaoManager\\Monolog\\Test\\TestCase' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Test/TestCase.php', '_ContaoManager\\Monolog\\Utils' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Utils.php', '_ContaoManager\\Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php', '_ContaoManager\\Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php', '_ContaoManager\\Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php', '_ContaoManager\\Psr\\Cache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/cache/src/InvalidArgumentException.php', '_ContaoManager\\Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', '_ContaoManager\\Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', '_ContaoManager\\Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', '_ContaoManager\\Psr\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/EventDispatcherInterface.php', '_ContaoManager\\Psr\\EventDispatcher\\ListenerProviderInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/ListenerProviderInterface.php', '_ContaoManager\\Psr\\EventDispatcher\\StoppableEventInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/StoppableEventInterface.php', '_ContaoManager\\Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', '_ContaoManager\\Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', '_ContaoManager\\Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', '_ContaoManager\\Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', '_ContaoManager\\Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', '_ContaoManager\\Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', '_ContaoManager\\Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', '_ContaoManager\\Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', '_ContaoManager\\Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/DummyTest.php', '_ContaoManager\\Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', '_ContaoManager\\Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php', '_ContaoManager\\Ramsey\\Uuid\\BinaryUtils' => __DIR__ . '/..' . '/ramsey/uuid/src/BinaryUtils.php', '_ContaoManager\\Ramsey\\Uuid\\Builder\\DefaultUuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/DefaultUuidBuilder.php', '_ContaoManager\\Ramsey\\Uuid\\Builder\\DegradedUuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/DegradedUuidBuilder.php', '_ContaoManager\\Ramsey\\Uuid\\Builder\\UuidBuilderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/UuidBuilderInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\CodecInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/CodecInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\GuidStringCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/GuidStringCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\OrderedTimeCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/OrderedTimeCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\StringCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/StringCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\TimestampFirstCombCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Codec\\TimestampLastCombCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/TimestampLastCombCodec.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\NumberConverterInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/NumberConverterInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Number\\BigNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/BigNumberConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Number\\DegradedNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\TimeConverterInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/TimeConverterInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Time\\BigNumberTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Time\\DegradedTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php', '_ContaoManager\\Ramsey\\Uuid\\Converter\\Time\\PhpTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php', '_ContaoManager\\Ramsey\\Uuid\\DegradedUuid' => __DIR__ . '/..' . '/ramsey/uuid/src/DegradedUuid.php', '_ContaoManager\\Ramsey\\Uuid\\Exception\\InvalidUuidStringException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/InvalidUuidStringException.php', '_ContaoManager\\Ramsey\\Uuid\\Exception\\UnsatisfiedDependencyException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php', '_ContaoManager\\Ramsey\\Uuid\\Exception\\UnsupportedOperationException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/UnsupportedOperationException.php', '_ContaoManager\\Ramsey\\Uuid\\FeatureSet' => __DIR__ . '/..' . '/ramsey/uuid/src/FeatureSet.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\CombGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/CombGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\DefaultTimeGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/DefaultTimeGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\MtRandGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/MtRandGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\OpenSslGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/OpenSslGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\PeclUuidRandomGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\PeclUuidTimeGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomBytesGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomBytesGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomGeneratorFactory.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomGeneratorInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\RandomLibAdapter' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomLibAdapter.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\SodiumRandomGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/SodiumRandomGenerator.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\TimeGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/TimeGeneratorFactory.php', '_ContaoManager\\Ramsey\\Uuid\\Generator\\TimeGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/TimeGeneratorInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\NodeProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/NodeProviderInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Node\\FallbackNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Node\\RandomNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Node\\SystemNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\TimeProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/TimeProviderInterface.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Time\\FixedTimeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Provider\\Time\\SystemTimeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php', '_ContaoManager\\Ramsey\\Uuid\\Uuid' => __DIR__ . '/..' . '/ramsey/uuid/src/Uuid.php', '_ContaoManager\\Ramsey\\Uuid\\UuidFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidFactory.php', '_ContaoManager\\Ramsey\\Uuid\\UuidFactoryInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidFactoryInterface.php', '_ContaoManager\\Ramsey\\Uuid\\UuidInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidInterface.php', '_ContaoManager\\Seld\\JsonLint\\DuplicateKeyException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php', '_ContaoManager\\Seld\\JsonLint\\JsonParser' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php', '_ContaoManager\\Seld\\JsonLint\\Lexer' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php', '_ContaoManager\\Seld\\JsonLint\\ParsingException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php', '_ContaoManager\\Seld\\JsonLint\\Undefined' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php', '_ContaoManager\\Seld\\PharUtils\\Linter' => __DIR__ . '/..' . '/seld/phar-utils/src/Linter.php', '_ContaoManager\\Seld\\PharUtils\\Timestamps' => __DIR__ . '/..' . '/seld/phar-utils/src/Timestamps.php', '_ContaoManager\\Seld\\Signal\\SignalHandler' => __DIR__ . '/..' . '/seld/signal-handler/src/SignalHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Command\\ServerLogCommand' => __DIR__ . '/..' . '/symfony/monolog-bridge/Command/ServerLogCommand.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Formatter\\ConsoleFormatter' => __DIR__ . '/..' . '/symfony/monolog-bridge/Formatter/ConsoleFormatter.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Formatter\\VarDumperFormatter' => __DIR__ . '/..' . '/symfony/monolog-bridge/Formatter/VarDumperFormatter.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ChromePhpHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/ChromePhpHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ConsoleHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/ConsoleHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ElasticsearchLogstashHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/ElasticsearchLogstashHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FingersCrossed\\HttpCodeActivationStrategy' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/FingersCrossed/HttpCodeActivationStrategy.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FingersCrossed\\NotFoundActivationStrategy' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/FingersCrossed/NotFoundActivationStrategy.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FirePHPHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/FirePHPHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\MailerHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/MailerHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\NotifierHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/NotifierHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ServerLogHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/ServerLogHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\SwiftMailerHandler' => __DIR__ . '/..' . '/symfony/monolog-bridge/Handler/SwiftMailerHandler.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Logger' => __DIR__ . '/..' . '/symfony/monolog-bridge/Logger.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Messenger\\ResetLoggersWorkerSubscriber' => __DIR__ . '/..' . '/symfony/monolog-bridge/Messenger/ResetLoggersWorkerSubscriber.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\AbstractTokenProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/AbstractTokenProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\ConsoleCommandProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/ConsoleCommandProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\DebugProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/DebugProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\RouteProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/RouteProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\SwitchUserTokenProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/SwitchUserTokenProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\TokenProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/TokenProcessor.php', '_ContaoManager\\Symfony\\Bridge\\Monolog\\Processor\\WebProcessor' => __DIR__ . '/..' . '/symfony/monolog-bridge/Processor/WebProcessor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AbstractPhpFileCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AnnotationsCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\CachePoolClearerCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ConfigBuilderCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\RouterCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\SerializerCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TranslationsCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ValidatorCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\AboutCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/AboutCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\AbstractConfigCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/AbstractConfigCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\AssetsInstallCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/AssetsInstallCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\BuildDebugContainerTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/BuildDebugContainerTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CacheClearCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolClearCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolDeleteCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolListCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolPruneCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CacheWarmupCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ConfigDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDumpReferenceCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ContainerDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerLintCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ContainerLintCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\DebugAutowiringCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/DebugAutowiringCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\EventDispatcherDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\RouterDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/RouterDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\RouterMatchCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/RouterMatchCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsDecryptToLocalCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsEncryptFromLocalCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsGenerateKeysCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsListCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsListCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsRemoveCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsRemoveCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsSetCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsSetCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/TranslationDebugCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationUpdateCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/TranslationUpdateCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\WorkflowDumpCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/WorkflowDumpCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\XliffLintCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/XliffLintCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Command\\YamlLintCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/YamlLintCommand.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Application' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Application.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Descriptor/Descriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Helper/DescriptorHelper.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/AbstractController.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/ControllerResolver.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/RedirectController.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/TemplateController.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DataCollector\\AbstractDataCollector' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/AbstractDataCollector.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DataCollector\\RouterDataCollector' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/RouterDataCollector.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DataCollector\\TemplateAwareDataCollectorInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddAnnotationsCachedReaderPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddDebugLogProcessorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddExpressionLanguageProvidersPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AssetsContextPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ContainerBuilderDebugDumpPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\DataCollectorTranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ErrorLoggerCompilerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ErrorLoggerCompilerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\LoggingTranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ProfilerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RemoveUnusedSessionMarshallingHandlerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SessionPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerRealRefPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerWeakRefPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\UnusedTagsPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\WorkflowGuardListenerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Configuration' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Configuration.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\FrameworkExtension' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle' => __DIR__ . '/..' . '/symfony/framework-bundle/FrameworkBundle.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache' => __DIR__ . '/..' . '/symfony/framework-bundle/HttpCache/HttpCache.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\KernelBrowser' => __DIR__ . '/..' . '/symfony/framework-bundle/KernelBrowser.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Kernel/MicroKernelTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\AnnotatedRouteControllerLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/DelegatingLoader.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RouteLoaderInterface.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Routing\\Router' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/Router.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/AbstractVault.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/DotenvVault.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/SodiumVault.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Session\\DeprecatedSessionFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Session/DeprecatedSessionFactory.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Session\\ServiceSessionFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Session/ServiceSessionFactory.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/BrowserKitAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\DomCrawlerAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/DomCrawlerAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/KernelTestCase.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/MailerAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\TestBrowserToken' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/TestBrowserToken.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\TestContainer' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/TestContainer.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestAssertionsTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/WebTestAssertionsTrait.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/WebTestCase.php', '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/Translator.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\AddProcessorsPass' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/Compiler/AddProcessorsPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\AddSwiftMailerTransportPass' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/Compiler/AddSwiftMailerTransportPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\DebugHandlerPass' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/Compiler/DebugHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\FixEmptyLoggerPass' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/Compiler/FixEmptyLoggerPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Compiler\\LoggerChannelPass' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/Compiler/LoggerChannelPass.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\Configuration' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/Configuration.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\DependencyInjection\\MonologExtension' => __DIR__ . '/..' . '/symfony/monolog-bundle/DependencyInjection/MonologExtension.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\MonologBundle' => __DIR__ . '/..' . '/symfony/monolog-bundle/MonologBundle.php', '_ContaoManager\\Symfony\\Bundle\\MonologBundle\\SwiftMailer\\MessageFactory' => __DIR__ . '/..' . '/symfony/monolog-bundle/SwiftMailer/MessageFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\CacheWarmer\\ExpressionCacheWarmer' => __DIR__ . '/..' . '/symfony/security-bundle/CacheWarmer/ExpressionCacheWarmer.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Command\\DebugFirewallCommand' => __DIR__ . '/..' . '/symfony/security-bundle/Command/DebugFirewallCommand.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Command\\UserPasswordEncoderCommand' => __DIR__ . '/..' . '/symfony/security-bundle/Command/UserPasswordEncoderCommand.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DataCollector\\SecurityDataCollector' => __DIR__ . '/..' . '/symfony/security-bundle/DataCollector/SecurityDataCollector.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableFirewallListener' => __DIR__ . '/..' . '/symfony/security-bundle/Debug/TraceableFirewallListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\TraceableListenerTrait' => __DIR__ . '/..' . '/symfony/security-bundle/Debug/TraceableListenerTrait.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\WrappedLazyListener' => __DIR__ . '/..' . '/symfony/security-bundle/Debug/WrappedLazyListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/security-bundle/Debug/WrappedListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\AddExpressionLanguageProvidersPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\AddSecurityVotersPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/AddSecurityVotersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\AddSessionDomainConstraintPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\CleanRememberMeVerifierPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterCsrfFeaturesPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterCsrfTokenClearingLogoutHandlerPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterEntryPointPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterEntryPointPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterGlobalSecurityEventListenersPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterLdapLocatorPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\RegisterTokenUsageTrackingPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\ReplaceDecoratedRememberMeHandlerPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Compiler\\SortFirewallListenersPass' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Compiler/SortFirewallListenersPass.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\MainConfiguration' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/MainConfiguration.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\SecurityExtension' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/SecurityExtension.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\AbstractFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/AbstractFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\AnonymousFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/AnonymousFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\AuthenticatorFactoryInterface' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\CustomAuthenticatorFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\FirewallListenerFactoryInterface' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\FormLoginFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\FormLoginLdapFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\GuardAuthenticationFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\HttpBasicFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\HttpBasicLdapFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\JsonLoginFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\JsonLoginLdapFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\LdapFactoryTrait' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/LdapFactoryTrait.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\LoginLinkFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/LoginLinkFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\LoginThrottlingFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\RememberMeFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/RememberMeFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\RemoteUserFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/RemoteUserFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\SecurityFactoryInterface' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\Factory\\X509Factory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/Factory/X509Factory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\UserProvider\\InMemoryFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\UserProvider\\LdapFactory' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/UserProvider/LdapFactory.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\DependencyInjection\\Security\\UserProvider\\UserProviderFactoryInterface' => __DIR__ . '/..' . '/symfony/security-bundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\EventListener\\FirewallListener' => __DIR__ . '/..' . '/symfony/security-bundle/EventListener/FirewallListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\EventListener\\VoteListener' => __DIR__ . '/..' . '/symfony/security-bundle/EventListener/VoteListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\LoginLink\\FirewallAwareLoginLinkHandler' => __DIR__ . '/..' . '/symfony/security-bundle/LoginLink/FirewallAwareLoginLinkHandler.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\RememberMe\\DecoratedRememberMeHandler' => __DIR__ . '/..' . '/symfony/security-bundle/RememberMe/DecoratedRememberMeHandler.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\RememberMe\\FirewallAwareRememberMeHandler' => __DIR__ . '/..' . '/symfony/security-bundle/RememberMe/FirewallAwareRememberMeHandler.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\SecurityBundle' => __DIR__ . '/..' . '/symfony/security-bundle/SecurityBundle.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallAwareTrait' => __DIR__ . '/..' . '/symfony/security-bundle/Security/FirewallAwareTrait.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallConfig' => __DIR__ . '/..' . '/symfony/security-bundle/Security/FirewallConfig.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext' => __DIR__ . '/..' . '/symfony/security-bundle/Security/FirewallContext.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap' => __DIR__ . '/..' . '/symfony/security-bundle/Security/FirewallMap.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\LazyFirewallContext' => __DIR__ . '/..' . '/symfony/security-bundle/Security/LazyFirewallContext.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\LegacyLogoutHandlerListener' => __DIR__ . '/..' . '/symfony/security-bundle/Security/LegacyLogoutHandlerListener.php', '_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\Security\\UserAuthenticator' => __DIR__ . '/..' . '/symfony/security-bundle/Security/UserAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/AdapterInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ApcuAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ArrayAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ChainAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/CouchbaseBucketAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/CouchbaseCollectionAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineDbalAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/MemcachedAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\NullAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/NullAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ParameterNormalizer' => __DIR__ . '/..' . '/symfony/cache/Adapter/ParameterNormalizer.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PdoAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpArrayAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpFilesAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ProxyAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/Psr16Adapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapterInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TraceableAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TraceableAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php', '_ContaoManager\\Symfony\\Component\\Cache\\CacheItem' => __DIR__ . '/..' . '/symfony/cache/CacheItem.php', '_ContaoManager\\Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => __DIR__ . '/..' . '/symfony/cache/DataCollector/CacheDataCollector.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CacheCollectorPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', '_ContaoManager\\Symfony\\Component\\Cache\\DoctrineProvider' => __DIR__ . '/..' . '/symfony/cache/DoctrineProvider.php', '_ContaoManager\\Symfony\\Component\\Cache\\Exception\\CacheException' => __DIR__ . '/..' . '/symfony/cache/Exception/CacheException.php', '_ContaoManager\\Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/cache/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Cache\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/cache/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Cache\\LockRegistry' => __DIR__ . '/..' . '/symfony/cache/LockRegistry.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DefaultMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DeflateMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => __DIR__ . '/..' . '/symfony/cache/Marshaller/MarshallerInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\SodiumMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/SodiumMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/TagAwareMarshaller.php', '_ContaoManager\\Symfony\\Component\\Cache\\Messenger\\EarlyExpirationDispatcher' => __DIR__ . '/..' . '/symfony/cache/Messenger/EarlyExpirationDispatcher.php', '_ContaoManager\\Symfony\\Component\\Cache\\Messenger\\EarlyExpirationHandler' => __DIR__ . '/..' . '/symfony/cache/Messenger/EarlyExpirationHandler.php', '_ContaoManager\\Symfony\\Component\\Cache\\Messenger\\EarlyExpirationMessage' => __DIR__ . '/..' . '/symfony/cache/Messenger/EarlyExpirationMessage.php', '_ContaoManager\\Symfony\\Component\\Cache\\PruneableInterface' => __DIR__ . '/..' . '/symfony/cache/PruneableInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Psr16Cache' => __DIR__ . '/..' . '/symfony/cache/Psr16Cache.php', '_ContaoManager\\Symfony\\Component\\Cache\\ResettableInterface' => __DIR__ . '/..' . '/symfony/cache/ResettableInterface.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractAdapterTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\ContractsTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ContractsTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemCommonTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ProxyTrait.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterNodeProxy.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterProxy.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisProxy.php', '_ContaoManager\\Symfony\\Component\\Cache\\Traits\\RedisTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisTrait.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ClassBuilder' => __DIR__ . '/..' . '/symfony/config/Builder/ClassBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGenerator.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\ConfigBuilderInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\Method' => __DIR__ . '/..' . '/symfony/config/Builder/Method.php', '_ContaoManager\\Symfony\\Component\\Config\\Builder\\Property' => __DIR__ . '/..' . '/symfony/config/Builder/Property.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCache' => __DIR__ . '/..' . '/symfony/config/ConfigCache.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactory.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\ConfigCacheInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\ArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/ArrayNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\BaseNode' => __DIR__ . '/..' . '/symfony/config/Definition/BaseNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/MergeBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeParentInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NormalizationBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NumericNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/TreeBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ValidationBuilder.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurationInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\EnumNode' => __DIR__ . '/..' . '/symfony/config/Definition/EnumNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/DuplicateKeyException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\Exception' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/Exception.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidTypeException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\NodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/NodeInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\NumericNode' => __DIR__ . '/..' . '/symfony/config/Definition/NumericNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\Processor' => __DIR__ . '/..' . '/symfony/config/Definition/Processor.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypeNodeInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypedArrayNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\ScalarNode' => __DIR__ . '/..' . '/symfony/config/Definition/ScalarNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Definition\\VariableNode' => __DIR__ . '/..' . '/symfony/config/Definition/VariableNode.php', '_ContaoManager\\Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Config\\Exception\\LoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/LoaderLoadException.php', '_ContaoManager\\Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php', '_ContaoManager\\Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/FileLoader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/GlobFileLoader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\Loader' => __DIR__ . '/..' . '/symfony/config/Loader/Loader.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\LoaderResolver' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolver.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolverInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Loader\\ParamConfigurator' => __DIR__ . '/..' . '/symfony/config/Loader/ParamConfigurator.php', '_ContaoManager\\Symfony\\Component\\Config\\ResourceCheckerConfigCache' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCache.php', '_ContaoManager\\Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCacheFactory.php', '_ContaoManager\\Symfony\\Component\\Config\\ResourceCheckerInterface' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/ClassExistenceResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ComposerResource' => __DIR__ . '/..' . '/symfony/config/Resource/ComposerResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\DirectoryResource' => __DIR__ . '/..' . '/symfony/config/Resource/DirectoryResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\FileExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileExistenceResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\FileResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\GlobResource' => __DIR__ . '/..' . '/symfony/config/Resource/GlobResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => __DIR__ . '/..' . '/symfony/config/Resource/ReflectionClassResource.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\ResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/ResourceInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceChecker.php', '_ContaoManager\\Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceInterface.php', '_ContaoManager\\Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/InvalidXmlException.php', '_ContaoManager\\Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/XmlParsingException.php', '_ContaoManager\\Symfony\\Component\\Config\\Util\\XmlUtils' => __DIR__ . '/..' . '/symfony/config/Util/XmlUtils.php', '_ContaoManager\\Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php', '_ContaoManager\\Symfony\\Component\\Console\\Attribute\\AsCommand' => __DIR__ . '/..' . '/symfony/console/Attribute/AsCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\CI\\GithubActionReporter' => __DIR__ . '/..' . '/symfony/console/CI/GithubActionReporter.php', '_ContaoManager\\Symfony\\Component\\Console\\Color' => __DIR__ . '/..' . '/symfony/console/Color.php', '_ContaoManager\\Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php', '_ContaoManager\\Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\CompleteCommand' => __DIR__ . '/..' . '/symfony/console/Command/CompleteCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => __DIR__ . '/..' . '/symfony/console/Command/DumpCompletionCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\LazyCommand' => __DIR__ . '/..' . '/symfony/console/Command/LazyCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php', '_ContaoManager\\Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => __DIR__ . '/..' . '/symfony/console/Command/SignalableCommandInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\CompletionInput' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionSuggestions.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/BashCompletionOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => __DIR__ . '/..' . '/symfony/console/Completion/Output/CompletionOutputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Completion\\Suggestion' => __DIR__ . '/..' . '/symfony/console/Completion/Suggestion.php', '_ContaoManager\\Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', '_ContaoManager\\Symfony\\Component\\Console\\Cursor' => __DIR__ . '/..' . '/symfony/console/Cursor.php', '_ContaoManager\\Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php', '_ContaoManager\\Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleSignalEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\MissingInputException' => __DIR__ . '/..' . '/symfony/console/Exception/MissingInputException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatter.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatterStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php', '_ContaoManager\\Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableCellStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableCellStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php', '_ContaoManager\\Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php', '_ContaoManager\\Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => __DIR__ . '/..' . '/symfony/console/Output/TrimmedBufferOutput.php', '_ContaoManager\\Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', '_ContaoManager\\Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', '_ContaoManager\\Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', '_ContaoManager\\Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalRegistry.php', '_ContaoManager\\Symfony\\Component\\Console\\SingleCommandApplication' => __DIR__ . '/..' . '/symfony/console/SingleCommandApplication.php', '_ContaoManager\\Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php', '_ContaoManager\\Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php', '_ContaoManager\\Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandCompletionTester.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => __DIR__ . '/..' . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php', '_ContaoManager\\Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Alias' => __DIR__ . '/..' . '/symfony/dependency-injection/Alias.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\AbstractArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/AbstractArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ArgumentInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/BoundArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/IteratorArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/RewindableGenerator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\AsTaggedItem' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AsTaggedItem.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Autoconfigure.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\AutoconfigureTag' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutoconfigureTag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/TaggedIterator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/TaggedLocator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\Target' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Target.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Attribute\\When' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/When.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ChildDefinition' => __DIR__ . '/..' . '/symfony/dependency-injection/ChildDefinition.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AbstractRecursivePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AbstractRecursivePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AliasDeprecatedPublicServicesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AttributeAutoconfigurationPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowirePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredMethodsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredPropertiesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckArgumentsValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckCircularReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CheckTypeDeclarationsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/Compiler.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\DefinitionErrorExceptionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ExtensionCompilerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\InlineServiceDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PassConfig.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\PriorityTaggedServiceTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterAutoconfigureAttributesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterEnvVarProcessorsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveBindingsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveBindingsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveChildDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveClassPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveClassPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDecoratorStackPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveEnvPlaceholdersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveFactoryClassPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveHotPathPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveHotPathPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInstanceofConditionalsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInvalidReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNamedArgumentsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNoPreloadPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolvePrivatesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveServiceSubscribersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ResolveTaggedIteratorArgumentPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceLocatorTagPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Compiler\\ValidateEnvPlaceholdersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResource' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/ContainerParametersResource.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResourceChecker' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Container' => __DIR__ . '/..' . '/symfony/dependency-injection/Container.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerAwareInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerAwareInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerAwareTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerAwareTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerBuilder' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerBuilder.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Definition' => __DIR__ . '/..' . '/symfony/dependency-injection/Definition.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\Dumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/Dumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/DumperInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/PhpDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\Preloader' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/Preloader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/XmlDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/YamlDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\EnvVarLoaderInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarLoaderInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\EnvVarProcessor' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarProcessor.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarProcessorInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\AutowiringFailedException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/AutowiringFailedException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/BadMethodCallException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\EnvNotFoundException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/EnvNotFoundException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\EnvParameterException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/EnvParameterException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\InvalidParameterTypeException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidParameterTypeException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ParameterNotFoundException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ServiceCircularReferenceException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ServiceNotFoundException.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ExpressionLanguage' => __DIR__ . '/..' . '/symfony/dependency-injection/ExpressionLanguage.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/dependency-injection/ExpressionLanguageProvider.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\ConfigurationExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\Extension' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/Extension.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ExtensionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/PrependExtensionInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\InstantiatorInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\RealServiceInstantiator' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\NullDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\LazyProxy\\ProxyHelper' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/ProxyHelper.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/ClosureLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractServiceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AliasConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ClosureReferenceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\DefaultsConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\EnvConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InlineServiceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InstanceofConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ParametersConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\PrototypeConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ReferenceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServiceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServicesConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\AbstractTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ArgumentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\AutoconfigureTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\AutowireTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\BindTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\CallTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ClassTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ConfiguratorTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DecorateTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\DeprecateTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FactoryTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\FileTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\LazyTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ParentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\PropertyTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\PublicTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\ShareTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\SyntheticTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\Traits\\TagTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\DirectoryLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/DirectoryLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/FileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/GlobFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\IniFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/IniFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/PhpFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/XmlFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/YamlFileLoader.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Parameter' => __DIR__ . '/..' . '/symfony/dependency-injection/Parameter.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ContainerBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\EnvPlaceholderParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Reference' => __DIR__ . '/..' . '/symfony/dependency-injection/Reference.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ReverseContainer' => __DIR__ . '/..' . '/symfony/dependency-injection/ReverseContainer.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/ServiceLocator.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/TaggedContainerInterface.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\TypedReference' => __DIR__ . '/..' . '/symfony/dependency-injection/TypedReference.php', '_ContaoManager\\Symfony\\Component\\DependencyInjection\\Variable' => __DIR__ . '/..' . '/symfony/dependency-injection/Variable.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\BufferingLogger' => __DIR__ . '/..' . '/symfony/error-handler/BufferingLogger.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Debug' => __DIR__ . '/..' . '/symfony/error-handler/Debug.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/error-handler/DebugClassLoader.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ClassNotFoundErrorEnhancer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ErrorEnhancerInterface' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedFunctionErrorEnhancer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedMethodErrorEnhancer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorHandler' => __DIR__ . '/..' . '/symfony/error-handler/ErrorHandler.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\CliErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\ErrorRendererInterface' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\HtmlErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ErrorRenderer\\SerializerErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError' => __DIR__ . '/..' . '/symfony/error-handler/Error/ClassNotFoundError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\FatalError' => __DIR__ . '/..' . '/symfony/error-handler/Error/FatalError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\OutOfMemoryError' => __DIR__ . '/..' . '/symfony/error-handler/Error/OutOfMemoryError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\UndefinedFunctionError' => __DIR__ . '/..' . '/symfony/error-handler/Error/UndefinedFunctionError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Error\\UndefinedMethodError' => __DIR__ . '/..' . '/symfony/error-handler/Error/UndefinedMethodError.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/error-handler/Exception/FlattenException.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Exception\\SilencedErrorContext' => __DIR__ . '/..' . '/symfony/error-handler/Exception/SilencedErrorContext.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Internal\\TentativeTypes' => __DIR__ . '/..' . '/symfony/error-handler/Internal/TentativeTypes.php', '_ContaoManager\\Symfony\\Component\\ErrorHandler\\ThrowableUtils' => __DIR__ . '/..' . '/symfony/error-handler/ThrowableUtils.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Attribute/AsEventListener.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/WrappedListener.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcher.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcherInterface.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', '_ContaoManager\\Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', '_ContaoManager\\Symfony\\Component\\Filesystem\\Path' => __DIR__ . '/..' . '/symfony/filesystem/Path.php', '_ContaoManager\\Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php', '_ContaoManager\\Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php', '_ContaoManager\\Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php', '_ContaoManager\\Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\LazyIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/LazyIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\Iterator\\VcsIgnoredFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/VcsIgnoredFilterIterator.php', '_ContaoManager\\Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\AcceptHeader' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeader.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeaderItem.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => __DIR__ . '/..' . '/symfony/http-foundation/BinaryFileResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Cookie' => __DIR__ . '/..' . '/symfony/http-foundation/Cookie.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\BadRequestException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/BadRequestException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\JsonException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/JsonException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/RequestExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\SessionNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/SessionNotFoundException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Exception\\SuspiciousOperationException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/SuspiciousOperationException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/ExpressionRequestMatcher.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\FileBag' => __DIR__ . '/..' . '/symfony/http-foundation/FileBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\CannotWriteFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/CannotWriteFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\ExtensionFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/ExtensionFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\FormSizeFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FormSizeFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\IniSizeFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/IniSizeFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\NoFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/NoFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/NoTmpDirFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/PartialFileException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UploadException.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\File' => __DIR__ . '/..' . '/symfony/http-foundation/File/File.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\Stream' => __DIR__ . '/..' . '/symfony/http-foundation/File/Stream.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => __DIR__ . '/..' . '/symfony/http-foundation/File/UploadedFile.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\HeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\HeaderUtils' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderUtils.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\InputBag' => __DIR__ . '/..' . '/symfony/http-foundation/InputBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\IpUtils' => __DIR__ . '/..' . '/symfony/http-foundation/IpUtils.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\JsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/JsonResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ParameterBag' => __DIR__ . '/..' . '/symfony/http-foundation/ParameterBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RateLimiter\\AbstractRequestRateLimiter' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RateLimiter\\RequestRateLimiterInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RedirectResponse' => __DIR__ . '/..' . '/symfony/http-foundation/RedirectResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Request' => __DIR__ . '/..' . '/symfony/http-foundation/Request.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestStack' => __DIR__ . '/..' . '/symfony/http-foundation/RequestStack.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Response' => __DIR__ . '/..' . '/symfony/http-foundation/Response.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/ResponseHeaderBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\ServerBag' => __DIR__ . '/..' . '/symfony/http-foundation/ServerBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/AttributeBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/FlashBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/FlashBagInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Session' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Session.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionBagInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionBagProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionBagProxy.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionFactoryInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\SessionUtils' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionUtils.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\IdentityMarshaller' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MarshallingSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\StrictSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MetadataBag.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/NativeSessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\ServiceSessionFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\StreamedResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedResponse.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\RequestAttributeValueSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseCookieValueSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseFormatSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasCookie' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHasHeader' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseHeaderSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsRedirected' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsSuccessful' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseIsUnprocessable' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Test\\Constraint\\ResponseStatusCodeSame' => __DIR__ . '/..' . '/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php', '_ContaoManager\\Symfony\\Component\\HttpFoundation\\UrlHelper' => __DIR__ . '/..' . '/symfony/http-foundation/UrlHelper.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Attribute\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/ArgumentInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Attribute\\AsController' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/AsController.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/Bundle.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/BundleInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/ChainCacheClearer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheClearer\\Psr6CacheClearer' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmer' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/WarmableInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/http-kernel/Config/FileLocator.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata' => __DIR__ . '/..' . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory' => __DIR__ . '/..' . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactoryInterface' => __DIR__ . '/..' . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerReference.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ErrorController.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/ConfigDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DataCollectorInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\DumpDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DumpDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\EventDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/EventDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\ExceptionDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/ExceptionDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\LateDataCollectorInterface' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\LoggerDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/LoggerDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\MemoryDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/MemoryDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/FileLinkFormatter.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\AddAnnotatedClassesToCachePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ControllerArgumentValueResolverPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/Extension.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\FragmentRendererPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\LazyLoadingFragmentHandler' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\LoggerPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/LoggerPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterControllerArgumentLocatorsPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterLocaleAwareServicesPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\RemoveEmptyControllerArgumentLocatorsPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\AbstractSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AbstractSessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DumpListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ErrorListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/FragmentListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleAwareListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ProfilerListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ResponseListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/RouterListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SurrogateListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TestSessionListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ExceptionEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FinishRequestEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/KernelEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/RequestEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ResponseEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/TerminateEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ViewEvent.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/BadRequestHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ConflictHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\ControllerDoesNotReturnResponseException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/GoneHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\HttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\InvalidMetadataException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/InvalidMetadataException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotFoundHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/PreconditionFailedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnexpectedSessionUsageException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\AbstractSurrogateFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/EsiFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentHandler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentRendererInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGenerator' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentUriGenerator.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGeneratorInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/InlineFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Fragment\\SsiFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/SsiFragmentRenderer.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\AbstractSurrogate' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/AbstractSurrogate.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\Esi' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/Esi.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/HttpCache.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategy' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategyInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\Ssi' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/Ssi.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\Store' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/Store.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/StoreInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpClientKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpClientKernel.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernel.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpKernelBrowser' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelBrowser.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\HttpKernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Kernel' => __DIR__ . '/..' . '/symfony/http-kernel/Kernel.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\KernelEvents' => __DIR__ . '/..' . '/symfony/http-kernel/KernelEvents.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\KernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/KernelInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Log/DebugLoggerInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Log\\Logger' => __DIR__ . '/..' . '/symfony/http-kernel/Log/Logger.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/FileProfilerStorage.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\Profile' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/Profile.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\Profiler' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/Profiler.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/ProfilerStorageInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\RebootableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/RebootableInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\TerminableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/TerminableInterface.php', '_ContaoManager\\Symfony\\Component\\HttpKernel\\UriSigner' => __DIR__ . '/..' . '/symfony/http-kernel/UriSigner.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Command\\UserPasswordHashCommand' => __DIR__ . '/..' . '/symfony/password-hasher/Command/UserPasswordHashCommand.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/password-hasher/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Exception\\InvalidPasswordException' => __DIR__ . '/..' . '/symfony/password-hasher/Exception/InvalidPasswordException.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/password-hasher/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\CheckPasswordLengthTrait' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/CheckPasswordLengthTrait.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\MessageDigestPasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/MessageDigestPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\MigratingPasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/MigratingPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\NativePasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/NativePasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherAwareInterface' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/PasswordHasherAwareInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactory' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/PasswordHasherFactory.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactoryInterface' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/PasswordHasherFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\Pbkdf2PasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/Pbkdf2PasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\PlaintextPasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/PlaintextPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\SodiumPasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/SodiumPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasher' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/UserPasswordHasher.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface' => __DIR__ . '/..' . '/symfony/password-hasher/Hasher/UserPasswordHasherInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\LegacyPasswordHasherInterface' => __DIR__ . '/..' . '/symfony/password-hasher/LegacyPasswordHasherInterface.php', '_ContaoManager\\Symfony\\Component\\PasswordHasher\\PasswordHasherInterface' => __DIR__ . '/..' . '/symfony/password-hasher/PasswordHasherInterface.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php', '_ContaoManager\\Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php', '_ContaoManager\\Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php', '_ContaoManager\\Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php', '_ContaoManager\\Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php', '_ContaoManager\\Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php', '_ContaoManager\\Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php', '_ContaoManager\\Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\AccessException' => __DIR__ . '/..' . '/symfony/property-access/Exception/AccessException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/property-access/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/property-access/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\InvalidPropertyPathException' => __DIR__ . '/..' . '/symfony/property-access/Exception/InvalidPropertyPathException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\NoSuchIndexException' => __DIR__ . '/..' . '/symfony/property-access/Exception/NoSuchIndexException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\NoSuchPropertyException' => __DIR__ . '/..' . '/symfony/property-access/Exception/NoSuchPropertyException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/property-access/Exception/OutOfBoundsException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/property-access/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/property-access/Exception/UnexpectedTypeException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\Exception\\UninitializedPropertyException' => __DIR__ . '/..' . '/symfony/property-access/Exception/UninitializedPropertyException.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccess' => __DIR__ . '/..' . '/symfony/property-access/PropertyAccess.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccessor' => __DIR__ . '/..' . '/symfony/property-access/PropertyAccessor.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder' => __DIR__ . '/..' . '/symfony/property-access/PropertyAccessorBuilder.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyAccessorInterface' => __DIR__ . '/..' . '/symfony/property-access/PropertyAccessorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPath' => __DIR__ . '/..' . '/symfony/property-access/PropertyPath.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathBuilder' => __DIR__ . '/..' . '/symfony/property-access/PropertyPathBuilder.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathInterface' => __DIR__ . '/..' . '/symfony/property-access/PropertyPathInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathIterator' => __DIR__ . '/..' . '/symfony/property-access/PropertyPathIterator.php', '_ContaoManager\\Symfony\\Component\\PropertyAccess\\PropertyPathIteratorInterface' => __DIR__ . '/..' . '/symfony/property-access/PropertyPathIteratorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\DependencyInjection\\PropertyInfoConstructorPass' => __DIR__ . '/..' . '/symfony/property-info/DependencyInjection/PropertyInfoConstructorPass.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\DependencyInjection\\PropertyInfoPass' => __DIR__ . '/..' . '/symfony/property-info/DependencyInjection/PropertyInfoPass.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorArgumentTypeExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/Extractor/ConstructorArgumentTypeExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorExtractor' => __DIR__ . '/..' . '/symfony/property-info/Extractor/ConstructorExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor' => __DIR__ . '/..' . '/symfony/property-info/Extractor/PhpDocExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor' => __DIR__ . '/..' . '/symfony/property-info/Extractor/PhpStanExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor' => __DIR__ . '/..' . '/symfony/property-info/Extractor/ReflectionExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor' => __DIR__ . '/..' . '/symfony/property-info/Extractor/SerializerExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PhpStan\\NameScope' => __DIR__ . '/..' . '/symfony/property-info/PhpStan/NameScope.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PhpStan\\NameScopeFactory' => __DIR__ . '/..' . '/symfony/property-info/PhpStan/NameScopeFactory.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyAccessExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyDescriptionExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInfoCacheExtractor' => __DIR__ . '/..' . '/symfony/property-info/PropertyInfoCacheExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor' => __DIR__ . '/..' . '/symfony/property-info/PropertyInfoExtractor.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInfoExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyInfoExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyInitializableExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyListExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyReadInfo' => __DIR__ . '/..' . '/symfony/property-info/PropertyReadInfo.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyReadInfoExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyReadInfoExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyTypeExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyWriteInfo' => __DIR__ . '/..' . '/symfony/property-info/PropertyWriteInfo.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\PropertyWriteInfoExtractorInterface' => __DIR__ . '/..' . '/symfony/property-info/PropertyWriteInfoExtractorInterface.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Type' => __DIR__ . '/..' . '/symfony/property-info/Type.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Util\\PhpDocTypeHelper' => __DIR__ . '/..' . '/symfony/property-info/Util/PhpDocTypeHelper.php', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Util\\PhpStanTypeHelper' => __DIR__ . '/..' . '/symfony/property-info/Util/PhpStanTypeHelper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Alias' => __DIR__ . '/..' . '/symfony/routing/Alias.php', '_ContaoManager\\Symfony\\Component\\Routing\\Annotation\\Route' => __DIR__ . '/..' . '/symfony/routing/Annotation/Route.php', '_ContaoManager\\Symfony\\Component\\Routing\\CompiledRoute' => __DIR__ . '/..' . '/symfony/routing/CompiledRoute.php', '_ContaoManager\\Symfony\\Component\\Routing\\DependencyInjection\\RoutingResolverPass' => __DIR__ . '/..' . '/symfony/routing/DependencyInjection/RoutingResolverPass.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/routing/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/routing/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => __DIR__ . '/..' . '/symfony/routing/Exception/InvalidParameterException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => __DIR__ . '/..' . '/symfony/routing/Exception/MethodNotAllowedException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => __DIR__ . '/..' . '/symfony/routing/Exception/MissingMandatoryParametersException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\NoConfigurationException' => __DIR__ . '/..' . '/symfony/routing/Exception/NoConfigurationException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/ResourceNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\RouteCircularReferenceException' => __DIR__ . '/..' . '/symfony/routing/Exception/RouteCircularReferenceException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/RouteNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/routing/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator' => __DIR__ . '/..' . '/symfony/routing/Generator/CompiledUrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\UrlGenerator' => __DIR__ . '/..' . '/symfony/routing/Generator/UrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/UrlGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationClassLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ClosureLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\AliasConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/AliasConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\CollectionConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/CollectionConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\ImportConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/ImportConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\RouteConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/RouteConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/RoutingConfigurator.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\AddTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/AddTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\HostTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/HostTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\LocalizedRouteTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\PrefixTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/RouteTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\ContainerLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ContainerLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DirectoryLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/GlobFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\ObjectLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/PhpFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/XmlFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/YamlFileLoader.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/CompiledUrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherTrait' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\Dumper\\StaticPrefixCollection' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/routing/Matcher/ExpressionLanguageProvider.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/RedirectableUrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/RequestMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\TraceableUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/TraceableUrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\UrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/UrlMatcher.php', '_ContaoManager\\Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/UrlMatcherInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\RequestContext' => __DIR__ . '/..' . '/symfony/routing/RequestContext.php', '_ContaoManager\\Symfony\\Component\\Routing\\RequestContextAwareInterface' => __DIR__ . '/..' . '/symfony/routing/RequestContextAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Route' => __DIR__ . '/..' . '/symfony/routing/Route.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCollection' => __DIR__ . '/..' . '/symfony/routing/RouteCollection.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCollectionBuilder' => __DIR__ . '/..' . '/symfony/routing/RouteCollectionBuilder.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCompiler' => __DIR__ . '/..' . '/symfony/routing/RouteCompiler.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouteCompilerInterface' => __DIR__ . '/..' . '/symfony/routing/RouteCompilerInterface.php', '_ContaoManager\\Symfony\\Component\\Routing\\Router' => __DIR__ . '/..' . '/symfony/routing/Router.php', '_ContaoManager\\Symfony\\Component\\Routing\\RouterInterface' => __DIR__ . '/..' . '/symfony/routing/RouterInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\AuthenticationEvents' => __DIR__ . '/..' . '/symfony/security-core/AuthenticationEvents.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/AuthenticationManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager' => __DIR__ . '/..' . '/symfony/security-core/Authentication/AuthenticationProviderManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationTrustResolver' => __DIR__ . '/..' . '/symfony/security-core/Authentication/AuthenticationTrustResolver.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationTrustResolverInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/AuthenticationTrustResolverInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AnonymousAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/AnonymousAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/AuthenticationProviderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/DaoAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\LdapBindAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/LdapBindAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\PreAuthenticatedAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\RememberMeAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/RememberMeAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\UserAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Provider/UserAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\CacheTokenVerifier' => __DIR__ . '/..' . '/symfony/security-core/Authentication/RememberMe/CacheTokenVerifier.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\InMemoryTokenProvider' => __DIR__ . '/..' . '/symfony/security-core/Authentication/RememberMe/InMemoryTokenProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\PersistentToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/RememberMe/PersistentToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\PersistentTokenInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/RememberMe/PersistentTokenInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\TokenProviderInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/RememberMe/TokenProviderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\RememberMe\\TokenVerifierInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/RememberMe/TokenVerifierInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/AbstractToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/AnonymousToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\NullToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/NullToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\PreAuthenticatedToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/PreAuthenticatedToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\RememberMeToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/RememberMeToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorage' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/Storage/TokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/Storage/TokenStorageInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\UsageTrackingTokenStorage' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/Storage/UsageTrackingTokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\SwitchUserToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/SwitchUserToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/TokenInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authentication\\Token\\UsernamePasswordToken' => __DIR__ . '/..' . '/symfony/security-core/Authentication/Token/UsernamePasswordToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager' => __DIR__ . '/..' . '/symfony/security-core/Authorization/AccessDecisionManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface' => __DIR__ . '/..' . '/symfony/security-core/Authorization/AccessDecisionManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker' => __DIR__ . '/..' . '/symfony/security-core/Authorization/AuthorizationChecker.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface' => __DIR__ . '/..' . '/symfony/security-core/Authorization/AuthorizationCheckerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\ExpressionLanguage' => __DIR__ . '/..' . '/symfony/security-core/Authorization/ExpressionLanguage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/security-core/Authorization/ExpressionLanguageProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AccessDecisionStrategyInterface' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Strategy/AccessDecisionStrategyInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AffirmativeStrategy' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Strategy/AffirmativeStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\ConsensusStrategy' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Strategy/ConsensusStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\PriorityStrategy' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Strategy/PriorityStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\UnanimousStrategy' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Strategy/UnanimousStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\TraceableAccessDecisionManager' => __DIR__ . '/..' . '/symfony/security-core/Authorization/TraceableAccessDecisionManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/AuthenticatedVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/CacheableVoterInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/ExpressionVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/RoleHierarchyVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/RoleVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\TraceableVoter' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/TraceableVoter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Voter' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/Voter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface' => __DIR__ . '/..' . '/symfony/security-core/Authorization/Voter/VoterInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/BasePasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\EncoderAwareInterface' => __DIR__ . '/..' . '/symfony/security-core/Encoder/EncoderAwareInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory' => __DIR__ . '/..' . '/symfony/security-core/Encoder/EncoderFactory.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactoryInterface' => __DIR__ . '/..' . '/symfony/security-core/Encoder/EncoderFactoryInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\LegacyEncoderTrait' => __DIR__ . '/..' . '/symfony/security-core/Encoder/LegacyEncoderTrait.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\LegacyPasswordHasherEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/LegacyPasswordHasherEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\MessageDigestPasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/MessageDigestPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\MigratingPasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/MigratingPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\NativePasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/NativePasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface' => __DIR__ . '/..' . '/symfony/security-core/Encoder/PasswordEncoderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PasswordHasherAdapter' => __DIR__ . '/..' . '/symfony/security-core/Encoder/PasswordHasherAdapter.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PasswordHasherEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/PasswordHasherEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\Pbkdf2PasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/Pbkdf2PasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PlaintextPasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/PlaintextPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\SelfSaltingEncoderInterface' => __DIR__ . '/..' . '/symfony/security-core/Encoder/SelfSaltingEncoderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\SodiumPasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/SodiumPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoder' => __DIR__ . '/..' . '/symfony/security-core/Encoder/UserPasswordEncoder.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoderInterface' => __DIR__ . '/..' . '/symfony/security-core/Encoder/UserPasswordEncoderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\AuthenticationEvent' => __DIR__ . '/..' . '/symfony/security-core/Event/AuthenticationEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent' => __DIR__ . '/..' . '/symfony/security-core/Event/AuthenticationFailureEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent' => __DIR__ . '/..' . '/symfony/security-core/Event/AuthenticationSuccessEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Event\\VoteEvent' => __DIR__ . '/..' . '/symfony/security-core/Event/VoteEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AccessDeniedException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AccountExpiredException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AccountExpiredException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AccountStatusException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AccountStatusException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AuthenticationCredentialsNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AuthenticationExpiredException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\AuthenticationServiceException' => __DIR__ . '/..' . '/symfony/security-core/Exception/AuthenticationServiceException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\BadCredentialsException' => __DIR__ . '/..' . '/symfony/security-core/Exception/BadCredentialsException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CookieTheftException' => __DIR__ . '/..' . '/symfony/security-core/Exception/CookieTheftException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CredentialsExpiredException' => __DIR__ . '/..' . '/symfony/security-core/Exception/CredentialsExpiredException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CustomUserMessageAccountStatusException' => __DIR__ . '/..' . '/symfony/security-core/Exception/CustomUserMessageAccountStatusException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\CustomUserMessageAuthenticationException' => __DIR__ . '/..' . '/symfony/security-core/Exception/CustomUserMessageAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\DisabledException' => __DIR__ . '/..' . '/symfony/security-core/Exception/DisabledException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/security-core/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\InsufficientAuthenticationException' => __DIR__ . '/..' . '/symfony/security-core/Exception/InsufficientAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/security-core/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\InvalidCsrfTokenException' => __DIR__ . '/..' . '/symfony/security-core/Exception/InvalidCsrfTokenException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LazyResponseException' => __DIR__ . '/..' . '/symfony/security-core/Exception/LazyResponseException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LockedException' => __DIR__ . '/..' . '/symfony/security-core/Exception/LockedException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/security-core/Exception/LogicException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\LogoutException' => __DIR__ . '/..' . '/symfony/security-core/Exception/LogoutException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\ProviderNotFoundException' => __DIR__ . '/..' . '/symfony/security-core/Exception/ProviderNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/security-core/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\SessionUnavailableException' => __DIR__ . '/..' . '/symfony/security-core/Exception/SessionUnavailableException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\TokenNotFoundException' => __DIR__ . '/..' . '/symfony/security-core/Exception/TokenNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\TooManyLoginAttemptsAuthenticationException' => __DIR__ . '/..' . '/symfony/security-core/Exception/TooManyLoginAttemptsAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\UnsupportedUserException' => __DIR__ . '/..' . '/symfony/security-core/Exception/UnsupportedUserException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\UserNotFoundException' => __DIR__ . '/..' . '/symfony/security-core/Exception/UserNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Exception\\UsernameNotFoundException' => __DIR__ . '/..' . '/symfony/security-core/Exception/UsernameNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\Role' => __DIR__ . '/..' . '/symfony/security-core/Role/Role.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\RoleHierarchy' => __DIR__ . '/..' . '/symfony/security-core/Role/RoleHierarchy.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\RoleHierarchyInterface' => __DIR__ . '/..' . '/symfony/security-core/Role/RoleHierarchyInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Role\\SwitchUserRole' => __DIR__ . '/..' . '/symfony/security-core/Role/SwitchUserRole.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Security' => __DIR__ . '/..' . '/symfony/security-core/Security.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\Exception\\ExpiredSignatureException' => __DIR__ . '/..' . '/symfony/security-core/Signature/Exception/ExpiredSignatureException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\Exception\\InvalidSignatureException' => __DIR__ . '/..' . '/symfony/security-core/Signature/Exception/InvalidSignatureException.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\ExpiredSignatureStorage' => __DIR__ . '/..' . '/symfony/security-core/Signature/ExpiredSignatureStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Signature\\SignatureHasher' => __DIR__ . '/..' . '/symfony/security-core/Signature/SignatureHasher.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Test\\AccessDecisionStrategyTestCase' => __DIR__ . '/..' . '/symfony/security-core/Test/AccessDecisionStrategyTestCase.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\ChainUserProvider' => __DIR__ . '/..' . '/symfony/security-core/User/ChainUserProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\EquatableInterface' => __DIR__ . '/..' . '/symfony/security-core/User/EquatableInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\InMemoryUser' => __DIR__ . '/..' . '/symfony/security-core/User/InMemoryUser.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\InMemoryUserChecker' => __DIR__ . '/..' . '/symfony/security-core/User/InMemoryUserChecker.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\InMemoryUserProvider' => __DIR__ . '/..' . '/symfony/security-core/User/InMemoryUserProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\LegacyPasswordAuthenticatedUserInterface' => __DIR__ . '/..' . '/symfony/security-core/User/LegacyPasswordAuthenticatedUserInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\MissingUserProvider' => __DIR__ . '/..' . '/symfony/security-core/User/MissingUserProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface' => __DIR__ . '/..' . '/symfony/security-core/User/PasswordAuthenticatedUserInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\PasswordUpgraderInterface' => __DIR__ . '/..' . '/symfony/security-core/User/PasswordUpgraderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\User' => __DIR__ . '/..' . '/symfony/security-core/User/User.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserChecker' => __DIR__ . '/..' . '/symfony/security-core/User/UserChecker.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface' => __DIR__ . '/..' . '/symfony/security-core/User/UserCheckerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserInterface' => __DIR__ . '/..' . '/symfony/security-core/User/UserInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserProviderInterface' => __DIR__ . '/..' . '/symfony/security-core/User/UserProviderInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Validator\\Constraints\\UserPassword' => __DIR__ . '/..' . '/symfony/security-core/Validator/Constraints/UserPassword.php', '_ContaoManager\\Symfony\\Component\\Security\\Core\\Validator\\Constraints\\UserPasswordValidator' => __DIR__ . '/..' . '/symfony/security-core/Validator/Constraints/UserPasswordValidator.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\CsrfToken' => __DIR__ . '/..' . '/symfony/security-csrf/CsrfToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\CsrfTokenManager' => __DIR__ . '/..' . '/symfony/security-csrf/CsrfTokenManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface' => __DIR__ . '/..' . '/symfony/security-csrf/CsrfTokenManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\Exception\\TokenNotFoundException' => __DIR__ . '/..' . '/symfony/security-csrf/Exception/TokenNotFoundException.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenGenerator\\TokenGeneratorInterface' => __DIR__ . '/..' . '/symfony/security-csrf/TokenGenerator/TokenGeneratorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenGenerator\\UriSafeTokenGenerator' => __DIR__ . '/..' . '/symfony/security-csrf/TokenGenerator/UriSafeTokenGenerator.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\ClearableTokenStorageInterface' => __DIR__ . '/..' . '/symfony/security-csrf/TokenStorage/ClearableTokenStorageInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\NativeSessionTokenStorage' => __DIR__ . '/..' . '/symfony/security-csrf/TokenStorage/NativeSessionTokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\SessionTokenStorage' => __DIR__ . '/..' . '/symfony/security-csrf/TokenStorage/SessionTokenStorage.php', '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\TokenStorageInterface' => __DIR__ . '/..' . '/symfony/security-csrf/TokenStorage/TokenStorageInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator' => __DIR__ . '/..' . '/symfony/security-guard/AbstractGuardAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\AuthenticatorInterface' => __DIR__ . '/..' . '/symfony/security-guard/AuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Authenticator\\AbstractFormLoginAuthenticator' => __DIR__ . '/..' . '/symfony/security-guard/Authenticator/AbstractFormLoginAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Authenticator\\GuardBridgeAuthenticator' => __DIR__ . '/..' . '/symfony/security-guard/Authenticator/GuardBridgeAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Firewall\\GuardAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-guard/Firewall/GuardAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\GuardAuthenticatorHandler' => __DIR__ . '/..' . '/symfony/security-guard/GuardAuthenticatorHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\PasswordAuthenticatedInterface' => __DIR__ . '/..' . '/symfony/security-guard/PasswordAuthenticatedInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Provider\\GuardAuthenticationProvider' => __DIR__ . '/..' . '/symfony/security-guard/Provider/GuardAuthenticationProvider.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Token\\GuardTokenInterface' => __DIR__ . '/..' . '/symfony/security-guard/Token/GuardTokenInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken' => __DIR__ . '/..' . '/symfony/security-guard/Token/PostAuthenticationGuardToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Guard\\Token\\PreAuthenticationGuardToken' => __DIR__ . '/..' . '/symfony/security-guard/Token/PreAuthenticationGuardToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\AccessMap' => __DIR__ . '/..' . '/symfony/security-http/AccessMap.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\AccessMapInterface' => __DIR__ . '/..' . '/symfony/security-http/AccessMapInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Attribute\\CurrentUser' => __DIR__ . '/..' . '/symfony/security-http/Attribute/CurrentUser.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationFailureHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/Authentication/AuthenticationFailureHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationSuccessHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/Authentication/AuthenticationSuccessHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationUtils' => __DIR__ . '/..' . '/symfony/security-http/Authentication/AuthenticationUtils.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticatorManager' => __DIR__ . '/..' . '/symfony/security-http/Authentication/AuthenticatorManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\AuthenticatorManagerInterface' => __DIR__ . '/..' . '/symfony/security-http/Authentication/AuthenticatorManagerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\CustomAuthenticationFailureHandler' => __DIR__ . '/..' . '/symfony/security-http/Authentication/CustomAuthenticationFailureHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\CustomAuthenticationSuccessHandler' => __DIR__ . '/..' . '/symfony/security-http/Authentication/CustomAuthenticationSuccessHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\DefaultAuthenticationFailureHandler' => __DIR__ . '/..' . '/symfony/security-http/Authentication/DefaultAuthenticationFailureHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\DefaultAuthenticationSuccessHandler' => __DIR__ . '/..' . '/symfony/security-http/Authentication/DefaultAuthenticationSuccessHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\NoopAuthenticationManager' => __DIR__ . '/..' . '/symfony/security-http/Authentication/NoopAuthenticationManager.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authentication\\UserAuthenticatorInterface' => __DIR__ . '/..' . '/symfony/security-http/Authentication/UserAuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AbstractAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/AbstractAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AbstractLoginFormAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/AbstractLoginFormAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AbstractPreAuthenticatedAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/AbstractPreAuthenticatedAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/AuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Debug\\TraceableAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Debug/TraceableAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Debug\\TraceableAuthenticatorManagerListener' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\FormLoginAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/FormLoginAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\HttpBasicAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/HttpBasicAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\InteractiveAuthenticatorInterface' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/InteractiveAuthenticatorInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\JsonLoginAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/JsonLoginAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\LoginLinkAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/LoginLinkAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\BadgeInterface' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Badge/BadgeInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\CsrfTokenBadge' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Badge/CsrfTokenBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PasswordUpgradeBadge' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PreAuthenticatedUserBadge' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Badge/PreAuthenticatedUserBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\RememberMeBadge' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Badge/RememberMeBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\UserBadge' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\CredentialsInterface' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Credentials/CredentialsInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\CustomCredentials' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Credentials/CustomCredentials.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Credentials/PasswordCredentials.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/Passport.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\PassportInterface' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/PassportInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\PassportTrait' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/PassportTrait.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\SelfValidatingPassport' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/SelfValidatingPassport.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\UserPassportInterface' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Passport/UserPassportInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\RememberMeAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/RememberMeAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\RemoteUserAuthenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/RemoteUserAuthenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\Token\\PostAuthenticationToken' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/Token/PostAuthenticationToken.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authenticator\\X509Authenticator' => __DIR__ . '/..' . '/symfony/security-http/Authenticator/X509Authenticator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Authorization\\AccessDeniedHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/Authorization/AccessDeniedHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver' => __DIR__ . '/..' . '/symfony/security-http/Controller/UserValueResolver.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface' => __DIR__ . '/..' . '/symfony/security-http/EntryPoint/AuthenticationEntryPointInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\BasicAuthenticationEntryPoint' => __DIR__ . '/..' . '/symfony/security-http/EntryPoint/BasicAuthenticationEntryPoint.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\Exception\\NotAnEntryPointException' => __DIR__ . '/..' . '/symfony/security-http/EntryPoint/Exception/NotAnEntryPointException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\FormAuthenticationEntryPoint' => __DIR__ . '/..' . '/symfony/security-http/EntryPoint/FormAuthenticationEntryPoint.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EntryPoint\\RetryAuthenticationEntryPoint' => __DIR__ . '/..' . '/symfony/security-http/EntryPoint/RetryAuthenticationEntryPoint.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CheckCredentialsListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/CheckCredentialsListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CheckRememberMeConditionsListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/CheckRememberMeConditionsListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CookieClearingLogoutListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/CookieClearingLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CsrfProtectionListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/CsrfProtectionListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\CsrfTokenClearingLogoutListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/CsrfTokenClearingLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\DefaultLogoutListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/DefaultLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\LoginThrottlingListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/LoginThrottlingListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\PasswordMigratingListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/PasswordMigratingListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\RememberMeListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/RememberMeListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\RememberMeLogoutListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/RememberMeLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\SessionLogoutListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/SessionLogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\SessionStrategyListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/SessionStrategyListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\UserCheckerListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/UserCheckerListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\EventListener\\UserProviderListener' => __DIR__ . '/..' . '/symfony/security-http/EventListener/UserProviderListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\AuthenticationTokenCreatedEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/AuthenticationTokenCreatedEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\CheckPassportEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/CheckPassportEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/DeauthenticatedEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/InteractiveLoginEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LazyResponseEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/LazyResponseEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LoginFailureEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/LoginFailureEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LoginSuccessEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/LoginSuccessEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\LogoutEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/LogoutEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/SwitchUserEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Event\\TokenDeauthenticatedEvent' => __DIR__ . '/..' . '/symfony/security-http/Event/TokenDeauthenticatedEvent.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall' => __DIR__ . '/..' . '/symfony/security-http/Firewall.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\FirewallMap' => __DIR__ . '/..' . '/symfony/security-http/FirewallMap.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\FirewallMapInterface' => __DIR__ . '/..' . '/symfony/security-http/FirewallMapInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AbstractAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/AbstractAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AbstractListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/AbstractListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AbstractPreAuthenticatedListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/AbstractPreAuthenticatedListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AccessListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/AccessListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AnonymousAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/AnonymousAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\AuthenticatorManagerListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/AuthenticatorManagerListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\BasicAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/BasicAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\ChannelListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/ChannelListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\ContextListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/ContextListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/ExceptionListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\FirewallListenerInterface' => __DIR__ . '/..' . '/symfony/security-http/Firewall/FirewallListenerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\LogoutListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/LogoutListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\RememberMeListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/RememberMeListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\RemoteUserAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/RemoteUserAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\SwitchUserListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/SwitchUserListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/UsernamePasswordFormAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordJsonAuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/UsernamePasswordJsonAuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Firewall\\X509AuthenticationListener' => __DIR__ . '/..' . '/symfony/security-http/Firewall/X509AuthenticationListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\HttpUtils' => __DIR__ . '/..' . '/symfony/security-http/HttpUtils.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Impersonate\\ImpersonateUrlGenerator' => __DIR__ . '/..' . '/symfony/security-http/Impersonate/ImpersonateUrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\ExpiredLoginLinkException' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/Exception/ExpiredLoginLinkException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\InvalidLoginLinkAuthenticationException' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/Exception/InvalidLoginLinkAuthenticationException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\InvalidLoginLinkException' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/Exception/InvalidLoginLinkException.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\Exception\\InvalidLoginLinkExceptionInterface' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/Exception/InvalidLoginLinkExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkDetails' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/LoginLinkDetails.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandler' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/LoginLinkHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/LoginLinkHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkNotification' => __DIR__ . '/..' . '/symfony/security-http/LoginLink/LoginLinkNotification.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\CookieClearingLogoutHandler' => __DIR__ . '/..' . '/symfony/security-http/Logout/CookieClearingLogoutHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\CsrfTokenClearingLogoutHandler' => __DIR__ . '/..' . '/symfony/security-http/Logout/CsrfTokenClearingLogoutHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\DefaultLogoutSuccessHandler' => __DIR__ . '/..' . '/symfony/security-http/Logout/DefaultLogoutSuccessHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\LogoutHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/Logout/LogoutHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\LogoutSuccessHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/Logout/LogoutSuccessHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\LogoutUrlGenerator' => __DIR__ . '/..' . '/symfony/security-http/Logout/LogoutUrlGenerator.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Logout\\SessionLogoutHandler' => __DIR__ . '/..' . '/symfony/security-http/Logout/SessionLogoutHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\ParameterBagUtils' => __DIR__ . '/..' . '/symfony/security-http/ParameterBagUtils.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RateLimiter\\DefaultLoginRateLimiter' => __DIR__ . '/..' . '/symfony/security-http/RateLimiter/DefaultLoginRateLimiter.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\AbstractRememberMeHandler' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/AbstractRememberMeHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\AbstractRememberMeServices' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/AbstractRememberMeServices.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\PersistentRememberMeHandler' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/PersistentRememberMeHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\PersistentTokenBasedRememberMeServices' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/PersistentTokenBasedRememberMeServices.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\RememberMeDetails' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/RememberMeDetails.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\RememberMeHandlerInterface' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/RememberMeHandlerInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\RememberMeServicesInterface' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/RememberMeServicesInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\ResponseListener' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/ResponseListener.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\SignatureRememberMeHandler' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/SignatureRememberMeHandler.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\RememberMe\\TokenBasedRememberMeServices' => __DIR__ . '/..' . '/symfony/security-http/RememberMe/TokenBasedRememberMeServices.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\SecurityEvents' => __DIR__ . '/..' . '/symfony/security-http/SecurityEvents.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Session\\SessionAuthenticationStrategy' => __DIR__ . '/..' . '/symfony/security-http/Session/SessionAuthenticationStrategy.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Session\\SessionAuthenticationStrategyInterface' => __DIR__ . '/..' . '/symfony/security-http/Session/SessionAuthenticationStrategyInterface.php', '_ContaoManager\\Symfony\\Component\\Security\\Http\\Util\\TargetPathTrait' => __DIR__ . '/..' . '/symfony/security-http/Util/TargetPathTrait.php', '_ContaoManager\\Symfony\\Component\\String\\AbstractString' => __DIR__ . '/..' . '/symfony/string/AbstractString.php', '_ContaoManager\\Symfony\\Component\\String\\AbstractUnicodeString' => __DIR__ . '/..' . '/symfony/string/AbstractUnicodeString.php', '_ContaoManager\\Symfony\\Component\\String\\ByteString' => __DIR__ . '/..' . '/symfony/string/ByteString.php', '_ContaoManager\\Symfony\\Component\\String\\CodePointString' => __DIR__ . '/..' . '/symfony/string/CodePointString.php', '_ContaoManager\\Symfony\\Component\\String\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/string/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\String\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/string/Exception/InvalidArgumentException.php', '_ContaoManager\\Symfony\\Component\\String\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/string/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\String\\Inflector\\EnglishInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/EnglishInflector.php', '_ContaoManager\\Symfony\\Component\\String\\Inflector\\FrenchInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/FrenchInflector.php', '_ContaoManager\\Symfony\\Component\\String\\Inflector\\InflectorInterface' => __DIR__ . '/..' . '/symfony/string/Inflector/InflectorInterface.php', '_ContaoManager\\Symfony\\Component\\String\\LazyString' => __DIR__ . '/..' . '/symfony/string/LazyString.php', '_ContaoManager\\Symfony\\Component\\String\\Slugger\\AsciiSlugger' => __DIR__ . '/..' . '/symfony/string/Slugger/AsciiSlugger.php', '_ContaoManager\\Symfony\\Component\\String\\Slugger\\SluggerInterface' => __DIR__ . '/..' . '/symfony/string/Slugger/SluggerInterface.php', '_ContaoManager\\Symfony\\Component\\String\\UnicodeString' => __DIR__ . '/..' . '/symfony/string/UnicodeString.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/AmqpCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ArgsStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\Caster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/Caster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ClassStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ClassStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ConstStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ConstStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/CutArrayStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\CutStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/CutStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DOMCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DateCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DateCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DoctrineCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsPairStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\EnumStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/EnumStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ExceptionCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\FiberCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FiberCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\FrameStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FrameStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/GmpCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImagineCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ImgStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImgStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/IntlCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\LinkStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/LinkStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MemcachedCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\MysqliCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MysqliCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PdoCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PgSqlCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RdKafkaCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RedisCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ReflectionCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ResourceCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SplCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/StubCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SymfonyCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\TraceStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/TraceStub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/UuidCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlReaderCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlResourceCaster.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/AbstractCloner.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\ClonerInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/ClonerInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\Cursor' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Cursor.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\Data' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Data.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/DumperInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\Stub' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Stub.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/VarCloner.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => __DIR__ . '/..' . '/symfony/var-dumper/Command/ServerDumpCommand.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/AbstractDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/CliDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/DataDumperInterface.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/HtmlDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ServerDumper.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => __DIR__ . '/..' . '/symfony/var-dumper/Exception/ThrowingCasterException.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Server\\Connection' => __DIR__ . '/..' . '/symfony/var-dumper/Server/Connection.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Server\\DumpServer' => __DIR__ . '/..' . '/symfony/var-dumper/Server/DumpServer.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => __DIR__ . '/..' . '/symfony/var-dumper/Test/VarDumperTestTrait.php', '_ContaoManager\\Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ClassNotFoundException.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Instantiator' => __DIR__ . '/..' . '/symfony/var-exporter/Instantiator.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Exporter' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Exporter.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Hydrator' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Hydrator.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Reference' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Reference.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Registry' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Registry.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\Internal\\Values' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Values.php', '_ContaoManager\\Symfony\\Component\\VarExporter\\VarExporter' => __DIR__ . '/..' . '/symfony/var-exporter/VarExporter.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/yaml/Exception/ExceptionInterface.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/yaml/Exception/ParseException.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', '_ContaoManager\\Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\CacheInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/CacheInterface.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\CacheTrait' => __DIR__ . '/..' . '/symfony/cache-contracts/CacheTrait.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\CallbackInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/CallbackInterface.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\ItemInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/ItemInterface.php', '_ContaoManager\\Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/TagAwareCacheInterface.php', '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/Event.php', '_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php', '_ContaoManager\\Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/SubscribedService.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php', '_ContaoManager\\Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php', '_ContaoManager\\Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\Annotation\\ServiceTag' => __DIR__ . '/..' . '/terminal42/service-annotation-bundle/src/Annotation/ServiceTag.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\Annotation\\ServiceTagInterface' => __DIR__ . '/..' . '/terminal42/service-annotation-bundle/src/Annotation/ServiceTagInterface.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\DependencyInjection\\Compiler\\ServiceAnnotationPass' => __DIR__ . '/..' . '/terminal42/service-annotation-bundle/src/DependencyInjection/Compiler/ServiceAnnotationPass.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\ServiceAnnotationInterface' => __DIR__ . '/..' . '/terminal42/service-annotation-bundle/src/ServiceAnnotationInterface.php', '_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\Terminal42ServiceAnnotationBundle' => __DIR__ . '/..' . '/terminal42/service-annotation-bundle/src/Terminal42ServiceAnnotationBundle.php', '_ContaoManager\\studio24\\Rotate\\Delete' => __DIR__ . '/..' . '/studio24/rotate/src/Delete.php', '_ContaoManager\\studio24\\Rotate\\DirectoryIterator' => __DIR__ . '/..' . '/studio24/rotate/src/DirectoryIterator.php', '_ContaoManager\\studio24\\Rotate\\FilenameFormat' => __DIR__ . '/..' . '/studio24/rotate/src/FilenameFormat.php', '_ContaoManager\\studio24\\Rotate\\FilenameFormatException' => __DIR__ . '/..' . '/studio24/rotate/src/FilenameFormatException.php', '_ContaoManager\\studio24\\Rotate\\Rotate' => __DIR__ . '/..' . '/studio24/rotate/src/Rotate.php', '_ContaoManager\\studio24\\Rotate\\RotateAbstract' => __DIR__ . '/..' . '/studio24/rotate/src/RotateAbstract.php', '_ContaoManager\\studio24\\Rotate\\RotateException' => __DIR__ . '/..' . '/studio24/rotate/src/RotateException.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitb3911d33d0e75dac2298e12b19b87edb::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitb3911d33d0e75dac2298e12b19b87edb::$prefixDirsPsr4; $loader->classMap = ComposerStaticInitb3911d33d0e75dac2298e12b19b87edb::$classMap; }, null, ClassLoader::class); } } setClassMapAuthoritative(true); $loader->register(true); $filesToLoad = \Composer\Autoload\ComposerStaticInitb3911d33d0e75dac2298e12b19b87edb::$files; $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; require $file; } }, null, null); foreach ($filesToLoad as $fileIdentifier => $file) { $requireFile($fileIdentifier, $file); } return $loader; } } parameters: ignoreErrors: - message: "#^Parameter \\#1 \\$operator of class Composer\\\\Semver\\\\Constraint\\\\Constraint constructor expects '\\!\\='\\|'\\<'\\|'\\<\\='\\|'\\<\\>'\\|'\\='\\|'\\=\\='\\|'\\>'\\|'\\>\\=', non\\-falsy\\-string given\\.$#" count: 1 path: src/VersionParser.php - message: "#^Strict comparison using \\=\\=\\= between null and non\\-empty\\-string will always evaluate to false\\.$#" count: 2 path: src/VersionParser.php Copyright (C) 2015 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ### [3.4.0] 2023-08-31 * Support larger major version numbers (#149) ### [3.3.2] 2022-04-01 * Fixed handling of non-string values (#134) ### [3.3.1] 2022-03-16 * Fixed possible cache key clash in the CompilingMatcher memoization (#132) ### [3.3.0] 2022-03-15 * Improved performance of CompilingMatcher by memoizing more (#131) * Added CompilingMatcher::clear to clear all memoization caches ### [3.2.9] 2022-02-04 * Revert #129 (Fixed MultiConstraint with MatchAllConstraint) which caused regressions ### [3.2.8] 2022-02-04 * Updates to latest phpstan / CI by @Seldaek in https://github.com/composer/semver/pull/130 * Fixed MultiConstraint with MatchAllConstraint by @Toflar in https://github.com/composer/semver/pull/129 ### [3.2.7] 2022-01-04 * Fixed: typo in type definition of Intervals class causing issues with Psalm scanning vendors ### [3.2.6] 2021-10-25 * Fixed: type improvements to parseStability ### [3.2.5] 2021-05-24 * Fixed: issue comparing disjunctive MultiConstraints to conjunctive ones (#127) * Fixed: added complete type information using phpstan annotations ### [3.2.4] 2020-11-13 * Fixed: code clean-up ### [3.2.3] 2020-11-12 * Fixed: constraints in the form of `X || Y, >=Y.1` and other such complex constructs were in some cases being optimized into a more restrictive constraint ### [3.2.2] 2020-10-14 * Fixed: internal code cleanups ### [3.2.1] 2020-09-27 * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases * Fixed: normalization of beta0 and such which was dropping the 0 ### [3.2.0] 2020-09-09 * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience ### [3.1.0] 2020-09-08 * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 3.0.1 * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package ### [3.0.1] 2020-09-08 * Fixed: handling of some invalid -dev versions which were seen as valid ### [3.0.0] 2020-05-26 * Break: Renamed `EmptyConstraint`, replace it with `MatchAllConstraint` * Break: Unlikely to affect anyone but strictly speaking a breaking change, `*.*` and such variants will not match all `dev-*` versions anymore, only `*` does * Break: ConstraintInterface is now considered internal/private and not meant to be implemented by third parties anymore * Added `Intervals` class to check if a constraint is a subsets of another one, and allow compacting complex MultiConstraints into simpler ones * Added `CompilingMatcher` class to speed up constraint matching against simple Constraint instances * Added `MatchAllConstraint` and `MatchNoneConstraint` which match everything and nothing * Added more advanced optimization of contiguous constraints inside MultiConstraint * Added tentative support for PHP 8 * Fixed ConstraintInterface::matches to be commutative in all cases ### [2.0.0] 2020-04-21 * Break: `dev-master`, `dev-trunk` and `dev-default` now normalize to `dev-master`, `dev-trunk` and `dev-default` instead of `9999999-dev` in 1.x * Break: Removed the deprecated `AbstractConstraint` * Added `getUpperBound` and `getLowerBound` to ConstraintInterface. They return `Composer\Semver\Constraint\Bound` instances * Added `MultiConstraint::create` to create the most-optimal form of ConstraintInterface from an array of constraint strings ### [1.7.2] 2020-12-03 * Fixed: Allow installing on php 8 ### [1.7.1] 2020-09-27 * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases * Fixed: normalization of beta0 and such which was dropping the 0 ### [1.7.0] 2020-09-09 * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience ### [1.6.0] 2020-09-08 * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 1.5.2 * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package ### [1.5.2] 2020-09-08 * Fixed: handling of some invalid -dev versions which were seen as valid * Fixed: some doctypes ### [1.5.1] 2020-01-13 * Fixed: Parsing of aliased version was not validating the alias to be a valid version ### [1.5.0] 2019-03-19 * Added: some support for date versions (e.g. 201903) in `~` operator * Fixed: support for stabilities in `~` operator was inconsistent ### [1.4.2] 2016-08-30 * Fixed: collapsing of complex constraints lead to buggy constraints ### [1.4.1] 2016-06-02 * Changed: branch-like requirements no longer strip build metadata - [composer/semver#38](https://github.com/composer/semver/pull/38). ### [1.4.0] 2016-03-30 * Added: getters on MultiConstraint - [composer/semver#35](https://github.com/composer/semver/pull/35). ### [1.3.0] 2016-02-25 * Fixed: stability parsing - [composer/composer#1234](https://github.com/composer/composer/issues/4889). * Changed: collapse contiguous constraints when possible. ### [1.2.0] 2015-11-10 * Changed: allow multiple numerical identifiers in 'pre-release' version part. * Changed: add more 'v' prefix support. ### [1.1.0] 2015-11-03 * Changed: dropped redundant `test` namespace. * Changed: minor adjustment in datetime parsing normalization. * Changed: `ConstraintInterface` relaxed, setPrettyString is not required anymore. * Changed: `AbstractConstraint` marked deprecated, will be removed in 2.0. * Changed: `Constraint` is now extensible. ### [1.0.0] 2015-09-21 * Break: `VersionConstraint` renamed to `Constraint`. * Break: `SpecificConstraint` renamed to `AbstractConstraint`. * Break: `LinkConstraintInterface` renamed to `ConstraintInterface`. * Break: `VersionParser::parseNameVersionPairs` was removed. * Changed: `VersionParser::parseConstraints` allows (but ignores) build metadata now. * Changed: `VersionParser::parseConstraints` allows (but ignores) prefixing numeric versions with a 'v' now. * Changed: Fixed namespace(s) of test files. * Changed: `Comparator::compare` no longer throws `InvalidArgumentException`. * Changed: `Constraint` now throws `InvalidArgumentException`. ### [0.1.0] 2015-07-23 * Added: `Composer\Semver\Comparator`, various methods to compare versions. * Added: various documents such as README.md, LICENSE, etc. * Added: configuration files for Git, Travis, php-cs-fixer, phpunit. * Break: the following namespaces were renamed: - Namespace: `Composer\Package\Version` -> `Composer\Semver` - Namespace: `Composer\Package\LinkConstraint` -> `Composer\Semver\Constraint` - Namespace: `Composer\Test\Package\Version` -> `Composer\Test\Semver` - Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint` * Changed: code style using php-cs-fixer. [3.4.0]: https://github.com/composer/semver/compare/3.3.2...3.4.0 [3.3.2]: https://github.com/composer/semver/compare/3.3.1...3.3.2 [3.3.1]: https://github.com/composer/semver/compare/3.3.0...3.3.1 [3.3.0]: https://github.com/composer/semver/compare/3.2.9...3.3.0 [3.2.9]: https://github.com/composer/semver/compare/3.2.8...3.2.9 [3.2.8]: https://github.com/composer/semver/compare/3.2.7...3.2.8 [3.2.7]: https://github.com/composer/semver/compare/3.2.6...3.2.7 [3.2.6]: https://github.com/composer/semver/compare/3.2.5...3.2.6 [3.2.5]: https://github.com/composer/semver/compare/3.2.4...3.2.5 [3.2.4]: https://github.com/composer/semver/compare/3.2.3...3.2.4 [3.2.3]: https://github.com/composer/semver/compare/3.2.2...3.2.3 [3.2.2]: https://github.com/composer/semver/compare/3.2.1...3.2.2 [3.2.1]: https://github.com/composer/semver/compare/3.2.0...3.2.1 [3.2.0]: https://github.com/composer/semver/compare/3.1.0...3.2.0 [3.1.0]: https://github.com/composer/semver/compare/3.0.1...3.1.0 [3.0.1]: https://github.com/composer/semver/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/composer/semver/compare/2.0.0...3.0.0 [2.0.0]: https://github.com/composer/semver/compare/1.5.1...2.0.0 [1.7.2]: https://github.com/composer/semver/compare/1.7.1...1.7.2 [1.7.1]: https://github.com/composer/semver/compare/1.7.0...1.7.1 [1.7.0]: https://github.com/composer/semver/compare/1.6.0...1.7.0 [1.6.0]: https://github.com/composer/semver/compare/1.5.2...1.6.0 [1.5.2]: https://github.com/composer/semver/compare/1.5.1...1.5.2 [1.5.1]: https://github.com/composer/semver/compare/1.5.0...1.5.1 [1.5.0]: https://github.com/composer/semver/compare/1.4.2...1.5.0 [1.4.2]: https://github.com/composer/semver/compare/1.4.1...1.4.2 [1.4.1]: https://github.com/composer/semver/compare/1.4.0...1.4.1 [1.4.0]: https://github.com/composer/semver/compare/1.3.0...1.4.0 [1.3.0]: https://github.com/composer/semver/compare/1.2.0...1.3.0 [1.2.0]: https://github.com/composer/semver/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/composer/semver/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/composer/semver/compare/0.1.0...1.0.0 [0.1.0]: https://github.com/composer/semver/compare/5e0b9a4da...0.1.0 composer/semver =============== Semver (Semantic Versioning) library that offers utilities, version constraint parsing and validation. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. [![Continuous Integration](https://github.com/composer/semver/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/continuous-integration.yml) [![PHP Lint](https://github.com/composer/semver/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/lint.yml) [![PHPStan](https://github.com/composer/semver/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/phpstan.yml) Installation ------------ Install the latest version with: ```bash composer require composer/semver ``` Requirements ------------ * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. Version Comparison ------------------ For details on how versions are compared, refer to the [Versions](https://getcomposer.org/doc/articles/versions.md) article in the documentation section of the [getcomposer.org](https://getcomposer.org) website. Basic usage ----------- ### Comparator The [`Composer\Semver\Comparator`](https://github.com/composer/semver/blob/main/src/Comparator.php) class provides the following methods for comparing versions: * greaterThan($v1, $v2) * greaterThanOrEqualTo($v1, $v2) * lessThan($v1, $v2) * lessThanOrEqualTo($v1, $v2) * equalTo($v1, $v2) * notEqualTo($v1, $v2) Each function takes two version strings as arguments and returns a boolean. For example: ```php use Composer\Semver\Comparator; Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0 ``` ### Semver The [`Composer\Semver\Semver`](https://github.com/composer/semver/blob/main/src/Semver.php) class provides the following methods: * satisfies($version, $constraints) * satisfiedBy(array $versions, $constraint) * sort($versions) * rsort($versions) ### Intervals The [`Composer\Semver\Intervals`](https://github.com/composer/semver/blob/main/src/Intervals.php) static class provides a few utilities to work with complex constraints or read version intervals from a constraint: ```php use Composer\Semver\Intervals; // Checks whether $candidate is a subset of $constraint Intervals::isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint); // Checks whether $a and $b have any intersection, equivalent to $a->matches($b) Intervals::haveIntersections(ConstraintInterface $a, ConstraintInterface $b); // Optimizes a complex multi constraint by merging all intervals down to the smallest // possible multi constraint. The drawbacks are this is not very fast, and the resulting // multi constraint will have no human readable prettyConstraint configured on it Intervals::compactConstraint(ConstraintInterface $constraint); // Creates an array of numeric intervals and branch constraints representing a given constraint Intervals::get(ConstraintInterface $constraint); // Clears the memoization cache when you are done processing constraints Intervals::clear() ``` See the class docblocks for more details. License ------- composer/semver is licensed under the MIT License, see the LICENSE file for details. { "name": "composer\/semver", "description": "Semver library that offers utilities, version constraint parsing and validation.", "type": "library", "license": "MIT", "keywords": [ "semver", "semantic", "versioning", "validation" ], "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "http:\/\/www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http:\/\/seld.be" }, { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http:\/\/robbast.nl" } ], "support": { "irc": "ircs:\/\/irc.libera.chat:6697\/composer", "issues": "https:\/\/github.com\/composer\/semver\/issues" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "symfony\/phpunit-bridge": "^4.2 || ^5", "phpstan\/phpstan": "^1.4" }, "autoload": { "psr-4": { "Composer\\Semver\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\Semver\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "3.x-dev" } }, "scripts": { "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit", "phpstan": "@php vendor\/bin\/phpstan analyse" } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; /** * Helper class to evaluate constraint by compiling and reusing the code to evaluate */ class CompilingMatcher { /** * @var array * @phpstan-var array */ private static $compiledCheckerCache = array(); /** * @var array * @phpstan-var array */ private static $resultCache = array(); /** @var bool */ private static $enabled; /** * @phpstan-var array */ private static $transOpInt = array(Constraint::OP_EQ => Constraint::STR_OP_EQ, Constraint::OP_LT => Constraint::STR_OP_LT, Constraint::OP_LE => Constraint::STR_OP_LE, Constraint::OP_GT => Constraint::STR_OP_GT, Constraint::OP_GE => Constraint::STR_OP_GE, Constraint::OP_NE => Constraint::STR_OP_NE); /** * Clears the memoization cache once you are done * * @return void */ public static function clear() { self::$resultCache = array(); self::$compiledCheckerCache = array(); } /** * Evaluates the expression: $constraint match $operator $version * * @param ConstraintInterface $constraint * @param int $operator * @phpstan-param Constraint::OP_* $operator * @param string $version * * @return mixed */ public static function match(ConstraintInterface $constraint, $operator, $version) { $resultCacheKey = $operator . $constraint . ';' . $version; if (isset(self::$resultCache[$resultCacheKey])) { return self::$resultCache[$resultCacheKey]; } if (self::$enabled === null) { self::$enabled = !\in_array('eval', \explode(',', (string) \ini_get('disable_functions')), \true); } if (!self::$enabled) { return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version)); } $cacheKey = $operator . $constraint; if (!isset(self::$compiledCheckerCache[$cacheKey])) { $code = $constraint->compile($operator); self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return ' . $code . ';};'); } else { $function = self::$compiledCheckerCache[$cacheKey]; } return self::$resultCache[$resultCacheKey] = $function($version, \strpos($version, 'dev-') === 0); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Interval { /** @var Constraint */ private $start; /** @var Constraint */ private $end; public function __construct(Constraint $start, Constraint $end) { $this->start = $start; $this->end = $end; } /** * @return Constraint */ public function getStart() { return $this->start; } /** * @return Constraint */ public function getEnd() { return $this->end; } /** * @return Constraint */ public static function fromZero() { static $zero; if (null === $zero) { $zero = new Constraint('>=', '0.0.0.0-dev'); } return $zero; } /** * @return Constraint */ public static function untilPositiveInfinity() { static $positiveInfinity; if (null === $positiveInfinity) { $positiveInfinity = new Constraint('<', \PHP_INT_MAX . '.0.0.0'); } return $positiveInfinity; } /** * @return self */ public static function any() { return new self(self::fromZero(), self::untilPositiveInfinity()); } /** * @return array{'names': string[], 'exclude': bool} */ public static function anyDev() { // any == exclude nothing return array('names' => array(), 'exclude' => \true); } /** * @return array{'names': string[], 'exclude': bool} */ public static function noDev() { // nothing == no names included return array('names' => array(), 'exclude' => \false); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Comparator { /** * Evaluates the expression: $version1 > $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function greaterThan($version1, $version2) { return self::compare($version1, '>', $version2); } /** * Evaluates the expression: $version1 >= $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function greaterThanOrEqualTo($version1, $version2) { return self::compare($version1, '>=', $version2); } /** * Evaluates the expression: $version1 < $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function lessThan($version1, $version2) { return self::compare($version1, '<', $version2); } /** * Evaluates the expression: $version1 <= $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function lessThanOrEqualTo($version1, $version2) { return self::compare($version1, '<=', $version2); } /** * Evaluates the expression: $version1 == $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function equalTo($version1, $version2) { return self::compare($version1, '==', $version2); } /** * Evaluates the expression: $version1 != $version2. * * @param string $version1 * @param string $version2 * * @return bool */ public static function notEqualTo($version1, $version2) { return self::compare($version1, '!=', $version2); } /** * Evaluates the expression: $version1 $operator $version2. * * @param string $version1 * @param string $operator * @param string $version2 * * @return bool * * @phpstan-param Constraint::STR_OP_* $operator */ public static function compare($version1, $operator, $version2) { $constraint = new Constraint($operator, $version2); return $constraint->matchSpecific(new Constraint('==', $version1), \true); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MatchNoneConstraint; use Composer\Semver\Constraint\MultiConstraint; /** * Helper class generating intervals from constraints * * This contains utilities for: * * - compacting an existing constraint which can be used to combine several into one * by creating a MultiConstraint out of the many constraints you have. * * - checking whether one subset is a subset of another. * * Note: You should call clear to free memoization memory usage when you are done using this class */ class Intervals { /** * @phpstan-var array */ private static $intervalsCache = array(); /** * @phpstan-var array */ private static $opSortOrder = array('>=' => -3, '<' => -2, '>' => 2, '<=' => 3); /** * Clears the memoization cache once you are done * * @return void */ public static function clear() { self::$intervalsCache = array(); } /** * Checks whether $candidate is a subset of $constraint * * @return bool */ public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint) { if ($constraint instanceof MatchAllConstraint) { return \true; } if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) { return \false; } $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), \true)); $candidateIntervals = self::get($candidate); if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) { return \false; } foreach ($intersectionIntervals['numeric'] as $index => $interval) { if (!isset($candidateIntervals['numeric'][$index])) { return \false; } if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) { return \false; } if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) { return \false; } } if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) { return \false; } if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) { return \false; } foreach ($intersectionIntervals['branches']['names'] as $index => $name) { if ($name !== $candidateIntervals['branches']['names'][$index]) { return \false; } } return \true; } /** * Checks whether $a and $b have any intersection, equivalent to $a->matches($b) * * @return bool */ public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b) { if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) { return \true; } if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) { return \false; } $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), \true), \true); return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0; } /** * Attempts to optimize a MultiConstraint * * When merging MultiConstraints together they can get very large, this will * compact it by looking at the real intervals covered by all the constraints * and then creates a new constraint containing only the smallest amount of rules * to match the same intervals. * * @return ConstraintInterface */ public static function compactConstraint(ConstraintInterface $constraint) { if (!$constraint instanceof MultiConstraint) { return $constraint; } $intervals = self::generateIntervals($constraint); $constraints = array(); $hasNumericMatchAll = \false; if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) \Composer\Semver\Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) \Composer\Semver\Interval::untilPositiveInfinity()) { $constraints[] = $intervals['numeric'][0]->getStart(); $hasNumericMatchAll = \true; } else { $unEqualConstraints = array(); for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) { $interval = $intervals['numeric'][$i]; // if current interval ends with < N and next interval begins with > N we can swap this out for != N // but this needs to happen as a conjunctive expression together with the start of the current interval // and end of next interval, so [>=M, N, [>=M, !=N, getEnd()->getOperator() === '<' && $i + 1 < $count) { $nextInterval = $intervals['numeric'][$i + 1]; if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') { // only add a start if we didn't already do so, can be skipped if we're looking at second // interval in [>=M, N, P, =M, !=N] already and we only want to add !=P right now if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) \Composer\Semver\Interval::fromZero()) { $unEqualConstraints[] = $interval->getStart(); } $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion()); continue; } } if (\count($unEqualConstraints) > 0) { // this is where the end of the following interval of a != constraint is added as explained above if ((string) $interval->getEnd() !== (string) \Composer\Semver\Interval::untilPositiveInfinity()) { $unEqualConstraints[] = $interval->getEnd(); } // count is 1 if entire constraint is just one != expression if (\count($unEqualConstraints) > 1) { $constraints[] = new MultiConstraint($unEqualConstraints, \true); } else { $constraints[] = $unEqualConstraints[0]; } $unEqualConstraints = array(); continue; } // convert back >= x - <= x intervals to == x if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') { $constraints[] = new Constraint('==', $interval->getStart()->getVersion()); continue; } if ((string) $interval->getStart() === (string) \Composer\Semver\Interval::fromZero()) { $constraints[] = $interval->getEnd(); } elseif ((string) $interval->getEnd() === (string) \Composer\Semver\Interval::untilPositiveInfinity()) { $constraints[] = $interval->getStart(); } else { $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), \true); } } } $devConstraints = array(); if (0 === \count($intervals['branches']['names'])) { if ($intervals['branches']['exclude']) { if ($hasNumericMatchAll) { return new MatchAllConstraint(); } // otherwise constraint should contain a != operator and already cover this } } else { foreach ($intervals['branches']['names'] as $branchName) { if ($intervals['branches']['exclude']) { $devConstraints[] = new Constraint('!=', $branchName); } else { $devConstraints[] = new Constraint('==', $branchName); } } // excluded branches, e.g. != dev-foo are conjunctive with the interval, so // > 2.0 != dev-foo must return a conjunctive constraint if ($intervals['branches']['exclude']) { if (\count($constraints) > 1) { return new MultiConstraint(\array_merge(array(new MultiConstraint($constraints, \false)), $devConstraints), \true); } if (\count($constraints) === 1 && (string) $constraints[0] === (string) \Composer\Semver\Interval::fromZero()) { if (\count($devConstraints) > 1) { return new MultiConstraint($devConstraints, \true); } return $devConstraints[0]; } return new MultiConstraint(\array_merge($constraints, $devConstraints), \true); } // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the // rest of the constraint $constraints = \array_merge($constraints, $devConstraints); } if (\count($constraints) > 1) { return new MultiConstraint($constraints, \false); } if (\count($constraints) === 1) { return $constraints[0]; } return new MatchNoneConstraint(); } /** * Creates an array of numeric intervals and branch constraints representing a given constraint * * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf) * if the returned branches array is empty it means no dev-* versions are matched * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev() * * @return array * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} */ public static function get(ConstraintInterface $constraint) { $key = (string) $constraint; if (!isset(self::$intervalsCache[$key])) { self::$intervalsCache[$key] = self::generateIntervals($constraint); } return self::$intervalsCache[$key]; } /** * @param bool $stopOnFirstValidInterval * * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} */ private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = \false) { if ($constraint instanceof MatchAllConstraint) { return array('numeric' => array(new \Composer\Semver\Interval(\Composer\Semver\Interval::fromZero(), \Composer\Semver\Interval::untilPositiveInfinity())), 'branches' => \Composer\Semver\Interval::anyDev()); } if ($constraint instanceof MatchNoneConstraint) { return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => \false)); } if ($constraint instanceof Constraint) { return self::generateSingleConstraintIntervals($constraint); } if (!$constraint instanceof MultiConstraint) { throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got ' . \get_class($constraint) . '.'); } $constraints = $constraint->getConstraints(); $numericGroups = array(); $constraintBranches = array(); foreach ($constraints as $c) { $res = self::get($c); $numericGroups[] = $res['numeric']; $constraintBranches[] = $res['branches']; } if ($constraint->isDisjunctive()) { $branches = \Composer\Semver\Interval::noDev(); foreach ($constraintBranches as $b) { if ($b['exclude']) { if ($branches['exclude']) { // disjunctive constraint, so only exclude what's excluded in all constraints // !=a,!=b || !=b,!=c => !=b $branches['names'] = \array_intersect($branches['names'], $b['names']); } else { // disjunctive constraint so exclude all names which are not explicitly included in the alternative // (==b || ==c) || !=a,!=b => !=a $branches['exclude'] = \true; $branches['names'] = \array_diff($b['names'], $branches['names']); } } else { if ($branches['exclude']) { // disjunctive constraint so exclude all names which are not explicitly included in the alternative // !=a,!=b || (==b || ==c) => !=a $branches['names'] = \array_diff($branches['names'], $b['names']); } else { // disjunctive constraint, so just add all the other branches // (==a || ==b) || ==c => ==a || ==b || ==c $branches['names'] = \array_merge($branches['names'], $b['names']); } } } } else { $branches = \Composer\Semver\Interval::anyDev(); foreach ($constraintBranches as $b) { if ($b['exclude']) { if ($branches['exclude']) { // conjunctive, so just add all branch names to be excluded // !=a && !=b => !=a,!=b $branches['names'] = \array_merge($branches['names'], $b['names']); } else { // conjunctive, so only keep included names which are not excluded // (==a||==c) && !=a,!=b => ==c $branches['names'] = \array_diff($branches['names'], $b['names']); } } else { if ($branches['exclude']) { // conjunctive, so only keep included names which are not excluded // !=a,!=b && (==a||==c) => ==c $branches['names'] = \array_diff($b['names'], $branches['names']); $branches['exclude'] = \false; } else { // conjunctive, so only keep names that are included in both // (==a||==b) && (==a||==c) => ==a $branches['names'] = \array_intersect($branches['names'], $b['names']); } } } } $branches['names'] = \array_unique($branches['names']); if (\count($numericGroups) === 1) { return array('numeric' => $numericGroups[0], 'branches' => $branches); } $borders = array(); foreach ($numericGroups as $group) { foreach ($group as $interval) { $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start'); $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end'); } } $opSortOrder = self::$opSortOrder; \usort($borders, function ($a, $b) use($opSortOrder) { $order = \version_compare($a['version'], $b['version']); if ($order === 0) { return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']]; } return $order; }); $activeIntervals = 0; $intervals = array(); $index = 0; $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1; $start = null; foreach ($borders as $border) { if ($border['side'] === 'start') { $activeIntervals++; } else { $activeIntervals--; } if (!$start && $activeIntervals >= $activationThreshold) { $start = new Constraint($border['operator'], $border['version']); } elseif ($start && $activeIntervals < $activationThreshold) { // filter out invalid intervals like > x - <= x, or >= x - < x if (\version_compare($start->getVersion(), $border['version'], '=') && ($start->getOperator() === '>' && $border['operator'] === '<=' || $start->getOperator() === '>=' && $border['operator'] === '<')) { unset($intervals[$index]); } else { $intervals[$index] = new \Composer\Semver\Interval($start, new Constraint($border['operator'], $border['version'])); $index++; if ($stopOnFirstValidInterval) { break; } } $start = null; } } return array('numeric' => $intervals, 'branches' => $branches); } /** * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} */ private static function generateSingleConstraintIntervals(Constraint $constraint) { $op = $constraint->getOperator(); // handle branch constraints first if (\strpos($constraint->getVersion(), 'dev-') === 0) { $intervals = array(); $branches = array('names' => array(), 'exclude' => \false); // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches if ($op === '!=') { $intervals[] = new \Composer\Semver\Interval(\Composer\Semver\Interval::fromZero(), \Composer\Semver\Interval::untilPositiveInfinity()); $branches = array('names' => array($constraint->getVersion()), 'exclude' => \true); } elseif ($op === '==') { $branches['names'][] = $constraint->getVersion(); } return array('numeric' => $intervals, 'branches' => $branches); } if ($op[0] === '>') { // > & >= return array('numeric' => array(new \Composer\Semver\Interval($constraint, \Composer\Semver\Interval::untilPositiveInfinity())), 'branches' => \Composer\Semver\Interval::noDev()); } if ($op[0] === '<') { // < & <= return array('numeric' => array(new \Composer\Semver\Interval(\Composer\Semver\Interval::fromZero(), $constraint)), 'branches' => \Composer\Semver\Interval::noDev()); } if ($op === '!=') { // convert !=x to intervals of 0 - x - +inf + dev* return array('numeric' => array(new \Composer\Semver\Interval(\Composer\Semver\Interval::fromZero(), new Constraint('<', $constraint->getVersion())), new \Composer\Semver\Interval(new Constraint('>', $constraint->getVersion()), \Composer\Semver\Interval::untilPositiveInfinity())), 'branches' => \Composer\Semver\Interval::anyDev()); } // convert ==x to an interval of >=x - <=x return array('numeric' => array(new \Composer\Semver\Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion()))), 'branches' => \Composer\Semver\Interval::noDev()); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\Constraint; class Semver { const SORT_ASC = 1; const SORT_DESC = -1; /** @var VersionParser */ private static $versionParser; /** * Determine if given version satisfies given constraints. * * @param string $version * @param string $constraints * * @return bool */ public static function satisfies($version, $constraints) { if (null === self::$versionParser) { self::$versionParser = new \Composer\Semver\VersionParser(); } $versionParser = self::$versionParser; $provider = new Constraint('==', $versionParser->normalize($version)); $parsedConstraints = $versionParser->parseConstraints($constraints); return $parsedConstraints->matches($provider); } /** * Return all versions that satisfy given constraints. * * @param string[] $versions * @param string $constraints * * @return string[] */ public static function satisfiedBy(array $versions, $constraints) { $versions = \array_filter($versions, function ($version) use($constraints) { return \Composer\Semver\Semver::satisfies($version, $constraints); }); return \array_values($versions); } /** * Sort given array of versions. * * @param string[] $versions * * @return string[] */ public static function sort(array $versions) { return self::usort($versions, self::SORT_ASC); } /** * Sort given array of versions in reverse. * * @param string[] $versions * * @return string[] */ public static function rsort(array $versions) { return self::usort($versions, self::SORT_DESC); } /** * @param string[] $versions * @param int $direction * * @return string[] */ private static function usort(array $versions, $direction) { if (null === self::$versionParser) { self::$versionParser = new \Composer\Semver\VersionParser(); } $versionParser = self::$versionParser; $normalized = array(); // Normalize outside of usort() scope for minor performance increase. // Creates an array of arrays: [[normalized, key], ...] foreach ($versions as $key => $version) { $normalizedVersion = $versionParser->normalize($version); $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion); $normalized[] = array($normalizedVersion, $key); } \usort($normalized, function (array $left, array $right) use($direction) { if ($left[0] === $right[0]) { return 0; } if (\Composer\Semver\Comparator::lessThan($left[0], $right[0])) { return -$direction; } return $direction; }); // Recreate input array, using the original indexes which are now in sorted order. $sorted = array(); foreach ($normalized as $item) { $sorted[] = $versions[$item[1]]; } return $sorted; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Constraint\Constraint; /** * Version parser. * * @author Jordi Boggiano */ class VersionParser { /** * Regex to match pre-release data (sort of). * * Due to backwards compatibility: * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. * - Numerical-only pre-release identifiers are not supported, see tests. * * |--------------| * [major].[minor].[patch] -[pre-release] +[build-metadata] * * @var string */ private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\\d+)*+)?)?([.-]?dev)?'; /** @var string */ private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev'; /** * Returns the stability of a version. * * @param string $version * * @return string * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' */ public static function parseStability($version) { $version = (string) \preg_replace('{#.+$}', '', (string) $version); if (\strpos($version, 'dev-') === 0 || '-dev' === \substr($version, -4)) { return 'dev'; } \preg_match('{' . self::$modifierRegex . '(?:\\+.*)?$}i', \strtolower($version), $match); if (!empty($match[3])) { return 'dev'; } if (!empty($match[1])) { if ('beta' === $match[1] || 'b' === $match[1]) { return 'beta'; } if ('alpha' === $match[1] || 'a' === $match[1]) { return 'alpha'; } if ('rc' === $match[1]) { return 'RC'; } } return 'stable'; } /** * @param string $stability * * @return string */ public static function normalizeStability($stability) { $stability = \strtolower((string) $stability); return $stability === 'rc' ? 'RC' : $stability; } /** * Normalizes a version string to be able to perform comparisons on it. * * @param string $version * @param ?string $fullVersion optional complete version string to give more context * * @throws \UnexpectedValueException * * @return string */ public function normalize($version, $fullVersion = null) { $version = \trim((string) $version); $origVersion = $version; if (null === $fullVersion) { $fullVersion = $version; } // strip off aliasing if (\preg_match('{^([^,\\s]++) ++as ++([^,\\s]++)$}', $version, $match)) { $version = $match[1]; } // strip off stability flag if (\preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) { $version = \substr($version, 0, \strlen($version) - \strlen($match[0])); } // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints if (\in_array($version, array('master', 'trunk', 'default'), \true)) { $version = 'dev-' . $version; } // if requirement is branch-like, use full name if (\stripos($version, 'dev-') === 0) { return 'dev-' . \substr($version, 4); } // strip off build metadata if (\preg_match('{^([^,\\s+]++)\\+[^\\s]++$}', $version, $match)) { $version = $match[1]; } // match classical versioning if (\preg_match('{^v?(\\d{1,5}+)(\\.\\d++)?(\\.\\d++)?(\\.\\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0'); $index = 5; // match date(time) based versioning } elseif (\preg_match('{^v?(\\d{4}(?:[.:-]?\\d{2}){1,6}(?:[.:-]?\\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) { $version = (string) \preg_replace('{\\D}', '.', $matches[1]); $index = 2; } // add version modifiers if a version was matched if (isset($index)) { if (!empty($matches[$index])) { if ('stable' === $matches[$index]) { return $version; } $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? \ltrim($matches[$index + 1], '.-') : ''); } if (!empty($matches[$index + 2])) { $version .= '-dev'; } return $version; } // match dev branches if (\preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { try { $normalized = $this->normalizeBranch($match[1]); // a branch ending with -dev is only valid if it is numeric // if it gets prefixed with dev- it means the branch name should // have had a dev- prefix already when passed to normalize if (\strpos($normalized, 'dev-') === \false) { return $normalized; } } catch (\Exception $e) { } } $extraMessage = ''; if (\preg_match('{ +as +' . \preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))?$}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; } elseif (\preg_match('{^' . \preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))? +as +}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; } throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); } /** * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. * * @param string $branch Branch name (e.g. 2.1.x-dev) * * @return string|false Numeric prefix if present (e.g. 2.1.) or false */ public function parseNumericAliasPrefix($branch) { if (\preg_match('{^(?P(\\d++\\.)*\\d++)(?:\\.x)?-dev$}i', (string) $branch, $matches)) { return $matches['version'] . '.'; } return \false; } /** * Normalizes a branch name to be able to perform comparisons on it. * * @param string $name * * @return string */ public function normalizeBranch($name) { $name = \trim((string) $name); if (\preg_match('{^v?(\\d++)(\\.(?:\\d++|[xX*]))?(\\.(?:\\d++|[xX*]))?(\\.(?:\\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; for ($i = 1; $i < 5; ++$i) { $version .= isset($matches[$i]) ? \str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; } return \str_replace('x', '9999999', $version) . '-dev'; } return 'dev-' . $name; } /** * Normalizes a default branch name (i.e. master on git) to 9999999-dev. * * @param string $name * * @return string * * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore */ public function normalizeDefaultBranch($name) { if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') { return '9999999-dev'; } return (string) $name; } /** * Parses a constraint string into MultiConstraint and/or Constraint objects. * * @param string $constraints * * @return ConstraintInterface */ public function parseConstraints($constraints) { $prettyConstraint = (string) $constraints; $orConstraints = \preg_split('{\\s*\\|\\|?\\s*}', \trim((string) $constraints)); if (\false === $orConstraints) { throw new \RuntimeException('Failed to preg_split string: ' . $constraints); } $orGroups = array(); foreach ($orConstraints as $orConstraint) { $andConstraints = \preg_split('{(?< ,]) *(? 1) { $constraintObjects = array(); foreach ($andConstraints as $andConstraint) { foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) { $constraintObjects[] = $parsedAndConstraint; } } } else { $constraintObjects = $this->parseConstraint($andConstraints[0]); } if (1 === \count($constraintObjects)) { $constraint = $constraintObjects[0]; } else { $constraint = new MultiConstraint($constraintObjects); } $orGroups[] = $constraint; } $parsedConstraint = MultiConstraint::create($orGroups, \false); $parsedConstraint->setPrettyString($prettyConstraint); return $parsedConstraint; } /** * @param string $constraint * * @throws \UnexpectedValueException * * @return array * * @phpstan-return non-empty-array */ private function parseConstraint($constraint) { // strip off aliasing if (\preg_match('{^([^,\\s]++) ++as ++([^,\\s]++)$}', $constraint, $match)) { $constraint = $match[1]; } // strip @stability flags, and keep it for later use if (\preg_match('{^([^,\\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { $constraint = '' !== $match[1] ? $match[1] : '*'; if ($match[2] !== 'stable') { $stabilityModifier = $match[2]; } } // get rid of #refs as those are used by composer only if (\preg_match('{^(dev-[^,\\s@]+?|[^,\\s@]+?\\.x-dev)#.+$}i', $constraint, $match)) { $constraint = $match[1]; } if (\preg_match('{^(v)?[xX*](\\.[xX*])*$}i', $constraint, $match)) { if (!empty($match[1]) || !empty($match[2])) { return array(new Constraint('>=', '0.0.0.0-dev')); } return array(new MatchAllConstraint()); } $versionRegex = 'v?(\\d++)(?:\\.(\\d++))?(?:\\.(\\d++))?(?:\\.(\\d++))?(?:' . self::$modifierRegex . '|\\.([xX*][.-]?dev))(?:\\+[^\\s]+)?'; // Tilde Range // // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous // version, to ensure that unstable instances of the current version are allowed. However, if a stability // suffix is added to the constraint, then a >= match on the current version is used instead. if (\preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { if (\strpos($constraint, '~>') === 0) { throw new \UnexpectedValueException('Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator'); } // Work out which position in the version we are operating at if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { $position = 4; } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above if (!empty($matches[8])) { $position++; } // Calculate the stability suffix $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(\substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal $highPosition = \max(1, $position - 1); $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array($lowerBound, $upperBound); } // Caret Range // // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for // versions 0.X >=0.1.0, and no updates for versions 0.0.X if (\preg_match('{^\\^' . $versionRegex . '($)}i', $constraint, $matches)) { // Work out which position in the version we are operating at if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { $position = 1; } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { $position = 2; } else { $position = 3; } // Calculate the stability suffix $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(\substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array($lowerBound, $upperBound); } // X Range // // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. // A partial version range is treated as an X-Range, so the special character is in fact optional. if (\preg_match('{^v?(\\d++)(?:\\.(\\d++))?(?:\\.(\\d++))?(?:\\.[xX*])++$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; if ($lowVersion === '0.0.0.0-dev') { return array(new Constraint('<', $highVersion)); } return array(new Constraint('>=', $lowVersion), new Constraint('<', $highVersion)); } // Hyphen Range // // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but // nothing that would be greater than the provided tuple parts. if (\preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { // Calculate the stability suffix $lowStabilitySuffix = ''; if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) { $lowStabilitySuffix = '-dev'; } $lowVersion = $this->normalize($matches['from']); $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); $empty = function ($x) { return $x === 0 || $x === '0' ? \false : empty($x); }; if (!$empty($matches[12]) && !$empty($matches[13]) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) { $highVersion = $this->normalize($matches['to']); $upperBound = new Constraint('<=', $highVersion); } else { $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]); // validate to version $this->normalize($matches['to']); $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); } return array($lowerBound, $upperBound); } // Basic Comparators if (\preg_match('{^(<>|!=|>=?|<=?|==?)?\\s*(.*)}', $constraint, $matches)) { try { try { $version = $this->normalize($matches[2]); } catch (\UnexpectedValueException $e) { // recover from an invalid constraint like foobar-dev which should be dev-foobar // except if the constraint uses a known operator, in which case it must be a parse error if (\substr($matches[2], -4) === '-dev' && \preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) { $version = $this->normalize('dev-' . \substr($matches[2], 0, -4)); } else { throw $e; } } $op = $matches[1] ?: '='; if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { $version .= '-' . $stabilityModifier; } elseif ('<' === $op || '>=' === $op) { if (!\preg_match('/-' . self::$modifierRegex . '$/', \strtolower($matches[2]))) { if (\strpos($matches[2], 'dev-') !== 0) { $version .= '-dev'; } } } return array(new Constraint($matches[1] ?: '=', $version)); } catch (\Exception $e) { } } $message = 'Could not parse version constraint ' . $constraint; if (isset($e)) { $message .= ': ' . $e->getMessage(); } throw new \UnexpectedValueException($message); } /** * Increment, decrement, or simply pad a version number. * * Support function for {@link parseConstraint()} * * @param array $matches Array with version parts in array indexes 1,2,3,4 * @param int $position 1,2,3,4 - which segment of the version to increment/decrement * @param int $increment * @param string $pad The string to pad version parts after $position * * @return string|null The new version * * @phpstan-param string[] $matches */ private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0') { for ($i = 4; $i > 0; --$i) { if ($i > $position) { $matches[$i] = $pad; } elseif ($i === $position && $increment) { $matches[$i] += $increment; // If $matches[$i] was 0, carry the decrement if ($matches[$i] < 0) { $matches[$i] = $pad; --$position; // Return null on a carry overflow if ($i === 1) { return null; } } } } return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; } /** * Expand shorthand stability string to long version. * * @param string $stability * * @return string */ private function expandStability($stability) { $stability = \strtolower($stability); switch ($stability) { case 'a': return 'alpha'; case 'b': return 'beta'; case 'p': case 'pl': return 'patch'; case 'rc': return 'RC'; default: return $stability; } } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * DO NOT IMPLEMENT this interface. It is only meant for usage as a type hint * in libraries relying on composer/semver but creating your own constraint class * that implements this interface is not a supported use case and will cause the * composer/semver components to return unexpected results. */ interface ConstraintInterface { /** * Checks whether the given constraint intersects in any way with this constraint * * @param ConstraintInterface $provider * * @return bool */ public function matches(\Composer\Semver\Constraint\ConstraintInterface $provider); /** * Provides a compiled version of the constraint for the given operator * The compiled version must be a PHP expression. * Executor of compile version must provide 2 variables: * - $v = the string version to compare with * - $b = whether or not the version is a non-comparable branch (starts with "dev-") * * @see Constraint::OP_* for the list of available operators. * @example return '!$b && version_compare($v, '1.0', '>')'; * * @param int $otherOperator one Constraint::OP_* * * @return string * * @phpstan-param Constraint::OP_* $otherOperator */ public function compile($otherOperator); /** * @return Bound */ public function getUpperBound(); /** * @return Bound */ public function getLowerBound(); /** * @return string */ public function getPrettyString(); /** * @param string|null $prettyString * * @return void */ public function setPrettyString($prettyString); /** * @return string */ public function __toString(); } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines a conjunctive or disjunctive set of constraints. */ class MultiConstraint implements \Composer\Semver\Constraint\ConstraintInterface { /** * @var ConstraintInterface[] * @phpstan-var non-empty-array */ protected $constraints; /** @var string|null */ protected $prettyString; /** @var string|null */ protected $string; /** @var bool */ protected $conjunctive; /** @var Bound|null */ protected $lowerBound; /** @var Bound|null */ protected $upperBound; /** * @param ConstraintInterface[] $constraints A set of constraints * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive * * @throws \InvalidArgumentException If less than 2 constraints are passed */ public function __construct(array $constraints, $conjunctive = \true) { if (\count($constraints) < 2) { throw new \InvalidArgumentException('Must provide at least two constraints for a MultiConstraint. Use ' . 'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use ' . 'MultiConstraint::create() which optimizes and handles those cases automatically.'); } $this->constraints = $constraints; $this->conjunctive = $conjunctive; } /** * @return ConstraintInterface[] */ public function getConstraints() { return $this->constraints; } /** * @return bool */ public function isConjunctive() { return $this->conjunctive; } /** * @return bool */ public function isDisjunctive() { return !$this->conjunctive; } /** * {@inheritDoc} */ public function compile($otherOperator) { $parts = array(); foreach ($this->constraints as $constraint) { $code = $constraint->compile($otherOperator); if ($code === 'true') { if (!$this->conjunctive) { return 'true'; } } elseif ($code === 'false') { if ($this->conjunctive) { return 'false'; } } else { $parts[] = '(' . $code . ')'; } } if (!$parts) { return $this->conjunctive ? 'true' : 'false'; } return $this->conjunctive ? \implode('&&', $parts) : \implode('||', $parts); } /** * @param ConstraintInterface $provider * * @return bool */ public function matches(\Composer\Semver\Constraint\ConstraintInterface $provider) { if (\false === $this->conjunctive) { foreach ($this->constraints as $constraint) { if ($provider->matches($constraint)) { return \true; } } return \false; } // when matching a conjunctive and a disjunctive multi constraint we have to iterate over the disjunctive one // otherwise we'd return true if different parts of the disjunctive constraint match the conjunctive one // which would lead to incorrect results, e.g. [>1 and <2] would match [<1 or >2] although they do not intersect if ($provider instanceof \Composer\Semver\Constraint\MultiConstraint && $provider->isDisjunctive()) { return $provider->matches($this); } foreach ($this->constraints as $constraint) { if (!$provider->matches($constraint)) { return \false; } } return \true; } /** * {@inheritDoc} */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * {@inheritDoc} */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } /** * {@inheritDoc} */ public function __toString() { if ($this->string !== null) { return $this->string; } $constraints = array(); foreach ($this->constraints as $constraint) { $constraints[] = (string) $constraint; } return $this->string = '[' . \implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; } /** * {@inheritDoc} */ public function getLowerBound() { $this->extractBounds(); if (null === $this->lowerBound) { throw new \LogicException('extractBounds should have populated the lowerBound property'); } return $this->lowerBound; } /** * {@inheritDoc} */ public function getUpperBound() { $this->extractBounds(); if (null === $this->upperBound) { throw new \LogicException('extractBounds should have populated the upperBound property'); } return $this->upperBound; } /** * Tries to optimize the constraints as much as possible, meaning * reducing/collapsing congruent constraints etc. * Does not necessarily return a MultiConstraint instance if * things can be reduced to a simple constraint * * @param ConstraintInterface[] $constraints A set of constraints * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive * * @return ConstraintInterface */ public static function create(array $constraints, $conjunctive = \true) { if (0 === \count($constraints)) { return new \Composer\Semver\Constraint\MatchAllConstraint(); } if (1 === \count($constraints)) { return $constraints[0]; } $optimized = self::optimizeConstraints($constraints, $conjunctive); if ($optimized !== null) { list($constraints, $conjunctive) = $optimized; if (\count($constraints) === 1) { return $constraints[0]; } } return new self($constraints, $conjunctive); } /** * @param ConstraintInterface[] $constraints * @param bool $conjunctive * @return ?array * * @phpstan-return array{0: list, 1: bool}|null */ private static function optimizeConstraints(array $constraints, $conjunctive) { // parse the two OR groups and if they are contiguous we collapse // them into one constraint // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4] if (!$conjunctive) { $left = $constraints[0]; $mergedConstraints = array(); $optimized = \false; for ($i = 1, $l = \count($constraints); $i < $l; $i++) { $right = $constraints[$i]; if ($left instanceof self && $left->conjunctive && $right instanceof self && $right->conjunctive && \count($left->constraints) === 2 && \count($right->constraints) === 2 && ($left0 = (string) $left->constraints[0]) && $left0[0] === '>' && $left0[1] === '=' && ($left1 = (string) $left->constraints[1]) && $left1[0] === '<' && ($right0 = (string) $right->constraints[0]) && $right0[0] === '>' && $right0[1] === '=' && ($right1 = (string) $right->constraints[1]) && $right1[0] === '<' && \substr($left1, 2) === \substr($right0, 3)) { $optimized = \true; $left = new \Composer\Semver\Constraint\MultiConstraint(array($left->constraints[0], $right->constraints[1]), \true); } else { $mergedConstraints[] = $left; $left = $right; } } if ($optimized) { $mergedConstraints[] = $left; return array($mergedConstraints, \false); } } // TODO: Here's the place to put more optimizations return null; } /** * @return void */ private function extractBounds() { if (null !== $this->lowerBound) { return; } foreach ($this->constraints as $constraint) { if (null === $this->lowerBound || null === $this->upperBound) { $this->lowerBound = $constraint->getLowerBound(); $this->upperBound = $constraint->getUpperBound(); continue; } if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) { $this->lowerBound = $constraint->getLowerBound(); } if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) { $this->upperBound = $constraint->getUpperBound(); } } } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines a constraint. */ class Constraint implements \Composer\Semver\Constraint\ConstraintInterface { /* operator integer values */ const OP_EQ = 0; const OP_LT = 1; const OP_LE = 2; const OP_GT = 3; const OP_GE = 4; const OP_NE = 5; /* operator string values */ const STR_OP_EQ = '=='; const STR_OP_EQ_ALT = '='; const STR_OP_LT = '<'; const STR_OP_LE = '<='; const STR_OP_GT = '>'; const STR_OP_GE = '>='; const STR_OP_NE = '!='; const STR_OP_NE_ALT = '<>'; /** * Operator to integer translation table. * * @var array * @phpstan-var array */ private static $transOpStr = array('=' => self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE); /** * Integer to operator translation table. * * @var array * @phpstan-var array */ private static $transOpInt = array(self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!='); /** * @var int * @phpstan-var self::OP_* */ protected $operator; /** @var string */ protected $version; /** @var string|null */ protected $prettyString; /** @var Bound */ protected $lowerBound; /** @var Bound */ protected $upperBound; /** * Sets operator and version to compare with. * * @param string $operator * @param string $version * * @throws \InvalidArgumentException if invalid operator is given. * * @phpstan-param self::STR_OP_* $operator */ public function __construct($operator, $version) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(\sprintf('Invalid operator "%s" given, expected one of: %s', $operator, \implode(', ', self::getSupportedOperators()))); } $this->operator = self::$transOpStr[$operator]; $this->version = $version; } /** * @return string */ public function getVersion() { return $this->version; } /** * @return string * * @phpstan-return self::STR_OP_* */ public function getOperator() { return self::$transOpInt[$this->operator]; } /** * @param ConstraintInterface $provider * * @return bool */ public function matches(\Composer\Semver\Constraint\ConstraintInterface $provider) { if ($provider instanceof self) { return $this->matchSpecific($provider); } // turn matching around to find a match return $provider->matches($this); } /** * {@inheritDoc} */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * {@inheritDoc} */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } /** * Get all supported comparison operators. * * @return array * * @phpstan-return list */ public static function getSupportedOperators() { return \array_keys(self::$transOpStr); } /** * @param string $operator * @return int * * @phpstan-param self::STR_OP_* $operator * @phpstan-return self::OP_* */ public static function getOperatorConstant($operator) { return self::$transOpStr[$operator]; } /** * @param string $a * @param string $b * @param string $operator * @param bool $compareBranches * * @throws \InvalidArgumentException if invalid operator is given. * * @return bool * * @phpstan-param self::STR_OP_* $operator */ public function versionCompare($a, $b, $operator, $compareBranches = \false) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(\sprintf('Invalid operator "%s" given, expected one of: %s', $operator, \implode(', ', self::getSupportedOperators()))); } $aIsBranch = \strpos($a, 'dev-') === 0; $bIsBranch = \strpos($b, 'dev-') === 0; if ($operator === '!=' && ($aIsBranch || $bIsBranch)) { return $a !== $b; } if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } // when branches are not comparable, we make sure dev branches never match anything if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return \false; } return \version_compare($a, $b, $operator); } /** * {@inheritDoc} */ public function compile($otherOperator) { if (\strpos($this->version, 'dev-') === 0) { if (self::OP_EQ === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('$b && $v === %s', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return \sprintf('!$b || $v !== %s', \var_export($this->version, \true)); } return 'false'; } if (self::OP_NE === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('!$b || $v !== %s', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return 'true'; } return '!$b'; } return 'false'; } if (self::OP_EQ === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('\\version_compare($v, %s, \'==\')', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return \sprintf('$b || \\version_compare($v, %s, \'!=\')', \var_export($this->version, \true)); } return \sprintf('!$b && \\version_compare(%s, $v, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$otherOperator]); } if (self::OP_NE === $this->operator) { if (self::OP_EQ === $otherOperator) { return \sprintf('$b || (!$b && \\version_compare($v, %s, \'!=\'))', \var_export($this->version, \true)); } if (self::OP_NE === $otherOperator) { return 'true'; } return '!$b'; } if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) { if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) { return '!$b'; } } else { // $this->operator must be self::OP_GT || self::OP_GE here if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) { return '!$b'; } } if (self::OP_NE === $otherOperator) { return 'true'; } $codeComparison = \sprintf('\\version_compare($v, %s, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$this->operator]); if ($this->operator === self::OP_LE) { if ($otherOperator === self::OP_GT) { return \sprintf('!$b && \\version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison; } } elseif ($this->operator === self::OP_GE) { if ($otherOperator === self::OP_LT) { return \sprintf('!$b && \\version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison; } } return \sprintf('!$b && %s', $codeComparison); } /** * @param Constraint $provider * @param bool $compareBranches * * @return bool */ public function matchSpecific(\Composer\Semver\Constraint\Constraint $provider, $compareBranches = \false) { $noEqualOp = \str_replace('=', '', self::$transOpInt[$this->operator]); $providerNoEqualOp = \str_replace('=', '', self::$transOpInt[$provider->operator]); $isEqualOp = self::OP_EQ === $this->operator; $isNonEqualOp = self::OP_NE === $this->operator; $isProviderEqualOp = self::OP_EQ === $provider->operator; $isProviderNonEqualOp = self::OP_NE === $provider->operator; // '!=' operator is match when other operator is not '==' operator or version is not match // these kinds of comparisons always have a solution if ($isNonEqualOp || $isProviderNonEqualOp) { if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && \strpos($provider->version, 'dev-') === 0) { return \false; } if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && \strpos($this->version, 'dev-') === 0) { return \false; } if (!$isEqualOp && !$isProviderEqualOp) { return \true; } return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); } // an example for the condition is <= 2.0 & < 1.0 // these kinds of comparisons always have a solution if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { return !(\strpos($this->version, 'dev-') === 0 || \strpos($provider->version, 'dev-') === 0); } $version1 = $isEqualOp ? $this->version : $provider->version; $version2 = $isEqualOp ? $provider->version : $this->version; $operator = $isEqualOp ? $provider->operator : $this->operator; if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) { // special case, e.g. require >= 1.0 and provide < 1.0 // 1.0 >= 1.0 but 1.0 is outside of the provided interval return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp && \version_compare($provider->version, $this->version, '==')); } return \false; } /** * @return string */ public function __toString() { return self::$transOpInt[$this->operator] . ' ' . $this->version; } /** * {@inheritDoc} */ public function getLowerBound() { $this->extractBounds(); return $this->lowerBound; } /** * {@inheritDoc} */ public function getUpperBound() { $this->extractBounds(); return $this->upperBound; } /** * @return void */ private function extractBounds() { if (null !== $this->lowerBound) { return; } // Branches if (\strpos($this->version, 'dev-') === 0) { $this->lowerBound = \Composer\Semver\Constraint\Bound::zero(); $this->upperBound = \Composer\Semver\Constraint\Bound::positiveInfinity(); return; } switch ($this->operator) { case self::OP_EQ: $this->lowerBound = new \Composer\Semver\Constraint\Bound($this->version, \true); $this->upperBound = new \Composer\Semver\Constraint\Bound($this->version, \true); break; case self::OP_LT: $this->lowerBound = \Composer\Semver\Constraint\Bound::zero(); $this->upperBound = new \Composer\Semver\Constraint\Bound($this->version, \false); break; case self::OP_LE: $this->lowerBound = \Composer\Semver\Constraint\Bound::zero(); $this->upperBound = new \Composer\Semver\Constraint\Bound($this->version, \true); break; case self::OP_GT: $this->lowerBound = new \Composer\Semver\Constraint\Bound($this->version, \false); $this->upperBound = \Composer\Semver\Constraint\Bound::positiveInfinity(); break; case self::OP_GE: $this->lowerBound = new \Composer\Semver\Constraint\Bound($this->version, \true); $this->upperBound = \Composer\Semver\Constraint\Bound::positiveInfinity(); break; case self::OP_NE: $this->lowerBound = \Composer\Semver\Constraint\Bound::zero(); $this->upperBound = \Composer\Semver\Constraint\Bound::positiveInfinity(); break; } } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Defines the absence of a constraint. * * This constraint matches everything. */ class MatchAllConstraint implements \Composer\Semver\Constraint\ConstraintInterface { /** @var string|null */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(\Composer\Semver\Constraint\ConstraintInterface $provider) { return \true; } /** * {@inheritDoc} */ public function compile($otherOperator) { return 'true'; } /** * {@inheritDoc} */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * {@inheritDoc} */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } /** * {@inheritDoc} */ public function __toString() { return '*'; } /** * {@inheritDoc} */ public function getUpperBound() { return \Composer\Semver\Constraint\Bound::positiveInfinity(); } /** * {@inheritDoc} */ public function getLowerBound() { return \Composer\Semver\Constraint\Bound::zero(); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; /** * Blackhole of constraints, nothing escapes it */ class MatchNoneConstraint implements \Composer\Semver\Constraint\ConstraintInterface { /** @var string|null */ protected $prettyString; /** * @param ConstraintInterface $provider * * @return bool */ public function matches(\Composer\Semver\Constraint\ConstraintInterface $provider) { return \false; } /** * {@inheritDoc} */ public function compile($otherOperator) { return 'false'; } /** * {@inheritDoc} */ public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } /** * {@inheritDoc} */ public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } /** * {@inheritDoc} */ public function __toString() { return '[]'; } /** * {@inheritDoc} */ public function getUpperBound() { return new \Composer\Semver\Constraint\Bound('0.0.0.0-dev', \false); } /** * {@inheritDoc} */ public function getLowerBound() { return new \Composer\Semver\Constraint\Bound('0.0.0.0-dev', \false); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Semver\Constraint; class Bound { /** * @var string */ private $version; /** * @var bool */ private $isInclusive; /** * @param string $version * @param bool $isInclusive */ public function __construct($version, $isInclusive) { $this->version = $version; $this->isInclusive = $isInclusive; } /** * @return string */ public function getVersion() { return $this->version; } /** * @return bool */ public function isInclusive() { return $this->isInclusive; } /** * @return bool */ public function isZero() { return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive(); } /** * @return bool */ public function isPositiveInfinity() { return $this->getVersion() === \PHP_INT_MAX . '.0.0.0' && !$this->isInclusive(); } /** * Compares a bound to another with a given operator. * * @param Bound $other * @param string $operator * * @return bool */ public function compareTo(\Composer\Semver\Constraint\Bound $other, $operator) { if (!\in_array($operator, array('<', '>'), \true)) { throw new \InvalidArgumentException('Does not support any other operator other than > or <.'); } // If they are the same it doesn't matter if ($this == $other) { return \false; } $compareResult = \version_compare($this->getVersion(), $other->getVersion()); // Not the same version means we don't need to check if the bounds are inclusive or not if (0 !== $compareResult) { return ('>' === $operator ? 1 : -1) === $compareResult; } // Question we're answering here is "am I higher than $other?" return '>' === $operator ? $other->isInclusive() : !$other->isInclusive(); } public function __toString() { return \sprintf('%s [%s]', $this->getVersion(), $this->isInclusive() ? 'inclusive' : 'exclusive'); } /** * @return self */ public static function zero() { return new \Composer\Semver\Constraint\Bound('0.0.0.0-dev', \true); } /** * @return self */ public static function positiveInfinity() { return new \Composer\Semver\Constraint\Bound(\PHP_INT_MAX . '.0.0.0', \false); } } $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', 'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php', ); * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; /** * This class is copied in every Composer installed project and available to all * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` * * @final */ class InstalledVersions { /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; /** * @var bool|null */ private static $canGetVendors; /** * @var array[] * @psalm-var array}> */ private static $installedByVendor = array(); /** * Returns a list of all package names which are present, either by being installed, replaced or provided * * @return string[] * @psalm-return list */ public static function getInstalledPackages() { $packages = array(); foreach (self::getInstalled() as $installed) { $packages[] = \array_keys($installed['versions']); } if (1 === \count($packages)) { return $packages[0]; } return \array_keys(\array_flip(\call_user_func_array('array_merge', $packages))); } /** * Returns a list of all package names with a specific type e.g. 'library' * * @param string $type * @return string[] * @psalm-return list */ public static function getInstalledPackagesByType($type) { $packagesByType = array(); foreach (self::getInstalled() as $installed) { foreach ($installed['versions'] as $name => $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } /** * Checks whether the given package is installed * * This also returns true if the package name is provided or replaced by another package * * @param string $packageName * @param bool $includeDevRequirements * @return bool */ public static function isInstalled($packageName, $includeDevRequirements = \true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === \false; } } return \false; } /** * Checks whether the given package satisfies a version constraint * * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: * * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') * * @param VersionParser $parser Install composer/semver to have access to this class and functionality * @param string $packageName * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package * @return bool */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } /** * Returns a version constraint representing all the range(s) which are installed for a given package * * It is easier to use this via isInstalled() with the $constraint argument if you need to check * whether a given version of a package is installed, and not just whether it exists * * @param string $packageName * @return string Version constraint usable with composer/semver */ public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (\array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = \array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (\array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = \array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (\array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = \array_merge($ranges, $installed['versions'][$packageName]['provided']); } return \implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference */ public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. */ public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @return array * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } /** * Returns the raw installed.php data for custom implementations * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @\trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', \E_USER_DEPRECATED); if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (\substr(__DIR__, -8, 1) !== 'C') { self::$installed = (include __DIR__ . '/installed.php'); } else { self::$installed = array(); } } return self::$installed; } /** * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] * @psalm-return list}> */ public static function getAllRawData() { return self::getInstalled(); } /** * Lets you reload the static array from another file * * This is only useful for complex integrations in which a project needs to use * this class but then also needs to execute another project's autoloader in process, * and wants to ensure both projects have access to their version of installed.php. * * A typical case would be PHPUnit, where it would need to make sure it reads all * the data it needs from this class, then call reload() with * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure * the project in which it runs can then also use this class safely, without * interference between PHPUnit's dependencies and the project's dependencies. * * @param array[] $data A vendor/composer/installed.php data set * @return void * * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } /** * @return array[] * @psalm-return list}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = \method_exists('Composer\\Autoload\\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (\is_file($vendorDir . '/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = (require $vendorDir . '/composer/installed.php'); $installed[] = self::$installedByVendor[$vendorDir] = $required; if (null === self::$installed && \strtr($vendorDir . '/composer', '\\', '/') === \strtr(__DIR__, '\\', '/')) { self::$installed = $installed[\count($installed) - 1]; } } } } if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (\substr(__DIR__, -8, 1) !== 'C') { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = (require __DIR__ . '/installed.php'); self::$installed = $required; } else { self::$installed = array(); } } if (self::$installed !== array()) { $installed[] = self::$installed; } return $installed; } } array('name' => 'contao/contao-manager', 'pretty_version' => '1.8.4', 'version' => '1.8.4.0', 'reference' => '360b6e5c1bad050929f1fb47c50d5c9fd6eed3bc', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \false), 'versions' => array('composer/ca-bundle' => array('pretty_version' => '1.4.1', 'version' => '1.4.1.0', 'reference' => '3ce240142f6d59b808dd65c1f52f7a1c252e6cfd', 'type' => 'library', 'install_path' => __DIR__ . '/./ca-bundle', 'aliases' => array(), 'dev_requirement' => \false), 'composer/class-map-generator' => array('pretty_version' => '1.1.0', 'version' => '1.1.0.0', 'reference' => '953cc4ea32e0c31f2185549c7d216d7921f03da9', 'type' => 'library', 'install_path' => __DIR__ . '/./class-map-generator', 'aliases' => array(), 'dev_requirement' => \false), 'composer/composer' => array('pretty_version' => '2.7.1', 'version' => '2.7.1.0', 'reference' => 'aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc', 'type' => 'library', 'install_path' => __DIR__ . '/./composer', 'aliases' => array(), 'dev_requirement' => \false), 'composer/metadata-minifier' => array('pretty_version' => '1.0.0', 'version' => '1.0.0.0', 'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207', 'type' => 'library', 'install_path' => __DIR__ . '/./metadata-minifier', 'aliases' => array(), 'dev_requirement' => \false), 'composer/pcre' => array('pretty_version' => '2.1.1', 'version' => '2.1.1.0', 'reference' => 'b439557066cd445732fa57cbc8d905394b4db8a0', 'type' => 'library', 'install_path' => __DIR__ . '/./pcre', 'aliases' => array(), 'dev_requirement' => \false), 'composer/semver' => array('pretty_version' => '3.4.0', 'version' => '3.4.0.0', 'reference' => '35e8d0af4486141bc745f23a29cc2091eb624a32', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), 'dev_requirement' => \false), 'composer/spdx-licenses' => array('pretty_version' => '1.5.8', 'version' => '1.5.8.0', 'reference' => '560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a', 'type' => 'library', 'install_path' => __DIR__ . '/./spdx-licenses', 'aliases' => array(), 'dev_requirement' => \false), 'composer/xdebug-handler' => array('pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => 'ced299686f41dce890debac69273b47ffe98a40c', 'type' => 'library', 'install_path' => __DIR__ . '/./xdebug-handler', 'aliases' => array(), 'dev_requirement' => \false), 'contao/contao-manager' => array('pretty_version' => '1.8.4', 'version' => '1.8.4.0', 'reference' => '360b6e5c1bad050929f1fb47c50d5c9fd6eed3bc', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'crell/api-problem' => array('pretty_version' => '3.6.1', 'version' => '3.6.1.0', 'reference' => '5acb0a8cc13ea740f631a60e5e73271c18e45803', 'type' => 'library', 'install_path' => __DIR__ . '/../crell/api-problem', 'aliases' => array(), 'dev_requirement' => \false), 'doctrine/annotations' => array('pretty_version' => '1.14.3', 'version' => '1.14.3.0', 'reference' => 'fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/annotations', 'aliases' => array(), 'dev_requirement' => \false), 'doctrine/deprecations' => array('pretty_version' => '1.1.3', 'version' => '1.1.3.0', 'reference' => 'dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/deprecations', 'aliases' => array(), 'dev_requirement' => \false), 'doctrine/lexer' => array('pretty_version' => '2.1.1', 'version' => '2.1.1.0', 'reference' => '861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/lexer', 'aliases' => array(), 'dev_requirement' => \false), 'firebase/php-jwt' => array('pretty_version' => 'v4.0.0', 'version' => '4.0.0.0', 'reference' => 'dccf163dc8ed7ed6a00afc06c51ee5186a428d35', 'type' => 'library', 'install_path' => __DIR__ . '/../firebase/php-jwt', 'aliases' => array(), 'dev_requirement' => \false), 'justinrainbow/json-schema' => array('pretty_version' => 'v5.2.13', 'version' => '5.2.13.0', 'reference' => 'fbbe7e5d79f618997bc3332a6f49246036c45793', 'type' => 'library', 'install_path' => __DIR__ . '/../justinrainbow/json-schema', 'aliases' => array(), 'dev_requirement' => \false), 'monolog/monolog' => array('pretty_version' => '2.9.2', 'version' => '2.9.2.0', 'reference' => '437cb3628f4cf6042cc10ae97fc2b8472e48ca1f', 'type' => 'library', 'install_path' => __DIR__ . '/../monolog/monolog', 'aliases' => array(), 'dev_requirement' => \false), 'paragonie/random_compat' => array('pretty_version' => 'v9.99.100', 'version' => '9.99.100.0', 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', 'type' => 'library', 'install_path' => __DIR__ . '/../paragonie/random_compat', 'aliases' => array(), 'dev_requirement' => \false), 'psr/cache' => array('pretty_version' => '1.0.1', 'version' => '1.0.1.0', 'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/cache', 'aliases' => array(), 'dev_requirement' => \false), 'psr/cache-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0|2.0')), 'psr/container' => array('pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/container', 'aliases' => array(), 'dev_requirement' => \false), 'psr/container-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0')), 'psr/event-dispatcher' => array('pretty_version' => '1.0.0', 'version' => '1.0.0.0', 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/event-dispatcher', 'aliases' => array(), 'dev_requirement' => \false), 'psr/event-dispatcher-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0')), 'psr/log' => array('pretty_version' => '1.1.4', 'version' => '1.1.4.0', 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => \false), 'psr/log-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0|2.0', 1 => '1.0.0 || 2.0.0 || 3.0.0')), 'psr/simple-cache-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0|2.0')), 'ramsey/uuid' => array('pretty_version' => '3.9.7', 'version' => '3.9.7.0', 'reference' => 'dc75aa439eb4c1b77f5379fd958b3dc0e6014178', 'type' => 'library', 'install_path' => __DIR__ . '/../ramsey/uuid', 'aliases' => array(), 'dev_requirement' => \false), 'react/promise' => array('pretty_version' => 'v3.1.0', 'version' => '3.1.0.0', 'reference' => 'e563d55d1641de1dea9f5e84f3cccc66d2bfe02c', 'type' => 'library', 'install_path' => __DIR__ . '/../react/promise', 'aliases' => array(), 'dev_requirement' => \false), 'rhumsaa/uuid' => array('dev_requirement' => \false, 'replaced' => array(0 => '3.9.7')), 'seld/jsonlint' => array('pretty_version' => '1.10.2', 'version' => '1.10.2.0', 'reference' => '9bb7db07b5d66d90f6ebf542f09fc67d800e5259', 'type' => 'library', 'install_path' => __DIR__ . '/../seld/jsonlint', 'aliases' => array(), 'dev_requirement' => \false), 'seld/phar-utils' => array('pretty_version' => '1.2.1', 'version' => '1.2.1.0', 'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c', 'type' => 'library', 'install_path' => __DIR__ . '/../seld/phar-utils', 'aliases' => array(), 'dev_requirement' => \false), 'seld/signal-handler' => array('pretty_version' => '2.0.2', 'version' => '2.0.2.0', 'reference' => '04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98', 'type' => 'library', 'install_path' => __DIR__ . '/../seld/signal-handler', 'aliases' => array(), 'dev_requirement' => \false), 'studio24/rotate' => array('pretty_version' => 'v1.0.1', 'version' => '1.0.1.0', 'reference' => '9d99d364bcf619bd9dd48f09ccf292f077c492e8', 'type' => 'library', 'install_path' => __DIR__ . '/../studio24/rotate', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/cache' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'db1adb004e2da984085d0178964eb6f319d3cba1', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/cache', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/cache-contracts' => array('pretty_version' => 'v2.5.2', 'version' => '2.5.2.0', 'reference' => '64be4a7acb83b6f2bf6de9a02cee6dad41277ebc', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/cache-contracts', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/cache-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0|2.0')), 'symfony/config' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '6b763438a22a4f20885e994ad6702f6a3f25430e', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/config', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/console' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'dbdf6adcb88d5f83790e1efb57ef4074309d3931', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/dependency-injection' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '45474d527212ca67cdb93f6c5e6da68f4bc67118', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dependency-injection', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/deprecation-contracts' => array('pretty_version' => 'v2.5.2', 'version' => '2.5.2.0', 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/error-handler' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '39225b1e47fdd91a6924b1e7d7a4523da2e1894b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/error-handler', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/event-dispatcher' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '7a69a85c7ea5bdd1e875806a99c51a87d3a74b38', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/event-dispatcher-contracts' => array('pretty_version' => 'v2.5.2', 'version' => '2.5.2.0', 'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/event-dispatcher-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '2.0')), 'symfony/filesystem' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '5a553607d4ffbfa9c0ab62facadea296c9db7086', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/finder' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'abe6d6f77d9465fed3cd2d029b29d03b56b56435', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/framework-bundle' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '89805687f360133f18bdedfb32138ce0ddd5383c', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/framework-bundle', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/http-foundation' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'f2ab692a22aef1cd54beb893aa0068bdfb093928', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-foundation', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/http-kernel' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '949bc7721c83fa9f81fc6c9697db0aa340c64f4d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-kernel', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/monolog-bridge' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '83e7438fd2ead9af4fd2fac7bb9b6fc0e8823387', 'type' => 'symfony-bridge', 'install_path' => __DIR__ . '/../symfony/monolog-bridge', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/monolog-bundle' => array('pretty_version' => 'v3.10.0', 'version' => '3.10.0.0', 'reference' => '414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/monolog-bundle', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/password-hasher' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '23b9782de5d06a7e61101558d3e887100fbf8f93', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/password-hasher', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-ctype' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => 'ef4d7e442ca910c4764bce785146269b30cb5fc4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-intl-grapheme' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => '32a9da87d7b3245e09ac426c83d334ae9f06f80f', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-intl-normalizer' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => 'bc45c394692b948b4d383a08d7753968bed9a83d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-mbstring' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-php73' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => '21bd091060673a1177ae842c0ef8fe30893114d2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php73', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-php80' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-php81' => array('pretty_version' => 'v1.29.0', 'version' => '1.29.0.0', 'reference' => 'c565ad1e63f30e7477fc40738343c62b40bc672d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php81', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/process' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'cbc28e34015ad50166fc2f9c8962d28d0fe861eb', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/process', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/property-access' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'f1341758d8046cfff0ac748a0cad238f917191d4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/property-access', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/property-info' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'd30d48f366ad2bfbf521256be85eb1c182c29198', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/property-info', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/routing' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '86c5a06a61ddaf17efa1403542e3d7146af96203', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/routing', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/security-bundle' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'ed194715515a87d0f9c80b8696baf37ae18beb81', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/security-bundle', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/security-core' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '3cbacefb2a350ed39950f93c8a054c2eb625fb69', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/security-core', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/security-csrf' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '6728ed79d7f9aae3b86fca7ea554f1c46bae1e0b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/security-csrf', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/security-guard' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'b6fb8c88f7cd544db761de2d1c3618cbc5c1b9e7', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/security-guard', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/security-http' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => '274a6aef49a0e1707bcb57217251885be749b6d8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/security-http', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/service-contracts' => array('pretty_version' => 'v2.5.2', 'version' => '2.5.2.0', 'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/service-contracts', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/service-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0|2.0')), 'symfony/string' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'c209c4d0559acce1c9a2067612cfb5d35756edc2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/string', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/var-dumper' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'ce4685b30e47d94dfc990c5566285ff99ddf012b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-dumper', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/var-exporter' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'abb0a151b62d6b07e816487e20040464af96cae7', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-exporter', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/yaml' => array('pretty_version' => 'v5.4.35', 'version' => '5.4.35.0', 'reference' => 'e78db7f5c70a21f0417a31f414c4a95fe76c07e4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), 'dev_requirement' => \false), 'terminal42/service-annotation-bundle' => array('pretty_version' => '1.1.5', 'version' => '1.1.5.0', 'reference' => 'f05105b65825d40234efb581f19088da0d843f6e', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../terminal42/service-annotation-bundle', 'aliases' => array(), 'dev_requirement' => \false))); Copyright (C) 2021 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer/pcre ============= PCRE wrapping library that offers type-safe `preg_*` replacements. This library gives you a way to ensure `preg_*` functions do not fail silently, returning unexpected `null`s that may not be handled. As of 2.0 this library also enforces [`PREG_UNMATCHED_AS_NULL`](#preg_unmatched_as_null) usage for all matching functions, [read more below](#preg_unmatched_as_null) to understand the implications. It thus makes it easier to work with static analysis tools like PHPStan or Psalm as it simplifies and reduces the possible return values from all the `preg_*` functions which are quite packed with edge cases. This library is a thin wrapper around `preg_*` functions with [some limitations](#restrictions--limitations). If you are looking for a richer API to handle regular expressions have a look at [rawr/t-regx](https://packagist.org/packages/rawr/t-regx) instead. [![Continuous Integration](https://github.com/composer/pcre/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/pcre/actions) Installation ------------ Install the latest version with: ```bash $ composer require composer/pcre ``` Requirements ------------ * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. Basic usage ----------- Instead of: ```php if (preg_match('{fo+}', $string, $matches)) { ... } if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... } if (preg_match_all('{fo+}', $string, $matches)) { ... } $newString = preg_replace('{fo+}', 'bar', $string); $newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); $newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); $filtered = preg_grep('{[a-z]}', $elements); $array = preg_split('{[a-z]+}', $string); ``` You can now call these on the `Preg` class: ```php use Composer\Pcre\Preg; if (Preg::match('{fo+}', $string, $matches)) { ... } if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... } if (Preg::matchAll('{fo+}', $string, $matches)) { ... } $newString = Preg::replace('{fo+}', 'bar', $string); $newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); $newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); $filtered = Preg::grep('{[a-z]}', $elements); $array = Preg::split('{[a-z]+}', $string); ``` The main difference is if anything fails to match/replace/.., it will throw a `Composer\Pcre\PcreException` instead of returning `null` (or false in some cases), so you can now use the return values safely relying on the fact that they can only be strings (for replace), ints (for match) or arrays (for grep/split). Additionally the `Preg` class provides match methods that return `bool` rather than `int`, for stricter type safety when the number of pattern matches is not useful: ```php use Composer\Pcre\Preg; if (Preg::isMatch('{fo+}', $string, $matches)) // bool if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool ``` Finally the `Preg` class provides a few `*StrictGroups` method variants that ensure match groups are always present and thus non-nullable, making it easier to write type-safe code: ```php use Composer\Pcre\Preg; // $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw if (Preg::matchStrictGroups('{fo+}', $string, $matches)) if (Preg::matchAllStrictGroups('{fo+}', $string, $matches)) ``` **Note:** This is generally safe to use as long as you do not have optional subpatterns (i.e. `(something)?` or `(something)*` or branches with a `|` that result in some groups not being matched at all). A subpattern that can match an empty string like `(.*)` is **not** optional, it will be present as an empty string in the matches. A non-matching subpattern, even if optional like `(?:foo)?` will anyway not be present in matches so it is also not a problem to use these with `*StrictGroups` methods. If you would prefer a slightly more verbose usage, replacing by-ref arguments by result objects, you can use the `Regex` class: ```php use Composer\Pcre\Regex; // this is useful when you are just interested in knowing if something matched // as it returns a bool instead of int(1/0) for match $bool = Regex::isMatch('{fo+}', $string); $result = Regex::match('{fo+}', $string); if ($result->matched) { something($result->matches); } $result = Regex::matchWithOffsets('{fo+}', $string); if ($result->matched) { something($result->matches); } $result = Regex::matchAll('{fo+}', $string); if ($result->matched && $result->count > 3) { something($result->matches); } $newString = Regex::replace('{fo+}', 'bar', $string)->result; $newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result; $newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result; ``` Note that `preg_grep` and `preg_split` are only callable via the `Preg` class as they do not have complex return types warranting a specific result object. See the [MatchResult](src/MatchResult.php), [MatchWithOffsetsResult](src/MatchWithOffsetsResult.php), [MatchAllResult](src/MatchAllResult.php), [MatchAllWithOffsetsResult](src/MatchAllWithOffsetsResult.php), and [ReplaceResult](src/ReplaceResult.php) class sources for more details. Restrictions / Limitations -------------------------- Due to type safety requirements a few restrictions are in place. - matching using `PREG_OFFSET_CAPTURE` is made available via `matchWithOffsets` and `matchAllWithOffsets`. You cannot pass the flag to `match`/`matchAll`. - `Preg::split` will also reject `PREG_SPLIT_OFFSET_CAPTURE` and you should use `splitWithOffsets` instead. - `matchAll` rejects `PREG_SET_ORDER` as it also changes the shape of the returned matches. There is no alternative provided as you can fairly easily code around it. - `preg_filter` is not supported as it has a rather crazy API, most likely you should rather use `Preg::grep` in combination with some loop and `Preg::replace`. - `replace`, `replaceCallback` and `replaceCallbackArray` do not support an array `$subject`, only simple strings. - As of 2.0, the library always uses `PREG_UNMATCHED_AS_NULL` for matching, which offers [much saner/more predictable results](#preg_unmatched_as_null). 3.x will also use the flag for `replaceCallback` and `replaceCallbackArray`. This is currently not doable due to the PHP version requirement and to keep things working the same across all PHP versions. If you use the library on a PHP 7.4+ project however we highly recommend already passing `PREG_UNMATCHED_AS_NULL` to `replaceCallback` and `replaceCallbackArray`. #### PREG_UNMATCHED_AS_NULL As of 2.0, this library always uses PREG_UNMATCHED_AS_NULL for all `match*` and `isMatch*` functions (unfortunately `preg_replace_callback[_array]` only support this from PHP 7.4 onwards so we cannot do the same there yet). This means your matches will always contain all matching groups, either as null if unmatched or as string if it matched. The advantages in clarity and predictability are clearer if you compare the two outputs of running this with and without PREG_UNMATCHED_AS_NULL in $flags: ```php preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags); ``` | no flag | PREG_UNMATCHED_AS_NULL | | --- | --- | | array (size=4) | array (size=5) | | 0 => string 'ac' (length=2) | 0 => string 'ac' (length=2) | | 1 => string 'a' (length=1) | 1 => string 'a' (length=1) | | 2 => string '' (length=0) | 2 => null | | 3 => string 'c' (length=1) | 3 => string 'c' (length=1) | | | 4 => null | | group 2 (any unmatched group preceding one that matched) is set to `''`. You cannot tell if it matched an empty string or did not match at all | group 2 is `null` when unmatched and a string if it matched, easy to check for | | group 4 (any optional group without a matching one following) is missing altogether. So you have to check with `isset()`, but really you want `isset($m[4]) && $m[4] !== ''` for safety unless you are very careful to check that a non-optional group follows it | group 4 is always set, and null in this case as there was no match, easy to check for with `$m[4] !== null` | License ------- composer/pcre is licensed under the MIT License, see the LICENSE file for details. { "name": "composer\/pcre", "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "type": "library", "license": "MIT", "keywords": [ "pcre", "regex", "preg", "regular expression" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http:\/\/seld.be" } ], "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "symfony\/phpunit-bridge": "^5", "phpstan\/phpstan": "^1.3", "phpstan\/phpstan-strict-rules": "^1.1" }, "autoload": { "psr-4": { "Composer\\Pcre\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\Pcre\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "2.x-dev" } }, "scripts": { "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit", "phpstan": "phpstan analyse" } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class MatchResult { /** * An array of match group => string matched * * @readonly * @var array */ public $matches; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; class Regex { /** * @param non-empty-string $pattern */ public static function isMatch(string $pattern, string $subject, int $offset = 0) : bool { return (bool) \Composer\Pcre\Preg::match($pattern, $subject, $matches, 0, $offset); } /** * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported */ public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0) : \Composer\Pcre\MatchResult { self::checkOffsetCapture($flags, 'matchWithOffsets'); $count = \Composer\Pcre\Preg::match($pattern, $subject, $matches, $flags, $offset); return new \Composer\Pcre\MatchResult($count, $matches); } /** * Variant of `match()` which returns non-null matches (or throws) * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @throws UnexpectedNullMatchException */ public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0) : \Composer\Pcre\MatchStrictGroupsResult { $count = \Composer\Pcre\Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); return new \Composer\Pcre\MatchStrictGroupsResult($count, $matches); } /** * Runs preg_match with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported */ public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0) : \Composer\Pcre\MatchWithOffsetsResult { $count = \Composer\Pcre\Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); return new \Composer\Pcre\MatchWithOffsetsResult($count, $matches); } /** * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported */ public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0) : \Composer\Pcre\MatchAllResult { self::checkOffsetCapture($flags, 'matchAllWithOffsets'); self::checkSetOrder($flags); $count = \Composer\Pcre\Preg::matchAll($pattern, $subject, $matches, $flags, $offset); return new \Composer\Pcre\MatchAllResult($count, $matches); } /** * Variant of `matchAll()` which returns non-null matches (or throws) * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @throws UnexpectedNullMatchException */ public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0) : \Composer\Pcre\MatchAllStrictGroupsResult { self::checkOffsetCapture($flags, 'matchAllWithOffsets'); self::checkSetOrder($flags); $count = \Composer\Pcre\Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); return new \Composer\Pcre\MatchAllStrictGroupsResult($count, $matches); } /** * Runs preg_match_all with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported */ public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0) : \Composer\Pcre\MatchAllWithOffsetsResult { self::checkSetOrder($flags); $count = \Composer\Pcre\Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); return new \Composer\Pcre\MatchAllWithOffsetsResult($count, $matches); } /** * @param string|string[] $pattern * @param string|string[] $replacement * @param string $subject */ public static function replace($pattern, $replacement, $subject, int $limit = -1) : \Composer\Pcre\ReplaceResult { $result = \Composer\Pcre\Preg::replace($pattern, $replacement, $subject, $limit, $count); return new \Composer\Pcre\ReplaceResult($count, $result); } /** * @param string|string[] $pattern * @param callable(array): string $replacement * @param string $subject * @param int-mask $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+ */ public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0) : \Composer\Pcre\ReplaceResult { $result = \Composer\Pcre\Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags); return new \Composer\Pcre\ReplaceResult($count, $result); } /** * @param array): string> $pattern * @param string $subject * @param int-mask $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+ */ public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0) : \Composer\Pcre\ReplaceResult { $result = \Composer\Pcre\Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags); return new \Composer\Pcre\ReplaceResult($count, $result); } private static function checkOffsetCapture(int $flags, string $useFunctionName) : void { if (($flags & \PREG_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use ' . $useFunctionName . '() instead'); } } private static function checkSetOrder(int $flags) : void { if (($flags & \PREG_SET_ORDER) !== 0) { throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type'); } } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class MatchAllWithOffsetsResult { /** * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match) * * @readonly * @var array> * @phpstan-var array}>> */ public $matches; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array> $matches * @phpstan-param array}>> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class MatchWithOffsetsResult { /** * An array of match group => pair of string matched + offset in bytes (or -1 if no match) * * @readonly * @var array * @phpstan-var array}> */ public $matches; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array $matches * @phpstan-param array}> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class MatchAllStrictGroupsResult { /** * An array of match group => list of matched strings * * @readonly * @var array> */ public $matches; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; class UnexpectedNullMatchException extends \Composer\Pcre\PcreException { public static function fromFunction($function, $pattern) { throw new \LogicException('fromFunction should not be called on ' . self::class . ', use ' . \Composer\Pcre\PcreException::class); } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class MatchStrictGroupsResult { /** * An array of match group => string matched * * @readonly * @var array */ public $matches; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class MatchAllResult { /** * An array of match group => list of matched strings * * @readonly * @var array> */ public $matches; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count * @param array> $matches */ public function __construct(int $count, array $matches) { $this->matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; final class ReplaceResult { /** * @readonly * @var string */ public $result; /** * @readonly * @var 0|positive-int */ public $count; /** * @readonly * @var bool */ public $matched; /** * @param 0|positive-int $count */ public function __construct(int $count, string $result) { $this->count = $count; $this->matched = (bool) $count; $this->result = $result; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; class Preg { /** @internal */ public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.'; /** @internal */ public const INVALID_TYPE_MSG = '$subject must be a string, %s given.'; /** * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|1 * * @param-out array $matches */ public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : int { self::checkOffsetCapture($flags, 'matchWithOffsets'); $result = self::pregMatch($pattern, $subject, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset); if ($result === \false) { throw \Composer\Pcre\PcreException::fromFunction('preg_match', $pattern); } return $result; } /** * Variant of `match()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|1 * @throws UnexpectedNullMatchException * * @param-out array $matches */ public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : int { $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset); $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match'); return $result; } /** * Runs preg_match with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported * @return 0|1 * * @param-out array}> $matches */ public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0) : int { $result = self::pregMatch($pattern, $subject, $matches, $flags | \PREG_UNMATCHED_AS_NULL | \PREG_OFFSET_CAPTURE, $offset); if ($result === \false) { throw \Composer\Pcre\PcreException::fromFunction('preg_match', $pattern); } return $result; } /** * @param non-empty-string $pattern * @param array> $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|positive-int * * @param-out array> $matches */ public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : int { self::checkOffsetCapture($flags, 'matchAllWithOffsets'); self::checkSetOrder($flags); $result = \preg_match_all($pattern, $subject, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset); if (!\is_int($result)) { // PHP < 8 may return null, 8+ returns int|false throw \Composer\Pcre\PcreException::fromFunction('preg_match_all', $pattern); } return $result; } /** * Variant of `match()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array> $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @return 0|positive-int * @throws UnexpectedNullMatchException * * @param-out array> $matches */ public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : int { $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset); $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll'); return $result; } /** * Runs preg_match_all with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array> $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported * @return 0|positive-int * * @param-out array}>> $matches */ public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0) : int { self::checkSetOrder($flags); $result = \preg_match_all($pattern, $subject, $matches, $flags | \PREG_UNMATCHED_AS_NULL | \PREG_OFFSET_CAPTURE, $offset); if (!\is_int($result)) { // PHP < 8 may return null, 8+ returns int|false throw \Composer\Pcre\PcreException::fromFunction('preg_match_all', $pattern); } return $result; } /** * @param string|string[] $pattern * @param string|string[] $replacement * @param string $subject * @param int $count Set by method * * @param-out int<0, max> $count */ public static function replace($pattern, $replacement, $subject, int $limit = -1, int &$count = null) : string { if (!\is_scalar($subject)) { if (\is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(\sprintf(static::INVALID_TYPE_MSG, \gettype($subject))); } $result = \preg_replace($pattern, $replacement, $subject, $limit, $count); if ($result === null) { throw \Composer\Pcre\PcreException::fromFunction('preg_replace', $pattern); } return $result; } /** * @param string|string[] $pattern * @param callable(array): string $replacement * @param string $subject * @param int $count Set by method * @param int-mask $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+ * * @param-out int<0, max> $count */ public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int &$count = null, int $flags = 0) : string { if (!\is_scalar($subject)) { if (\is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(\sprintf(static::INVALID_TYPE_MSG, \gettype($subject))); } if (\PHP_VERSION_ID >= 70400) { $result = \preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags); } else { $result = \preg_replace_callback($pattern, $replacement, $subject, $limit, $count); } if ($result === null) { throw \Composer\Pcre\PcreException::fromFunction('preg_replace_callback', $pattern); } return $result; } /** * @param array): string> $pattern * @param string $subject * @param int $count Set by method * @param int-mask $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+ * * @param-out int<0, max> $count */ public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int &$count = null, int $flags = 0) : string { if (!\is_scalar($subject)) { if (\is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(\sprintf(static::INVALID_TYPE_MSG, \gettype($subject))); } if (\PHP_VERSION_ID >= 70400) { $result = \preg_replace_callback_array($pattern, $subject, $limit, $count, $flags); } else { $result = \preg_replace_callback_array($pattern, $subject, $limit, $count); } if ($result === null) { $pattern = \array_keys($pattern); throw \Composer\Pcre\PcreException::fromFunction('preg_replace_callback_array', $pattern); } return $result; } /** * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE * @return list */ public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0) : array { if (($flags & \PREG_SPLIT_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead'); } $result = \preg_split($pattern, $subject, $limit, $flags); if ($result === \false) { throw \Composer\Pcre\PcreException::fromFunction('preg_split', $pattern); } return $result; } /** * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set * @return list * @phpstan-return list}> */ public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0) : array { $result = \preg_split($pattern, $subject, $limit, $flags | \PREG_SPLIT_OFFSET_CAPTURE); if ($result === \false) { throw \Composer\Pcre\PcreException::fromFunction('preg_split', $pattern); } return $result; } /** * @template T of string|\Stringable * @param string $pattern * @param array $array * @param int-mask $flags PREG_GREP_INVERT * @return array */ public static function grep(string $pattern, array $array, int $flags = 0) : array { $result = \preg_grep($pattern, $array, $flags); if ($result === \false) { throw \Composer\Pcre\PcreException::fromFunction('preg_grep', $pattern); } return $result; } /** * Variant of match() which returns a bool instead of int * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array $matches */ public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : bool { return (bool) static::match($pattern, $subject, $matches, $flags, $offset); } /** * Variant of `isMatch()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * @throws UnexpectedNullMatchException * * @param-out array $matches */ public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : bool { return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); } /** * Variant of matchAll() which returns a bool instead of int * * @param non-empty-string $pattern * @param array> $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array> $matches */ public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : bool { return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset); } /** * Variant of `isMatchAll()` which outputs non-null matches (or throws) * * @param non-empty-string $pattern * @param array> $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array> $matches */ public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : bool { return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); } /** * Variant of matchWithOffsets() which returns a bool instead of int * * Runs preg_match with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array}> $matches */ public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0) : bool { return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); } /** * Variant of matchAllWithOffsets() which returns a bool instead of int * * Runs preg_match_all with PREG_OFFSET_CAPTURE * * @param non-empty-string $pattern * @param array> $matches Set by method * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported * * @param-out array}>> $matches */ public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0) : bool { return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); } private static function checkOffsetCapture(int $flags, string $useFunctionName) : void { if (($flags & \PREG_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead'); } } private static function checkSetOrder(int $flags) : void { if (($flags & \PREG_SET_ORDER) !== 0) { throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches'); } } /** * @param array $matches * @return array * @throws UnexpectedNullMatchException */ private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod) { foreach ($matches as $group => $match) { if (null === $match) { throw new \Composer\Pcre\UnexpectedNullMatchException('Pattern "' . $pattern . '" had an unexpected unmatched group "' . $group . '", make sure the pattern always matches or use ' . $variantMethod . '() instead.'); } } /** @var array */ return $matches; } /** * @param array> $matches * @return array> * @throws UnexpectedNullMatchException */ private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod) { foreach ($matches as $group => $groupMatches) { foreach ($groupMatches as $match) { if (null === $match) { throw new \Composer\Pcre\UnexpectedNullMatchException('Pattern "' . $pattern . '" had an unexpected unmatched group "' . $group . '", make sure the pattern always matches or use ' . $variantMethod . '() instead.'); } } } /** @var array> */ return $matches; } /** * @param non-empty-string $pattern * @param array $matches Set by method * @param int-mask $flags * @return 0|1|false * * @param-out array $matches */ private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) { if (\PHP_VERSION_ID >= 70400) { return \preg_match($pattern, $subject, $matches, $flags, $offset); } // On PHP 7.2 and 7.3, PREG_UNMATCHED_AS_NULL only works correctly in preg_match_all // as preg_match does not set trailing unmatched groups to null // e.g. preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, PREG_UNMATCHED_AS_NULL); would // have $match[4] unset instead of null // // So we use preg_match_all here as workaround to ensure old-PHP meets the expectations // set by the library's documentation $result = \preg_match_all($pattern, $subject, $matchesInternal, $flags, $offset); if (!\is_int($result)) { // PHP < 8 may return null, 8+ returns int|false throw \Composer\Pcre\PcreException::fromFunction('preg_match', $pattern); } if ($result === 0) { $matches = []; } else { $matches = \array_map(function ($m) { return \reset($m); }, $matchesInternal); $result = \min($result, 1); } return $result; } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Pcre; class PcreException extends \RuntimeException { /** * @param string $function * @param string|string[] $pattern * @return self */ public static function fromFunction($function, $pattern) { $code = \preg_last_error(); if (\is_array($pattern)) { $pattern = \implode(', ', $pattern); } return new \Composer\Pcre\PcreException($function . '(): failed executing "' . $pattern . '": ' . self::pcreLastErrorMessage($code), $code); } /** * @param int $code * @return string */ private static function pcreLastErrorMessage($code) { if (\function_exists('preg_last_error_msg')) { return \preg_last_error_msg(); } // older php versions did not set the code properly in all cases if (\PHP_VERSION_ID < 70201 && $code === 0) { return 'UNDEFINED_ERROR'; } $constants = \get_defined_constants(\true); if (!isset($constants['pcre'])) { return 'UNDEFINED_ERROR'; } foreach ($constants['pcre'] as $const => $val) { if ($val === $code && \substr($const, -6) === '_ERROR') { return $const; } } return 'UNDEFINED_ERROR'; } } Copyright (C) 2021 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer/metadata-minifier ========================== Small utility library that handles metadata minification and expansion. This is used by [Composer](https://github.com/composer/composer)'s 2.x repository metadata protocol. Installation ------------ Install the latest version with: ```bash $ composer require composer/metadata-minifier ``` Requirements ------------ * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. Basic usage ----------- ### `Composer\MetadataMinifier\MetadataMinifier` - `MetadataMinifier::expand()`: Expands an array of minified versions back to their original format - `MetadataMinifier::minify()`: Minifies an array of versions into a set of version diffs License ------- composer/metadata-minifier is licensed under the MIT License, see the LICENSE file for details. parameters: level: 8 paths: - src - tests { "name": "composer\/metadata-minifier", "description": "Small utility library that handles metadata minification and expansion.", "type": "library", "license": "MIT", "keywords": [ "compression", "composer" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http:\/\/seld.be" } ], "support": { "issues": "https:\/\/github.com\/composer\/metadata-minifier\/issues" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "symfony\/phpunit-bridge": "^4.2 || ^5", "phpstan\/phpstan": "^0.12.55", "composer\/composer": "^2" }, "autoload": { "psr-4": { "Composer\\MetadataMinifier\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\Test\\MetadataMinifier\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "scripts": { "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit", "phpstan": "vendor\/bin\/phpstan analyse" } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\MetadataMinifier; class MetadataMinifier { /** * Expands an array of minified versions back to their original format * * @param array[] $versions A list of minified version arrays * @return array[] A list of version arrays */ public static function expand(array $versions) { $expanded = array(); $expandedVersion = null; foreach ($versions as $versionData) { if (!$expandedVersion) { $expandedVersion = $versionData; $expanded[] = $expandedVersion; continue; } // add any changes from the previous version to the expanded one foreach ($versionData as $key => $val) { if ($val === '__unset') { unset($expandedVersion[$key]); } else { $expandedVersion[$key] = $val; } } $expanded[] = $expandedVersion; } return $expanded; } /** * Minifies an array of versions into a set of version diffs * * @param array[] $versions A list of version arrays * @return array[] A list of versions minified with each array only containing the differences to the previous one */ public static function minify(array $versions) { $minifiedVersions = array(); $lastKnownVersionData = null; foreach ($versions as $version) { if (!$lastKnownVersionData) { $lastKnownVersionData = $version; $minifiedVersions[] = $version; continue; } $minifiedVersion = array(); // add any changes from the previous version foreach ($version as $key => $val) { if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) { $minifiedVersion[$key] = $val; $lastKnownVersionData[$key] = $val; } } // store any deletions from the previous version for keys missing in current one foreach ($lastKnownVersionData as $key => $val) { if (!isset($version[$key])) { $minifiedVersion[$key] = "__unset"; unset($lastKnownVersionData[$key]); } } $minifiedVersions[] = $minifiedVersion; } return $minifiedVersions; } } { "389-exception": [ "389 Directory Server Exception" ], "Asterisk-exception": [ "Asterisk exception" ], "Autoconf-exception-2.0": [ "Autoconf exception 2.0" ], "Autoconf-exception-3.0": [ "Autoconf exception 3.0" ], "Autoconf-exception-generic": [ "Autoconf generic exception" ], "Autoconf-exception-generic-3.0": [ "Autoconf generic exception for GPL-3.0" ], "Autoconf-exception-macro": [ "Autoconf macro exception" ], "Bison-exception-2.2": [ "Bison exception 2.2" ], "Bootloader-exception": [ "Bootloader Distribution Exception" ], "Classpath-exception-2.0": [ "Classpath exception 2.0" ], "CLISP-exception-2.0": [ "CLISP exception 2.0" ], "cryptsetup-OpenSSL-exception": [ "cryptsetup OpenSSL exception" ], "DigiRule-FOSS-exception": [ "DigiRule FOSS License Exception" ], "eCos-exception-2.0": [ "eCos exception 2.0" ], "Fawkes-Runtime-exception": [ "Fawkes Runtime Exception" ], "FLTK-exception": [ "FLTK exception" ], "Font-exception-2.0": [ "Font exception 2.0" ], "freertos-exception-2.0": [ "FreeRTOS Exception 2.0" ], "GCC-exception-2.0": [ "GCC Runtime Library exception 2.0" ], "GCC-exception-2.0-note": [ "GCC Runtime Library exception 2.0 - note variant" ], "GCC-exception-3.1": [ "GCC Runtime Library exception 3.1" ], "GNAT-exception": [ "GNAT exception" ], "GNU-compiler-exception": [ "GNU Compiler Exception" ], "gnu-javamail-exception": [ "GNU JavaMail exception" ], "GPL-3.0-interface-exception": [ "GPL-3.0 Interface Exception" ], "GPL-3.0-linking-exception": [ "GPL-3.0 Linking Exception" ], "GPL-3.0-linking-source-exception": [ "GPL-3.0 Linking Exception (with Corresponding Source)" ], "GPL-CC-1.0": [ "GPL Cooperation Commitment 1.0" ], "GStreamer-exception-2005": [ "GStreamer Exception (2005)" ], "GStreamer-exception-2008": [ "GStreamer Exception (2008)" ], "i2p-gpl-java-exception": [ "i2p GPL+Java Exception" ], "KiCad-libraries-exception": [ "KiCad Libraries Exception" ], "LGPL-3.0-linking-exception": [ "LGPL-3.0 Linking Exception" ], "libpri-OpenH323-exception": [ "libpri OpenH323 exception" ], "Libtool-exception": [ "Libtool Exception" ], "Linux-syscall-note": [ "Linux Syscall Note" ], "LLGPL": [ "LLGPL Preamble" ], "LLVM-exception": [ "LLVM Exception" ], "LZMA-exception": [ "LZMA exception" ], "mif-exception": [ "Macros and Inline Functions Exception" ], "Nokia-Qt-exception-1.1": [ "Nokia Qt LGPL exception 1.1" ], "OCaml-LGPL-linking-exception": [ "OCaml LGPL Linking Exception" ], "OCCT-exception-1.0": [ "Open CASCADE Exception 1.0" ], "OpenJDK-assembly-exception-1.0": [ "OpenJDK Assembly exception 1.0" ], "openvpn-openssl-exception": [ "OpenVPN OpenSSL Exception" ], "PS-or-PDF-font-exception-20170817": [ "PS/PDF font exception (2017-08-17)" ], "QPL-1.0-INRIA-2004-exception": [ "INRIA QPL 1.0 2004 variant exception" ], "Qt-GPL-exception-1.0": [ "Qt GPL exception 1.0" ], "Qt-LGPL-exception-1.1": [ "Qt LGPL exception 1.1" ], "Qwt-exception-1.0": [ "Qwt exception 1.0" ], "SANE-exception": [ "SANE Exception" ], "SHL-2.0": [ "Solderpad Hardware License v2.0" ], "SHL-2.1": [ "Solderpad Hardware License v2.1" ], "stunnel-exception": [ "stunnel Exception" ], "SWI-exception": [ "SWI exception" ], "Swift-exception": [ "Swift Exception" ], "Texinfo-exception": [ "Texinfo exception" ], "u-boot-exception-2.0": [ "U-Boot exception 2.0" ], "UBDL-exception": [ "Unmodified Binary Distribution exception" ], "Universal-FOSS-exception-1.0": [ "Universal FOSS Exception, Version 1.0" ], "vsftpd-openssl-exception": [ "vsftpd OpenSSL exception" ], "WxWindows-exception-3.1": [ "WxWindows Library Exception 3.1" ], "x11vnc-openssl-exception": [ "x11vnc OpenSSL Exception" ] }{ "0BSD": [ "BSD Zero Clause License", true, false ], "AAL": [ "Attribution Assurance License", true, false ], "Abstyles": [ "Abstyles License", false, false ], "AdaCore-doc": [ "AdaCore Doc License", false, false ], "Adobe-2006": [ "Adobe Systems Incorporated Source Code License Agreement", false, false ], "Adobe-Display-PostScript": [ "Adobe Display PostScript License", false, false ], "Adobe-Glyph": [ "Adobe Glyph List License", false, false ], "Adobe-Utopia": [ "Adobe Utopia Font License", false, false ], "ADSL": [ "Amazon Digital Services License", false, false ], "AFL-1.1": [ "Academic Free License v1.1", true, false ], "AFL-1.2": [ "Academic Free License v1.2", true, false ], "AFL-2.0": [ "Academic Free License v2.0", true, false ], "AFL-2.1": [ "Academic Free License v2.1", true, false ], "AFL-3.0": [ "Academic Free License v3.0", true, false ], "Afmparse": [ "Afmparse License", false, false ], "AGPL-1.0": [ "Affero General Public License v1.0", false, true ], "AGPL-1.0-only": [ "Affero General Public License v1.0 only", false, false ], "AGPL-1.0-or-later": [ "Affero General Public License v1.0 or later", false, false ], "AGPL-3.0": [ "GNU Affero General Public License v3.0", true, true ], "AGPL-3.0-only": [ "GNU Affero General Public License v3.0 only", true, false ], "AGPL-3.0-or-later": [ "GNU Affero General Public License v3.0 or later", true, false ], "Aladdin": [ "Aladdin Free Public License", false, false ], "AMDPLPA": [ "AMD's plpa_map.c License", false, false ], "AML": [ "Apple MIT License", false, false ], "AML-glslang": [ "AML glslang variant License", false, false ], "AMPAS": [ "Academy of Motion Picture Arts and Sciences BSD", false, false ], "ANTLR-PD": [ "ANTLR Software Rights Notice", false, false ], "ANTLR-PD-fallback": [ "ANTLR Software Rights Notice with license fallback", false, false ], "Apache-1.0": [ "Apache License 1.0", false, false ], "Apache-1.1": [ "Apache License 1.1", true, false ], "Apache-2.0": [ "Apache License 2.0", true, false ], "APAFML": [ "Adobe Postscript AFM License", false, false ], "APL-1.0": [ "Adaptive Public License 1.0", true, false ], "App-s2p": [ "App::s2p License", false, false ], "APSL-1.0": [ "Apple Public Source License 1.0", true, false ], "APSL-1.1": [ "Apple Public Source License 1.1", true, false ], "APSL-1.2": [ "Apple Public Source License 1.2", true, false ], "APSL-2.0": [ "Apple Public Source License 2.0", true, false ], "Arphic-1999": [ "Arphic Public License", false, false ], "Artistic-1.0": [ "Artistic License 1.0", true, false ], "Artistic-1.0-cl8": [ "Artistic License 1.0 w/clause 8", true, false ], "Artistic-1.0-Perl": [ "Artistic License 1.0 (Perl)", true, false ], "Artistic-2.0": [ "Artistic License 2.0", true, false ], "ASWF-Digital-Assets-1.0": [ "ASWF Digital Assets License version 1.0", false, false ], "ASWF-Digital-Assets-1.1": [ "ASWF Digital Assets License 1.1", false, false ], "Baekmuk": [ "Baekmuk License", false, false ], "Bahyph": [ "Bahyph License", false, false ], "Barr": [ "Barr License", false, false ], "Beerware": [ "Beerware License", false, false ], "Bitstream-Charter": [ "Bitstream Charter Font License", false, false ], "Bitstream-Vera": [ "Bitstream Vera Font License", false, false ], "BitTorrent-1.0": [ "BitTorrent Open Source License v1.0", false, false ], "BitTorrent-1.1": [ "BitTorrent Open Source License v1.1", false, false ], "blessing": [ "SQLite Blessing", false, false ], "BlueOak-1.0.0": [ "Blue Oak Model License 1.0.0", false, false ], "Boehm-GC": [ "Boehm-Demers-Weiser GC License", false, false ], "Borceux": [ "Borceux license", false, false ], "Brian-Gladman-3-Clause": [ "Brian Gladman 3-Clause License", false, false ], "BSD-1-Clause": [ "BSD 1-Clause License", true, false ], "BSD-2-Clause": [ "BSD 2-Clause \"Simplified\" License", true, false ], "BSD-2-Clause-FreeBSD": [ "BSD 2-Clause FreeBSD License", false, true ], "BSD-2-Clause-NetBSD": [ "BSD 2-Clause NetBSD License", false, true ], "BSD-2-Clause-Patent": [ "BSD-2-Clause Plus Patent License", true, false ], "BSD-2-Clause-Views": [ "BSD 2-Clause with views sentence", false, false ], "BSD-3-Clause": [ "BSD 3-Clause \"New\" or \"Revised\" License", true, false ], "BSD-3-Clause-Attribution": [ "BSD with attribution", false, false ], "BSD-3-Clause-Clear": [ "BSD 3-Clause Clear License", false, false ], "BSD-3-Clause-flex": [ "BSD 3-Clause Flex variant", false, false ], "BSD-3-Clause-HP": [ "Hewlett-Packard BSD variant license", false, false ], "BSD-3-Clause-LBNL": [ "Lawrence Berkeley National Labs BSD variant license", true, false ], "BSD-3-Clause-Modification": [ "BSD 3-Clause Modification", false, false ], "BSD-3-Clause-No-Military-License": [ "BSD 3-Clause No Military License", false, false ], "BSD-3-Clause-No-Nuclear-License": [ "BSD 3-Clause No Nuclear License", false, false ], "BSD-3-Clause-No-Nuclear-License-2014": [ "BSD 3-Clause No Nuclear License 2014", false, false ], "BSD-3-Clause-No-Nuclear-Warranty": [ "BSD 3-Clause No Nuclear Warranty", false, false ], "BSD-3-Clause-Open-MPI": [ "BSD 3-Clause Open MPI variant", false, false ], "BSD-3-Clause-Sun": [ "BSD 3-Clause Sun Microsystems", false, false ], "BSD-4-Clause": [ "BSD 4-Clause \"Original\" or \"Old\" License", false, false ], "BSD-4-Clause-Shortened": [ "BSD 4 Clause Shortened", false, false ], "BSD-4-Clause-UC": [ "BSD-4-Clause (University of California-Specific)", false, false ], "BSD-4.3RENO": [ "BSD 4.3 RENO License", false, false ], "BSD-4.3TAHOE": [ "BSD 4.3 TAHOE License", false, false ], "BSD-Advertising-Acknowledgement": [ "BSD Advertising Acknowledgement License", false, false ], "BSD-Attribution-HPND-disclaimer": [ "BSD with Attribution and HPND disclaimer", false, false ], "BSD-Inferno-Nettverk": [ "BSD-Inferno-Nettverk", false, false ], "BSD-Protection": [ "BSD Protection License", false, false ], "BSD-Source-Code": [ "BSD Source Code Attribution", false, false ], "BSD-Systemics": [ "Systemics BSD variant license", false, false ], "BSL-1.0": [ "Boost Software License 1.0", true, false ], "BUSL-1.1": [ "Business Source License 1.1", false, false ], "bzip2-1.0.5": [ "bzip2 and libbzip2 License v1.0.5", false, true ], "bzip2-1.0.6": [ "bzip2 and libbzip2 License v1.0.6", false, false ], "C-UDA-1.0": [ "Computational Use of Data Agreement v1.0", false, false ], "CAL-1.0": [ "Cryptographic Autonomy License 1.0", true, false ], "CAL-1.0-Combined-Work-Exception": [ "Cryptographic Autonomy License 1.0 (Combined Work Exception)", true, false ], "Caldera": [ "Caldera License", false, false ], "CATOSL-1.1": [ "Computer Associates Trusted Open Source License 1.1", true, false ], "CC-BY-1.0": [ "Creative Commons Attribution 1.0 Generic", false, false ], "CC-BY-2.0": [ "Creative Commons Attribution 2.0 Generic", false, false ], "CC-BY-2.5": [ "Creative Commons Attribution 2.5 Generic", false, false ], "CC-BY-2.5-AU": [ "Creative Commons Attribution 2.5 Australia", false, false ], "CC-BY-3.0": [ "Creative Commons Attribution 3.0 Unported", false, false ], "CC-BY-3.0-AT": [ "Creative Commons Attribution 3.0 Austria", false, false ], "CC-BY-3.0-DE": [ "Creative Commons Attribution 3.0 Germany", false, false ], "CC-BY-3.0-IGO": [ "Creative Commons Attribution 3.0 IGO", false, false ], "CC-BY-3.0-NL": [ "Creative Commons Attribution 3.0 Netherlands", false, false ], "CC-BY-3.0-US": [ "Creative Commons Attribution 3.0 United States", false, false ], "CC-BY-4.0": [ "Creative Commons Attribution 4.0 International", false, false ], "CC-BY-NC-1.0": [ "Creative Commons Attribution Non Commercial 1.0 Generic", false, false ], "CC-BY-NC-2.0": [ "Creative Commons Attribution Non Commercial 2.0 Generic", false, false ], "CC-BY-NC-2.5": [ "Creative Commons Attribution Non Commercial 2.5 Generic", false, false ], "CC-BY-NC-3.0": [ "Creative Commons Attribution Non Commercial 3.0 Unported", false, false ], "CC-BY-NC-3.0-DE": [ "Creative Commons Attribution Non Commercial 3.0 Germany", false, false ], "CC-BY-NC-4.0": [ "Creative Commons Attribution Non Commercial 4.0 International", false, false ], "CC-BY-NC-ND-1.0": [ "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", false, false ], "CC-BY-NC-ND-2.0": [ "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", false, false ], "CC-BY-NC-ND-2.5": [ "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", false, false ], "CC-BY-NC-ND-3.0": [ "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", false, false ], "CC-BY-NC-ND-3.0-DE": [ "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany", false, false ], "CC-BY-NC-ND-3.0-IGO": [ "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", false, false ], "CC-BY-NC-ND-4.0": [ "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", false, false ], "CC-BY-NC-SA-1.0": [ "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", false, false ], "CC-BY-NC-SA-2.0": [ "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", false, false ], "CC-BY-NC-SA-2.0-DE": [ "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany", false, false ], "CC-BY-NC-SA-2.0-FR": [ "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France", false, false ], "CC-BY-NC-SA-2.0-UK": [ "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales", false, false ], "CC-BY-NC-SA-2.5": [ "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", false, false ], "CC-BY-NC-SA-3.0": [ "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", false, false ], "CC-BY-NC-SA-3.0-DE": [ "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany", false, false ], "CC-BY-NC-SA-3.0-IGO": [ "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO", false, false ], "CC-BY-NC-SA-4.0": [ "Creative Commons Attribution Non Commercial Share Alike 4.0 International", false, false ], "CC-BY-ND-1.0": [ "Creative Commons Attribution No Derivatives 1.0 Generic", false, false ], "CC-BY-ND-2.0": [ "Creative Commons Attribution No Derivatives 2.0 Generic", false, false ], "CC-BY-ND-2.5": [ "Creative Commons Attribution No Derivatives 2.5 Generic", false, false ], "CC-BY-ND-3.0": [ "Creative Commons Attribution No Derivatives 3.0 Unported", false, false ], "CC-BY-ND-3.0-DE": [ "Creative Commons Attribution No Derivatives 3.0 Germany", false, false ], "CC-BY-ND-4.0": [ "Creative Commons Attribution No Derivatives 4.0 International", false, false ], "CC-BY-SA-1.0": [ "Creative Commons Attribution Share Alike 1.0 Generic", false, false ], "CC-BY-SA-2.0": [ "Creative Commons Attribution Share Alike 2.0 Generic", false, false ], "CC-BY-SA-2.0-UK": [ "Creative Commons Attribution Share Alike 2.0 England and Wales", false, false ], "CC-BY-SA-2.1-JP": [ "Creative Commons Attribution Share Alike 2.1 Japan", false, false ], "CC-BY-SA-2.5": [ "Creative Commons Attribution Share Alike 2.5 Generic", false, false ], "CC-BY-SA-3.0": [ "Creative Commons Attribution Share Alike 3.0 Unported", false, false ], "CC-BY-SA-3.0-AT": [ "Creative Commons Attribution Share Alike 3.0 Austria", false, false ], "CC-BY-SA-3.0-DE": [ "Creative Commons Attribution Share Alike 3.0 Germany", false, false ], "CC-BY-SA-3.0-IGO": [ "Creative Commons Attribution-ShareAlike 3.0 IGO", false, false ], "CC-BY-SA-4.0": [ "Creative Commons Attribution Share Alike 4.0 International", false, false ], "CC-PDDC": [ "Creative Commons Public Domain Dedication and Certification", false, false ], "CC0-1.0": [ "Creative Commons Zero v1.0 Universal", false, false ], "CDDL-1.0": [ "Common Development and Distribution License 1.0", true, false ], "CDDL-1.1": [ "Common Development and Distribution License 1.1", false, false ], "CDL-1.0": [ "Common Documentation License 1.0", false, false ], "CDLA-Permissive-1.0": [ "Community Data License Agreement Permissive 1.0", false, false ], "CDLA-Permissive-2.0": [ "Community Data License Agreement Permissive 2.0", false, false ], "CDLA-Sharing-1.0": [ "Community Data License Agreement Sharing 1.0", false, false ], "CECILL-1.0": [ "CeCILL Free Software License Agreement v1.0", false, false ], "CECILL-1.1": [ "CeCILL Free Software License Agreement v1.1", false, false ], "CECILL-2.0": [ "CeCILL Free Software License Agreement v2.0", false, false ], "CECILL-2.1": [ "CeCILL Free Software License Agreement v2.1", true, false ], "CECILL-B": [ "CeCILL-B Free Software License Agreement", false, false ], "CECILL-C": [ "CeCILL-C Free Software License Agreement", false, false ], "CERN-OHL-1.1": [ "CERN Open Hardware Licence v1.1", false, false ], "CERN-OHL-1.2": [ "CERN Open Hardware Licence v1.2", false, false ], "CERN-OHL-P-2.0": [ "CERN Open Hardware Licence Version 2 - Permissive", true, false ], "CERN-OHL-S-2.0": [ "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", true, false ], "CERN-OHL-W-2.0": [ "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", true, false ], "CFITSIO": [ "CFITSIO License", false, false ], "check-cvs": [ "check-cvs License", false, false ], "checkmk": [ "Checkmk License", false, false ], "ClArtistic": [ "Clarified Artistic License", false, false ], "Clips": [ "Clips License", false, false ], "CMU-Mach": [ "CMU Mach License", false, false ], "CNRI-Jython": [ "CNRI Jython License", false, false ], "CNRI-Python": [ "CNRI Python License", true, false ], "CNRI-Python-GPL-Compatible": [ "CNRI Python Open Source GPL Compatible License Agreement", false, false ], "COIL-1.0": [ "Copyfree Open Innovation License", false, false ], "Community-Spec-1.0": [ "Community Specification License 1.0", false, false ], "Condor-1.1": [ "Condor Public License v1.1", false, false ], "copyleft-next-0.3.0": [ "copyleft-next 0.3.0", false, false ], "copyleft-next-0.3.1": [ "copyleft-next 0.3.1", false, false ], "Cornell-Lossless-JPEG": [ "Cornell Lossless JPEG License", false, false ], "CPAL-1.0": [ "Common Public Attribution License 1.0", true, false ], "CPL-1.0": [ "Common Public License 1.0", true, false ], "CPOL-1.02": [ "Code Project Open License 1.02", false, false ], "Cronyx": [ "Cronyx License", false, false ], "Crossword": [ "Crossword License", false, false ], "CrystalStacker": [ "CrystalStacker License", false, false ], "CUA-OPL-1.0": [ "CUA Office Public License v1.0", true, false ], "Cube": [ "Cube License", false, false ], "curl": [ "curl License", false, false ], "D-FSL-1.0": [ "Deutsche Freie Software Lizenz", false, false ], "DEC-3-Clause": [ "DEC 3-Clause License", false, false ], "diffmark": [ "diffmark license", false, false ], "DL-DE-BY-2.0": [ "Data licence Germany \u2013 attribution \u2013 version 2.0", false, false ], "DL-DE-ZERO-2.0": [ "Data licence Germany \u2013 zero \u2013 version 2.0", false, false ], "DOC": [ "DOC License", false, false ], "Dotseqn": [ "Dotseqn License", false, false ], "DRL-1.0": [ "Detection Rule License 1.0", false, false ], "DRL-1.1": [ "Detection Rule License 1.1", false, false ], "DSDP": [ "DSDP License", false, false ], "dtoa": [ "David M. Gay dtoa License", false, false ], "dvipdfm": [ "dvipdfm License", false, false ], "ECL-1.0": [ "Educational Community License v1.0", true, false ], "ECL-2.0": [ "Educational Community License v2.0", true, false ], "eCos-2.0": [ "eCos license version 2.0", false, true ], "EFL-1.0": [ "Eiffel Forum License v1.0", true, false ], "EFL-2.0": [ "Eiffel Forum License v2.0", true, false ], "eGenix": [ "eGenix.com Public License 1.1.0", false, false ], "Elastic-2.0": [ "Elastic License 2.0", false, false ], "Entessa": [ "Entessa Public License v1.0", true, false ], "EPICS": [ "EPICS Open License", false, false ], "EPL-1.0": [ "Eclipse Public License 1.0", true, false ], "EPL-2.0": [ "Eclipse Public License 2.0", true, false ], "ErlPL-1.1": [ "Erlang Public License v1.1", false, false ], "etalab-2.0": [ "Etalab Open License 2.0", false, false ], "EUDatagrid": [ "EU DataGrid Software License", true, false ], "EUPL-1.0": [ "European Union Public License 1.0", false, false ], "EUPL-1.1": [ "European Union Public License 1.1", true, false ], "EUPL-1.2": [ "European Union Public License 1.2", true, false ], "Eurosym": [ "Eurosym License", false, false ], "Fair": [ "Fair License", true, false ], "FBM": [ "Fuzzy Bitmap License", false, false ], "FDK-AAC": [ "Fraunhofer FDK AAC Codec Library", false, false ], "Ferguson-Twofish": [ "Ferguson Twofish License", false, false ], "Frameworx-1.0": [ "Frameworx Open License 1.0", true, false ], "FreeBSD-DOC": [ "FreeBSD Documentation License", false, false ], "FreeImage": [ "FreeImage Public License v1.0", false, false ], "FSFAP": [ "FSF All Permissive License", false, false ], "FSFUL": [ "FSF Unlimited License", false, false ], "FSFULLR": [ "FSF Unlimited License (with License Retention)", false, false ], "FSFULLRWD": [ "FSF Unlimited License (With License Retention and Warranty Disclaimer)", false, false ], "FTL": [ "Freetype Project License", false, false ], "Furuseth": [ "Furuseth License", false, false ], "fwlw": [ "fwlw License", false, false ], "GCR-docs": [ "Gnome GCR Documentation License", false, false ], "GD": [ "GD License", false, false ], "GFDL-1.1": [ "GNU Free Documentation License v1.1", false, true ], "GFDL-1.1-invariants-only": [ "GNU Free Documentation License v1.1 only - invariants", false, false ], "GFDL-1.1-invariants-or-later": [ "GNU Free Documentation License v1.1 or later - invariants", false, false ], "GFDL-1.1-no-invariants-only": [ "GNU Free Documentation License v1.1 only - no invariants", false, false ], "GFDL-1.1-no-invariants-or-later": [ "GNU Free Documentation License v1.1 or later - no invariants", false, false ], "GFDL-1.1-only": [ "GNU Free Documentation License v1.1 only", false, false ], "GFDL-1.1-or-later": [ "GNU Free Documentation License v1.1 or later", false, false ], "GFDL-1.2": [ "GNU Free Documentation License v1.2", false, true ], "GFDL-1.2-invariants-only": [ "GNU Free Documentation License v1.2 only - invariants", false, false ], "GFDL-1.2-invariants-or-later": [ "GNU Free Documentation License v1.2 or later - invariants", false, false ], "GFDL-1.2-no-invariants-only": [ "GNU Free Documentation License v1.2 only - no invariants", false, false ], "GFDL-1.2-no-invariants-or-later": [ "GNU Free Documentation License v1.2 or later - no invariants", false, false ], "GFDL-1.2-only": [ "GNU Free Documentation License v1.2 only", false, false ], "GFDL-1.2-or-later": [ "GNU Free Documentation License v1.2 or later", false, false ], "GFDL-1.3": [ "GNU Free Documentation License v1.3", false, true ], "GFDL-1.3-invariants-only": [ "GNU Free Documentation License v1.3 only - invariants", false, false ], "GFDL-1.3-invariants-or-later": [ "GNU Free Documentation License v1.3 or later - invariants", false, false ], "GFDL-1.3-no-invariants-only": [ "GNU Free Documentation License v1.3 only - no invariants", false, false ], "GFDL-1.3-no-invariants-or-later": [ "GNU Free Documentation License v1.3 or later - no invariants", false, false ], "GFDL-1.3-only": [ "GNU Free Documentation License v1.3 only", false, false ], "GFDL-1.3-or-later": [ "GNU Free Documentation License v1.3 or later", false, false ], "Giftware": [ "Giftware License", false, false ], "GL2PS": [ "GL2PS License", false, false ], "Glide": [ "3dfx Glide License", false, false ], "Glulxe": [ "Glulxe License", false, false ], "GLWTPL": [ "Good Luck With That Public License", false, false ], "gnuplot": [ "gnuplot License", false, false ], "GPL-1.0": [ "GNU General Public License v1.0 only", false, true ], "GPL-1.0+": [ "GNU General Public License v1.0 or later", false, true ], "GPL-1.0-only": [ "GNU General Public License v1.0 only", false, false ], "GPL-1.0-or-later": [ "GNU General Public License v1.0 or later", false, false ], "GPL-2.0": [ "GNU General Public License v2.0 only", true, true ], "GPL-2.0+": [ "GNU General Public License v2.0 or later", true, true ], "GPL-2.0-only": [ "GNU General Public License v2.0 only", true, false ], "GPL-2.0-or-later": [ "GNU General Public License v2.0 or later", true, false ], "GPL-2.0-with-autoconf-exception": [ "GNU General Public License v2.0 w/Autoconf exception", false, true ], "GPL-2.0-with-bison-exception": [ "GNU General Public License v2.0 w/Bison exception", false, true ], "GPL-2.0-with-classpath-exception": [ "GNU General Public License v2.0 w/Classpath exception", false, true ], "GPL-2.0-with-font-exception": [ "GNU General Public License v2.0 w/Font exception", false, true ], "GPL-2.0-with-GCC-exception": [ "GNU General Public License v2.0 w/GCC Runtime Library exception", false, true ], "GPL-3.0": [ "GNU General Public License v3.0 only", true, true ], "GPL-3.0+": [ "GNU General Public License v3.0 or later", true, true ], "GPL-3.0-only": [ "GNU General Public License v3.0 only", true, false ], "GPL-3.0-or-later": [ "GNU General Public License v3.0 or later", true, false ], "GPL-3.0-with-autoconf-exception": [ "GNU General Public License v3.0 w/Autoconf exception", false, true ], "GPL-3.0-with-GCC-exception": [ "GNU General Public License v3.0 w/GCC Runtime Library exception", true, true ], "Graphics-Gems": [ "Graphics Gems License", false, false ], "gSOAP-1.3b": [ "gSOAP Public License v1.3b", false, false ], "HaskellReport": [ "Haskell Language Report License", false, false ], "hdparm": [ "hdparm License", false, false ], "Hippocratic-2.1": [ "Hippocratic License 2.1", false, false ], "HP-1986": [ "Hewlett-Packard 1986 License", false, false ], "HP-1989": [ "Hewlett-Packard 1989 License", false, false ], "HPND": [ "Historical Permission Notice and Disclaimer", true, false ], "HPND-DEC": [ "Historical Permission Notice and Disclaimer - DEC variant", false, false ], "HPND-doc": [ "Historical Permission Notice and Disclaimer - documentation variant", false, false ], "HPND-doc-sell": [ "Historical Permission Notice and Disclaimer - documentation sell variant", false, false ], "HPND-export-US": [ "HPND with US Government export control warning", false, false ], "HPND-export-US-modify": [ "HPND with US Government export control warning and modification rqmt", false, false ], "HPND-Markus-Kuhn": [ "Historical Permission Notice and Disclaimer - Markus Kuhn variant", false, false ], "HPND-Pbmplus": [ "Historical Permission Notice and Disclaimer - Pbmplus variant", false, false ], "HPND-sell-MIT-disclaimer-xserver": [ "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer", false, false ], "HPND-sell-regexpr": [ "Historical Permission Notice and Disclaimer - sell regexpr variant", false, false ], "HPND-sell-variant": [ "Historical Permission Notice and Disclaimer - sell variant", false, false ], "HPND-sell-variant-MIT-disclaimer": [ "HPND sell variant with MIT disclaimer", false, false ], "HPND-UC": [ "Historical Permission Notice and Disclaimer - University of California variant", false, false ], "HTMLTIDY": [ "HTML Tidy License", false, false ], "IBM-pibs": [ "IBM PowerPC Initialization and Boot Software", false, false ], "ICU": [ "ICU License", true, false ], "IEC-Code-Components-EULA": [ "IEC Code Components End-user licence agreement", false, false ], "IJG": [ "Independent JPEG Group License", false, false ], "IJG-short": [ "Independent JPEG Group License - short", false, false ], "ImageMagick": [ "ImageMagick License", false, false ], "iMatix": [ "iMatix Standard Function Library Agreement", false, false ], "Imlib2": [ "Imlib2 License", false, false ], "Info-ZIP": [ "Info-ZIP License", false, false ], "Inner-Net-2.0": [ "Inner Net License v2.0", false, false ], "Intel": [ "Intel Open Source License", true, false ], "Intel-ACPI": [ "Intel ACPI Software License Agreement", false, false ], "Interbase-1.0": [ "Interbase Public License v1.0", false, false ], "IPA": [ "IPA Font License", true, false ], "IPL-1.0": [ "IBM Public License v1.0", true, false ], "ISC": [ "ISC License", true, false ], "Jam": [ "Jam License", true, false ], "JasPer-2.0": [ "JasPer License", false, false ], "JPL-image": [ "JPL Image Use Policy", false, false ], "JPNIC": [ "Japan Network Information Center License", false, false ], "JSON": [ "JSON License", false, false ], "Kastrup": [ "Kastrup License", false, false ], "Kazlib": [ "Kazlib License", false, false ], "Knuth-CTAN": [ "Knuth CTAN License", false, false ], "LAL-1.2": [ "Licence Art Libre 1.2", false, false ], "LAL-1.3": [ "Licence Art Libre 1.3", false, false ], "Latex2e": [ "Latex2e License", false, false ], "Latex2e-translated-notice": [ "Latex2e with translated notice permission", false, false ], "Leptonica": [ "Leptonica License", false, false ], "LGPL-2.0": [ "GNU Library General Public License v2 only", true, true ], "LGPL-2.0+": [ "GNU Library General Public License v2 or later", true, true ], "LGPL-2.0-only": [ "GNU Library General Public License v2 only", true, false ], "LGPL-2.0-or-later": [ "GNU Library General Public License v2 or later", true, false ], "LGPL-2.1": [ "GNU Lesser General Public License v2.1 only", true, true ], "LGPL-2.1+": [ "GNU Lesser General Public License v2.1 or later", true, true ], "LGPL-2.1-only": [ "GNU Lesser General Public License v2.1 only", true, false ], "LGPL-2.1-or-later": [ "GNU Lesser General Public License v2.1 or later", true, false ], "LGPL-3.0": [ "GNU Lesser General Public License v3.0 only", true, true ], "LGPL-3.0+": [ "GNU Lesser General Public License v3.0 or later", true, true ], "LGPL-3.0-only": [ "GNU Lesser General Public License v3.0 only", true, false ], "LGPL-3.0-or-later": [ "GNU Lesser General Public License v3.0 or later", true, false ], "LGPLLR": [ "Lesser General Public License For Linguistic Resources", false, false ], "Libpng": [ "libpng License", false, false ], "libpng-2.0": [ "PNG Reference Library version 2", false, false ], "libselinux-1.0": [ "libselinux public domain notice", false, false ], "libtiff": [ "libtiff License", false, false ], "libutil-David-Nugent": [ "libutil David Nugent License", false, false ], "LiLiQ-P-1.1": [ "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1", true, false ], "LiLiQ-R-1.1": [ "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1", true, false ], "LiLiQ-Rplus-1.1": [ "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1", true, false ], "Linux-man-pages-1-para": [ "Linux man-pages - 1 paragraph", false, false ], "Linux-man-pages-copyleft": [ "Linux man-pages Copyleft", false, false ], "Linux-man-pages-copyleft-2-para": [ "Linux man-pages Copyleft - 2 paragraphs", false, false ], "Linux-man-pages-copyleft-var": [ "Linux man-pages Copyleft Variant", false, false ], "Linux-OpenIB": [ "Linux Kernel Variant of OpenIB.org license", false, false ], "LOOP": [ "Common Lisp LOOP License", false, false ], "LPL-1.0": [ "Lucent Public License Version 1.0", true, false ], "LPL-1.02": [ "Lucent Public License v1.02", true, false ], "LPPL-1.0": [ "LaTeX Project Public License v1.0", false, false ], "LPPL-1.1": [ "LaTeX Project Public License v1.1", false, false ], "LPPL-1.2": [ "LaTeX Project Public License v1.2", false, false ], "LPPL-1.3a": [ "LaTeX Project Public License v1.3a", false, false ], "LPPL-1.3c": [ "LaTeX Project Public License v1.3c", true, false ], "lsof": [ "lsof License", false, false ], "Lucida-Bitmap-Fonts": [ "Lucida Bitmap Fonts License", false, false ], "LZMA-SDK-9.11-to-9.20": [ "LZMA SDK License (versions 9.11 to 9.20)", false, false ], "LZMA-SDK-9.22": [ "LZMA SDK License (versions 9.22 and beyond)", false, false ], "magaz": [ "magaz License", false, false ], "MakeIndex": [ "MakeIndex License", false, false ], "Martin-Birgmeier": [ "Martin Birgmeier License", false, false ], "McPhee-slideshow": [ "McPhee Slideshow License", false, false ], "metamail": [ "metamail License", false, false ], "Minpack": [ "Minpack License", false, false ], "MirOS": [ "The MirOS Licence", true, false ], "MIT": [ "MIT License", true, false ], "MIT-0": [ "MIT No Attribution", true, false ], "MIT-advertising": [ "Enlightenment License (e16)", false, false ], "MIT-CMU": [ "CMU License", false, false ], "MIT-enna": [ "enna License", false, false ], "MIT-feh": [ "feh License", false, false ], "MIT-Festival": [ "MIT Festival Variant", false, false ], "MIT-Modern-Variant": [ "MIT License Modern Variant", true, false ], "MIT-open-group": [ "MIT Open Group variant", false, false ], "MIT-testregex": [ "MIT testregex Variant", false, false ], "MIT-Wu": [ "MIT Tom Wu Variant", false, false ], "MITNFA": [ "MIT +no-false-attribs license", false, false ], "MMIXware": [ "MMIXware License", false, false ], "Motosoto": [ "Motosoto License", true, false ], "MPEG-SSG": [ "MPEG Software Simulation", false, false ], "mpi-permissive": [ "mpi Permissive License", false, false ], "mpich2": [ "mpich2 License", false, false ], "MPL-1.0": [ "Mozilla Public License 1.0", true, false ], "MPL-1.1": [ "Mozilla Public License 1.1", true, false ], "MPL-2.0": [ "Mozilla Public License 2.0", true, false ], "MPL-2.0-no-copyleft-exception": [ "Mozilla Public License 2.0 (no copyleft exception)", true, false ], "mplus": [ "mplus Font License", false, false ], "MS-LPL": [ "Microsoft Limited Public License", false, false ], "MS-PL": [ "Microsoft Public License", true, false ], "MS-RL": [ "Microsoft Reciprocal License", true, false ], "MTLL": [ "Matrix Template Library License", false, false ], "MulanPSL-1.0": [ "Mulan Permissive Software License, Version 1", false, false ], "MulanPSL-2.0": [ "Mulan Permissive Software License, Version 2", true, false ], "Multics": [ "Multics License", true, false ], "Mup": [ "Mup License", false, false ], "NAIST-2003": [ "Nara Institute of Science and Technology License (2003)", false, false ], "NASA-1.3": [ "NASA Open Source Agreement 1.3", true, false ], "Naumen": [ "Naumen Public License", true, false ], "NBPL-1.0": [ "Net Boolean Public License v1", false, false ], "NCGL-UK-2.0": [ "Non-Commercial Government Licence", false, false ], "NCSA": [ "University of Illinois/NCSA Open Source License", true, false ], "Net-SNMP": [ "Net-SNMP License", false, false ], "NetCDF": [ "NetCDF license", false, false ], "Newsletr": [ "Newsletr License", false, false ], "NGPL": [ "Nethack General Public License", true, false ], "NICTA-1.0": [ "NICTA Public Software License, Version 1.0", false, false ], "NIST-PD": [ "NIST Public Domain Notice", false, false ], "NIST-PD-fallback": [ "NIST Public Domain Notice with license fallback", false, false ], "NIST-Software": [ "NIST Software License", false, false ], "NLOD-1.0": [ "Norwegian Licence for Open Government Data (NLOD) 1.0", false, false ], "NLOD-2.0": [ "Norwegian Licence for Open Government Data (NLOD) 2.0", false, false ], "NLPL": [ "No Limit Public License", false, false ], "Nokia": [ "Nokia Open Source License", true, false ], "NOSL": [ "Netizen Open Source License", false, false ], "Noweb": [ "Noweb License", false, false ], "NPL-1.0": [ "Netscape Public License v1.0", false, false ], "NPL-1.1": [ "Netscape Public License v1.1", false, false ], "NPOSL-3.0": [ "Non-Profit Open Software License 3.0", true, false ], "NRL": [ "NRL License", false, false ], "NTP": [ "NTP License", true, false ], "NTP-0": [ "NTP No Attribution", false, false ], "Nunit": [ "Nunit License", false, true ], "O-UDA-1.0": [ "Open Use of Data Agreement v1.0", false, false ], "OCCT-PL": [ "Open CASCADE Technology Public License", false, false ], "OCLC-2.0": [ "OCLC Research Public License 2.0", true, false ], "ODbL-1.0": [ "Open Data Commons Open Database License v1.0", false, false ], "ODC-By-1.0": [ "Open Data Commons Attribution License v1.0", false, false ], "OFFIS": [ "OFFIS License", false, false ], "OFL-1.0": [ "SIL Open Font License 1.0", false, false ], "OFL-1.0-no-RFN": [ "SIL Open Font License 1.0 with no Reserved Font Name", false, false ], "OFL-1.0-RFN": [ "SIL Open Font License 1.0 with Reserved Font Name", false, false ], "OFL-1.1": [ "SIL Open Font License 1.1", true, false ], "OFL-1.1-no-RFN": [ "SIL Open Font License 1.1 with no Reserved Font Name", true, false ], "OFL-1.1-RFN": [ "SIL Open Font License 1.1 with Reserved Font Name", true, false ], "OGC-1.0": [ "OGC Software License, Version 1.0", false, false ], "OGDL-Taiwan-1.0": [ "Taiwan Open Government Data License, version 1.0", false, false ], "OGL-Canada-2.0": [ "Open Government Licence - Canada", false, false ], "OGL-UK-1.0": [ "Open Government Licence v1.0", false, false ], "OGL-UK-2.0": [ "Open Government Licence v2.0", false, false ], "OGL-UK-3.0": [ "Open Government Licence v3.0", false, false ], "OGTSL": [ "Open Group Test Suite License", true, false ], "OLDAP-1.1": [ "Open LDAP Public License v1.1", false, false ], "OLDAP-1.2": [ "Open LDAP Public License v1.2", false, false ], "OLDAP-1.3": [ "Open LDAP Public License v1.3", false, false ], "OLDAP-1.4": [ "Open LDAP Public License v1.4", false, false ], "OLDAP-2.0": [ "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", false, false ], "OLDAP-2.0.1": [ "Open LDAP Public License v2.0.1", false, false ], "OLDAP-2.1": [ "Open LDAP Public License v2.1", false, false ], "OLDAP-2.2": [ "Open LDAP Public License v2.2", false, false ], "OLDAP-2.2.1": [ "Open LDAP Public License v2.2.1", false, false ], "OLDAP-2.2.2": [ "Open LDAP Public License 2.2.2", false, false ], "OLDAP-2.3": [ "Open LDAP Public License v2.3", false, false ], "OLDAP-2.4": [ "Open LDAP Public License v2.4", false, false ], "OLDAP-2.5": [ "Open LDAP Public License v2.5", false, false ], "OLDAP-2.6": [ "Open LDAP Public License v2.6", false, false ], "OLDAP-2.7": [ "Open LDAP Public License v2.7", false, false ], "OLDAP-2.8": [ "Open LDAP Public License v2.8", true, false ], "OLFL-1.3": [ "Open Logistics Foundation License Version 1.3", true, false ], "OML": [ "Open Market License", false, false ], "OpenPBS-2.3": [ "OpenPBS v2.3 Software License", false, false ], "OpenSSL": [ "OpenSSL License", false, false ], "OPL-1.0": [ "Open Public License v1.0", false, false ], "OPL-UK-3.0": [ "United Kingdom Open Parliament Licence v3.0", false, false ], "OPUBL-1.0": [ "Open Publication License v1.0", false, false ], "OSET-PL-2.1": [ "OSET Public License version 2.1", true, false ], "OSL-1.0": [ "Open Software License 1.0", true, false ], "OSL-1.1": [ "Open Software License 1.1", false, false ], "OSL-2.0": [ "Open Software License 2.0", true, false ], "OSL-2.1": [ "Open Software License 2.1", true, false ], "OSL-3.0": [ "Open Software License 3.0", true, false ], "PADL": [ "PADL License", false, false ], "Parity-6.0.0": [ "The Parity Public License 6.0.0", false, false ], "Parity-7.0.0": [ "The Parity Public License 7.0.0", false, false ], "PDDL-1.0": [ "Open Data Commons Public Domain Dedication & License 1.0", false, false ], "PHP-3.0": [ "PHP License v3.0", true, false ], "PHP-3.01": [ "PHP License v3.01", true, false ], "Pixar": [ "Pixar License", false, false ], "Plexus": [ "Plexus Classworlds License", false, false ], "pnmstitch": [ "pnmstitch License", false, false ], "PolyForm-Noncommercial-1.0.0": [ "PolyForm Noncommercial License 1.0.0", false, false ], "PolyForm-Small-Business-1.0.0": [ "PolyForm Small Business License 1.0.0", false, false ], "PostgreSQL": [ "PostgreSQL License", true, false ], "PSF-2.0": [ "Python Software Foundation License 2.0", false, false ], "psfrag": [ "psfrag License", false, false ], "psutils": [ "psutils License", false, false ], "Python-2.0": [ "Python License 2.0", true, false ], "Python-2.0.1": [ "Python License 2.0.1", false, false ], "python-ldap": [ "Python ldap License", false, false ], "Qhull": [ "Qhull License", false, false ], "QPL-1.0": [ "Q Public License 1.0", true, false ], "QPL-1.0-INRIA-2004": [ "Q Public License 1.0 - INRIA 2004 variant", false, false ], "Rdisc": [ "Rdisc License", false, false ], "RHeCos-1.1": [ "Red Hat eCos Public License v1.1", false, false ], "RPL-1.1": [ "Reciprocal Public License 1.1", true, false ], "RPL-1.5": [ "Reciprocal Public License 1.5", true, false ], "RPSL-1.0": [ "RealNetworks Public Source License v1.0", true, false ], "RSA-MD": [ "RSA Message-Digest License", false, false ], "RSCPL": [ "Ricoh Source Code Public License", true, false ], "Ruby": [ "Ruby License", false, false ], "SAX-PD": [ "Sax Public Domain Notice", false, false ], "Saxpath": [ "Saxpath License", false, false ], "SCEA": [ "SCEA Shared Source License", false, false ], "SchemeReport": [ "Scheme Language Report License", false, false ], "Sendmail": [ "Sendmail License", false, false ], "Sendmail-8.23": [ "Sendmail License 8.23", false, false ], "SGI-B-1.0": [ "SGI Free Software License B v1.0", false, false ], "SGI-B-1.1": [ "SGI Free Software License B v1.1", false, false ], "SGI-B-2.0": [ "SGI Free Software License B v2.0", false, false ], "SGI-OpenGL": [ "SGI OpenGL License", false, false ], "SGP4": [ "SGP4 Permission Notice", false, false ], "SHL-0.5": [ "Solderpad Hardware License v0.5", false, false ], "SHL-0.51": [ "Solderpad Hardware License, Version 0.51", false, false ], "SimPL-2.0": [ "Simple Public License 2.0", true, false ], "SISSL": [ "Sun Industry Standards Source License v1.1", true, false ], "SISSL-1.2": [ "Sun Industry Standards Source License v1.2", false, false ], "SL": [ "SL License", false, false ], "Sleepycat": [ "Sleepycat License", true, false ], "SMLNJ": [ "Standard ML of New Jersey License", false, false ], "SMPPL": [ "Secure Messaging Protocol Public License", false, false ], "SNIA": [ "SNIA Public License 1.1", false, false ], "snprintf": [ "snprintf License", false, false ], "Soundex": [ "Soundex License", false, false ], "Spencer-86": [ "Spencer License 86", false, false ], "Spencer-94": [ "Spencer License 94", false, false ], "Spencer-99": [ "Spencer License 99", false, false ], "SPL-1.0": [ "Sun Public License v1.0", true, false ], "ssh-keyscan": [ "ssh-keyscan License", false, false ], "SSH-OpenSSH": [ "SSH OpenSSH license", false, false ], "SSH-short": [ "SSH short notice", false, false ], "SSPL-1.0": [ "Server Side Public License, v 1", false, false ], "StandardML-NJ": [ "Standard ML of New Jersey License", false, true ], "SugarCRM-1.1.3": [ "SugarCRM Public License v1.1.3", false, false ], "SunPro": [ "SunPro License", false, false ], "SWL": [ "Scheme Widget Library (SWL) Software License Agreement", false, false ], "swrule": [ "swrule License", false, false ], "Symlinks": [ "Symlinks License", false, false ], "TAPR-OHL-1.0": [ "TAPR Open Hardware License v1.0", false, false ], "TCL": [ "TCL/TK License", false, false ], "TCP-wrappers": [ "TCP Wrappers License", false, false ], "TermReadKey": [ "TermReadKey License", false, false ], "TMate": [ "TMate Open Source License", false, false ], "TORQUE-1.1": [ "TORQUE v2.5+ Software License v1.1", false, false ], "TOSL": [ "Trusster Open Source License", false, false ], "TPDL": [ "Time::ParseDate License", false, false ], "TPL-1.0": [ "THOR Public License 1.0", false, false ], "TTWL": [ "Text-Tabs+Wrap License", false, false ], "TTYP0": [ "TTYP0 License", false, false ], "TU-Berlin-1.0": [ "Technische Universitaet Berlin License 1.0", false, false ], "TU-Berlin-2.0": [ "Technische Universitaet Berlin License 2.0", false, false ], "UCAR": [ "UCAR License", false, false ], "UCL-1.0": [ "Upstream Compatibility License v1.0", true, false ], "ulem": [ "ulem License", false, false ], "Unicode-DFS-2015": [ "Unicode License Agreement - Data Files and Software (2015)", false, false ], "Unicode-DFS-2016": [ "Unicode License Agreement - Data Files and Software (2016)", true, false ], "Unicode-TOU": [ "Unicode Terms of Use", false, false ], "UnixCrypt": [ "UnixCrypt License", false, false ], "Unlicense": [ "The Unlicense", true, false ], "UPL-1.0": [ "Universal Permissive License v1.0", true, false ], "URT-RLE": [ "Utah Raster Toolkit Run Length Encoded License", false, false ], "Vim": [ "Vim License", false, false ], "VOSTROM": [ "VOSTROM Public License for Open Source", false, false ], "VSL-1.0": [ "Vovida Software License v1.0", true, false ], "W3C": [ "W3C Software Notice and License (2002-12-31)", true, false ], "W3C-19980720": [ "W3C Software Notice and License (1998-07-20)", false, false ], "W3C-20150513": [ "W3C Software Notice and Document License (2015-05-13)", false, false ], "w3m": [ "w3m License", false, false ], "Watcom-1.0": [ "Sybase Open Watcom Public License 1.0", true, false ], "Widget-Workshop": [ "Widget Workshop License", false, false ], "Wsuipa": [ "Wsuipa License", false, false ], "WTFPL": [ "Do What The F*ck You Want To Public License", false, false ], "wxWindows": [ "wxWindows Library License", true, true ], "X11": [ "X11 License", false, false ], "X11-distribute-modifications-variant": [ "X11 License Distribution Modification Variant", false, false ], "Xdebug-1.03": [ "Xdebug License v 1.03", false, false ], "Xerox": [ "Xerox License", false, false ], "Xfig": [ "Xfig License", false, false ], "XFree86-1.1": [ "XFree86 License 1.1", false, false ], "xinetd": [ "xinetd License", false, false ], "xlock": [ "xlock License", false, false ], "Xnet": [ "X.Net License", true, false ], "xpp": [ "XPP License", false, false ], "XSkat": [ "XSkat License", false, false ], "YPL-1.0": [ "Yahoo! Public License v1.0", false, false ], "YPL-1.1": [ "Yahoo! Public License v1.1", false, false ], "Zed": [ "Zed License", false, false ], "Zeeff": [ "Zeeff License", false, false ], "Zend-2.0": [ "Zend License v2.0", false, false ], "Zimbra-1.3": [ "Zimbra Public License v1.3", false, false ], "Zimbra-1.4": [ "Zimbra Public License v1.4", false, false ], "Zlib": [ "zlib License", true, false ], "zlib-acknowledgement": [ "zlib/libpng License with Acknowledgement", false, false ], "ZPL-1.1": [ "Zope Public License 1.1", false, false ], "ZPL-2.0": [ "Zope Public License 2.0", true, false ], "ZPL-2.1": [ "Zope Public License 2.1", true, false ] }Copyright (C) 2015 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [main] * ... ## [.1.5.6] 2022-05-23 * Changed: updated licenses list to SPDX 3.17 * Changed: `${var}` PHP 8.2 deprecations resolved ## [1.5.6] 2021-11-18 * Changed: updated licenses list to SPDX 3.15 ## [1.5.5] 2020-12-03 * Changed: updated licenses list to SPDX 3.11 ## [1.5.4] 2020-07-15 * Changed: updated licenses list to SPDX 3.9 ## [1.5.3] 2020-02-14 * Changed: updated licenses list to SPDX 3.8 ## [1.5.2] 2019-07-29 * Changed: updated licenses list to SPDX 3.6 ## [1.5.1] 2019-03-26 * Changed: updated licenses list to SPDX 3.4 ## [1.5.0] 2018-11-01 * Changed: updated licenses list to SPDX 3.3 ## [1.4.0] 2018-05-04 * Changed: updated licenses list to SPDX 3.1 ## [1.3.0] 2018-01-31 * Added: `SpdxLicenses::getLicenses` to get the whole list of methods. * Changed: license identifiers are now case insensitive. ## [1.2.0] 2018-01-03 * Added: deprecation status for all licenses and a `SpdxLicenses::isDeprecatedByIdentifier` method. * Changed: updated licenses list to SPDX 3.0. ## [1.1.6] 2017-04-03 * Changed: updated licenses list. ## [1.1.5] 2016-09-28 * Changed: updated licenses list. ## [1.1.4] 2016-05-04 * Changed: updated licenses list. ## [1.1.3] 2016-03-25 * Changed: updated licenses list. * Changed: dropped `test` namespace. * Changed: tedious small things. ## [1.1.2] 2015-10-05 * Changed: updated licenses list. ## [1.1.1] 2015-09-07 * Changed: improved performance when looking up just one license. * Changed: updated licenses list. ## [1.1.0] 2015-07-17 * Changed: updater now sorts licenses and exceptions by key. * Changed: filenames now class constants of SpdxLicenses (`LICENSES_FILE` and `EXCEPTIONS_FILE`). * Changed: resources directory now available via static method `SpdxLicenses::getResourcesDir()`. * Changed: updated licenses list. * Changed: removed json-schema requirement. ## [1.0.0] 2015-07-15 * Break: the following classes and namespaces were renamed: - Namespace: `Composer\Util` -> `Composer\Spdx` - Classname: `SpdxLicense` -> `SpdxLicenses` - Classname: `SpdxLicenseTest` -> `SpdxLicensesTest` - Classname: `Updater` -> `SpdxLicensesUpdater` * Changed: validation via regex implementation instead of lexer. [main]: https://github.com/composer/spdx-licenses/compare/1.5.7...main [1.5.7]: https://github.com/composer/spdx-licenses/compare/1.5.6...1.5.7 [1.5.6]: https://github.com/composer/spdx-licenses/compare/1.5.5...1.5.6 [1.5.5]: https://github.com/composer/spdx-licenses/compare/1.5.4...1.5.5 [1.5.4]: https://github.com/composer/spdx-licenses/compare/1.5.3...1.5.4 [1.5.3]: https://github.com/composer/spdx-licenses/compare/1.5.2...1.5.3 [1.5.2]: https://github.com/composer/spdx-licenses/compare/1.5.1...1.5.2 [1.5.1]: https://github.com/composer/spdx-licenses/compare/1.5.0...1.5.1 [1.5.0]: https://github.com/composer/spdx-licenses/compare/1.4.0...1.5.0 [1.4.0]: https://github.com/composer/spdx-licenses/compare/1.3.0...1.4.0 [1.3.0]: https://github.com/composer/spdx-licenses/compare/1.2.0...1.3.0 [1.2.0]: https://github.com/composer/spdx-licenses/compare/1.1.6...1.2.0 [1.1.6]: https://github.com/composer/spdx-licenses/compare/1.1.5...1.1.6 [1.1.5]: https://github.com/composer/spdx-licenses/compare/1.1.4...1.1.5 [1.1.4]: https://github.com/composer/spdx-licenses/compare/1.1.3...1.1.4 [1.1.3]: https://github.com/composer/spdx-licenses/compare/1.1.2...1.1.3 [1.1.2]: https://github.com/composer/spdx-licenses/compare/1.1.1...1.1.2 [1.1.1]: https://github.com/composer/spdx-licenses/compare/1.1.0...1.1.1 [1.1.0]: https://github.com/composer/spdx-licenses/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/composer/spdx-licenses/compare/0281a7fe7820c990db3058844e7d448d7b70e3ac...1.0.0 composer/spdx-licenses ====================== SPDX (Software Package Data Exchange) licenses list and validation library. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. [![Continuous Integration](https://github.com/composer/spdx-licenses/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/spdx-licenses/actions) Installation ------------ Install the latest version with: ```bash $ composer require composer/spdx-licenses ``` Basic Usage ----------- ```php getLicenseByIdentifier('MIT'); // get a license exception by identifier $licenses->getExceptionByIdentifier('Autoconf-exception-3.0'); // get a license identifier by name $licenses->getIdentifierByName('MIT License'); // check if a license is OSI approved by identifier $licenses->isOsiApprovedByIdentifier('MIT'); // check if a license identifier is deprecated $licenses->isDeprecatedByIdentifier('MIT'); // check if input is a valid SPDX license expression $licenses->validate($input); ``` > Read the [specifications](https://spdx.org/specifications) > to find out more about valid license expressions. Requirements ------------ * PHP 5.3.2 is required but using the latest version of PHP is highly recommended. License ------- composer/spdx-licenses is licensed under the MIT License, see the LICENSE file for details. Source ------ License information is curated by [SPDX](https://spdx.org/). The data is pulled from the [License List Data](https://github.com/spdx/license-list-data) repository. * [Licenses](https://spdx.org/licenses/index.html) * [License Exceptions](https://spdx.org/licenses/exceptions-index.html) parameters: level: 8 paths: - src - tests typeAliases: SPDXLicense: 'array{0: string, 1: bool, 2: string, 3: bool}' SPDXLicenseException: 'array{0: string, 1: string}' { "name": "composer\/spdx-licenses", "description": "SPDX licenses list and validation library.", "type": "library", "license": "MIT", "keywords": [ "spdx", "license", "validator" ], "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "http:\/\/www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http:\/\/seld.be" }, { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http:\/\/robbast.nl" } ], "support": { "irc": "ircs:\/\/irc.libera.chat:6697\/composer", "issues": "https:\/\/github.com\/composer\/spdx-licenses\/issues" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "symfony\/phpunit-bridge": "^4.2 || ^5", "phpstan\/phpstan": "^0.12.55" }, "autoload": { "psr-4": { "Composer\\Spdx\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\Spdx\\": "tests" } }, "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "scripts": { "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor\/bin\/simple-phpunit", "phpstan": "vendor\/bin\/phpstan analyse", "sync-licenses": "bin\/update-spdx-licenses" } } * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\Spdx; class SpdxLicenses { /** @var string */ const LICENSES_FILE = 'spdx-licenses.json'; /** @var string */ const EXCEPTIONS_FILE = 'spdx-exceptions.json'; /** * Contains all the licenses. * * The array is indexed by license identifiers, which contain * a numerically indexed array with license details. * * [ lowercased license identifier => * [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ] * , ... * ] * * @var array */ private $licenses; /** * @var string */ private $licensesExpression; /** * Contains all the license exceptions. * * The array is indexed by license exception identifiers, which contain * a numerically indexed array with license exception details. * * [ lowercased exception identifier => * [ 0 => exception identifier (string), 1 => full name (string) ] * , ... * ] * * @var array */ private $exceptions; /** * @var string */ private $exceptionsExpression; public function __construct() { $this->loadLicenses(); $this->loadExceptions(); } /** * Returns license metadata by license identifier. * * This function adds a link to the full license text to the license metadata. * The array returned is in the form of: * * [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string), 3 => deprecation status (bool) ] * * @param string $identifier * * @return array{0: string, 1: bool, 2: string, 3: bool}|null */ public function getLicenseByIdentifier($identifier) { $key = \strtolower($identifier); if (!isset($this->licenses[$key])) { return null; } list($identifier, $name, $isOsiApproved, $isDeprecatedLicenseId) = $this->licenses[$key]; return array($name, $isOsiApproved, 'https://spdx.org/licenses/' . $identifier . '.html#licenseText', $isDeprecatedLicenseId); } /** * Returns all licenses information, keyed by the lowercased license identifier. * * @return array{0: string, 1: string, 2: bool, 3: bool}[] Each item is [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ] */ public function getLicenses() { return $this->licenses; } /** * Returns license exception metadata by license exception identifier. * * This function adds a link to the full license exception text to the license exception metadata. * The array returned is in the form of: * * [ 0 => full name (string), 1 => link to license text (string) ] * * @param string $identifier * * @return array{0: string, 1: string}|null */ public function getExceptionByIdentifier($identifier) { $key = \strtolower($identifier); if (!isset($this->exceptions[$key])) { return null; } list($identifier, $name) = $this->exceptions[$key]; return array($name, 'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText'); } /** * Returns the short identifier of a license (or license exception) by full name. * * @param string $name * * @return string|null */ public function getIdentifierByName($name) { foreach ($this->licenses as $licenseData) { if ($licenseData[1] === $name) { return $licenseData[0]; } } foreach ($this->exceptions as $licenseData) { if ($licenseData[1] === $name) { return $licenseData[0]; } } return null; } /** * Returns the OSI Approved status for a license by identifier. * * @param string $identifier * * @return bool */ public function isOsiApprovedByIdentifier($identifier) { return $this->licenses[\strtolower($identifier)][2]; } /** * Returns the deprecation status for a license by identifier. * * @param string $identifier * * @return bool */ public function isDeprecatedByIdentifier($identifier) { return $this->licenses[\strtolower($identifier)][3]; } /** * @param string[]|string $license * * @throws \InvalidArgumentException * * @return bool */ public function validate($license) { if (\is_array($license)) { $count = \count($license); if ($count !== \count(\array_filter($license, 'is_string'))) { throw new \InvalidArgumentException('Array of strings expected.'); } $license = $count > 1 ? '(' . \implode(' OR ', $license) . ')' : (string) \reset($license); } if (!\is_string($license)) { throw new \InvalidArgumentException(\sprintf('Array or String expected, %s given.', \gettype($license))); } return $this->isValidLicenseString($license); } /** * @return string */ public static function getResourcesDir() { return \dirname(__DIR__) . '/res'; } /** * @return void */ private function loadLicenses() { if (null !== $this->licenses) { return; } $json = \file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE); if (\false === $json) { throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE); } $this->licenses = array(); foreach (\json_decode($json, \true) as $identifier => $license) { $this->licenses[\strtolower($identifier)] = array($identifier, $license[0], $license[1], $license[2]); } } /** * @return void */ private function loadExceptions() { if (null !== $this->exceptions) { return; } $json = \file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); if (\false === $json) { throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); } $this->exceptions = array(); foreach (\json_decode($json, \true) as $identifier => $exception) { $this->exceptions[\strtolower($identifier)] = array($identifier, $exception[0]); } } /** * @return string */ private function getLicensesExpression() { if (null === $this->licensesExpression) { $licenses = \array_map('preg_quote', \array_keys($this->licenses)); \rsort($licenses); $licenses = \implode('|', $licenses); $this->licensesExpression = $licenses; } return $this->licensesExpression; } /** * @return string */ private function getExceptionsExpression() { if (null === $this->exceptionsExpression) { $exceptions = \array_map('preg_quote', \array_keys($this->exceptions)); \rsort($exceptions); $exceptions = \implode('|', $exceptions); $this->exceptionsExpression = $exceptions; } return $this->exceptionsExpression; } /** * @param string $license * * @throws \RuntimeException * * @return bool */ private function isValidLicenseString($license) { if (isset($this->licenses[\strtolower($license)])) { return \true; } $licenses = $this->getLicensesExpression(); $exceptions = $this->getExceptionsExpression(); $regex = <<[\\pL\\pN.-]{1,}) # license-id: taken from list (?{$licenses}) # license-exception-id: taken from list (?{$exceptions}) # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring) (?(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring)) # simple-expresssion: license-id / license-id+ / license-ref (?(?&licenseid)\\+? | (?&licenseid) | (?&licenseref)) # compound-expression: 1*( # simple-expression / # simple-expression WITH license-exception-id / # compound-expression AND compound-expression / # compound-expression OR compound-expression # ) / ( compound-expression ) ) (? (?&simple_expression) ( \\s+ WITH \\s+ (?&licenseexceptionid))? | \\( \\s* (?&compound_expression) \\s* \\) ) (? (?&compound_head) (?: \\s+ (?:AND|OR) \\s+ (?&compound_expression))? ) # license-expression: 1*1(simple-expression / compound-expression) (?(?&compound_expression) | (?&simple_expression)) ) # end of define ^(NONE | NOASSERTION | (?&license_expression))\$ }xi REGEX; $match = \preg_match($regex, $license); if (0 === $match) { return \false; } if (\false === $match) { throw new \RuntimeException('Regex failed to compile/run.'); } return \true; } } The MIT License (MIT) Copyright (c) 2015 Paragon Initiative Enterprises Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p +h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc -----END PUBLIC KEY----- -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (MingW32) iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg 1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= =B6+8 -----END PGP SIGNATURE----- buildFromDirectory(\dirname(__DIR__) . '/lib'); \rename(\dirname(__DIR__) . '/lib/index.php', \dirname(__DIR__) . '/lib/random.php'); /** * If we pass an (optional) path to a private key as a second argument, we will * sign the Phar with OpenSSL. * * If you leave this out, it will produce an unsigned .phar! */ if ($argc > 1) { if (!@\is_readable($argv[1])) { echo 'Could not read the private key file:', $argv[1], "\n"; exit(255); } $pkeyFile = \file_get_contents($argv[1]); $private = \openssl_get_privatekey($pkeyFile); if ($private !== \false) { $pkey = ''; \openssl_pkey_export($private, $pkey); $phar->setSignatureAlgorithm(\Phar::OPENSSL, $pkey); /** * Save the corresponding public key to the file */ if (!@\is_readable($dist . '/random_compat.phar.pubkey')) { $details = \openssl_pkey_get_details($private); \file_put_contents($dist . '/random_compat.phar.pubkey', $details['key']); } } else { echo 'An error occurred reading the private key from OpenSSL.', "\n"; exit(255); } } #!/usr/bin/env bash basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) php -dphar.readonly=0 "$basedir/other/build_phar.php" $*= 7" }, "require-dev": { "vimeo\/psalm": "^1", "phpunit\/phpunit": "4.*|5.*" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." } } Copyright (c) 2006-2018 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Doctrine Lexer [![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions) Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). https://www.doctrine-project.org/projects/lexer.html Note about upgrading: Doctrine uses static and runtime mechanisms to raise awareness about deprecated code. - Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or Static Analysis tools (like Psalm, phpstan) - Use of our low-overhead runtime deprecation API, details: https://github.com/doctrine/deprecations/ # Upgrade to 2.0.0 `AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return instances of `Doctrine\Common\Lexer\Token`, which is an array-like class Using it as an array is deprecated in favor of using properties of that class. Using `count()` on it is deprecated with no replacement. { "name": "doctrine\/lexer", "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", "license": "MIT", "type": "library", "keywords": [ "php", "parser", "lexer", "annotations", "docblock" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "homepage": "https:\/\/www.doctrine-project.org\/projects\/lexer.html", "require": { "php": "^7.1 || ^8.0", "doctrine\/deprecations": "^1.0" }, "require-dev": { "doctrine\/coding-standard": "^9 || ^12", "phpstan\/phpstan": "^1.3", "phpunit\/phpunit": "^7.5 || ^8.5 || ^9.6", "psalm\/plugin-phpunit": "^0.18.3", "vimeo\/psalm": "^4.11 || ^5.21" }, "autoload": { "psr-4": { "_ContaoManager\\Doctrine\\Common\\Lexer\\": "src" } }, "autoload-dev": { "psr-4": { "_ContaoManager\\Doctrine\\Tests\\Common\\Lexer\\": "tests" } }, "config": { "allow-plugins": { "composer\/package-versions-deprecated": true, "dealerdirect\/phpcodesniffer-composer-installer": true }, "sort-packages": true } } */ final class Token implements ArrayAccess { /** * The string value of the token in the input string * * @readonly * @var V */ public $value; /** * The type of the token (identifier, numeric, string, input parameter, none) * * @readonly * @var T|null */ public $type; /** * The position of the token in the input string * * @readonly * @var int */ public $position; /** * @param V $value * @param T|null $type */ public function __construct($value, $type, int $position) { $this->value = $value; $this->type = $type; $this->position = $position; } /** @param T ...$types */ public function isA(...$types) : bool { return in_array($this->type, $types, \true); } /** * @deprecated Use the value, type or position property instead * {@inheritDoc} */ public function offsetExists($offset) : bool { Deprecation::trigger('doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', self::class); return in_array($offset, ['value', 'type', 'position'], \true); } /** * @deprecated Use the value, type or position property instead * {@inheritDoc} * * @param O $offset * * @return mixed * @psalm-return ( * O is 'value' * ? V * : ( * O is 'type' * ? T|null * : ( * O is 'position' * ? int * : mixed * ) * ) * ) * * @template O of array-key */ #[\ReturnTypeWillChange] public function offsetGet($offset) { Deprecation::trigger('doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', self::class); return $this->{$offset}; } /** * @deprecated no replacement planned * {@inheritDoc} */ public function offsetSet($offset, $value) : void { Deprecation::trigger('doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Setting %s properties via ArrayAccess is deprecated', self::class); $this->{$offset} = $value; } /** * @deprecated no replacement planned * {@inheritDoc} */ public function offsetUnset($offset) : void { Deprecation::trigger('doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Setting %s properties via ArrayAccess is deprecated', self::class); $this->{$offset} = null; } } > */ private $tokens = []; /** * Current lexer position in input string. * * @var int */ private $position = 0; /** * Current peek of current lexer position. * * @var int */ private $peek = 0; /** * The next token in the input. * * @var Token|null */ public $lookahead; /** * The last matched/seen token. * * @var Token|null */ public $token; /** * Composed regex for input parsing. * * @var non-empty-string|null */ private $regex; /** * Sets the input data to be tokenized. * * The Lexer is immediately reset and the new input tokenized. * Any unprocessed tokens from any previous input are lost. * * @param string $input The input to be tokenized. * * @return void */ public function setInput($input) { $this->input = $input; $this->tokens = []; $this->reset(); $this->scan($input); } /** * Resets the lexer. * * @return void */ public function reset() { $this->lookahead = null; $this->token = null; $this->peek = 0; $this->position = 0; } /** * Resets the peek pointer to 0. * * @return void */ public function resetPeek() { $this->peek = 0; } /** * Resets the lexer position on the input to the given position. * * @param int $position Position to place the lexical scanner. * * @return void */ public function resetPosition($position = 0) { $this->position = $position; } /** * Retrieve the original lexer's input until a given position. * * @param int $position * * @return string */ public function getInputUntilPosition($position) { return substr($this->input, 0, $position); } /** * Checks whether a given token matches the current lookahead. * * @param T $type * * @return bool * * @psalm-assert-if-true !=null $this->lookahead */ public function isNextToken($type) { return $this->lookahead !== null && $this->lookahead->isA($type); } /** * Checks whether any of the given tokens matches the current lookahead. * * @param list $types * * @return bool * * @psalm-assert-if-true !=null $this->lookahead */ public function isNextTokenAny(array $types) { return $this->lookahead !== null && $this->lookahead->isA(...$types); } /** * Moves to the next token in the input string. * * @return bool * * @psalm-assert-if-true !null $this->lookahead */ public function moveNext() { $this->peek = 0; $this->token = $this->lookahead; $this->lookahead = isset($this->tokens[$this->position]) ? $this->tokens[$this->position++] : null; return $this->lookahead !== null; } /** * Tells the lexer to skip input tokens until it sees a token with the given value. * * @param T $type The token type to skip until. * * @return void */ public function skipUntil($type) { while ($this->lookahead !== null && !$this->lookahead->isA($type)) { $this->moveNext(); } } /** * Checks if given value is identical to the given token. * * @param string $value * @param int|string $token * * @return bool */ public function isA($value, $token) { return $this->getType($value) === $token; } /** * Moves the lookahead token forward. * * @return Token|null The next token or NULL if there are no more tokens ahead. */ public function peek() { if (isset($this->tokens[$this->position + $this->peek])) { return $this->tokens[$this->position + $this->peek++]; } return null; } /** * Peeks at the next token, returns it and immediately resets the peek. * * @return Token|null The next token or NULL if there are no more tokens ahead. */ public function glimpse() { $peek = $this->peek(); $this->peek = 0; return $peek; } /** * Scans the input string for tokens. * * @param string $input A query string. * * @return void */ protected function scan($input) { if (!isset($this->regex)) { $this->regex = sprintf('/(%s)|%s/%s', implode(')|(', $this->getCatchablePatterns()), implode('|', $this->getNonCatchablePatterns()), $this->getModifiers()); } $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($this->regex, $input, -1, $flags); if ($matches === \false) { // Work around https://bugs.php.net/78122 $matches = [[$input, 0]]; } foreach ($matches as $match) { // Must remain before 'value' assignment since it can change content $firstMatch = $match[0]; $type = $this->getType($firstMatch); $this->tokens[] = new Token($firstMatch, $type, $match[1]); } } /** * Gets the literal for a given token. * * @param T $token * * @return int|string */ public function getLiteral($token) { if ($token instanceof UnitEnum) { return get_class($token) . '::' . $token->name; } $className = static::class; $reflClass = new ReflectionClass($className); $constants = $reflClass->getConstants(); foreach ($constants as $name => $value) { if ($value === $token) { return $className . '::' . $name; } } return $token; } /** * Regex modifiers * * @return string */ protected function getModifiers() { return 'iu'; } /** * Lexical catchable patterns. * * @return string[] */ protected abstract function getCatchablePatterns(); /** * Lexical non-catchable patterns. * * @return string[] */ protected abstract function getNonCatchablePatterns(); /** * Retrieve token type. Also processes the token value if necessary. * * @param string $value * * @return T|null * * @param-out V $value */ protected abstract function getType(&$value); } Copyright (c) 2006-2013 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ⚠️ PHP 8 introduced [attributes](https://www.php.net/manual/en/language.attributes.overview.php), which are a native replacement for annotations. As such, this library is considered feature complete, and should receive exclusively bugfixes and security fixes. # Doctrine Annotations [![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions) [![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations) [![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references) [![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations) [![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations) Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). ## Documentation See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). ## Contributing When making a pull request, make sure your changes follow the [Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction). ReflectionClass object. * * @return array A list with use statements in the form (Alias => FQN). */ public function parseClass(ReflectionClass $class) { return $this->parseUseStatements($class); } /** * Parse a class or function for use statements. * * @param ReflectionClass|ReflectionFunction $reflection * * @psalm-return array a list with use statements in the form (Alias => FQN). */ public function parseUseStatements($reflection) : array { if (method_exists($reflection, 'getUseStatements')) { return $reflection->getUseStatements(); } $filename = $reflection->getFileName(); if ($filename === \false) { return []; } $content = $this->getFileContent($filename, $reflection->getStartLine()); if ($content === null) { return []; } $namespace = preg_quote($reflection->getNamespaceName()); $content = preg_replace('/^.*?(\\bnamespace\\s+' . $namespace . '\\s*[;{].*)$/s', '\\1', $content); $tokenizer = new TokenParser('parseUseStatements($reflection->getNamespaceName()); } /** * Gets the content of the file right up to the given line number. * * @param string $filename The name of the file to load. * @param int $lineNumber The number of lines to read from file. * * @return string|null The content of the file or null if the file does not exist. */ private function getFileContent($filename, $lineNumber) { if (!is_file($filename)) { return null; } $content = ''; $lineCnt = 0; $file = new SplFileObject($filename); while (!$file->eof()) { if ($lineCnt++ === $lineNumber) { break; } $content .= $file->fgets(); } return $content; } } $available * * @return AnnotationException */ public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) { return new self(sprintf('[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.', $attributeName, $annotationName, $context, implode(', ', $available), is_object($given) ? get_class($given) : $given)); } /** @return AnnotationException */ public static function optimizerPlusSaveComments() { return new self('You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'); } /** @return AnnotationException */ public static function optimizerPlusLoadComments() { return new self('You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'); } } > */ private $loadedAnnotations = []; /** @var array */ private $classNameHashes = []; /** @var int */ private $umask; /** * @param string $cacheDir * @param bool $debug * @param int $umask * * @throws InvalidArgumentException */ public function __construct(Reader $reader, $cacheDir, $debug = \false, $umask = 02) { if (!is_int($umask)) { throw new InvalidArgumentException(sprintf('The parameter umask must be an integer, was: %s', gettype($umask))); } $this->reader = $reader; $this->umask = $umask; if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777 & ~$this->umask, \true)) { throw new InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $cacheDir)); } $this->dir = rtrim($cacheDir, '\\/'); $this->debug = $debug; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { if (!isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name]; if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; if (!is_file($path)) { $annot = $this->reader->getClassAnnotations($class); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } $filename = $class->getFilename(); if ($this->debug && $filename !== \false && filemtime($path) < filemtime($filename)) { @unlink($path); $annot = $this->reader->getClassAnnotations($class); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = (include $path); } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); if (!isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name] . '$' . $property->getName(); if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; if (!is_file($path)) { $annot = $this->reader->getPropertyAnnotations($property); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } $filename = $class->getFilename(); if ($this->debug && $filename !== \false && filemtime($path) < filemtime($filename)) { @unlink($path); $annot = $this->reader->getPropertyAnnotations($property); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = (include $path); } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); if (!isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name] . '#' . $method->getName(); if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; if (!is_file($path)) { $annot = $this->reader->getMethodAnnotations($method); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } $filename = $class->getFilename(); if ($this->debug && $filename !== \false && filemtime($path) < filemtime($filename)) { @unlink($path); $annot = $this->reader->getMethodAnnotations($method); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = (include $path); } /** * Saves the cache file. * * @param string $path * @param mixed $data * * @return void */ private function saveCacheFile($path, $data) { if (!is_writable($this->dir)) { throw new InvalidArgumentException(sprintf(<<<'EXCEPTION' The directory "%s" is not writable. Both the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package., EXCEPTION , $this->dir)); } $tempfile = tempnam($this->dir, uniqid('', \true)); if ($tempfile === \false) { throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); } @chmod($tempfile, 0666 & ~$this->umask); $written = file_put_contents($tempfile, 'umask); if (rename($tempfile, $path) === \false) { @unlink($tempfile); throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); } } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Clears loaded annotations. * * @return void */ public function clearLoadedAnnotations() { $this->loadedAnnotations = []; } } */ public $names; /** * @phpstan-param array{value: string|list} $values * * @throws RuntimeException */ public function __construct(array $values) { if (is_string($values['value'])) { $values['value'] = [$values['value']]; } if (!is_array($values['value'])) { throw new RuntimeException(sprintf('@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', json_encode($values['value']))); } $this->names = $values['value']; } } */ public $value; } */ public $value; /** * Literal target declaration. * * @var mixed[] */ public $literal; /** * @phpstan-param array{literal?: mixed[], value: list} $values * * @throws InvalidArgumentException */ public function __construct(array $values) { if (!isset($values['literal'])) { $values['literal'] = []; } foreach ($values['value'] as $var) { if (!is_scalar($var)) { throw new InvalidArgumentException(sprintf('@Enum supports only scalar values "%s" given.', is_object($var) ? get_class($var) : gettype($var))); } } foreach ($values['literal'] as $key => $var) { if (!in_array($key, $values['value'])) { throw new InvalidArgumentException(sprintf('Undefined enumerator value "%s" for literal "%s".', $key, $var)); } } $this->value = $values['value']; $this->literal = $values['literal']; } } */ private static $map = ['ALL' => self::TARGET_ALL, 'CLASS' => self::TARGET_CLASS, 'METHOD' => self::TARGET_METHOD, 'PROPERTY' => self::TARGET_PROPERTY, 'FUNCTION' => self::TARGET_FUNCTION, 'ANNOTATION' => self::TARGET_ANNOTATION]; /** @phpstan-var list */ public $value; /** * Targets as bitmask. * * @var int */ public $targets; /** * Literal target declaration. * * @var string */ public $literal; /** * @phpstan-param array{value?: string|list} $values * * @throws InvalidArgumentException */ public function __construct(array $values) { if (!isset($values['value'])) { $values['value'] = null; } if (is_string($values['value'])) { $values['value'] = [$values['value']]; } if (!is_array($values['value'])) { throw new InvalidArgumentException(sprintf('@Target expects either a string value, or an array of strings, "%s" given.', is_object($values['value']) ? get_class($values['value']) : gettype($values['value']))); } $bitmask = 0; foreach ($values['value'] as $literal) { if (!isset(self::$map[$literal])) { throw new InvalidArgumentException(sprintf('Invalid Target "%s". Available targets: [%s]', $literal, implode(', ', array_keys(self::$map)))); } $bitmask |= self::$map[$literal]; } $this->targets = $bitmask; $this->value = $values['value']; $this->literal = implode(', ', $this->value); } } delegate = $reader; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $annotations = []; foreach ($this->delegate->getClassAnnotations($class) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { return $this->delegate->getClassAnnotation($class, $annotationName); } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $annotations = []; foreach ($this->delegate->getMethodAnnotations($method) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { return $this->delegate->getMethodAnnotation($method, $annotationName); } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $annotations = []; foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { return $this->delegate->getPropertyAnnotation($property, $annotationName); } /** * Proxies all methods to the delegate. * * @param string $method * @param mixed[] $args * * @return mixed */ public function __call($method, $args) { return call_user_func_array([$this->delegate, $method], $args); } } An array of Annotations. */ public function getClassAnnotations(ReflectionClass $class); /** * Gets a class annotation. * * @param ReflectionClass $class The ReflectionClass of the class from which * the class annotations should be read. * @param class-string $annotationName The name of the annotation. * * @return T|null The Annotation or NULL, if the requested annotation does not exist. * * @template T */ public function getClassAnnotation(ReflectionClass $class, $annotationName); /** * Gets the annotations applied to a method. * * @param ReflectionMethod $method The ReflectionMethod of the method from which * the annotations should be read. * * @return array An array of Annotations. */ public function getMethodAnnotations(ReflectionMethod $method); /** * Gets a method annotation. * * @param ReflectionMethod $method The ReflectionMethod to read the annotations from. * @param class-string $annotationName The name of the annotation. * * @return T|null The Annotation or NULL, if the requested annotation does not exist. * * @template T */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName); /** * Gets the annotations applied to a property. * * @param ReflectionProperty $property The ReflectionProperty of the property * from which the annotations should be read. * * @return array An array of Annotations. */ public function getPropertyAnnotations(ReflectionProperty $property); /** * Gets a property annotation. * * @param ReflectionProperty $property The ReflectionProperty to read the annotations from. * @param class-string $annotationName The name of the annotation. * * @return T|null The Annotation or NULL, if the requested annotation does not exist. * * @template T */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName); } $data Key-value for properties to be defined in this class. */ public final function __construct(array $data) { foreach ($data as $key => $value) { $this->{$key} = $value; } } /** * Error handler for unknown property accessor in Annotation class. * * @param string $name Unknown property name. * * @throws BadMethodCallException */ public function __get($name) { throw new BadMethodCallException(sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)); } /** * Error handler for unknown property mutator in Annotation class. * * @param string $name Unknown property name. * @param mixed $value Property value. * * @throws BadMethodCallException */ public function __set($name, $value) { throw new BadMethodCallException(sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)); } } parser = new DocParser(); $this->parser->setIgnoreNotImportedAnnotations(\true); } /** * Adds a namespace in which we will look for annotations. * * @param string $namespace * * @return void */ public function addNamespace($namespace) { $this->parser->addNamespace($namespace); } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { return $this->parser->parse($method->getDocComment(), 'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()'); } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { return $this->parser->parse($property->getDocComment(), 'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName()); } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } } > */ private $loadedAnnotations = []; /** @var int[] */ private $loadedFilemtimes = []; public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = \false) { $this->delegate = $reader; $this->cache = $cache; $this->debug = (bool) $debug; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $cacheKey = $class->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class); return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $cacheKey = $class->getName() . '$' . $property->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property); return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $cacheKey = $class->getName() . '#' . $method->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method); return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function clearLoadedAnnotations() : void { $this->loadedAnnotations = []; $this->loadedFilemtimes = []; } /** @return mixed[] */ private function fetchFromCache(string $cacheKey, ReflectionClass $class, string $method, Reflector $reflector) : array { $cacheKey = rawurlencode($cacheKey); $item = $this->cache->getItem($cacheKey); if ($this->debug && !$this->refresh($cacheKey, $class) || !$item->isHit()) { $this->cache->save($item->set($this->delegate->{$method}($reflector))); } return $item->get(); } /** * Used in debug mode to check if the cache is fresh. * * @return bool Returns true if the cache was fresh, or false if the class * being read was modified since writing to the cache. */ private function refresh(string $cacheKey, ReflectionClass $class) : bool { $lastModification = $this->getLastModification($class); if ($lastModification === 0) { return \true; } $item = $this->cache->getItem('[C]' . $cacheKey); if ($item->isHit() && $item->get() >= $lastModification) { return \true; } $this->cache->save($item->set(time())); return \false; } /** * Returns the time the class was last modified, testing traits and parents */ private function getLastModification(ReflectionClass $class) : int { $filename = $class->getFileName(); if (isset($this->loadedFilemtimes[$filename])) { return $this->loadedFilemtimes[$filename]; } $parent = $class->getParentClass(); $lastModification = max(array_merge([$filename ? filemtime($filename) : 0], array_map(function (ReflectionClass $reflectionTrait) : int { return $this->getTraitLastModificationTime($reflectionTrait); }, $class->getTraits()), array_map(function (ReflectionClass $class) : int { return $this->getLastModification($class); }, $class->getInterfaces()), $parent ? [$this->getLastModification($parent)] : [])); assert($lastModification !== \false); return $this->loadedFilemtimes[$filename] = $lastModification; } private function getTraitLastModificationTime(ReflectionClass $reflectionTrait) : int { $fileName = $reflectionTrait->getFileName(); if (isset($this->loadedFilemtimes[$fileName])) { return $this->loadedFilemtimes[$fileName]; } $lastModificationTime = max(array_merge([$fileName ? filemtime($fileName) : 0], array_map(function (ReflectionClass $reflectionTrait) : int { return $this->getTraitLastModificationTime($reflectionTrait); }, $reflectionTrait->getTraits()))); assert($lastModificationTime !== \false); return $this->loadedFilemtimes[$fileName] = $lastModificationTime; } } */ private static $classIdentifiers = [DocLexer::T_IDENTIFIER, DocLexer::T_TRUE, DocLexer::T_FALSE, DocLexer::T_NULL]; /** * The lexer. * * @var DocLexer */ private $lexer; /** * Current target context. * * @var int */ private $target; /** * Doc parser used to collect annotation target. * * @var DocParser */ private static $metadataParser; /** * Flag to control if the current annotation is nested or not. * * @var bool */ private $isNestedAnnotation = \false; /** * Hashmap containing all use-statements that are to be used when parsing * the given doc block. * * @var array */ private $imports = []; /** * This hashmap is used internally to cache results of class_exists() * look-ups. * * @var array */ private $classExists = []; /** * Whether annotations that have not been imported should be ignored. * * @var bool */ private $ignoreNotImportedAnnotations = \false; /** * An array of default namespaces if operating in simple mode. * * @var string[] */ private $namespaces = []; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names must be the raw names as used in the class, not the fully qualified * * @var bool[] indexed by annotation name */ private $ignoredAnnotationNames = []; /** * A list with annotations in namespaced format * that are not causing exceptions when not resolved to an annotation class. * * @var bool[] indexed by namespace name */ private $ignoredAnnotationNamespaces = []; /** @var string */ private $context = ''; /** * Hash-map for caching annotation metadata. * * @var array */ private static $annotationMetadata = [Annotation\Target::class => ['is_annotation' => \true, 'has_constructor' => \true, 'has_named_argument_constructor' => \false, 'properties' => [], 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => 'value', 'attribute_types' => ['value' => ['required' => \false, 'type' => 'array', 'array_type' => 'string', 'value' => 'array']]], Annotation\Attribute::class => ['is_annotation' => \true, 'has_constructor' => \false, 'has_named_argument_constructor' => \false, 'targets_literal' => 'ANNOTATION_ANNOTATION', 'targets' => Target::TARGET_ANNOTATION, 'default_property' => 'name', 'properties' => ['name' => 'name', 'type' => 'type', 'required' => 'required'], 'attribute_types' => ['value' => ['required' => \true, 'type' => 'string', 'value' => 'string'], 'type' => ['required' => \true, 'type' => 'string', 'value' => 'string'], 'required' => ['required' => \false, 'type' => 'boolean', 'value' => 'boolean']]], Annotation\Attributes::class => ['is_annotation' => \true, 'has_constructor' => \false, 'has_named_argument_constructor' => \false, 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => 'value', 'properties' => ['value' => 'value'], 'attribute_types' => ['value' => ['type' => 'array', 'required' => \true, 'array_type' => Annotation\Attribute::class, 'value' => 'array<' . Annotation\Attribute::class . '>']]], Annotation\Enum::class => ['is_annotation' => \true, 'has_constructor' => \true, 'has_named_argument_constructor' => \false, 'targets_literal' => 'ANNOTATION_PROPERTY', 'targets' => Target::TARGET_PROPERTY, 'default_property' => 'value', 'properties' => ['value' => 'value'], 'attribute_types' => ['value' => ['type' => 'array', 'required' => \true], 'literal' => ['type' => 'array', 'required' => \false]]], Annotation\NamedArgumentConstructor::class => ['is_annotation' => \true, 'has_constructor' => \false, 'has_named_argument_constructor' => \false, 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => null, 'properties' => [], 'attribute_types' => []]]; /** * Hash-map for handle types declaration. * * @var array */ private static $typeMap = [ 'float' => 'double', 'bool' => 'boolean', // allow uppercase Boolean in honor of George Boole 'Boolean' => 'boolean', 'int' => 'integer', ]; /** * Constructs a new DocParser. */ public function __construct() { $this->lexer = new DocLexer(); } /** * Sets the annotation names that are ignored during the parsing process. * * The names are supposed to be the raw names as used in the class, not the * fully qualified class names. * * @param bool[] $names indexed by annotation name * * @return void */ public function setIgnoredAnnotationNames(array $names) { $this->ignoredAnnotationNames = $names; } /** * Sets the annotation namespaces that are ignored during the parsing process. * * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name * * @return void */ public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) { $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; } /** * Sets ignore on not-imported annotations. * * @param bool $bool * * @return void */ public function setIgnoreNotImportedAnnotations($bool) { $this->ignoreNotImportedAnnotations = (bool) $bool; } /** * Sets the default namespaces. * * @param string $namespace * * @return void * * @throws RuntimeException */ public function addNamespace($namespace) { if ($this->imports) { throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); } $this->namespaces[] = $namespace; } /** * Sets the imports. * * @param array $imports * * @return void * * @throws RuntimeException */ public function setImports(array $imports) { if ($this->namespaces) { throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); } $this->imports = $imports; } /** * Sets current target context as bitmask. * * @param int $target * * @return void */ public function setTarget($target) { $this->target = $target; } /** * Parses the given docblock string for annotations. * * @param string $input The docblock string to parse. * @param string $context The parsing context. * * @phpstan-return list Array of annotations. If no annotations are found, an empty array is returned. * * @throws AnnotationException * @throws ReflectionException */ public function parse($input, $context = '') { $pos = $this->findInitialTokenPosition($input); if ($pos === null) { return []; } $this->context = $context; $this->lexer->setInput(trim(substr($input, $pos), '* /')); $this->lexer->moveNext(); return $this->Annotations(); } /** * Finds the first valid annotation * * @param string $input The docblock string to parse */ private function findInitialTokenPosition($input) : ?int { $pos = 0; // search for first valid annotation while (($pos = strpos($input, '@', $pos)) !== \false) { $preceding = substr($input, $pos - 1, 1); // if the @ is preceded by a space, a tab or * it is valid if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { return $pos; } $pos++; } return null; } /** * Attempts to match the given token with the current lookahead token. * If they match, updates the lookahead token; otherwise raises a syntax error. * * @param int $token Type of token. * * @return bool True if tokens match; false otherwise. * * @throws AnnotationException */ private function match(int $token) : bool { if (!$this->lexer->isNextToken($token)) { throw $this->syntaxError($this->lexer->getLiteral($token)); } return $this->lexer->moveNext(); } /** * Attempts to match the current lookahead token with any of the given tokens. * * If any of them matches, this method updates the lookahead token; otherwise * a syntax error is raised. * * @phpstan-param list $tokens * * @throws AnnotationException */ private function matchAny(array $tokens) : bool { if (!$this->lexer->isNextTokenAny($tokens)) { throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens))); } return $this->lexer->moveNext(); } /** * Generates a new syntax error. * * @param string $expected Expected string. * @param mixed[]|null $token Optional token. */ private function syntaxError(string $expected, ?array $token = null) : AnnotationException { if ($token === null) { $token = $this->lexer->lookahead; } $message = sprintf('Expected %s, got ', $expected); $message .= $this->lexer->lookahead === null ? 'end of string' : sprintf("'%s' at position %s", $token['value'], $token['position']); if (strlen($this->context)) { $message .= ' in ' . $this->context; } $message .= '.'; return AnnotationException::syntaxError($message); } /** * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism * but uses the {@link AnnotationRegistry} to load classes. * * @param class-string $fqcn */ private function classExists(string $fqcn) : bool { if (isset($this->classExists[$fqcn])) { return $this->classExists[$fqcn]; } // first check if the class already exists, maybe loaded through another AnnotationReader if (class_exists($fqcn, \false)) { return $this->classExists[$fqcn] = \true; } // final check, does this class exist? return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); } /** * Collects parsing metadata for a given annotation class * * @param class-string $name The annotation name * * @throws AnnotationException * @throws ReflectionException */ private function collectAnnotationMetadata(string $name) : void { if (self::$metadataParser === null) { self::$metadataParser = new self(); self::$metadataParser->setIgnoreNotImportedAnnotations(\true); self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); self::$metadataParser->setImports(['enum' => Enum::class, 'target' => Target::class, 'attribute' => Attribute::class, 'attributes' => Attributes::class, 'namedargumentconstructor' => NamedArgumentConstructor::class]); // Make sure that annotations from metadata are loaded class_exists(Enum::class); class_exists(Target::class); class_exists(Attribute::class); class_exists(Attributes::class); class_exists(NamedArgumentConstructor::class); } $class = new ReflectionClass($name); $docComment = $class->getDocComment(); // Sets default values for annotation metadata $constructor = $class->getConstructor(); $metadata = ['default_property' => null, 'has_constructor' => $constructor !== null && $constructor->getNumberOfParameters() > 0, 'constructor_args' => [], 'properties' => [], 'property_types' => [], 'attribute_types' => [], 'targets_literal' => null, 'targets' => Target::TARGET_ALL, 'is_annotation' => strpos($docComment, '@Annotation') !== \false]; $metadata['has_named_argument_constructor'] = $metadata['has_constructor'] && $class->implementsInterface(NamedArgumentConstructorAnnotation::class); // verify that the class is really meant to be an annotation if ($metadata['is_annotation']) { self::$metadataParser->setTarget(Target::TARGET_CLASS); foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { if ($annotation instanceof Target) { $metadata['targets'] = $annotation->targets; $metadata['targets_literal'] = $annotation->literal; continue; } if ($annotation instanceof NamedArgumentConstructor) { $metadata['has_named_argument_constructor'] = $metadata['has_constructor']; if ($metadata['has_named_argument_constructor']) { // choose the first argument as the default property $metadata['default_property'] = $constructor->getParameters()[0]->getName(); } } if (!$annotation instanceof Attributes) { continue; } foreach ($annotation->value as $attribute) { $this->collectAttributeTypeMetadata($metadata, $attribute); } } // if not has a constructor will inject values into public properties if ($metadata['has_constructor'] === \false) { // collect all public properties foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { $metadata['properties'][$property->name] = $property->name; $propertyComment = $property->getDocComment(); if ($propertyComment === \false) { continue; } $attribute = new Attribute(); $attribute->required = strpos($propertyComment, '@Required') !== \false; $attribute->name = $property->name; $attribute->type = strpos($propertyComment, '@var') !== \false && preg_match('/@var\\s+([^\\s]+)/', $propertyComment, $matches) ? $matches[1] : 'mixed'; $this->collectAttributeTypeMetadata($metadata, $attribute); // checks if the property has @Enum if (strpos($propertyComment, '@Enum') === \false) { continue; } $context = 'property ' . $class->name . '::$' . $property->name; self::$metadataParser->setTarget(Target::TARGET_PROPERTY); foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { if (!$annotation instanceof Enum) { continue; } $metadata['enum'][$property->name]['value'] = $annotation->value; $metadata['enum'][$property->name]['literal'] = !empty($annotation->literal) ? $annotation->literal : $annotation->value; } } // choose the first property as default property $metadata['default_property'] = reset($metadata['properties']); } elseif ($metadata['has_named_argument_constructor']) { foreach ($constructor->getParameters() as $parameter) { if ($parameter->isVariadic()) { break; } $metadata['constructor_args'][$parameter->getName()] = ['position' => $parameter->getPosition(), 'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null]; } } } self::$annotationMetadata[$name] = $metadata; } /** * Collects parsing metadata for a given attribute. * * @param mixed[] $metadata */ private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute) : void { // handle internal type declaration $type = self::$typeMap[$attribute->type] ?? $attribute->type; // handle the case if the property type is mixed if ($type === 'mixed') { return; } // Evaluate type $pos = strpos($type, '<'); if ($pos !== \false) { // Checks if the property has array $arrayType = substr($type, $pos + 1, -1); $type = 'array'; if (isset(self::$typeMap[$arrayType])) { $arrayType = self::$typeMap[$arrayType]; } $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; } else { // Checks if the property has type[] $pos = strrpos($type, '['); if ($pos !== \false) { $arrayType = substr($type, 0, $pos); $type = 'array'; if (isset(self::$typeMap[$arrayType])) { $arrayType = self::$typeMap[$arrayType]; } $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; } } $metadata['attribute_types'][$attribute->name]['type'] = $type; $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; } /** * Annotations ::= Annotation {[ "*" ]* [Annotation]}* * * @phpstan-return list * * @throws AnnotationException * @throws ReflectionException */ private function Annotations() : array { $annotations = []; while ($this->lexer->lookahead !== null) { if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) { $this->lexer->moveNext(); continue; } // make sure the @ is preceded by non-catchable pattern if ($this->lexer->token !== null && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) { $this->lexer->moveNext(); continue; } // make sure the @ is followed by either a namespace separator, or // an identifier token $peek = $this->lexer->glimpse(); if ($peek === null || $peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && !in_array($peek['type'], self::$classIdentifiers, \true) || $peek['position'] !== $this->lexer->lookahead['position'] + 1) { $this->lexer->moveNext(); continue; } $this->isNestedAnnotation = \false; $annot = $this->Annotation(); if ($annot === \false) { continue; } $annotations[] = $annot; } return $annotations; } /** * Annotation ::= "@" AnnotationName MethodCall * AnnotationName ::= QualifiedName | SimpleName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * NameSpacePart ::= identifier | null | false | true * SimpleName ::= identifier | null | false | true * * @return object|false False if it is not a valid annotation. * * @throws AnnotationException * @throws ReflectionException */ private function Annotation() { $this->match(DocLexer::T_AT); // check if we have an annotation $name = $this->Identifier(); if ($this->lexer->isNextToken(DocLexer::T_MINUS) && $this->lexer->nextTokenIsAdjacent()) { // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded return \false; } // only process names which are not fully qualified, yet // fully qualified names must start with a \ $originalName = $name; if ($name[0] !== '\\') { $pos = strpos($name, '\\'); $alias = $pos === \false ? $name : substr($name, 0, $pos); $found = \false; $loweredAlias = strtolower($alias); if ($this->namespaces) { foreach ($this->namespaces as $namespace) { if ($this->classExists($namespace . '\\' . $name)) { $name = $namespace . '\\' . $name; $found = \true; break; } } } elseif (isset($this->imports[$loweredAlias])) { $namespace = ltrim($this->imports[$loweredAlias], '\\'); $name = $pos !== \false ? $namespace . substr($name, $pos) : $namespace; $found = $this->classExists($name); } elseif (!isset($this->ignoredAnnotationNames[$name]) && isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)) { $name = $this->imports['__NAMESPACE__'] . '\\' . $name; $found = \true; } elseif (!isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { $found = \true; } if (!$found) { if ($this->isIgnoredAnnotation($name)) { return \false; } throw AnnotationException::semanticalError(sprintf(<<<'EXCEPTION' The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation? EXCEPTION , $name, $this->context)); } } $name = ltrim($name, '\\'); if (!$this->classExists($name)) { throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); } // at this point, $name contains the fully qualified class name of the // annotation, and it is also guaranteed that this class exists, and // that it is loaded // collects the metadata annotation only if there is not yet if (!isset(self::$annotationMetadata[$name])) { $this->collectAnnotationMetadata($name); } // verify that the class is really meant to be an annotation and not just any ordinary class if (self::$annotationMetadata[$name]['is_annotation'] === \false) { if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) { return \false; } throw AnnotationException::semanticalError(sprintf(<<<'EXCEPTION' The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s. EXCEPTION , $name, $name, $originalName, $this->context)); } //if target is nested annotation $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; // Next will be nested $this->isNestedAnnotation = \true; //if annotation does not support current target if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) { throw AnnotationException::semanticalError(sprintf(<<<'EXCEPTION' Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s. EXCEPTION , $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'])); } $arguments = $this->MethodCall(); $values = $this->resolvePositionalValues($arguments, $name); if (isset(self::$annotationMetadata[$name]['enum'])) { // checks all declared attributes foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { // checks if the attribute is a valid enumerator if (isset($values[$property]) && !in_array($values[$property], $enum['value'])) { throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]); } } } // checks all declared attributes foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { if ($property === self::$annotationMetadata[$name]['default_property'] && !isset($values[$property]) && isset($values['value'])) { $property = 'value'; } // handle a not given attribute or null value if (!isset($values[$property])) { if ($type['required']) { throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) ' . $type['value']); } continue; } if ($type['type'] === 'array') { // handle the case of a single value if (!is_array($values[$property])) { $values[$property] = [$values[$property]]; } // checks if the attribute has array type declaration, such as "array" if (isset($type['array_type'])) { foreach ($values[$property] as $item) { if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) { throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's', $item); } } } } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) { throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'a(n) ' . $type['value'], $values[$property]); } } if (self::$annotationMetadata[$name]['has_named_argument_constructor']) { if (PHP_VERSION_ID >= 80000) { foreach ($values as $property => $value) { if (!isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { throw AnnotationException::creationError(sprintf(<<<'EXCEPTION' The annotation @%s declared on %s does not have a property named "%s" that can be set through its named arguments constructor. Available named arguments: %s EXCEPTION , $originalName, $this->context, $property, implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])))); } } return $this->instantiateAnnotiation($originalName, $this->context, $name, $values); } $positionalValues = []; foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { $positionalValues[$parameter['position']] = $parameter['default']; } foreach ($values as $property => $value) { if (!isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { throw AnnotationException::creationError(sprintf(<<<'EXCEPTION' The annotation @%s declared on %s does not have a property named "%s" that can be set through its named arguments constructor. Available named arguments: %s EXCEPTION , $originalName, $this->context, $property, implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])))); } $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value; } return $this->instantiateAnnotiation($originalName, $this->context, $name, $positionalValues); } // check if the annotation expects values via the constructor, // or directly injected into public properties if (self::$annotationMetadata[$name]['has_constructor'] === \true) { return $this->instantiateAnnotiation($originalName, $this->context, $name, [$values]); } $instance = $this->instantiateAnnotiation($originalName, $this->context, $name, []); foreach ($values as $property => $value) { if (!isset(self::$annotationMetadata[$name]['properties'][$property])) { if ($property !== 'value') { throw AnnotationException::creationError(sprintf(<<<'EXCEPTION' The annotation @%s declared on %s does not have a property named "%s". Available properties: %s EXCEPTION , $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']))); } // handle the case if the property has no annotations $property = self::$annotationMetadata[$name]['default_property']; if (!$property) { throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values))); } } $instance->{$property} = $value; } return $instance; } /** * MethodCall ::= ["(" [Values] ")"] * * @return mixed[] * * @throws AnnotationException * @throws ReflectionException */ private function MethodCall() : array { $values = []; if (!$this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { return $values; } $this->match(DocLexer::T_OPEN_PARENTHESIS); if (!$this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); return $values; } /** * Values ::= Array | Value {"," Value}* [","] * * @return mixed[] * * @throws AnnotationException * @throws ReflectionException */ private function Values() : array { $values = [$this->Value()]; while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { $this->match(DocLexer::T_COMMA); if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { break; } $token = $this->lexer->lookahead; $value = $this->Value(); $values[] = $value; } $namedArguments = []; $positionalArguments = []; foreach ($values as $k => $value) { if (is_object($value) && $value instanceof stdClass) { $namedArguments[$value->name] = $value->value; } else { $positionalArguments[$k] = $value; } } return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments]; } /** * Constant ::= integer | string | float | boolean * * @return mixed * * @throws AnnotationException */ private function Constant() { $identifier = $this->Identifier(); if (!defined($identifier) && strpos($identifier, '::') !== \false && $identifier[0] !== '\\') { [$className, $const] = explode('::', $identifier); $pos = strpos($className, '\\'); $alias = $pos === \false ? $className : substr($className, 0, $pos); $found = \false; $loweredAlias = strtolower($alias); switch (\true) { case !empty($this->namespaces): foreach ($this->namespaces as $ns) { if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { $className = $ns . '\\' . $className; $found = \true; break; } } break; case isset($this->imports[$loweredAlias]): $found = \true; $className = $pos !== \false ? $this->imports[$loweredAlias] . substr($className, $pos) : $this->imports[$loweredAlias]; break; default: if (isset($this->imports['__NAMESPACE__'])) { $ns = $this->imports['__NAMESPACE__']; if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { $className = $ns . '\\' . $className; $found = \true; } } break; } if ($found) { $identifier = $className . '::' . $const; } } /** * Checks if identifier ends with ::class and remove the leading backslash if it exists. */ if ($this->identifierEndsWithClassConstant($identifier) && !$this->identifierStartsWithBackslash($identifier)) { return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier)); } if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) { return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1); } if (!defined($identifier)) { throw AnnotationException::semanticalErrorConstants($identifier, $this->context); } return constant($identifier); } private function identifierStartsWithBackslash(string $identifier) : bool { return $identifier[0] === '\\'; } private function identifierEndsWithClassConstant(string $identifier) : bool { return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class'); } /** @return int|false */ private function getClassConstantPositionInIdentifier(string $identifier) { return stripos($identifier, '::class'); } /** * Identifier ::= string * * @throws AnnotationException */ private function Identifier() : string { // check if we have an annotation if (!$this->lexer->isNextTokenAny(self::$classIdentifiers)) { throw $this->syntaxError('namespace separator or identifier'); } $this->lexer->moveNext(); $className = $this->lexer->token['value']; while ($this->lexer->lookahead !== null && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $className .= '\\' . $this->lexer->token['value']; } return $className; } /** * Value ::= PlainValue | FieldAssignment * * @return mixed * * @throws AnnotationException * @throws ReflectionException */ private function Value() { $peek = $this->lexer->glimpse(); if ($peek['type'] === DocLexer::T_EQUALS) { return $this->FieldAssignment(); } return $this->PlainValue(); } /** * PlainValue ::= integer | string | float | boolean | Array | Annotation * * @return mixed * * @throws AnnotationException * @throws ReflectionException */ private function PlainValue() { if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { return $this->Arrayx(); } if ($this->lexer->isNextToken(DocLexer::T_AT)) { return $this->Annotation(); } if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { return $this->Constant(); } switch ($this->lexer->lookahead['type']) { case DocLexer::T_STRING: $this->match(DocLexer::T_STRING); return $this->lexer->token['value']; case DocLexer::T_INTEGER: $this->match(DocLexer::T_INTEGER); return (int) $this->lexer->token['value']; case DocLexer::T_FLOAT: $this->match(DocLexer::T_FLOAT); return (float) $this->lexer->token['value']; case DocLexer::T_TRUE: $this->match(DocLexer::T_TRUE); return \true; case DocLexer::T_FALSE: $this->match(DocLexer::T_FALSE); return \false; case DocLexer::T_NULL: $this->match(DocLexer::T_NULL); return null; default: throw $this->syntaxError('PlainValue'); } } /** * FieldAssignment ::= FieldName "=" PlainValue * FieldName ::= identifier * * @throws AnnotationException * @throws ReflectionException */ private function FieldAssignment() : stdClass { $this->match(DocLexer::T_IDENTIFIER); $fieldName = $this->lexer->token['value']; $this->match(DocLexer::T_EQUALS); $item = new stdClass(); $item->name = $fieldName; $item->value = $this->PlainValue(); return $item; } /** * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" * * @return mixed[] * * @throws AnnotationException * @throws ReflectionException */ private function Arrayx() : array { $array = $values = []; $this->match(DocLexer::T_OPEN_CURLY_BRACES); // If the array is empty, stop parsing and return. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { $this->match(DocLexer::T_CLOSE_CURLY_BRACES); return $array; } $values[] = $this->ArrayEntry(); while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { $this->match(DocLexer::T_COMMA); // optional trailing comma if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { break; } $values[] = $this->ArrayEntry(); } $this->match(DocLexer::T_CLOSE_CURLY_BRACES); foreach ($values as $value) { [$key, $val] = $value; if ($key !== null) { $array[$key] = $val; } else { $array[] = $val; } } return $array; } /** * ArrayEntry ::= Value | KeyValuePair * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant * Key ::= string | integer | Constant * * @phpstan-return array{mixed, mixed} * * @throws AnnotationException * @throws ReflectionException */ private function ArrayEntry() : array { $peek = $this->lexer->glimpse(); if ($peek['type'] === DocLexer::T_EQUALS || $peek['type'] === DocLexer::T_COLON) { if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { $key = $this->Constant(); } else { $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]); $key = $this->lexer->token['value']; } $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]); return [$key, $this->PlainValue()]; } return [null, $this->Value()]; } /** * Checks whether the given $name matches any ignored annotation name or namespace */ private function isIgnoredAnnotation(string $name) : bool { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { return \true; } foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) { return \true; } } return \false; } /** * Resolve positional arguments (without name) to named ones * * @param array $arguments * * @return array */ private function resolvePositionalValues(array $arguments, string $name) : array { $positionalArguments = $arguments['positional_arguments'] ?? []; $values = $arguments['named_arguments'] ?? []; if (self::$annotationMetadata[$name]['has_named_argument_constructor'] && self::$annotationMetadata[$name]['default_property'] !== null) { // We must ensure that we don't have positional arguments after named ones $positions = array_keys($positionalArguments); $lastPosition = null; foreach ($positions as $position) { if ($lastPosition === null && $position !== 0 || $lastPosition !== null && $position !== $lastPosition + 1) { throw $this->syntaxError('Positional arguments after named arguments is not allowed'); } $lastPosition = $position; } foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { $position = $parameter['position']; if (isset($values[$property]) || !isset($positionalArguments[$position])) { continue; } $values[$property] = $positionalArguments[$position]; } } else { if (count($positionalArguments) > 0 && !isset($values['value'])) { if (count($positionalArguments) === 1) { $value = array_pop($positionalArguments); } else { $value = array_values($positionalArguments); } $values['value'] = $value; } } return $values; } /** * Try to instantiate the annotation and catch and process any exceptions related to failure * * @param class-string $name * @param array $arguments * * @return object * * @throws AnnotationException */ private function instantiateAnnotiation(string $originalName, string $context, string $name, array $arguments) { try { return new $name(...$arguments); } catch (Throwable $exception) { throw AnnotationException::creationError(sprintf('An error occurred while instantiating the annotation @%s declared on %s: "%s".', $originalName, $context, $exception->getMessage()), $exception); } } } > */ private $loadedAnnotations = []; /** @var int[] */ private $loadedFilemtimes = []; /** @param bool $debug */ public function __construct(Reader $reader, Cache $cache, $debug = \false) { $this->delegate = $reader; $this->cache = $cache; $this->debug = (bool) $debug; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $cacheKey = $class->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class); if ($annots === \false) { $annots = $this->delegate->getClassAnnotations($class); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $cacheKey = $class->getName() . '$' . $property->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class); if ($annots === \false) { $annots = $this->delegate->getPropertyAnnotations($property); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $cacheKey = $class->getName() . '#' . $method->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class); if ($annots === \false) { $annots = $this->delegate->getMethodAnnotations($method); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * Clears loaded annotations. * * @return void */ public function clearLoadedAnnotations() { $this->loadedAnnotations = []; $this->loadedFilemtimes = []; } /** * Fetches a value from the cache. * * @param string $cacheKey The cache key. * * @return mixed The cached value or false when the value is not in cache. */ private function fetchFromCache($cacheKey, ReflectionClass $class) { $data = $this->cache->fetch($cacheKey); if ($data !== \false) { if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) { return $data; } } return \false; } /** * Saves a value to the cache. * * @param string $cacheKey The cache key. * @param mixed $value The value. * * @return void */ private function saveToCache($cacheKey, $value) { $this->cache->save($cacheKey, $value); if (!$this->debug) { return; } $this->cache->save('[C]' . $cacheKey, time()); } /** * Checks if the cache is fresh. * * @param string $cacheKey * * @return bool */ private function isCacheFresh($cacheKey, ReflectionClass $class) { $lastModification = $this->getLastModification($class); if ($lastModification === 0) { return \true; } return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; } /** * Returns the time the class was last modified, testing traits and parents */ private function getLastModification(ReflectionClass $class) : int { $filename = $class->getFileName(); if (isset($this->loadedFilemtimes[$filename])) { return $this->loadedFilemtimes[$filename]; } $parent = $class->getParentClass(); $lastModification = max(array_merge([$filename ? filemtime($filename) : 0], array_map(function (ReflectionClass $reflectionTrait) : int { return $this->getTraitLastModificationTime($reflectionTrait); }, $class->getTraits()), array_map(function (ReflectionClass $class) : int { return $this->getLastModification($class); }, $class->getInterfaces()), $parent ? [$this->getLastModification($parent)] : [])); assert($lastModification !== \false); return $this->loadedFilemtimes[$filename] = $lastModification; } private function getTraitLastModificationTime(ReflectionClass $reflectionTrait) : int { $fileName = $reflectionTrait->getFileName(); if (isset($this->loadedFilemtimes[$fileName])) { return $this->loadedFilemtimes[$fileName]; } $lastModificationTime = max(array_merge([$fileName ? filemtime($fileName) : 0], array_map(function (ReflectionClass $reflectionTrait) : int { return $this->getTraitLastModificationTime($reflectionTrait); }, $reflectionTrait->getTraits()))); assert($lastModificationTime !== \false); return $this->loadedFilemtimes[$fileName] = $lastModificationTime; } } */ private $tokens; /** * The number of tokens. * * @var int */ private $numTokens; /** * The current array pointer. * * @var int */ private $pointer = 0; /** @param string $contents */ public function __construct($contents) { $this->tokens = token_get_all($contents); // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a // docblock. If the first thing in the file is a class without a doc block this would cause calls to // getDocBlock() on said class to return our long lost doc_comment. Argh. // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least // it's harmless to us. token_get_all("numTokens = count($this->tokens); } /** * Gets the next non whitespace and non comment token. * * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. * If FALSE then only whitespace and normal comments are skipped. * * @return mixed[]|string|null The token if exists, null otherwise. */ public function next($docCommentIsComment = \true) { for ($i = $this->pointer; $i < $this->numTokens; $i++) { $this->pointer++; if ($this->tokens[$i][0] === T_WHITESPACE || $this->tokens[$i][0] === T_COMMENT || $docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) { continue; } return $this->tokens[$i]; } return null; } /** * Parses a single use statement. * * @return array A list with all found class names for a use statement. */ public function parseUseStatement() { $groupRoot = ''; $class = ''; $alias = ''; $statements = []; $explicitAlias = \false; while ($token = $this->next()) { if (!$explicitAlias && $token[0] === T_STRING) { $class .= $token[1]; $alias = $token[1]; } elseif ($explicitAlias && $token[0] === T_STRING) { $alias = $token[1]; } elseif (PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)) { $class .= $token[1]; $classSplit = explode('\\', $token[1]); $alias = $classSplit[count($classSplit) - 1]; } elseif ($token[0] === T_NS_SEPARATOR) { $class .= '\\'; $alias = ''; } elseif ($token[0] === T_AS) { $explicitAlias = \true; $alias = ''; } elseif ($token === ',') { $statements[strtolower($alias)] = $groupRoot . $class; $class = ''; $alias = ''; $explicitAlias = \false; } elseif ($token === ';') { $statements[strtolower($alias)] = $groupRoot . $class; break; } elseif ($token === '{') { $groupRoot = $class; $class = ''; } elseif ($token === '}') { continue; } else { break; } } return $statements; } /** * Gets all use statements. * * @param string $namespaceName The namespace name of the reflected class. * * @return array A list with all found use statements. */ public function parseUseStatements($namespaceName) { $statements = []; while ($token = $this->next()) { if ($token[0] === T_USE) { $statements = array_merge($statements, $this->parseUseStatement()); continue; } if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) { continue; } // Get fresh array for new namespace. This is to prevent the parser to collect the use statements // for a previous namespace with the same name. This is the case if a namespace is defined twice // or if a namespace with the same name is commented out. $statements = []; } return $statements; } /** * Gets the namespace. * * @return string The found namespace. */ public function parseNamespace() { $name = ''; while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED))) { $name .= $token[1]; } return $name; } /** * Gets the class name. * * @return string The found class name. */ public function parseClass() { // Namespaces and class names are tokenized the same: T_STRINGs // separated by T_NS_SEPARATOR so we can use one function to provide // both. return $this->parseNamespace(); } } \true, 'Attribute' => \true, 'Attributes' => \true, /* Can we enable this? 'Enum' => true, */ 'Required' => \true, 'Target' => \true, 'NamedArgumentConstructor' => \true, ]; private const WidelyUsedNonStandard = ['fix' => \true, 'fixme' => \true, 'override' => \true]; private const PhpDocumentor1 = ['abstract' => \true, 'access' => \true, 'code' => \true, 'deprec' => \true, 'endcode' => \true, 'exception' => \true, 'final' => \true, 'ingroup' => \true, 'inheritdoc' => \true, 'inheritDoc' => \true, 'magic' => \true, 'name' => \true, 'private' => \true, 'static' => \true, 'staticvar' => \true, 'staticVar' => \true, 'toc' => \true, 'tutorial' => \true, 'throw' => \true]; private const PhpDocumentor2 = [ 'api' => \true, 'author' => \true, 'category' => \true, 'copyright' => \true, 'deprecated' => \true, 'example' => \true, 'filesource' => \true, 'global' => \true, 'ignore' => \true, /* Can we enable this? 'index' => true, */ 'internal' => \true, 'license' => \true, 'link' => \true, 'method' => \true, 'package' => \true, 'param' => \true, 'property' => \true, 'property-read' => \true, 'property-write' => \true, 'return' => \true, 'see' => \true, 'since' => \true, 'source' => \true, 'subpackage' => \true, 'throws' => \true, 'todo' => \true, 'TODO' => \true, 'usedby' => \true, 'uses' => \true, 'var' => \true, 'version' => \true, ]; private const PHPUnit = ['author' => \true, 'after' => \true, 'afterClass' => \true, 'backupGlobals' => \true, 'backupStaticAttributes' => \true, 'before' => \true, 'beforeClass' => \true, 'codeCoverageIgnore' => \true, 'codeCoverageIgnoreStart' => \true, 'codeCoverageIgnoreEnd' => \true, 'covers' => \true, 'coversDefaultClass' => \true, 'coversNothing' => \true, 'dataProvider' => \true, 'depends' => \true, 'doesNotPerformAssertions' => \true, 'expectedException' => \true, 'expectedExceptionCode' => \true, 'expectedExceptionMessage' => \true, 'expectedExceptionMessageRegExp' => \true, 'group' => \true, 'large' => \true, 'medium' => \true, 'preserveGlobalState' => \true, 'requires' => \true, 'runTestsInSeparateProcesses' => \true, 'runInSeparateProcess' => \true, 'small' => \true, 'test' => \true, 'testdox' => \true, 'testWith' => \true, 'ticket' => \true, 'uses' => \true]; private const PhpCheckStyle = ['SuppressWarnings' => \true]; private const PhpStorm = ['noinspection' => \true]; private const PEAR = ['package_version' => \true]; private const PlainUML = ['startuml' => \true, 'enduml' => \true]; private const Symfony = ['experimental' => \true]; private const PhpCodeSniffer = ['codingStandardsIgnoreStart' => \true, 'codingStandardsIgnoreEnd' => \true]; private const SlevomatCodingStandard = ['phpcsSuppress' => \true]; private const Phan = ['suppress' => \true]; private const Rector = ['noRector' => \true]; private const StaticAnalysis = [ // PHPStan, Psalm 'extends' => \true, 'implements' => \true, 'readonly' => \true, 'template' => \true, 'use' => \true, // Psalm 'pure' => \true, 'immutable' => \true, ]; public const LIST = self::Reserved + self::WidelyUsedNonStandard + self::PhpDocumentor1 + self::PhpDocumentor2 + self::PHPUnit + self::PhpCheckStyle + self::PhpStorm + self::PEAR + self::PlainUML + self::Symfony + self::SlevomatCodingStandard + self::PhpCodeSniffer + self::Phan + self::Rector + self::StaticAnalysis; private function __construct() { } } */ private static $globalImports = ['ignoreannotation' => Annotation\IgnoreAnnotation::class]; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array */ private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array */ private static $globalIgnoredNamespaces = []; /** * Add a new annotation to the globally ignored annotation names with regard to exception handling. * * @param string $name */ public static function addGlobalIgnoredName($name) { self::$globalIgnoredNames[$name] = \true; } /** * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. * * @param string $namespace */ public static function addGlobalIgnoredNamespace($namespace) { self::$globalIgnoredNamespaces[$namespace] = \true; } /** * Annotations parser. * * @var DocParser */ private $parser; /** * Annotations parser used to collect parsing metadata. * * @var DocParser */ private $preParser; /** * PHP parser used to collect imports. * * @var PhpParser */ private $phpParser; /** * In-memory cache mechanism to store imported annotations per class. * * @psalm-var array<'class'|'function', array>> */ private $imports = []; /** * In-memory cache mechanism to store ignored annotations per class. * * @psalm-var array<'class'|'function', array>> */ private $ignoredAnnotationNames = []; /** * Initializes a new AnnotationReader. * * @throws AnnotationException */ public function __construct(?DocParser $parser = null) { if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || ini_get('opcache.save_comments') === '0')) { throw AnnotationException::optimizerPlusSaveComments(); } if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) { throw AnnotationException::optimizerPlusSaveComments(); } // Make sure that the IgnoreAnnotation annotation is loaded class_exists(IgnoreAnnotation::class); $this->parser = $parser ?: new DocParser(); $this->preParser = new DocParser(); $this->preParser->setImports(self::$globalImports); $this->preParser->setIgnoreNotImportedAnnotations(\true); $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); $this->phpParser = new PhpParser(); } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $this->parser->setTarget(Target::TARGET_CLASS); $this->parser->setImports($this->getImports($class)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $context = 'property ' . $class->getName() . '::$' . $property->getName(); $this->parser->setTarget(Target::TARGET_PROPERTY); $this->parser->setImports($this->getPropertyImports($property)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($property->getDocComment(), $context); } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; $this->parser->setTarget(Target::TARGET_METHOD); $this->parser->setImports($this->getMethodImports($method)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($method->getDocComment(), $context); } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Gets the annotations applied to a function. * * @phpstan-return list An array of Annotations. */ public function getFunctionAnnotations(ReflectionFunction $function) : array { $context = 'function ' . $function->getName(); $this->parser->setTarget(Target::TARGET_FUNCTION); $this->parser->setImports($this->getImports($function)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($function->getDocComment(), $context); } /** * Gets a function annotation. * * @return object|null The Annotation or NULL, if the requested annotation does not exist. */ public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName) { $annotations = $this->getFunctionAnnotations($function); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Returns the ignored annotations for the given class or function. * * @param ReflectionClass|ReflectionFunction $reflection * * @return array */ private function getIgnoredAnnotationNames($reflection) : array { $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; $name = $reflection->getName(); if (isset($this->ignoredAnnotationNames[$type][$name])) { return $this->ignoredAnnotationNames[$type][$name]; } $this->collectParsingMetadata($reflection); return $this->ignoredAnnotationNames[$type][$name]; } /** * Retrieves imports for a class or a function. * * @param ReflectionClass|ReflectionFunction $reflection * * @return array */ private function getImports($reflection) : array { $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; $name = $reflection->getName(); if (isset($this->imports[$type][$name])) { return $this->imports[$type][$name]; } $this->collectParsingMetadata($reflection); return $this->imports[$type][$name]; } /** * Retrieves imports for methods. * * @return array */ private function getMethodImports(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $classImports = $this->getImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if (!$trait->hasMethod($method->getName()) || $trait->getFileName() !== $method->getFileName()) { continue; } $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); } return array_merge($classImports, $traitImports); } /** * Retrieves imports for properties. * * @return array */ private function getPropertyImports(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $classImports = $this->getImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if (!$trait->hasProperty($property->getName())) { continue; } $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); } return array_merge($classImports, $traitImports); } /** * Collects parsing metadata for a given class or function. * * @param ReflectionClass|ReflectionFunction $reflection */ private function collectParsingMetadata($reflection) : void { $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; $name = $reflection->getName(); $ignoredAnnotationNames = self::$globalIgnoredNames; $annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name); foreach ($annotations as $annotation) { if (!$annotation instanceof IgnoreAnnotation) { continue; } foreach ($annotation->names as $annot) { $ignoredAnnotationNames[$annot] = \true; } } $this->imports[$type][$name] = array_merge(self::$globalImports, $this->phpParser->parseUseStatements($reflection), ['__NAMESPACE__' => $reflection->getNamespaceName(), 'self' => $name]); $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames; } } |null $dirs */ public static function registerAutoloadNamespace(string $namespace, $dirs = null) : void { self::$autoloadNamespaces[$namespace] = $dirs; } /** * Registers multiple namespaces. * * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. * * @param string[][]|string[]|null[] $namespaces indexed by namespace name */ public static function registerAutoloadNamespaces(array $namespaces) : void { self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); } /** * Registers an autoloading callable for annotations, much like spl_autoload_register(). * * NOTE: These class loaders HAVE to be silent when a class was not found! * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. */ public static function registerLoader(callable $callable) : void { // Reset our static cache now that we have a new loader to work with self::$failedToAutoload = []; self::$loaders[] = $callable; } /** * Registers an autoloading callable for annotations, if it is not already registered * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. */ public static function registerUniqueLoader(callable $callable) : void { if (in_array($callable, self::$loaders, \true)) { return; } self::registerLoader($callable); } /** * Autoloads an annotation class silently. */ public static function loadAnnotationClass(string $class) : bool { if (class_exists($class, \false)) { return \true; } if (array_key_exists($class, self::$failedToAutoload)) { return \false; } foreach (self::$autoloadNamespaces as $namespace => $dirs) { if (strpos($class, $namespace) !== 0) { continue; } $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; if ($dirs === null) { $path = stream_resolve_include_path($file); if ($path) { require $path; return \true; } } else { foreach ((array) $dirs as $dir) { if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { require $dir . DIRECTORY_SEPARATOR . $file; return \true; } } } } foreach (self::$loaders as $loader) { if ($loader($class) === \true) { return \true; } } if (self::$loaders === [] && self::$autoloadNamespaces === [] && self::$registerFileUsed === \false && class_exists($class)) { return \true; } self::$failedToAutoload[$class] = null; return \false; } } */ final class DocLexer extends AbstractLexer { public const T_NONE = 1; public const T_INTEGER = 2; public const T_STRING = 3; public const T_FLOAT = 4; // All tokens that are also identifiers should be >= 100 public const T_IDENTIFIER = 100; public const T_AT = 101; public const T_CLOSE_CURLY_BRACES = 102; public const T_CLOSE_PARENTHESIS = 103; public const T_COMMA = 104; public const T_EQUALS = 105; public const T_FALSE = 106; public const T_NAMESPACE_SEPARATOR = 107; public const T_OPEN_CURLY_BRACES = 108; public const T_OPEN_PARENTHESIS = 109; public const T_TRUE = 110; public const T_NULL = 111; public const T_COLON = 112; public const T_MINUS = 113; /** @var array */ protected $noCase = ['@' => self::T_AT, ',' => self::T_COMMA, '(' => self::T_OPEN_PARENTHESIS, ')' => self::T_CLOSE_PARENTHESIS, '{' => self::T_OPEN_CURLY_BRACES, '}' => self::T_CLOSE_CURLY_BRACES, '=' => self::T_EQUALS, ':' => self::T_COLON, '-' => self::T_MINUS, '\\' => self::T_NAMESPACE_SEPARATOR]; /** @var array */ protected $withCase = ['true' => self::T_TRUE, 'false' => self::T_FALSE, 'null' => self::T_NULL]; /** * Whether the next token starts immediately, or if there were * non-captured symbols before that */ public function nextTokenIsAdjacent() : bool { return $this->token === null || $this->lookahead !== null && $this->lookahead['position'] - $this->token['position'] === strlen($this->token['value']); } /** * {@inheritdoc} */ protected function getCatchablePatterns() { return ['[a-z_\\\\][a-z0-9_\\:\\\\]*[a-z_][a-z0-9_]*', '(?:[+-]?[0-9]+(?:[\\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', '"(?:""|[^"])*+"']; } /** * {@inheritdoc} */ protected function getNonCatchablePatterns() { return ['\\s+', '\\*+', '(.)']; } /** * {@inheritdoc} */ protected function getType(&$value) { $type = self::T_NONE; if ($value[0] === '"') { $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); return self::T_STRING; } if (isset($this->noCase[$value])) { return $this->noCase[$value]; } if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { return self::T_IDENTIFIER; } $lowerValue = strtolower($value); if (isset($this->withCase[$lowerValue])) { return $this->withCase[$lowerValue]; } // Checking numeric value if (is_numeric($value)) { return strpos($value, '.') !== \false || stripos($value, 'e') !== \false ? self::T_FLOAT : self::T_INTEGER; } return $type; } /** @return array{value: int|string, type:self::T_*|null, position:int} */ public function peek() : ?array { $token = parent::peek(); if ($token === null) { return null; } return (array) $token; } } Copyright (c) 2020-2021 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Doctrine Deprecations A small (side-effect free by default) layer on top of `trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. - no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under - options to avoid having to rely on error handlers global state by using PSR-3 logging - deduplicate deprecation messages to avoid excessive triggering and reduce overhead We recommend to collect Deprecations using a PSR logger instead of relying on the global error handler. ## Usage from consumer perspective: Enable Doctrine deprecations to be sent to a PSR3 logger: ```php \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); ``` Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`. Alternatively, call: ```php \Doctrine\Deprecations\Deprecation::enableWithTriggerError(); ``` If you only want to enable deprecation tracking, without logging or calling `trigger_error` then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`. Alternatively, call: ```php \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); ``` Tracking is enabled with all three modes and provides access to all triggered deprecations and their individual count: ```php $deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); foreach ($deprecations as $identifier => $count) { echo $identifier . " was triggered " . $count . " times\n"; } ``` ### Suppressing Specific Deprecations Disable triggering about specific deprecations: ```php \Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); ``` Disable all deprecations from a package ```php \Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); ``` ### Other Operations When used within PHPUnit or other tools that could collect multiple instances of the same deprecations the deduplication can be disabled: ```php \Doctrine\Deprecations\Deprecation::withoutDeduplication(); ``` Disable deprecation tracking again: ```php \Doctrine\Deprecations\Deprecation::disable(); ``` ## Usage from a library/producer perspective: When you want to unconditionally trigger a deprecation even when called from the library itself then the `trigger` method is the way to go: ```php \Doctrine\Deprecations\Deprecation::trigger( "doctrine/orm", "https://link/to/deprecations-description", "message" ); ``` If variable arguments are provided at the end, they are used with `sprintf` on the message. ```php \Doctrine\Deprecations\Deprecation::trigger( "doctrine/orm", "https://github.com/doctrine/orm/issue/1234", "message %s %d", "foo", 1234 ); ``` When you want to trigger a deprecation only when it is called by a function outside of the current package, but not trigger when the package itself is the cause, then use: ```php \Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( "doctrine/orm", "https://link/to/deprecations-description", "message" ); ``` Based on the issue link each deprecation message is only triggered once per request. A limited stacktrace is included in the deprecation message to find the offending location. Note: A producer/library should never call `Deprecation::enableWith` methods and leave the decision how to handle deprecations to application and frameworks. ## Usage in PHPUnit tests There is a `VerifyDeprecations` trait that you can use to make assertions on the occurrence of deprecations within a test. ```php use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; class MyTest extends TestCase { use VerifyDeprecations; public function testSomethingDeprecation() { $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); triggerTheCodeWithDeprecation(); } public function testSomethingDeprecationFixed() { $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); triggerTheCodeWithoutDeprecation(); } } ``` ## What is a deprecation identifier? An identifier for deprecations is just a link to any resource, most often a Github Issue or Pull Request explaining the deprecation and potentially its alternative. */ private $doctrineDeprecationsExpectations = []; /** @var array */ private $doctrineNoDeprecationsExpectations = []; public function expectDeprecationWithIdentifier(string $identifier) : void { $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } public function expectNoDeprecationWithIdentifier(string $identifier) : void { $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } /** * @before */ public function enableDeprecationTracking() : void { Deprecation::enableTrackingDeprecations(); } /** * @after */ public function verifyDeprecationsAreTriggered() : void { foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue($actualCount > $expectation, sprintf("Expected deprecation with identifier '%s' was not triggered by code executed in test.", $identifier)); } foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue($actualCount === $expectation, sprintf("Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", $identifier)); } } } |null */ private static $type; /** @var LoggerInterface|null */ private static $logger; /** @var array */ private static $ignoredPackages = []; /** @var array */ private static $triggeredDeprecations = []; /** @var array */ private static $ignoredLinks = []; /** @var bool */ private static $deduplication = \true; /** * Trigger a deprecation for the given package and identfier. * * The link should point to a Github issue or Wiki entry detailing the * deprecation. It is additionally used to de-duplicate the trigger of the * same deprecation during a request. * * @param float|int|string $args */ public static function trigger(string $package, string $link, string $message, ...$args) : void { $type = self::$type ?? self::getTypeFromEnv(); if ($type === self::TYPE_NONE) { return; } if (isset(self::$ignoredLinks[$link])) { return; } if (array_key_exists($link, self::$triggeredDeprecations)) { self::$triggeredDeprecations[$link]++; } else { self::$triggeredDeprecations[$link] = 1; } if (self::$deduplication === \true && self::$triggeredDeprecations[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * Trigger a deprecation for the given package and identifier when called from outside. * * "Outside" means we assume that $package is currently installed as a * dependency and the caller is not a file in that package. When $package * is installed as a root package then deprecations triggered from the * tests folder are also considered "outside". * * This deprecation method assumes that you are using Composer to install * the dependency and are using the default /vendor/ folder and not a * Composer plugin to change the install location. The assumption is also * that $package is the exact composer packge name. * * Compared to {@link trigger()} this method causes some overhead when * deprecation tracking is enabled even during deduplication, because it * needs to call {@link debug_backtrace()} * * @param float|int|string $args */ public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args) : void { $type = self::$type ?? self::getTypeFromEnv(); if ($type === self::TYPE_NONE) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); // first check that the caller is not from a tests folder, in which case we always let deprecations pass if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === \false) { $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $package) . DIRECTORY_SEPARATOR; if (strpos($backtrace[0]['file'], $path) === \false) { return; } if (strpos($backtrace[1]['file'], $path) !== \false) { return; } } if (isset(self::$ignoredLinks[$link])) { return; } if (array_key_exists($link, self::$triggeredDeprecations)) { self::$triggeredDeprecations[$link]++; } else { self::$triggeredDeprecations[$link] = 1; } if (self::$deduplication === \true && self::$triggeredDeprecations[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * @param list $backtrace */ private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package) : void { $type = self::$type ?? self::getTypeFromEnv(); if (($type & self::TYPE_PSR_LOGGER) > 0) { $context = ['file' => $backtrace[0]['file'] ?? null, 'line' => $backtrace[0]['line'] ?? null, 'package' => $package, 'link' => $link]; assert(self::$logger !== null); self::$logger->notice($message, $context); } if (!(($type & self::TYPE_TRIGGER_ERROR) > 0)) { return; } $message .= sprintf(' (%s:%d called by %s:%d, %s, package %s)', self::basename($backtrace[0]['file'] ?? 'native code'), $backtrace[0]['line'] ?? 0, self::basename($backtrace[1]['file'] ?? 'native code'), $backtrace[1]['line'] ?? 0, $link, $package); @trigger_error($message, E_USER_DEPRECATED); } /** * A non-local-aware version of PHPs basename function. */ private static function basename(string $filename) : string { $pos = strrpos($filename, DIRECTORY_SEPARATOR); if ($pos === \false) { return $filename; } return substr($filename, $pos + 1); } public static function enableTrackingDeprecations() : void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_TRACK_DEPRECATIONS; } public static function enableWithTriggerError() : void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_TRIGGER_ERROR; } public static function enableWithPsrLogger(LoggerInterface $logger) : void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_PSR_LOGGER; self::$logger = $logger; } public static function withoutDeduplication() : void { self::$deduplication = \false; } public static function disable() : void { self::$type = self::TYPE_NONE; self::$logger = null; self::$deduplication = \true; self::$ignoredLinks = []; foreach (self::$triggeredDeprecations as $link => $count) { self::$triggeredDeprecations[$link] = 0; } } public static function ignorePackage(string $packageName) : void { self::$ignoredPackages[$packageName] = \true; } public static function ignoreDeprecations(string ...$links) : void { foreach ($links as $link) { self::$ignoredLinks[$link] = \true; } } public static function getUniqueTriggeredDeprecationsCount() : int { return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) { return $carry + $count; }, 0); } /** * Returns each triggered deprecation link identifier and the amount of occurrences. * * @return array */ public static function getTriggeredDeprecations() : array { return self::$triggeredDeprecations; } /** * @return int-mask-of */ private static function getTypeFromEnv() : int { switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) { case 'trigger': self::$type = self::TYPE_TRIGGER_ERROR; break; case 'track': self::$type = self::TYPE_TRACK_DEPRECATIONS; break; default: self::$type = self::TYPE_NONE; break; } return self::$type; } } { "name": "doctrine\/deprecations", "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", "license": "MIT", "type": "library", "homepage": "https:\/\/www.doctrine-project.org\/", "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine\/coding-standard": "^9", "phpstan\/phpstan": "1.4.10 || 1.10.15", "phpstan\/phpstan-phpunit": "^1.0", "phpunit\/phpunit": "^7.5 || ^8.5 || ^9.5", "psalm\/plugin-phpunit": "0.18.4", "psr\/log": "^1 || ^2 || ^3", "vimeo\/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr\/log": "Allows logging deprecations via PSR-3 logger implementation" }, "autoload": { "psr-4": { "_ContaoManager\\Doctrine\\Deprecations\\": "lib\/Doctrine\/Deprecations" } }, "autoload-dev": { "psr-4": { "_ContaoManager\\DeprecationTests\\": "test_fixtures\/src", "_ContaoManager\\Doctrine\\Foo\\": "test_fixtures\/vendor\/doctrine\/foo" } }, "config": { "allow-plugins": { "dealerdirect\/phpcodesniffer-composer-installer": true } } }{ "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], "hash": "60a5df5d283a7ae9000173248eba8909", "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "dev", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=5.2.0" }, "platform-dev": [] } Copyright (c) 2011, Neuman Vong All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Neuman Vong nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. JWT pear.php.net A JWT encoder/decoder. A JWT encoder/decoder library for PHP. Neuman Vong lcfrs neuman+pear@twilio.com yes Firebase Operations firebase operations@firebase.com yes 2015-07-22 3.0.0 3.0.0 beta beta BSD 3-Clause License Initial release with basic support for JWT encoding, decoding and signature verification. 5.1 1.7.0 json hash 0.1.0 0.1.0 beta beta 2015-04-01 BSD 3-Clause License Initial release with basic support for JWT encoding, decoding and signature verification. [![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt) [![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) [![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) [![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) PHP-JWT ======= A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). Installation ------------ Use composer to manage your dependencies and download PHP-JWT: ```bash composer require firebase/php-jwt ``` Example ------- ```php "http://example.org", "aud" => "http://example.com", "iat" => 1356999524, "nbf" => 1357000000 ); /** * IMPORTANT: * You must specify supported algorithms for your application. See * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 * for a list of spec-compliant algorithms. */ $jwt = JWT::encode($token, $key); $decoded = JWT::decode($jwt, $key, array('HS256')); print_r($decoded); /* NOTE: This will now be an object instead of an associative array. To get an associative array, you will need to cast it as such: */ $decoded_array = (array) $decoded; /** * You can add a leeway to account for when there is a clock skew times between * the signing and verifying servers. It is recommended that this leeway should * not be bigger than a few minutes. * * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef */ JWT::$leeway = 60; // $leeway in seconds $decoded = JWT::decode($jwt, $key, array('HS256')); ?> ``` Changelog --------- #### 4.0.0 / 2016-07-17 - Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! - Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! - Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! - Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! #### 3.0.0 / 2015-07-22 - Minimum PHP version updated from `5.2.0` to `5.3.0`. - Add `\Firebase\JWT` namespace. See [#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to [@Dashron](https://github.com/Dashron)! - Require a non-empty key to decode and verify a JWT. See [#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to [@sjones608](https://github.com/sjones608)! - Cleaner documentation blocks in the code. See [#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to [@johanderuijter](https://github.com/johanderuijter)! #### 2.2.0 / 2015-06-22 - Add support for adding custom, optional JWT headers to `JWT::encode()`. See [#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to [@mcocaro](https://github.com/mcocaro)! #### 2.1.0 / 2015-05-20 - Add support for adding a leeway to `JWT:decode()` that accounts for clock skew between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! - Add support for passing an object implementing the `ArrayAccess` interface for `$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! #### 2.0.0 / 2015-04-01 - **Note**: It is strongly recommended that you update to > v2.0.0 to address known security vulnerabilities in prior versions when both symmetric and asymmetric keys are used together. - Update signature for `JWT::decode(...)` to require an array of supported algorithms to use when verifying token signatures. Tests ----- Run the tests using phpunit: ```bash $ pear install PHPUnit $ phpunit --configuration phpunit.xml.dist PHPUnit 3.7.10 by Sebastian Bergmann. ..... Time: 0 seconds, Memory: 2.50Mb OK (5 tests, 5 assertions) ``` License ------- [3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). { "name": "firebase\/php-jwt", "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", "homepage": "https:\/\/github.com\/firebase\/php-jwt", "authors": [ { "name": "Neuman Vong", "email": "neuman+pear@twilio.com", "role": "Developer" }, { "name": "Anant Narayanan", "email": "anant@php.net", "role": "Developer" } ], "license": "BSD-3-Clause", "require": { "php": ">=5.3.0" }, "autoload": { "psr-4": { "_ContaoManager\\Firebase\\JWT\\": "src" } }, "minimum-stability": "dev" } * @author Anant Narayanan * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD * @link https://github.com/firebase/php-jwt */ class JWT { /** * When checking nbf, iat or expiration times, * we want to provide some extra leeway time to * account for clock skew. */ public static $leeway = 0; /** * Allow the current timestamp to be specified. * Useful for fixing a value within unit testing. * * Will default to PHP time() value if null. */ public static $timestamp = null; public static $supported_algs = array('HS256' => array('hash_hmac', 'SHA256'), 'HS512' => array('hash_hmac', 'SHA512'), 'HS384' => array('hash_hmac', 'SHA384'), 'RS256' => array('openssl', 'SHA256')); /** * Decodes a JWT string into a PHP object. * * @param string $jwt The JWT * @param string|array $key The key, or map of keys. * If the algorithm used is asymmetric, this is the public key * @param array $allowed_algs List of supported verification algorithms * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' * * @return object The JWT's payload as a PHP object * * @throws UnexpectedValueException Provided JWT was invalid * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim * * @uses jsonDecode * @uses urlsafeB64Decode */ public static function decode($jwt, $key, $allowed_algs = array()) { $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; if (empty($key)) { throw new InvalidArgumentException('Key may not be empty'); } if (!\is_array($allowed_algs)) { throw new InvalidArgumentException('Algorithm not allowed'); } $tks = \explode('.', $jwt); if (\count($tks) != 3) { throw new UnexpectedValueException('Wrong number of segments'); } list($headb64, $bodyb64, $cryptob64) = $tks; if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) { throw new UnexpectedValueException('Invalid header encoding'); } if (null === ($payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64)))) { throw new UnexpectedValueException('Invalid claims encoding'); } $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); } if (empty(static::$supported_algs[$header->alg])) { throw new UnexpectedValueException('Algorithm not supported'); } if (!\in_array($header->alg, $allowed_algs)) { throw new UnexpectedValueException('Algorithm not allowed'); } if (\is_array($key) || $key instanceof \ArrayAccess) { if (isset($header->kid)) { $key = $key[$header->kid]; } else { throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); } } // Check the signature if (!static::verify("{$headb64}.{$bodyb64}", $sig, $key, $header->alg)) { throw new SignatureInvalidException('Signature verification failed'); } // Check if the nbf if it is defined. This is the time that the // token can actually be used. If it's not yet that time, abort. if (isset($payload->nbf) && $payload->nbf > $timestamp + static::$leeway) { throw new BeforeValidException('Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)); } // Check that this token has been created before 'now'. This prevents // using tokens that have been created for later use (and haven't // correctly used the nbf claim). if (isset($payload->iat) && $payload->iat > $timestamp + static::$leeway) { throw new BeforeValidException('Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)); } // Check if this token has expired. if (isset($payload->exp) && $timestamp - static::$leeway >= $payload->exp) { throw new ExpiredException('Expired token'); } return $payload; } /** * Converts and signs a PHP object or array into a JWT string. * * @param object|array $payload PHP object or array * @param string $key The secret key. * If the algorithm used is asymmetric, this is the private key * @param string $alg The signing algorithm. * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' * @param mixed $keyId * @param array $head An array with header elements to attach * * @return string A signed JWT * * @uses jsonEncode * @uses urlsafeB64Encode */ public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) { $header = array('typ' => 'JWT', 'alg' => $alg); if ($keyId !== null) { $header['kid'] = $keyId; } if (isset($head) && \is_array($head)) { $header = \array_merge($head, $header); } $segments = array(); $segments[] = static::urlsafeB64Encode(static::jsonEncode($header)); $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload)); $signing_input = \implode('.', $segments); $signature = static::sign($signing_input, $key, $alg); $segments[] = static::urlsafeB64Encode($signature); return \implode('.', $segments); } /** * Sign a string with a given key and algorithm. * * @param string $msg The message to sign * @param string|resource $key The secret key * @param string $alg The signing algorithm. * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' * * @return string An encrypted message * * @throws DomainException Unsupported algorithm was specified */ public static function sign($msg, $key, $alg = 'HS256') { if (empty(static::$supported_algs[$alg])) { throw new DomainException('Algorithm not supported'); } list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'hash_hmac': return \hash_hmac($algorithm, $msg, $key, \true); case 'openssl': $signature = ''; $success = \openssl_sign($msg, $signature, $key, $algorithm); if (!$success) { throw new DomainException("OpenSSL unable to sign data"); } else { return $signature; } } } /** * Verify a signature with the message, key and method. Not all methods * are symmetric, so we must have a separate verify and sign method. * * @param string $msg The original message (header and body) * @param string $signature The original signature * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key * @param string $alg The algorithm * * @return bool * * @throws DomainException Invalid Algorithm or OpenSSL failure */ private static function verify($msg, $signature, $key, $alg) { if (empty(static::$supported_algs[$alg])) { throw new DomainException('Algorithm not supported'); } list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'openssl': $success = \openssl_verify($msg, $signature, $key, $algorithm); if (!$success) { throw new DomainException("OpenSSL unable to verify data: " . \openssl_error_string()); } else { return $signature; } case 'hash_hmac': default: $hash = \hash_hmac($algorithm, $msg, $key, \true); if (\function_exists('hash_equals')) { return \hash_equals($signature, $hash); } $len = \min(static::safeStrlen($signature), static::safeStrlen($hash)); $status = 0; for ($i = 0; $i < $len; $i++) { $status |= \ord($signature[$i]) ^ \ord($hash[$i]); } $status |= static::safeStrlen($signature) ^ static::safeStrlen($hash); return $status === 0; } } /** * Decode a JSON string into a PHP object. * * @param string $input JSON string * * @return object Object representation of JSON string * * @throws DomainException Provided string was invalid JSON */ public static function jsonDecode($input) { if (\version_compare(\PHP_VERSION, '5.4.0', '>=') && !(\defined('_ContaoManager\\JSON_C_VERSION') && \PHP_INT_SIZE > 4)) { /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you * to specify that large ints (like Steam Transaction IDs) should be treated as * strings, rather than the PHP default behaviour of converting them to floats. */ $obj = \json_decode($input, \false, 512, \JSON_BIGINT_AS_STRING); } else { /** Not all servers will support that, however, so for older versions we must * manually detect large ints in the JSON string and quote them (thus converting *them to strings) before decoding, hence the preg_replace() call. */ $max_int_length = \strlen((string) \PHP_INT_MAX) - 1; $json_without_bigints = \preg_replace('/:\\s*(-?\\d{' . $max_int_length . ',})/', ': "$1"', $input); $obj = \json_decode($json_without_bigints); } if (\function_exists('json_last_error') && ($errno = \json_last_error())) { static::handleJsonError($errno); } elseif ($obj === null && $input !== 'null') { throw new DomainException('Null result with non-null input'); } return $obj; } /** * Encode a PHP object into a JSON string. * * @param object|array $input A PHP object or array * * @return string JSON representation of the PHP object or array * * @throws DomainException Provided object could not be encoded to valid JSON */ public static function jsonEncode($input) { $json = \json_encode($input); if (\function_exists('json_last_error') && ($errno = \json_last_error())) { static::handleJsonError($errno); } elseif ($json === 'null' && $input !== null) { throw new DomainException('Null result with non-null input'); } return $json; } /** * Decode a string with URL-safe Base64. * * @param string $input A Base64 encoded string * * @return string A decoded string */ public static function urlsafeB64Decode($input) { $remainder = \strlen($input) % 4; if ($remainder) { $padlen = 4 - $remainder; $input .= \str_repeat('=', $padlen); } return \base64_decode(\strtr($input, '-_', '+/')); } /** * Encode a string with URL-safe Base64. * * @param string $input The string you want encoded * * @return string The base64 encode of what you passed in */ public static function urlsafeB64Encode($input) { return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); } /** * Helper method to create a JSON error. * * @param int $errno An error number from json_last_error() * * @return void */ private static function handleJsonError($errno) { $messages = array(\JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', \JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', \JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'); throw new DomainException(isset($messages[$errno]) ? $messages[$errno] : 'Unknown JSON error: ' . $errno); } /** * Get the number of bytes in cryptographic strings. * * @param string * * @return int */ private static function safeStrlen($str) { if (\function_exists('mb_strlen')) { return \mb_strlen($str, '8bit'); } return \strlen($str); } } MIT License Copyright (c) 2016 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #!/usr/bin/env php */ /** * Dead simple autoloader * * @param string $className Name of class to load * * @return void */ \spl_autoload_register(function ($className) { $className = \ltrim($className, '\\'); $fileName = ''; if ($lastNsPos = \strrpos($className, '\\')) { $namespace = \substr($className, 0, $lastNsPos); $className = \substr($className, $lastNsPos + 1); $fileName = \str_replace('\\', \DIRECTORY_SEPARATOR, $namespace) . \DIRECTORY_SEPARATOR; } $fileName .= \str_replace('_', \DIRECTORY_SEPARATOR, $className) . '.php'; if (\stream_resolve_include_path($fileName)) { require_once $fileName; } }); // support running this tool from git checkout if (\is_dir(__DIR__ . '/../src/JsonSchema')) { \set_include_path(__DIR__ . '/../src' . \PATH_SEPARATOR . \get_include_path()); } $arOptions = array(); $arArgs = array(); \array_shift($argv); //script itself foreach ($argv as $arg) { if ($arg[0] == '-') { $arOptions[$arg] = \true; } else { $arArgs[] = $arg; } } if (\count($arArgs) == 0 || isset($arOptions['--help']) || isset($arOptions['-h'])) { echo << $value) { if (!\strncmp($name, 'JSON_ERROR_', 11)) { $json_errors[$value] = $name; } } output('JSON parse error: ' . $json_errors[\json_last_error()] . "\n"); } function getUrlFromPath($path) { if (\parse_url($path, \PHP_URL_SCHEME) !== null) { //already an URL return $path; } if ($path[0] == '/') { //absolute path return 'file://' . $path; } //relative path: make absolute return 'file://' . \getcwd() . '/' . $path; } /** * Take a HTTP header value and split it up into parts. * * @param $headerValue * @return array Key "_value" contains the main value, all others * as given in the header value */ function parseHeaderValue($headerValue) { if (\strpos($headerValue, ';') === \false) { return array('_value' => $headerValue); } $parts = \explode(';', $headerValue); $arData = array('_value' => \array_shift($parts)); foreach ($parts as $part) { list($name, $value) = \explode('=', $part); $arData[$name] = \trim($value, ' "\''); } return $arData; } /** * Send a string to the output stream, but only if --quiet is not enabled * * @param $str string A string output */ function output($str) { global $arOptions; if (!isset($arOptions['--quiet'])) { echo $str; } } $urlData = getUrlFromPath($pathData); $context = \stream_context_create(array('http' => array('header' => array('Accept: */*', 'Connection: Close'), 'max_redirects' => 5))); $dataString = \file_get_contents($pathData, \false, $context); if ($dataString == '') { output("Data file is not readable or empty.\n"); exit(3); } $data = \json_decode($dataString); unset($dataString); if ($data === null) { output("Error loading JSON data file\n"); showJsonError(); exit(5); } if ($pathSchema === null) { if (isset($http_response_header)) { \array_shift($http_response_header); //HTTP/1.0 line foreach ($http_response_header as $headerLine) { list($hName, $hValue) = \explode(':', $headerLine, 2); $hName = \strtolower($hName); if ($hName == 'link') { //Link: ; rel="describedBy" $hParts = parseHeaderValue($hValue); if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') { $pathSchema = \trim($hParts['_value'], ' <>'); } } else { if ($hName == 'content-type') { //Content-Type: application/my-media-type+json; // profile=http://example.org/schema# $hParts = parseHeaderValue($hValue); if (isset($hParts['profile'])) { $pathSchema = $hParts['profile']; } } } } } if (\is_object($data) && \property_exists($data, '$schema')) { $pathSchema = $data->{'$schema'}; } //autodetect schema if ($pathSchema === null) { output("JSON data must be an object and have a \$schema property.\n"); output("You can pass the schema file on the command line as well.\n"); output("Schema autodetection failed.\n"); exit(6); } } if ($pathSchema[0] == '/') { $pathSchema = 'file://' . $pathSchema; } $resolver = new JsonSchema\Uri\UriResolver(); $retriever = new JsonSchema\Uri\UriRetriever(); try { $urlSchema = $resolver->resolve($pathSchema, $urlData); if (isset($arOptions['--dump-schema-url'])) { echo $urlSchema . "\n"; exit; } } catch (\Exception $e) { output("Error loading JSON schema file\n"); output($urlSchema . "\n"); output($e->getMessage() . "\n"); exit(2); } $refResolver = new JsonSchema\SchemaStorage($retriever, $resolver); $schema = $refResolver->resolveRef($urlSchema); if (isset($arOptions['--dump-schema'])) { $options = \defined('JSON_PRETTY_PRINT') ? \JSON_PRETTY_PRINT : 0; echo \json_encode($schema, $options) . "\n"; exit; } try { $validator = new JsonSchema\Validator(); $validator->check($data, $schema); if ($validator->isValid()) { if (isset($arOptions['--verbose'])) { output("OK. The supplied JSON validates against the schema.\n"); } } else { output("JSON does not validate. Violations:\n"); foreach ($validator->getErrors() as $error) { output(\sprintf("[%s] %s\n", $error['property'], $error['message'])); } exit(23); } } catch (\Exception $e) { output("JSON does not validate. Error:\n"); output($e->getMessage() . "\n"); output("Error code: " . $e->getCode() . "\n"); exit(24); } { "$schema": "http://json-schema.org/draft-03/schema#", "id": "http://json-schema.org/draft-03/schema#", "type": "object", "properties": { "type": { "type": [ "string", "array" ], "items": { "type": [ "string", { "$ref": "#" } ] }, "uniqueItems": true, "default": "any" }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "additionalProperties": { "type": [ { "$ref": "#" }, "boolean" ], "default": {} }, "items": { "type": [ { "$ref": "#" }, "array" ], "items": { "$ref": "#" }, "default": {} }, "additionalItems": { "type": [ { "$ref": "#" }, "boolean" ], "default": {} }, "required": { "type": "boolean", "default": false }, "dependencies": { "type": "object", "additionalProperties": { "type": [ "string", "array", { "$ref": "#" } ], "items": { "type": "string" } }, "default": {} }, "minimum": { "type": "number" }, "maximum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minItems": { "type": "integer", "minimum": 0, "default": 0 }, "maxItems": { "type": "integer", "minimum": 0 }, "uniqueItems": { "type": "boolean", "default": false }, "pattern": { "type": "string", "format": "regex" }, "minLength": { "type": "integer", "minimum": 0, "default": 0 }, "maxLength": { "type": "integer" }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "default": { "type": "any" }, "title": { "type": "string" }, "description": { "type": "string" }, "format": { "type": "string" }, "divisibleBy": { "type": "number", "minimum": 0, "exclusiveMinimum": true, "default": 1 }, "disallow": { "type": [ "string", "array" ], "items": { "type": [ "string", { "$ref": "#" } ] }, "uniqueItems": true }, "extends": { "type": [ { "$ref": "#" }, "array" ], "items": { "$ref": "#" }, "default": {} }, "id": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" } }, "dependencies": { "exclusiveMinimum": "minimum", "exclusiveMaximum": "maximum" }, "default": {} } { "id": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#", "description": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", "minItems": 1, "items": { "$ref": "#" } }, "positiveInteger": { "type": "integer", "minimum": 0 }, "positiveIntegerDefault0": { "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] }, "simpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "type": "object", "properties": { "id": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" }, "title": { "type": "string" }, "description": { "type": "string" }, "default": {}, "multipleOf": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "maximum": { "type": "number" }, "exclusiveMaximum": { "type": "boolean", "default": false }, "minimum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": false }, "maxLength": { "$ref": "#/definitions/positiveInteger" }, "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, "pattern": { "type": "string", "format": "regex" }, "additionalItems": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "items": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/schemaArray" } ], "default": {} }, "maxItems": { "$ref": "#/definitions/positiveInteger" }, "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, "uniqueItems": { "type": "boolean", "default": false }, "maxProperties": { "$ref": "#/definitions/positiveInteger" }, "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, "additionalProperties": { "anyOf": [ { "type": "boolean" }, { "$ref": "#" } ], "default": {} }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "properties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": { "$ref": "#" }, "default": {} }, "dependencies": { "type": "object", "additionalProperties": { "anyOf": [ { "$ref": "#" }, { "$ref": "#/definitions/stringArray" } ] } }, "enum": { "type": "array", "minItems": 1, "uniqueItems": true }, "type": { "anyOf": [ { "$ref": "#/definitions/simpleTypes" }, { "type": "array", "items": { "$ref": "#/definitions/simpleTypes" }, "minItems": 1, "uniqueItems": true } ] }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, "dependencies": { "exclusiveMaximum": [ "maximum" ], "exclusiveMinimum": [ "minimum" ] }, "default": {} } # JSON Schema for PHP [![Build Status](https://travis-ci.org/justinrainbow/json-schema.svg?branch=master)](https://travis-ci.org/justinrainbow/json-schema) [![Latest Stable Version](https://poser.pugx.org/justinrainbow/json-schema/v/stable.png)](https://packagist.org/packages/justinrainbow/json-schema) [![Total Downloads](https://poser.pugx.org/justinrainbow/json-schema/downloads.png)](https://packagist.org/packages/justinrainbow/json-schema) A PHP Implementation for validating `JSON` Structures against a given `Schema`. See [json-schema](http://json-schema.org/) for more details. ## Installation ### Library ```bash git clone https://github.com/justinrainbow/json-schema.git ``` ### Composer [Install PHP Composer](https://getcomposer.org/doc/00-intro.md) ```bash composer require justinrainbow/json-schema ``` ## Usage ```php validate($data, (object)['$ref' => 'file://' . realpath('schema.json')]); if ($validator->isValid()) { echo "The supplied JSON validates against the schema.\n"; } else { echo "JSON does not validate. Violations:\n"; foreach ($validator->getErrors() as $error) { echo sprintf("[%s] %s\n", $error['property'], $error['message']); } } ``` ### Type coercion If you're validating data passed to your application via HTTP, you can cast strings and booleans to the expected types defined by your schema: ```php "true", 'refundAmount'=>"17" ]; $validator->validate( $request, (object) [ "type"=>"object", "properties"=>(object)[ "processRefund"=>(object)[ "type"=>"boolean" ], "refundAmount"=>(object)[ "type"=>"number" ] ] ], Constraint::CHECK_MODE_COERCE_TYPES ); // validates! is_bool($request->processRefund); // true is_int($request->refundAmount); // true ``` A shorthand method is also available: ```PHP $validator->coerce($request, $schema); // equivalent to $validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES); ``` ### Default values If your schema contains default values, you can have these automatically applied during validation: ```php 17 ]; $validator = new Validator(); $validator->validate( $request, (object)[ "type"=>"object", "properties"=>(object)[ "processRefund"=>(object)[ "type"=>"boolean", "default"=>true ] ] ], Constraint::CHECK_MODE_APPLY_DEFAULTS ); //validates, and sets defaults for missing properties is_bool($request->processRefund); // true $request->processRefund; // true ``` ### With inline references ```php addSchema('file://mySchema', $jsonSchemaObject); // Provide $schemaStorage to the Validator so that references can be resolved during validation $jsonValidator = new Validator( new Factory($schemaStorage)); // JSON must be decoded before it can be validated $jsonToValidateObject = json_decode('{"data":123}'); // Do validation (use isValid() and getErrors() to check the result) $jsonValidator->validate($jsonToValidateObject, $jsonSchemaObject); ``` ### Configuration Options A number of flags are available to alter the behavior of the validator. These can be passed as the third argument to `Validator::validate()`, or can be provided as the third argument to `Factory::__construct()` if you wish to persist them across multiple `validate()` calls. | Flag | Description | |------|-------------| | `Constraint::CHECK_MODE_NORMAL` | Validate in 'normal' mode - this is the default | | `Constraint::CHECK_MODE_TYPE_CAST` | Enable fuzzy type checking for associative arrays and objects | | `Constraint::CHECK_MODE_COERCE_TYPES` | Convert data types to match the schema where possible | | `Constraint::CHECK_MODE_APPLY_DEFAULTS` | Apply default values from the schema if not set | | `Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS` | When applying defaults, only set values that are required | | `Constraint::CHECK_MODE_EXCEPTIONS` | Throw an exception immediately if validation fails | | `Constraint::CHECK_MODE_DISABLE_FORMAT` | Do not validate "format" constraints | | `Constraint::CHECK_MODE_VALIDATE_SCHEMA` | Validate the schema as well as the provided document | Please note that using `Constraint::CHECK_MODE_COERCE_TYPES` or `Constraint::CHECK_MODE_APPLY_DEFAULTS` will modify your original data. ## Running the tests ```bash composer test # run all unit tests composer testOnly TestClass # run specific unit test class composer testOnly TestClass::testMethod # run specific unit test method composer style-check # check code style for errors composer style-fix # automatically fix code style errors ``` { "name": "justinrainbow\/json-schema", "type": "library", "description": "A library to validate a json schema.", "keywords": [ "json", "schema" ], "homepage": "https:\/\/github.com\/justinrainbow\/json-schema", "license": "MIT", "authors": [ { "name": "Bruno Prieto Reis", "email": "bruno.p.reis@gmail.com" }, { "name": "Justin Rainbow", "email": "justin.rainbow@gmail.com" }, { "name": "Igor Wiedler", "email": "igor@wiedler.ch" }, { "name": "Robert Sch\u00f6nthal", "email": "seroscho@googlemail.com" } ], "require": { "php": ">=5.3.3" }, "require-dev": { "friendsofphp\/php-cs-fixer": "~2.2.20||~2.15.1", "json-schema\/json-schema-test-suite": "1.2.0", "phpunit\/phpunit": "^4.8.35" }, "extra": { "branch-alias": { "dev-master": "5.0.x-dev" } }, "autoload": { "psr-4": { "_ContaoManager\\JsonSchema\\": "src\/JsonSchema\/" } }, "autoload-dev": { "psr-4": { "_ContaoManager\\JsonSchema\\Tests\\": "tests\/" } }, "repositories": [ { "type": "package", "package": { "name": "json-schema\/json-schema-test-suite", "version": "1.2.0", "source": { "type": "git", "url": "https:\/\/github.com\/json-schema\/JSON-Schema-Test-Suite", "reference": "1.2.0" } } } ], "bin": [ "bin\/validate-json" ], "scripts": { "coverage": "phpunit --coverage-text", "style-check": "php-cs-fixer fix --dry-run --verbose --diff", "style-fix": "php-cs-fixer fix --verbose", "test": "phpunit", "testOnly": "phpunit --colors --filter" } } * @author Bruno Prieto Reis * * @see README.md */ class Validator extends BaseConstraint { const SCHEMA_MEDIA_TYPE = 'application/schema+json'; const ERROR_NONE = 0x0; const ERROR_ALL = 0xffffffff; const ERROR_DOCUMENT_VALIDATION = 0x1; const ERROR_SCHEMA_VALIDATION = 0x2; /** * Validates the given data against the schema and returns an object containing the results * Both the php object and the schema are supposed to be a result of a json_decode call. * The validation works as defined by the schema proposal in http://json-schema.org. * * Note that the first argument is passed by reference, so you must pass in a variable. */ public function validate(&$value, $schema = null, $checkMode = null) { // make sure $schema is an object if (\is_array($schema)) { $schema = self::arrayToObjectRecursive($schema); } // set checkMode $initialCheckMode = $this->factory->getConfig(); if ($checkMode !== null) { $this->factory->setConfig($checkMode); } // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution if (\is_object($schema) && \property_exists($schema, 'id')) { $schemaURI = $schema->id; } else { $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; } $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema); $validator = $this->factory->createInstanceFor('schema'); $validator->check($value, $this->factory->getSchemaStorage()->getSchema($schemaURI)); $this->factory->setConfig($initialCheckMode); $this->addErrors(\array_unique($validator->getErrors(), \SORT_REGULAR)); return $validator->getErrorMask(); } /** * Alias to validate(), to maintain backwards-compatibility with the previous API */ public function check($value, $schema) { return $this->validate($value, $schema); } /** * Alias to validate(), to maintain backwards-compatibility with the previous API */ public function coerce(&$value, $schema) { return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); } } * @author Bruno Prieto Reis */ class ObjectConstraint extends Constraint { /** * @var array List of properties to which a default value has been applied */ protected $appliedDefaults = array(); /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null, $additionalProp = null, $patternProperties = null, $appliedDefaults = array()) { if ($element instanceof UndefinedConstraint) { return; } $this->appliedDefaults = $appliedDefaults; $matches = array(); if ($patternProperties) { // validate the element pattern properties $matches = $this->validatePatternProperties($element, $path, $patternProperties); } if ($properties) { // validate the element properties $this->validateProperties($element, $properties, $path); } // validate additional element properties & constraints $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); } public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties) { $try = array('/', '#', '+', '~', '%'); $matches = array(); foreach ($patternProperties as $pregex => $schema) { $delimiter = '/'; // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error foreach ($try as $delimiter) { if (\strpos($pregex, $delimiter) === \false) { // safe to use break; } } // Validate the pattern before using it to test for matches if (@\preg_match($delimiter . $pregex . $delimiter . 'u', '') === \false) { $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex)); continue; } foreach ($element as $i => $value) { if (\preg_match($delimiter . $pregex . $delimiter . 'u', $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, \in_array($i, $this->appliedDefaults)); } } } return $matches; } /** * Validates the element properties * * @param \StdClass $element Element to validate * @param array $matches Matches from patternProperties (if any) * @param \StdClass $schema ObjectConstraint definition * @param JsonPointer|null $path Current test path * @param \StdClass $properties Properties * @param mixed $additionalProp Additional properties */ public function validateElement($element, $matches, $schema = null, JsonPointer $path = null, $properties = null, $additionalProp = null) { $this->validateMinMaxConstraint($element, $schema, $path); foreach ($element as $i => $value) { $definition = $this->getProperty($properties, $i); // no additional properties allowed if (!\in_array($i, $matches) && $additionalProp === \false && $this->inlineSchemaProperty !== $i && !$definition) { $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp'); } // additional properties defined if (!\in_array($i, $matches) && $additionalProp && !$definition) { if ($additionalProp === \true) { $this->checkUndefined($value, null, $path, $i, \in_array($i, $this->appliedDefaults)); } else { $this->checkUndefined($value, $additionalProp, $path, $i, \in_array($i, $this->appliedDefaults)); } } // property requires presence of another $require = $this->getProperty($definition, 'requires'); if ($require && !$this->getProperty($element, $require)) { $this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires'); } $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); if (\is_object($property)) { $this->validateMinMaxConstraint(!$property instanceof UndefinedConstraint ? $property : $element, $definition, $path); } } } /** * Validates the definition properties * * @param \stdClass $element Element to validate * @param \stdClass $properties Property definitions * @param JsonPointer|null $path Path? */ public function validateProperties(&$element, $properties = null, JsonPointer $path = null) { $undefinedConstraint = $this->factory->createInstanceFor('undefined'); foreach ($properties as $i => $value) { $property =& $this->getProperty($element, $i, $undefinedConstraint); $definition = $this->getProperty($properties, $i); if (\is_object($definition)) { // Undefined constraint will check for is_object() and quit if is not - so why pass it? $this->checkUndefined($property, $definition, $path, $i, \in_array($i, $this->appliedDefaults)); } } } /** * retrieves a property from an object or array * * @param mixed $element Element to validate * @param string $property Property to retrieve * @param mixed $fallback Default value if property is not found * * @return mixed */ protected function &getProperty(&$element, $property, $fallback = null) { if (\is_array($element) && (isset($element[$property]) || \array_key_exists($property, $element))) { return $element[$property]; } elseif (\is_object($element) && \property_exists($element, $property)) { return $element->{$property}; } return $fallback; } /** * validating minimum and maximum property constraints (if present) against an element * * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition ObjectConstraint definition * @param JsonPointer|null $path Path to test? */ protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) { // Verify minimum number of properties if (isset($objectDefinition->minProperties) && !\is_object($objectDefinition->minProperties)) { if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties)); } } // Verify maximum number of properties if (isset($objectDefinition->maxProperties) && !\is_object($objectDefinition->maxProperties)) { if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) { $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties)); } } } } '_ContaoManager\\JsonSchema\\Constraints\\CollectionConstraint', 'collection' => '_ContaoManager\\JsonSchema\\Constraints\\CollectionConstraint', 'object' => '_ContaoManager\\JsonSchema\\Constraints\\ObjectConstraint', 'type' => '_ContaoManager\\JsonSchema\\Constraints\\TypeConstraint', 'undefined' => '_ContaoManager\\JsonSchema\\Constraints\\UndefinedConstraint', 'string' => '_ContaoManager\\JsonSchema\\Constraints\\StringConstraint', 'number' => '_ContaoManager\\JsonSchema\\Constraints\\NumberConstraint', 'enum' => '_ContaoManager\\JsonSchema\\Constraints\\EnumConstraint', 'format' => '_ContaoManager\\JsonSchema\\Constraints\\FormatConstraint', 'schema' => '_ContaoManager\\JsonSchema\\Constraints\\SchemaConstraint', 'validator' => '_ContaoManager\\JsonSchema\\Validator'); /** * @var array */ private $instanceCache = array(); /** * @param SchemaStorage $schemaStorage * @param UriRetrieverInterface $uriRetriever * @param int $checkMode */ public function __construct(SchemaStorageInterface $schemaStorage = null, UriRetrieverInterface $uriRetriever = null, $checkMode = Constraint::CHECK_MODE_NORMAL) { // set provided config options $this->setConfig($checkMode); $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); } /** * Set config values * * @param int $checkMode Set checkMode options - does not preserve existing flags */ public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) { $this->checkMode = $checkMode; } /** * Enable checkMode flags * * @param int $options */ public function addConfig($options) { $this->checkMode |= $options; } /** * Disable checkMode flags * * @param int $options */ public function removeConfig($options) { $this->checkMode &= ~$options; } /** * Get checkMode option * * @param int $options Options to get, if null then return entire bitmask * * @return int */ public function getConfig($options = null) { if ($options === null) { return $this->checkMode; } return $this->checkMode & $options; } /** * @return UriRetrieverInterface */ public function getUriRetriever() { return $this->uriRetriever; } public function getSchemaStorage() { return $this->schemaStorage; } public function getTypeCheck() { if (!isset($this->typeCheck[$this->checkMode])) { $this->typeCheck[$this->checkMode] = $this->checkMode & Constraint::CHECK_MODE_TYPE_CAST ? new TypeCheck\LooseTypeCheck() : new TypeCheck\StrictTypeCheck(); } return $this->typeCheck[$this->checkMode]; } /** * @param string $name * @param string $class * * @return Factory */ public function setConstraintClass($name, $class) { // Ensure class exists if (!\class_exists($class)) { throw new InvalidArgumentException('Unknown constraint ' . $name); } // Ensure class is appropriate if (!\in_array('_ContaoManager\\JsonSchema\\Constraints\\ConstraintInterface', \class_implements($class))) { throw new InvalidArgumentException('Invalid class ' . $name); } $this->constraintMap[$name] = $class; return $this; } /** * Create a constraint instance for the given constraint name. * * @param string $constraintName * * @throws InvalidArgumentException if is not possible create the constraint instance * * @return ConstraintInterface|ObjectConstraint */ public function createInstanceFor($constraintName) { if (!isset($this->constraintMap[$constraintName])) { throw new InvalidArgumentException('Unknown constraint ' . $constraintName); } if (!isset($this->instanceCache[$constraintName])) { $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); } return clone $this->instanceCache[$constraintName]; } /** * Get the error context * * @return string */ public function getErrorContext() { return $this->errorContext; } /** * Set the error context * * @param string $validationContext */ public function setErrorContext($errorContext) { $this->errorContext = $errorContext; } } * @author Bruno Prieto Reis */ class NumberConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify minimum if (isset($schema->exclusiveMinimum)) { if (isset($schema->minimum)) { if ($schema->exclusiveMinimum && $element <= $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum)); } elseif ($element < $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); } } else { $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum'); } } elseif (isset($schema->minimum) && $element < $schema->minimum) { $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); } // Verify maximum if (isset($schema->exclusiveMaximum)) { if (isset($schema->maximum)) { if ($schema->exclusiveMaximum && $element >= $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum)); } elseif ($element > $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); } } else { $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum'); } } elseif (isset($schema->maximum) && $element > $schema->maximum) { $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); } // Verify divisibleBy - Draft v3 if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); } // Verify multipleOf - Draft v4 if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf)); } $this->checkFormat($element, $schema, $path, $i); } private function fmod($number1, $number2) { $modulus = $number1 - \round($number1 / $number2) * $number2; $precision = 1.0E-10; if (-$precision < $modulus && $modulus < $precision) { return 0.0; } return $modulus; } } * @author Bruno Prieto Reis */ class TypeConstraint extends Constraint { /** * @var array|string[] type wordings for validation error messages */ public static $wording = array( 'integer' => 'an integer', 'number' => 'a number', 'boolean' => 'a boolean', 'object' => 'an object', 'array' => 'an array', 'string' => 'a string', 'null' => 'a null', 'any' => null, // validation of 'any' is always true so is not needed in message wording 0 => null, ); /** * {@inheritdoc} */ public function check(&$value = null, $schema = null, JsonPointer $path = null, $i = null) { $type = isset($schema->type) ? $schema->type : null; $isValid = \false; $wording = array(); if (\is_array($type)) { $this->validateTypesArray($value, $type, $wording, $isValid, $path); } elseif (\is_object($type)) { $this->checkUndefined($value, $type, $path); return; } else { $isValid = $this->validateType($value, $type); } if ($isValid === \false) { if (!\is_array($type)) { $this->validateTypeNameWording($type); $wording[] = self::$wording[$type]; } $this->addError($path, \ucwords(\gettype($value)) . ' value found, but ' . $this->implodeWith($wording, ', ', 'or') . ' is required', 'type'); } } /** * Validates the given $value against the array of types in $type. Sets the value * of $isValid to true, if at least one $type mateches the type of $value or the value * passed as $isValid is already true. * * @param mixed $value Value to validate * @param array $type TypeConstraints to check agains * @param array $validTypesWording An array of wordings of the valid types of the array $type * @param bool $isValid The current validation value * @param $path */ protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path) { foreach ($type as $tp) { // $tp can be an object, if it's a schema instead of a simple type, validate it // with a new type constraint if (\is_object($tp)) { if (!$isValid) { $validator = $this->factory->createInstanceFor('type'); $subSchema = new \stdClass(); $subSchema->type = $tp; $validator->check($value, $subSchema, $path, null); $error = $validator->getErrors(); $isValid = !(bool) $error; $validTypesWording[] = self::$wording['object']; } } else { $this->validateTypeNameWording($tp); $validTypesWording[] = self::$wording[$tp]; if (!$isValid) { $isValid = $this->validateType($value, $tp); } } } } /** * Implodes the given array like implode() with turned around parameters and with the * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of * $delimiter. * * @param array $elements The elements to implode * @param string $delimiter The delimiter to use * @param bool $listEnd The last delimiter to use (defaults to $delimiter) * * @return string */ protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = \false) { if ($listEnd === \false || !isset($elements[1])) { return \implode($delimiter, $elements); } $lastElement = \array_slice($elements, -1); $firsElements = \join($delimiter, \array_slice($elements, 0, -1)); $implodedElements = \array_merge(array($firsElements), $lastElement); return \join(" {$listEnd} ", $implodedElements); } /** * Validates the given $type, if there's an associated self::$wording. If not, throws an * exception. * * @param string $type The type to validate * * @throws StandardUnexpectedValueException */ protected function validateTypeNameWording($type) { if (!\array_key_exists($type, self::$wording)) { throw new StandardUnexpectedValueException(\sprintf('No wording for %s available, expected wordings are: [%s]', \var_export($type, \true), \implode(', ', \array_filter(self::$wording)))); } } /** * Verifies that a given value is of a certain type * * @param mixed $value Value to validate * @param string $type TypeConstraint to check against * * @throws InvalidArgumentException * * @return bool */ protected function validateType(&$value, $type) { //mostly the case for inline schema if (!$type) { return \true; } if ('any' === $type) { return \true; } if ('object' === $type) { return $this->getTypeCheck()->isObject($value); } if ('array' === $type) { return $this->getTypeCheck()->isArray($value); } $coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES); if ('integer' === $type) { if ($coerce) { $value = $this->toInteger($value); } return \is_int($value); } if ('number' === $type) { if ($coerce) { $value = $this->toNumber($value); } return \is_numeric($value) && !\is_string($value); } if ('boolean' === $type) { if ($coerce) { $value = $this->toBoolean($value); } return \is_bool($value); } if ('string' === $type) { return \is_string($value); } if ('email' === $type) { return \is_string($value); } if ('null' === $type) { return \is_null($value); } throw new InvalidArgumentException((\is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); } /** * Converts a value to boolean. For example, "true" becomes true. * * @param $value The value to convert to boolean * * @return bool|mixed */ protected function toBoolean($value) { if ($value === 'true') { return \true; } if ($value === 'false') { return \false; } return $value; } /** * Converts a numeric string to a number. For example, "4" becomes 4. * * @param mixed $value the value to convert to a number * * @return int|float|mixed */ protected function toNumber($value) { if (\is_numeric($value)) { return $value + 0; // cast to number } return $value; } protected function toInteger($value) { if (\is_numeric($value) && (int) $value == $value) { return (int) $value; // cast to number } return $value; } } factory = $factory ?: new Factory(); } public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) { $error = array('property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), 'pointer' => \ltrim(\strval($path ?: new JsonPointer('')), '#'), 'message' => $message, 'constraint' => $constraint, 'context' => $this->factory->getErrorContext()); if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { throw new ValidationException(\sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); } if (\is_array($more) && \count($more) > 0) { $error += $more; } $this->errors[] = $error; $this->errorMask |= $error['context']; } public function addErrors(array $errors) { if ($errors) { $this->errors = \array_merge($this->errors, $errors); $errorMask =& $this->errorMask; \array_walk($errors, function ($error) use(&$errorMask) { if (isset($error['context'])) { $errorMask |= $error['context']; } }); } } public function getErrors($errorContext = Validator::ERROR_ALL) { if ($errorContext === Validator::ERROR_ALL) { return $this->errors; } return \array_filter($this->errors, function ($error) use($errorContext) { if ($errorContext & $error['context']) { return \true; } }); } public function numErrors($errorContext = Validator::ERROR_ALL) { if ($errorContext === Validator::ERROR_ALL) { return \count($this->errors); } return \count($this->getErrors($errorContext)); } public function isValid() { return !$this->getErrors(); } /** * Clears any reported errors. Should be used between * multiple validation checks. */ public function reset() { $this->errors = array(); $this->errorMask = Validator::ERROR_NONE; } /** * Get the error mask * * @return int */ public function getErrorMask() { return $this->errorMask; } /** * Recursively cast an associative array to an object * * @param array $array * * @return object */ public static function arrayToObjectRecursive($array) { $json = \json_encode($array); if (\json_last_error() !== \JSON_ERROR_NONE) { $message = 'Unable to encode schema array as JSON'; if (\function_exists('json_last_error_msg')) { $message .= ': ' . \json_last_error_msg(); } throw new InvalidArgumentException($message); } return (object) \json_decode($json); } } * @author Bruno Prieto Reis */ class EnumConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Only validate enum if the attribute exists if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { return; } $type = \gettype($element); foreach ($schema->enum as $enum) { $enumType = \gettype($enum); if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') { if ((object) $element == $enum) { return; } } if ($type === \gettype($enum)) { if ($type == 'object') { if ($element == $enum) { return; } } elseif ($element === $enum) { return; } } } $this->addError($path, 'Does not have a value in the enumeration ' . \json_encode($schema->enum), 'enum', array('enum' => $schema->enum)); } } * @author Bruno Prieto Reis */ class StringConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { // Verify maxLength if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array('maxLength' => $schema->maxLength)); } //verify minLength if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array('minLength' => $schema->minLength)); } // Verify a regex pattern if (isset($schema->pattern) && !\preg_match('#' . \str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array('pattern' => $schema->pattern)); } $this->checkFormat($element, $schema, $path, $i); } private function strlen($string) { if (\extension_loaded('mbstring')) { return \mb_strlen($string, \mb_detect_encoding($string)); } // mbstring is present on all test platforms, so strlen() can be ignored for coverage return \strlen($string); // @codeCoverageIgnore } } */ interface ConstraintInterface { /** * returns all collected errors * * @return array */ public function getErrors(); /** * adds errors to this validator * * @param array $errors */ public function addErrors(array $errors); /** * adds an error * * @param JsonPointer|null $path * @param string $message * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' * @param array $more more array elements to add to the error */ public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null); /** * checks if the validator has not raised errors * * @return bool */ public function isValid(); /** * invokes the validation of an element * * @abstract * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i * * @throws \JsonSchema\Exception\ExceptionInterface */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null); } * @author Bruno Prieto Reis */ class CollectionConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null) { // Verify minItems if (isset($schema->minItems) && \count($value) < $schema->minItems) { $this->addError($path, 'There must be a minimum of ' . $schema->minItems . ' items in the array', 'minItems', array('minItems' => $schema->minItems)); } // Verify maxItems if (isset($schema->maxItems) && \count($value) > $schema->maxItems) { $this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems)); } // Verify uniqueItems if (isset($schema->uniqueItems) && $schema->uniqueItems) { $unique = $value; if (\is_array($value) && \count($value)) { $unique = \array_map(function ($e) { return \var_export($e, \true); }, $value); } if (\count(\array_unique($unique)) != \count($value)) { $this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems'); } } // Verify items if (isset($schema->items)) { $this->validateItems($value, $schema, $path, $i); } } /** * Validates the items * * @param array $value * @param \stdClass $schema * @param JsonPointer|null $path * @param string $i */ protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null) { if (\is_object($schema->items)) { // just one type definition for the whole array foreach ($value as $k => &$v) { $initErrors = $this->getErrors(); // First check if its defined in "items" $this->checkUndefined($v, $schema->items, $path, $k); // Recheck with "additionalItems" if the first test fails if (\count($initErrors) < \count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== \false)) { $secondErrors = $this->getErrors(); $this->checkUndefined($v, $schema->additionalItems, $path, $k); } // Reset errors if needed if (isset($secondErrors) && \count($secondErrors) < \count($this->getErrors())) { $this->errors = $secondErrors; } elseif (isset($secondErrors) && \count($secondErrors) === \count($this->getErrors())) { $this->errors = $initErrors; } } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ } else { // Defined item type definitions foreach ($value as $k => &$v) { if (\array_key_exists($k, $schema->items)) { $this->checkUndefined($v, $schema->items[$k], $path, $k); } else { // Additional items if (\property_exists($schema, 'additionalItems')) { if ($schema->additionalItems !== \false) { $this->checkUndefined($v, $schema->additionalItems, $path, $k); } else { $this->addError($path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems)); } } else { // Should be valid against an empty schema $this->checkUndefined($v, new \stdClass(), $path, $k); } } } unset($v); /* remove dangling reference to prevent any future bugs * caused by accidentally using $v elsewhere */ // Treat when we have more schema definitions than values, not for empty arrays if (\count($value) > 0) { for ($k = \count($value); $k < \count($schema->items); $k++) { $undefinedInstance = $this->factory->createInstanceFor('undefined'); $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k); } } } } } * @author Bruno Prieto Reis */ abstract class Constraint extends BaseConstraint implements ConstraintInterface { protected $inlineSchemaProperty = '$schema'; const CHECK_MODE_NONE = 0x0; const CHECK_MODE_NORMAL = 0x1; const CHECK_MODE_TYPE_CAST = 0x2; const CHECK_MODE_COERCE_TYPES = 0x4; const CHECK_MODE_APPLY_DEFAULTS = 0x8; const CHECK_MODE_EXCEPTIONS = 0x10; const CHECK_MODE_DISABLE_FORMAT = 0x20; const CHECK_MODE_ONLY_REQUIRED_DEFAULTS = 0x80; const CHECK_MODE_VALIDATE_SCHEMA = 0x100; /** * Bubble down the path * * @param JsonPointer|null $path Current path * @param mixed $i What to append to the path * * @return JsonPointer; */ protected function incrementPath(JsonPointer $path = null, $i) { $path = $path ?: new JsonPointer(''); if ($i === null || $i === '') { return $path; } $path = $path->withPropertyPaths(\array_merge($path->getPropertyPaths(), array($i))); return $path; } /** * Validates an array * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('collection'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Validates an object * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $properties * @param mixed $additionalProperties * @param mixed $patternProperties */ protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null, $additionalProperties = null, $patternProperties = null, $appliedDefaults = array()) { $validator = $this->factory->createInstanceFor('object'); $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults); $this->addErrors($validator->getErrors()); } /** * Validates the type of a property * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('type'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a undefined element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = \false) { $validator = $this->factory->createInstanceFor('undefined'); $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault); $this->addErrors($validator->getErrors()); } /** * Checks a string element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkString($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('string'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a number element * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param mixed $i */ protected function checkNumber($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('number'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a enum element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkEnum($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('enum'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks format of an element * * @param mixed $value * @param mixed $schema * @param JsonPointer|null $path * @param mixed $i */ protected function checkFormat($value, $schema = null, JsonPointer $path = null, $i = null) { $validator = $this->factory->createInstanceFor('format'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Get the type check based on the set check mode. * * @return TypeCheck\TypeCheckInterface */ protected function getTypeCheck() { return $this->factory->getTypeCheck(); } /** * @param JsonPointer $pointer * * @return string property path */ protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer) { $result = \array_map(function ($path) { return \sprintf(\is_numeric($path) ? '[%d]' : '.%s', $path); }, $pointer->getPropertyPaths()); return \trim(\implode('', $result), '.'); } } * * @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 */ class FormatConstraint extends Constraint { /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { return; } switch ($schema->format) { case 'date': if (!($date = $this->validateDateTime($element, 'Y-m-d'))) { $this->addError($path, \sprintf('Invalid date %s, expected format YYYY-MM-DD', \json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'time': if (!$this->validateDateTime($element, 'H:i:s')) { $this->addError($path, \sprintf('Invalid time %s, expected format hh:mm:ss', \json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'date-time': if (null === Rfc3339::createFromString($element)) { $this->addError($path, \sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', \json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'utc-millisec': if (!$this->validateDateTime($element, 'U')) { $this->addError($path, \sprintf('Invalid time %s, expected integer of milliseconds since Epoch', \json_encode($element)), 'format', array('format' => $schema->format)); } break; case 'regex': if (!$this->validateRegex($element)) { $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); } break; case 'color': if (!$this->validateColor($element)) { $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); } break; case 'style': if (!$this->validateStyle($element)) { $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); } break; case 'phone': if (!$this->validatePhone($element)) { $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); } break; case 'uri': if (null === \filter_var($element, \FILTER_VALIDATE_URL, \FILTER_NULL_ON_FAILURE)) { $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); } break; case 'uriref': case 'uri-reference': if (null === \filter_var($element, \FILTER_VALIDATE_URL, \FILTER_NULL_ON_FAILURE)) { // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. if (\substr($element, 0, 2) === '//') { // network-path reference $validURL = \filter_var('scheme:' . $element, \FILTER_VALIDATE_URL, \FILTER_NULL_ON_FAILURE); } elseif (\substr($element, 0, 1) === '/') { // absolute-path reference $validURL = \filter_var('scheme://host' . $element, \FILTER_VALIDATE_URL, \FILTER_NULL_ON_FAILURE); } elseif (\strlen($element)) { // relative-path reference $pathParts = \explode('/', $element, 2); if (\strpos($pathParts[0], ':') !== \false) { $validURL = null; } else { $validURL = \filter_var('scheme://host/' . $element, \FILTER_VALIDATE_URL, \FILTER_NULL_ON_FAILURE); } } else { $validURL = null; } if ($validURL === null) { $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); } } break; case 'email': $filterFlags = \FILTER_NULL_ON_FAILURE; if (\defined('FILTER_FLAG_EMAIL_UNICODE')) { // Only available from PHP >= 7.1.0, so ignore it for coverage checks $filterFlags |= \constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore } if (null === \filter_var($element, \FILTER_VALIDATE_EMAIL, $filterFlags)) { $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); } break; case 'ip-address': case 'ipv4': if (null === \filter_var($element, \FILTER_VALIDATE_IP, \FILTER_NULL_ON_FAILURE | \FILTER_FLAG_IPV4)) { $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); } break; case 'ipv6': if (null === \filter_var($element, \FILTER_VALIDATE_IP, \FILTER_NULL_ON_FAILURE | \FILTER_FLAG_IPV6)) { $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($element)) { $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); } break; default: // Empty as it should be: // The value of this keyword is called a format attribute. It MUST be a string. // A format attribute can generally only validate a given set of instance types. // If the type of the instance to validate is not in this set, validation for // this format attribute and instance SHOULD succeed. // http://json-schema.org/latest/json-schema-validation.html#anchor105 break; } } protected function validateDateTime($datetime, $format) { $dt = \DateTime::createFromFormat($format, $datetime); if (!$dt) { return \false; } if ($datetime === $dt->format($format)) { return \true; } // handles the case where a non-6 digit microsecond datetime is passed // which will fail the above string comparison because the passed // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return // '2000-05-01T12:12:12.123000Z' if (\strpos('u', $format) !== -1 && \preg_match('/\\.\\d+Z$/', $datetime)) { return \true; } return \false; } protected function validateRegex($regex) { return \false !== @\preg_match('/' . $regex . '/u', ''); } protected function validateColor($color) { if (\in_array(\strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'))) { return \true; } return \preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); } protected function validateStyle($style) { $properties = \explode(';', \rtrim($style, ';')); $invalidEntries = \preg_grep('/^\\s*[-a-z]+\\s*:\\s*.+$/i', $properties, \PREG_GREP_INVERT); return empty($invalidEntries); } protected function validatePhone($phone) { return \preg_match('/^\\+?(\\(\\d{3}\\)|\\d{3}) \\d{3} \\d{4}$/', $phone); } protected function validateHostname($host) { $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$/i'; return \preg_match($hostnameRegex, $host); } } {$property}; } public static function propertySet(&$value, $property, $data) { $value->{$property} = $data; } public static function propertyExists($value, $property) { return \property_exists($value, $property); } public static function propertyCount($value) { if (!\is_object($value)) { return 0; } return \count(\get_object_vars($value)); } } {$property}; } return $value[$property]; } public static function propertySet(&$value, $property, $data) { if (\is_object($value)) { $value->{$property} = $data; } else { $value[$property] = $data; } } public static function propertyExists($value, $property) { if (\is_object($value)) { return \property_exists($value, $property); } return \array_key_exists($property, $value); } public static function propertyCount($value) { if (\is_object($value)) { return \count(\get_object_vars($value)); } return \count($value); } /** * Check if the provided array is associative or not * * @param array $arr * * @return bool */ private static function isAssociativeArray($arr) { return \array_keys($arr) !== \range(0, \count($arr) - 1); } } * @author Bruno Prieto Reis */ #[\AllowDynamicProperties] class UndefinedConstraint extends Constraint { /** * @var array List of properties to which a default value has been applied */ protected $appliedDefaults = array(); /** * {@inheritdoc} */ public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = \false) { if (\is_null($schema) || !\is_object($schema)) { return; } $path = $this->incrementPath($path ?: new JsonPointer(''), $i); if ($fromDefault) { $path->setFromDefault(); } // check special properties $this->validateCommonProperties($value, $schema, $path, $i); // check allOf, anyOf, and oneOf properties $this->validateOfProperties($value, $schema, $path, ''); // check known types $this->validateTypes($value, $schema, $path, $i); } /** * Validates the value against the types * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ public function validateTypes(&$value, $schema, JsonPointer $path, $i = null) { // check array if ($this->getTypeCheck()->isArray($value)) { $this->checkArray($value, $schema, $path, $i); } // check object if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays, // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST // is not set (i.e. don't use $this->getTypeCheck() here). $this->checkObject($value, $schema, $path, isset($schema->properties) ? $schema->properties : null, isset($schema->additionalProperties) ? $schema->additionalProperties : null, isset($schema->patternProperties) ? $schema->patternProperties : null, $this->appliedDefaults); } // check string if (\is_string($value)) { $this->checkString($value, $schema, $path, $i); } // check numeric if (\is_numeric($value)) { $this->checkNumber($value, $schema, $path, $i); } // check enum if (isset($schema->enum)) { $this->checkEnum($value, $schema, $path, $i); } } /** * Validates common properties * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '') { // if it extends another schema, it must pass that schema as well if (isset($schema->extends)) { if (\is_string($schema->extends)) { $schema->extends = $this->validateUri($schema, $schema->extends); } if (\is_array($schema->extends)) { foreach ($schema->extends as $extends) { $this->checkUndefined($value, $extends, $path, $i); } } else { $this->checkUndefined($value, $schema->extends, $path, $i); } } // Apply default values from schema if (!$path->fromDefault()) { $this->applyDefaultValues($value, $schema, $path); } // Verify required values if ($this->getTypeCheck()->isObject($value)) { if (!$value instanceof self && isset($schema->required) && \is_array($schema->required)) { // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...] foreach ($schema->required as $required) { if (!$this->getTypeCheck()->propertyExists($value, $required)) { $this->addError($this->incrementPath($path ?: new JsonPointer(''), $required), 'The property ' . $required . ' is required', 'required'); } } } elseif (isset($schema->required) && !\is_array($schema->required)) { // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} if ($schema->required && $value instanceof self) { $propertyPaths = $path->getPropertyPaths(); $propertyName = \end($propertyPaths); $this->addError($path, 'The property ' . $propertyName . ' is required', 'required'); } } else { // if the value is both undefined and not required, skip remaining checks // in this method which assume an actual, defined instance when validating. if ($value instanceof self) { return; } } } // Verify type if (!$value instanceof self) { $this->checkType($value, $schema, $path, $i); } // Verify disallowed items if (isset($schema->disallow)) { $initErrors = $this->getErrors(); $typeSchema = new \stdClass(); $typeSchema->type = $schema->disallow; $this->checkType($value, $typeSchema, $path); // if no new errors were raised it must be a disallowed value if (\count($this->getErrors()) == \count($initErrors)) { $this->addError($path, 'Disallowed value was matched', 'disallow'); } else { $this->errors = $initErrors; } } if (isset($schema->not)) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $schema->not, $path, $i); // if no new errors were raised then the instance validated against the "not" schema if (\count($this->getErrors()) == \count($initErrors)) { $this->addError($path, 'Matched a schema which it should not', 'not'); } else { $this->errors = $initErrors; } } // Verify that dependencies are met if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) { $this->validateDependencies($value, $schema->dependencies, $path); } } /** * Check whether a default should be applied for this value * * @param mixed $schema * @param mixed $parentSchema * @param bool $requiredOnly * * @return bool */ private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) { // required-only mode is off if (!$requiredOnly) { return \true; } // draft-04 required is set if ($name !== null && isset($parentSchema->required) && \is_array($parentSchema->required) && \in_array($name, $parentSchema->required)) { return \true; } // draft-03 required is set if (isset($schema->required) && !\is_array($schema->required) && $schema->required) { return \true; } // default case return \false; } /** * Apply default values * * @param mixed $value * @param mixed $schema * @param JsonPointer $path */ protected function applyDefaultValues(&$value, $schema, $path) { // only apply defaults if feature is enabled if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { return; } // apply defaults if appropriate $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { // $value is an object or assoc array, and properties are defined - treat as an object foreach ($schema->properties as $currentProperty => $propertyDefinition) { $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition); if (!LooseTypeCheck::propertyExists($value, $currentProperty) && \property_exists($propertyDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)) { // assign default value if (\is_object($propertyDefinition->default)) { LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default); } else { LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default); } $this->appliedDefaults[] = $currentProperty; } } } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) { $items = array(); if (LooseTypeCheck::isArray($schema->items)) { $items = $schema->items; } elseif (isset($schema->minItems) && \count($value) < $schema->minItems) { $items = \array_fill(\count($value), $schema->minItems - \count($value), $schema->items); } // $value is an array, and items are defined - treat as plain array foreach ($items as $currentItem => $itemDefinition) { $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition); if (!\array_key_exists($currentItem, $value) && \property_exists($itemDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { if (\is_object($itemDefinition->default)) { $value[$currentItem] = clone $itemDefinition->default; } else { $value[$currentItem] = $itemDefinition->default; } } $path->setFromDefault(); } } elseif ($value instanceof self && \property_exists($schema, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { // $value is a leaf, not a container - apply the default directly $value = \is_object($schema->default) ? clone $schema->default : $schema->default; $path->setFromDefault(); } } /** * Validate allOf, anyOf, and oneOf properties * * @param mixed $value * @param mixed $schema * @param JsonPointer $path * @param string $i */ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '') { // Verify type if ($value instanceof self) { return; } if (isset($schema->allOf)) { $isValid = \true; foreach ($schema->allOf as $allOf) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $allOf, $path, $i); $isValid = $isValid && \count($this->getErrors()) == \count($initErrors); } if (!$isValid) { $this->addError($path, 'Failed to match all schemas', 'allOf'); } } if (isset($schema->anyOf)) { $isValid = \false; $startErrors = $this->getErrors(); $caughtException = null; foreach ($schema->anyOf as $anyOf) { $initErrors = $this->getErrors(); try { $this->checkUndefined($value, $anyOf, $path, $i); if ($isValid = \count($this->getErrors()) == \count($initErrors)) { break; } } catch (ValidationException $e) { $isValid = \false; } } if (!$isValid) { $this->addError($path, 'Failed to match at least one schema', 'anyOf'); } else { $this->errors = $startErrors; } } if (isset($schema->oneOf)) { $allErrors = array(); $matchedSchemas = 0; $startErrors = $this->getErrors(); foreach ($schema->oneOf as $oneOf) { try { $this->errors = array(); $this->checkUndefined($value, $oneOf, $path, $i); if (\count($this->getErrors()) == 0) { $matchedSchemas++; } $allErrors = \array_merge($allErrors, \array_values($this->getErrors())); } catch (ValidationException $e) { // deliberately do nothing here - validation failed, but we want to check // other schema options in the OneOf field. } } if ($matchedSchemas !== 1) { $this->addErrors(\array_merge($allErrors, $startErrors)); $this->addError($path, 'Failed to match exactly one schema', 'oneOf'); } else { $this->errors = $startErrors; } } } /** * Validate dependencies * * @param mixed $value * @param mixed $dependencies * @param JsonPointer $path * @param string $i */ protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '') { foreach ($dependencies as $key => $dependency) { if ($this->getTypeCheck()->propertyExists($value, $key)) { if (\is_string($dependency)) { // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { $this->addError($path, "{$key} depends on {$dependency} and {$dependency} is missing", 'dependencies'); } } elseif (\is_array($dependency)) { // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} foreach ($dependency as $d) { if (!$this->getTypeCheck()->propertyExists($value, $d)) { $this->addError($path, "{$key} depends on {$d} and {$d} is missing", 'dependencies'); } } } elseif (\is_object($dependency)) { // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}} $this->checkUndefined($value, $dependency, $path, $i); } } } } protected function validateUri($schema, $schemaUri = null) { $resolver = new UriResolver(); $retriever = $this->factory->getUriRetriever(); $jsonSchema = null; if ($resolver->isValid($schemaUri)) { $schemaId = \property_exists($schema, 'id') ? $schema->id : null; $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); } return $jsonSchema; } } * @author Bruno Prieto Reis */ class SchemaConstraint extends Constraint { const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#'; /** * {@inheritdoc} */ public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) { if ($schema !== null) { // passed schema $validationSchema = $schema; } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { // inline schema $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); } else { throw new InvalidArgumentException('no schema found to verify against'); } // cast array schemas to object if (\is_array($validationSchema)) { $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); } // validate schema against whatever is defined in $validationSchema->$schema. If no // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04). if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { if (!$this->getTypeCheck()->isObject($validationSchema)) { throw new RuntimeException('Cannot validate the schema of a non-object'); } if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); } else { $schemaSpec = self::DEFAULT_SCHEMA_SPEC; } // get the spec schema $schemaStorage = $this->factory->getSchemaStorage(); if (!$this->getTypeCheck()->isObject($schemaSpec)) { $schemaSpec = $schemaStorage->getSchema($schemaSpec); } // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA $initialErrorCount = $this->numErrors(); $initialConfig = $this->factory->getConfig(); $initialContext = $this->factory->getErrorContext(); $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); // validate schema try { $this->check($validationSchema, $schemaSpec); } catch (\Exception $e) { if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { throw new InvalidSchemaException('Schema did not pass validation', 0, $e); } } if ($this->numErrors() > $initialErrorCount) { $this->addError($path, 'Schema is not valid', 'schema'); } // restore the initial config $this->factory->setConfig($initialConfig); $this->factory->setErrorContext($initialContext); } // validate element against $validationSchema $this->checkUndefined($element, $validationSchema, $path, $i); } } */ class JsonPointer { /** @var string */ private $filename; /** @var string[] */ private $propertyPaths = array(); /** * @var bool Whether the value at this path was set from a schema default */ private $fromDefault = \false; /** * @param string $value * * @throws InvalidArgumentException when $value is not a string */ public function __construct($value) { if (!\is_string($value)) { throw new InvalidArgumentException('Ref value must be a string'); } $splitRef = \explode('#', $value, 2); $this->filename = $splitRef[0]; if (\array_key_exists(1, $splitRef)) { $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); } } /** * @param string $propertyPathString * * @return string[] */ private function decodePropertyPaths($propertyPathString) { $paths = array(); foreach (\explode('/', \trim($propertyPathString, '/')) as $path) { $path = $this->decodePath($path); if (\is_string($path) && '' !== $path) { $paths[] = $path; } } return $paths; } /** * @return array */ private function encodePropertyPaths() { return \array_map(array($this, 'encodePath'), $this->getPropertyPaths()); } /** * @param string $path * * @return string */ private function decodePath($path) { return \strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); } /** * @param string $path * * @return string */ private function encodePath($path) { return \strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25')); } /** * @return string */ public function getFilename() { return $this->filename; } /** * @return string[] */ public function getPropertyPaths() { return $this->propertyPaths; } /** * @param array $propertyPaths * * @return JsonPointer */ public function withPropertyPaths(array $propertyPaths) { $new = clone $this; $new->propertyPaths = $propertyPaths; return $new; } /** * @return string */ public function getPropertyPathAsString() { return \rtrim('#/' . \implode('/', $this->encodePropertyPaths()), '/'); } /** * @return string */ public function __toString() { return $this->getFilename() . $this->getPropertyPathAsString(); } /** * Mark the value at this path as being set from a schema default */ public function setFromDefault() { $this->fromDefault = \true; } /** * Check whether the value at this path was set from a schema default * * @return bool */ public function fromDefault() { return $this->fromDefault; } } */ class ObjectIterator implements \Iterator, \Countable { /** @var object */ private $object; /** @var int */ private $position = 0; /** @var array */ private $data = array(); /** @var bool */ private $initialized = \false; /** * @param object $object */ public function __construct($object) { $this->object = $object; } /** * {@inheritdoc} */ public function current() { $this->initialize(); return $this->data[$this->position]; } /** * {@inheritdoc} */ public function next() { $this->initialize(); $this->position++; } /** * {@inheritdoc} */ public function key() { $this->initialize(); return $this->position; } /** * {@inheritdoc} */ public function valid() { $this->initialize(); return isset($this->data[$this->position]); } /** * {@inheritdoc} */ public function rewind() { $this->initialize(); $this->position = 0; } /** * {@inheritdoc} */ public function count() { $this->initialize(); return \count($this->data); } /** * Initializer */ private function initialize() { if (!$this->initialized) { $this->data = $this->buildDataFromObject($this->object); $this->initialized = \true; } } /** * @param object $object * * @return array */ private function buildDataFromObject($object) { $result = array(); $stack = new \SplStack(); $stack->push($object); while (!$stack->isEmpty()) { $current = $stack->pop(); if (\is_object($current)) { \array_push($result, $current); } foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) { if (\is_object($propertyValue) || \is_array($propertyValue)) { $stack->push($propertyValue); } } } return $result; } /** * @param object|array $item * * @return array */ private function getDataFromItem($item) { if (!\is_object($item) && !\is_array($item)) { return array(); } return \is_object($item) ? \get_object_vars($item) : $item; } } uriRetriever = $uriRetriever ?: new UriRetriever(); $this->uriResolver = $uriResolver ?: new UriResolver(); } /** * @return UriRetrieverInterface */ public function getUriRetriever() { return $this->uriRetriever; } /** * @return UriResolverInterface */ public function getUriResolver() { return $this->uriResolver; } /** * {@inheritdoc} */ public function addSchema($id, $schema = null) { if (\is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { // if the schema was user-provided to Validator and is still null, then assume this is // what the user intended, as there's no way for us to retrieve anything else. User-supplied // schemas do not have an associated URI when passed via Validator::validate(). $schema = $this->uriRetriever->retrieve($id); } // cast array schemas to object if (\is_array($schema)) { $schema = BaseConstraint::arrayToObjectRecursive($schema); } // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format) // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367 if (\is_object($schema) && \property_exists($schema, 'id')) { if ($schema->id == 'http://json-schema.org/draft-04/schema#') { $schema->properties->id->format = 'uri-reference'; } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') { $schema->properties->id->format = 'uri-reference'; $schema->properties->{'$ref'}->format = 'uri-reference'; } } // resolve references $this->expandRefs($schema, $id); $this->schemas[$id] = $schema; } /** * Recursively resolve all references against the provided base * * @param mixed $schema * @param string $base */ private function expandRefs(&$schema, $base = null) { if (!\is_object($schema)) { if (\is_array($schema)) { foreach ($schema as &$member) { $this->expandRefs($member, $base); } } return; } if (\property_exists($schema, 'id') && \is_string($schema->id) && $base != $schema->id) { $base = $this->uriResolver->resolve($schema->id, $base); } if (\property_exists($schema, '$ref') && \is_string($schema->{'$ref'})) { $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base)); $schema->{'$ref'} = (string) $refPointer; } foreach ($schema as &$member) { $this->expandRefs($member, $base); } } /** * {@inheritdoc} */ public function getSchema($id) { if (!\array_key_exists($id, $this->schemas)) { $this->addSchema($id); } return $this->schemas[$id]; } /** * {@inheritdoc} */ public function resolveRef($ref) { $jsonPointer = new JsonPointer($ref); // resolve filename for pointer $fileName = $jsonPointer->getFilename(); if (!\strlen($fileName)) { throw new UnresolvableJsonPointerException(\sprintf("Could not resolve fragment '%s': no file is defined", $jsonPointer->getPropertyPathAsString())); } // get & process the schema $refSchema = $this->getSchema($fileName); foreach ($jsonPointer->getPropertyPaths() as $path) { if (\is_object($refSchema) && \property_exists($refSchema, $path)) { $refSchema = $this->resolveRefSchema($refSchema->{$path}); } elseif (\is_array($refSchema) && \array_key_exists($path, $refSchema)) { $refSchema = $this->resolveRefSchema($refSchema[$path]); } else { throw new UnresolvableJsonPointerException(\sprintf('File: %s is found, but could not resolve fragment: %s', $jsonPointer->getFilename(), $jsonPointer->getPropertyPathAsString())); } } return $refSchema; } /** * {@inheritdoc} */ public function resolveRefSchema($refSchema) { if (\is_object($refSchema) && \property_exists($refSchema, '$ref') && \is_string($refSchema->{'$ref'})) { $newSchema = $this->resolveRef($refSchema->{'$ref'}); $refSchema = (object) (\get_object_vars($refSchema) + \get_object_vars($newSchema)); unset($refSchema->{'$ref'}); } return $refSchema; } } */ interface UriRetrieverInterface { /** * Retrieve a schema from the specified URI * * @param string $uri URI that resolves to a JSON schema * * @throws \JsonSchema\Exception\ResourceNotFoundException * * @return mixed string|null */ public function retrieve($uri); /** * Get media content type * * @return string */ public function getContentType(); } */ abstract class AbstractRetriever implements UriRetrieverInterface { /** * Media content type * * @var string */ protected $contentType; /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType() */ public function getContentType() { return $this->contentType; } } */ class Curl extends AbstractRetriever { protected $messageBody; public function __construct() { if (!\function_exists('curl_init')) { // Cannot test this, because curl_init is present on all test platforms plus mock throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore } } /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $ch = \curl_init(); \curl_setopt($ch, \CURLOPT_URL, $uri); \curl_setopt($ch, \CURLOPT_HEADER, \true); \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, \true); \curl_setopt($ch, \CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE)); $response = \curl_exec($ch); if (\false === $response) { throw new \_ContaoManager\JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); } $this->fetchMessageBody($response); $this->fetchContentType($response); \curl_close($ch); return $this->messageBody; } /** * @param string $response cURL HTTP response */ private function fetchMessageBody($response) { \preg_match("/(?:\r\n){2}(.*)\$/ms", $response, $match); $this->messageBody = $match[1]; } /** * @param string $response cURL HTTP response * * @return bool Whether the Content-Type header was found or not */ protected function fetchContentType($response) { if (0 < \preg_match("/Content-Type:(\\V*)/ims", $response, $match)) { $this->contentType = \trim($match[1]); return \true; } return \false; } } */ class FileGetContents extends AbstractRetriever { protected $messageBody; /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $errorMessage = null; \set_error_handler(function ($errno, $errstr) use(&$errorMessage) { $errorMessage = $errstr; }); $response = \file_get_contents($uri); \restore_error_handler(); if ($errorMessage) { throw new ResourceNotFoundException($errorMessage); } if (\false === $response) { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } if ($response == '' && \substr($uri, 0, 7) == 'file://' && \substr($uri, -1) == '/') { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } $this->messageBody = $response; if (!empty($http_response_header)) { // $http_response_header cannot be tested, because it's defined in the method's local scope // See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info. $this->fetchContentType($http_response_header); // @codeCoverageIgnore } else { // @codeCoverageIgnore // Could be a "file://" url or something else - fake up the response $this->contentType = null; } return $this->messageBody; } /** * @param array $headers HTTP Response Headers * * @return bool Whether the Content-Type header was found or not */ private function fetchContentType(array $headers) { foreach ($headers as $header) { if ($this->contentType = self::getContentTypeMatchInHeader($header)) { return \true; } } return \false; } /** * @param string $header * * @return string|null */ protected static function getContentTypeMatchInHeader($header) { if (0 < \preg_match("/Content-Type:(\\V*)/ims", $header, $match)) { return \trim($match[1]); } return null; } } '{ ... }', * 'http://acme.com/schemas/address#' => '{ ... }', * )) * * $schema = $retriever->retrieve('http://acme.com/schemas/person#'); */ class PredefinedArray extends AbstractRetriever { /** * Contains schemas as URI => JSON * * @var array */ private $schemas; /** * Constructor * * @param array $schemas * @param string $contentType */ public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE) { $this->schemas = $schemas; $this->contentType = $contentType; } /** * {@inheritdoc} * * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { if (!\array_key_exists($uri, $this->schemas)) { throw new \_ContaoManager\JsonSchema\Exception\ResourceNotFoundException(\sprintf('The JSON schema "%s" was not found.', $uri)); } return $this->schemas[$uri]; } } */ class UriRetriever implements BaseUriRetrieverInterface { /** * @var array Map of URL translations */ protected $translationMap = array( // use local copies of the spec schemas '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json', ); /** * @var array A list of endpoints for media type check exclusion */ protected $allowedInvalidContentTypeEndpoints = array('http://json-schema.org/', 'https://json-schema.org/'); /** * @var null|UriRetrieverInterface */ protected $uriRetriever = null; /** * @var array|object[] * * @see loadSchema */ private $schemaCache = array(); /** * Adds an endpoint to the media type validation exclusion list * * @param string $endpoint */ public function addInvalidContentTypeEndpoint($endpoint) { $this->allowedInvalidContentTypeEndpoints[] = $endpoint; } /** * Guarantee the correct media type was encountered * * @param UriRetrieverInterface $uriRetriever * @param string $uri * * @return bool|void */ public function confirmMediaType($uriRetriever, $uri) { $contentType = $uriRetriever->getContentType(); if (\is_null($contentType)) { // Well, we didn't get an invalid one return; } if (\in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) { return; } foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) { if (\strpos($uri, $endpoint) === 0) { return \true; } } throw new InvalidSchemaMediaTypeException(\sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); } /** * Get a URI Retriever * * If none is specified, sets a default FileGetContents retriever and * returns that object. * * @return UriRetrieverInterface */ public function getUriRetriever() { if (\is_null($this->uriRetriever)) { $this->setUriRetriever(new FileGetContents()); } return $this->uriRetriever; } /** * Resolve a schema based on pointer * * URIs can have a fragment at the end in the format of * #/path/to/object and we are to look up the 'path' property of * the first object then the 'to' and 'object' properties. * * @param object $jsonSchema JSON Schema contents * @param string $uri JSON Schema URI * * @throws ResourceNotFoundException * * @return object JSON Schema after walking down the fragment pieces */ public function resolvePointer($jsonSchema, $uri) { $resolver = new UriResolver(); $parsed = $resolver->parse($uri); if (empty($parsed['fragment'])) { return $jsonSchema; } $path = \explode('/', $parsed['fragment']); while ($path) { $pathElement = \array_shift($path); if (!empty($pathElement)) { $pathElement = \str_replace('~1', '/', $pathElement); $pathElement = \str_replace('~0', '~', $pathElement); if (!empty($jsonSchema->{$pathElement})) { $jsonSchema = $jsonSchema->{$pathElement}; } else { throw new ResourceNotFoundException('Fragment "' . $parsed['fragment'] . '" not found' . ' in ' . $uri); } if (!\is_object($jsonSchema)) { throw new ResourceNotFoundException('Fragment part "' . $pathElement . '" is no object ' . ' in ' . $uri); } } } return $jsonSchema; } /** * {@inheritdoc} */ public function retrieve($uri, $baseUri = null, $translate = \true) { $resolver = new UriResolver(); $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); //fetch URL without #fragment $arParts = $resolver->parse($resolvedUri); if (isset($arParts['fragment'])) { unset($arParts['fragment']); $fetchUri = $resolver->generate($arParts); } // apply URI translations if ($translate) { $fetchUri = $this->translate($fetchUri); } $jsonSchema = $this->loadSchema($fetchUri); // Use the JSON pointer if specified $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); if ($jsonSchema instanceof \stdClass) { $jsonSchema->id = $resolvedUri; } return $jsonSchema; } /** * Fetch a schema from the given URI, json-decode it and return it. * Caches schema objects. * * @param string $fetchUri Absolute URI * * @return object JSON schema object */ protected function loadSchema($fetchUri) { if (isset($this->schemaCache[$fetchUri])) { return $this->schemaCache[$fetchUri]; } $uriRetriever = $this->getUriRetriever(); $contents = $this->uriRetriever->retrieve($fetchUri); $this->confirmMediaType($uriRetriever, $fetchUri); $jsonSchema = \json_decode($contents); if (\JSON_ERROR_NONE < ($error = \json_last_error())) { throw new JsonDecodingException($error); } $this->schemaCache[$fetchUri] = $jsonSchema; return $jsonSchema; } /** * Set the URI Retriever * * @param UriRetrieverInterface $uriRetriever * * @return $this for chaining */ public function setUriRetriever(UriRetrieverInterface $uriRetriever) { $this->uriRetriever = $uriRetriever; return $this; } /** * Parses a URI into five main components * * @param string $uri * * @return array */ public function parse($uri) { \preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < \count($match)) { $components = array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5]); } if (7 < \count($match)) { $components['query'] = $match[7]; } if (9 < \count($match)) { $components['fragment'] = $match[9]; } return $components; } /** * Builds a URI based on n array with the main components * * @param array $components * * @return string */ public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (\array_key_exists('query', $components)) { $uri .= $components['query']; } if (\array_key_exists('fragment', $components)) { $uri .= $components['fragment']; } return $uri; } /** * Resolves a URI * * @param string $uri Absolute or relative * @param string $baseUri Optional base URI * * @return string */ public function resolve($uri, $baseUri = null) { $components = $this->parse($uri); $path = $components['path']; if (\array_key_exists('scheme', $components) && 'http' === $components['scheme']) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); return $this->generate($baseComponents); } /** * @param string $uri * * @return bool */ public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } /** * Set a URL translation rule */ public function setTranslation($from, $to) { $this->translationMap[$from] = $to; } /** * Apply URI translation rules */ public function translate($uri) { foreach ($this->translationMap as $from => $to) { $uri = \preg_replace($from, $to, $uri); } // translate references to local files within the json-schema package $uri = \preg_replace('|^package://|', \sprintf('file://%s/', \realpath(__DIR__ . '/../../..')), $uri); return $uri; } } */ class UriResolver implements UriResolverInterface { /** * Parses a URI into five main components * * @param string $uri * * @return array */ public function parse($uri) { \preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < \count($match)) { $components = array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5]); } if (7 < \count($match)) { $components['query'] = $match[7]; } if (9 < \count($match)) { $components['fragment'] = $match[9]; } return $components; } /** * Builds a URI based on n array with the main components * * @param array $components * * @return string */ public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (\array_key_exists('query', $components) && \strlen($components['query'])) { $uri .= '?' . $components['query']; } if (\array_key_exists('fragment', $components)) { $uri .= '#' . $components['fragment']; } return $uri; } /** * {@inheritdoc} */ public function resolve($uri, $baseUri = null) { // treat non-uri base as local file path if (!\is_null($baseUri) && !\filter_var($baseUri, \FILTER_VALIDATE_URL) && !\preg_match('|^[^/]+://|u', $baseUri)) { if (\is_file($baseUri)) { $baseUri = 'file://' . \realpath($baseUri); } elseif (\is_dir($baseUri)) { $baseUri = 'file://' . \realpath($baseUri) . '/'; } else { $baseUri = 'file://' . \getcwd() . '/' . $baseUri; } } if ($uri == '') { return $baseUri; } $components = $this->parse($uri); $path = $components['path']; if (!empty($components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); if (isset($components['fragment'])) { $baseComponents['fragment'] = $components['fragment']; } return $this->generate($baseComponents); } /** * Tries to glue a relative path onto an absolute one * * @param string $relativePath * @param string $basePath * * @throws UriResolverException * * @return string Merged path */ public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); if ($relativePath == '') { return $basePath; } if ($relativePath[0] == '/') { return $relativePath; } $basePathSegments = \explode('/', $basePath); \preg_match('|^/?(\\.\\./(?:\\./)*)*|', $relativePath, $match); $numLevelUp = \strlen($match[0]) / 3 + 1; if ($numLevelUp >= \count($basePathSegments)) { throw new UriResolverException(\sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } $basePathSegments = \array_slice($basePathSegments, 0, -$numLevelUp); $path = \preg_replace('|^/?(\\.\\./(\\./)*)*|', '', $relativePath); return \implode('/', $basePathSegments) . '/' . $path; } /** * Normalizes a URI path component by removing dot-slash and double slashes * * @param string $path * * @return string */ private static function normalizePath($path) { $path = \preg_replace('|((?parse($uri); return !empty($components); } } */ class UnresolvableJsonPointerException extends InvalidArgumentException { } responseFactory = $responseFactory; $this->pretty = $pretty; } /** * Converts a problem to a JSON HTTP Response object, provided. * * @param ApiProblem $problem * The problem to convert. * * @return ResponseInterface * The appropriate response object. */ public function toJsonResponse(ApiProblem $problem) : ResponseInterface { $response = $this->toResponse($problem); $body = $response->getBody(); $body->write($problem->asJson($this->pretty)); $body->rewind(); return $response->withHeader('Content-Type', ApiProblem::CONTENT_TYPE_JSON)->withBody($body); } /** * Converts a problem to an XML HTTP Response object, provided. * * @param ApiProblem $problem * The problem to convert. * * @return ResponseInterface * The appropriate response object. */ public function toXmlResponse(ApiProblem $problem) : ResponseInterface { $response = $this->toResponse($problem); $body = $response->getBody(); $body->write($problem->asXml($this->pretty)); $body->rewind(); return $this->toResponse($problem)->withHeader('Content-Type', ApiProblem::CONTENT_TYPE_XML)->withBody($body); } /** * Converts a problem to a provided Response, without the format-sensitive bits. * * @param ApiProblem $problem * The problem to convert. * * @return ResponseInterface * The appropriate response object. */ protected function toResponse(ApiProblem $problem) : ResponseInterface { $status = $problem->getStatus() ?: 500; return $this->responseFactory->createResponse($status); } } setFailedValue($failedValue); } } 'Maximum stack depth exceeded', \JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', \JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', \JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', \JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', \JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded', \JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded', \JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given', \JSON_ERROR_INVALID_PROPERTY_NAME => 'A property name that cannot be encoded was given', \JSON_ERROR_UTF16 => 'Malformed UTF-16 characters, possibly incorrectly encoded']; /** * @var mixed */ protected $failedValue; /** * Maps a JSON error code to a human-friendly error message. * * @param int $jsonError * the JSON error code, as returned by json_last_error(). * @return string */ protected static function getExceptionMessage(int $jsonError) : string { return self::EXCEPTION_MESSAGES[$jsonError] ?? 'Unknown error'; } /** * Creates a new exception object based on the JSON error code. * * @param int $jsonError * the JSON error code. * @param mixed $failedValue * The value that failed to parse or encode. * @return JsonException * A new exception object. */ public static function fromJsonError(int $jsonError, $failedValue) : self { // This is a valid use of `new static`, even if PHPStan is wrong about it. // @phpstan-ignore-next-line return new static(static::getExceptionMessage($jsonError), $jsonError, null, $failedValue); } /** * @param mixed $failedValue */ public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, $failedValue = null) { parent::__construct($message, $code, $previous); $this->setFailedValue($failedValue); } /** * Sets the value that failed to parse or encode so it can be analyzed later. * * @param mixed $failedValue * The value that failed to parse or encode correctly. * @return JsonException * The invoked object. */ public function setFailedValue($failedValue) : self { $this->failedValue = $failedValue; return $this; } /** * Returns the value that failed to parse or encode properly. * * @return mixed * The value that failed to process. */ public function getFailedValue() { return $this->failedValue; } } \JsonSerializable */ class ApiProblem implements \ArrayAccess, \JsonSerializable { /** * The content type for a JSON based HTTP response carrying * problem details. * * @var string */ public const CONTENT_TYPE_JSON = 'application/problem+json'; /** * The content type for a XML based HTTP response carrying * problem details. * * @var string */ public const CONTENT_TYPE_XML = 'application/problem+xml'; /** * A short, human-readable summary of the problem type. * * It SHOULD NOT change from occurrence to occurrence of the problem, * except for purposes of localization. * * @var string */ protected $title; /** * A URI reference (RFC3986) that identifies the problem type. * * This specification encourages that, when dereferenced, it provide * human-readable documentation for the problem type (e.g., using HTML * [W3C.REC-html5-20141028]). When this member is not present, its value * is assumed to be "about:blank". * * Consumers MUST use the type string as the primary identifier for the * problem type. * * This value may be an absolute or or relative URI. If relative, it MUST be * resolved relative to the document's base URI, as per RFC3986, Section 5. * * @link http://tools.ietf.org/html/rfc3986 * * @var string */ protected $type; /** * The HTTP status code set by the origin server for this occurrence of the problem. * * The status member, if present, is only advisory; it conveys the HTTP * status code used for the convenience of the consumer. Generators MUST * use the same status code in the actual HTTP response, to assure that * generic HTTP software that does not understand this format still behaves * correctly. * * @var int */ protected $status = 0; /** * An human readable explanation specific to this occurrence of the problem. * * The "detail" member, if present, ought to focus on helping the client * correct the problem, rather than giving debugging information. * * Consumers SHOULD NOT parse the "detail" member for information; extensions * are more suitable and less error-prone ways to obtain such information. * * @var string */ protected $detail = ''; /** * A URI reference that identifies the specific occurrence of the problem. * * It may or may not yield further information if dereferenced. * * This value may be an absolute or or relative URI. If relative, it MUST be * resolved relative to the document's base URI, as per RFC3986, Section 5. * * @link http://tools.ietf.org/html/rfc3986 * * @var string */ protected $instance = ''; /** * Any arbitrary extension properties that have been assigned on this object. * * @var array */ protected $extensions = []; /** * Parses a JSON string into a Problem object. * * @param string $json * The JSON string to parse. * @return ApiProblem * A newly constructed problem object. * * @throws JsonParseException * Invalid JSON strings will result in a thrown exception. */ public static function fromJson(string $json) : self { if (\trim($json) === '') { throw new JsonParseException('An empty string is not a valid JSON value', \JSON_ERROR_SYNTAX, null, $json); } $parsed = \json_decode($json, \true); $lastError = \json_last_error(); if (\JSON_ERROR_NONE !== $lastError) { throw JsonParseException::fromJsonError($lastError, $json); } return static::decompile($parsed); } /** * Converts a SimpleXMLElement to a nested array. * * @param \SimpleXMLElement $element * The XML * @return array * A nested array corresponding to the XML element provided. */ protected static function xmlToArray(\SimpleXMLElement $element) : array { $data = (array) $element; foreach ($data as $key => $value) { if ($value instanceof \SimpleXMLElement) { $data[$key] = static::xmlToArray($value); } } return $data; } /** * Parses an XML string into a Problem object. * * @param string $string * The XML string to parse. * @return ApiProblem * A newly constructed problem object. */ public static function fromXml(string $string) : self { $xml = new \SimpleXMLElement($string); $data = static::xmlToArray($xml); return static::decompile($data); } /** * Parses an array into a Problem object. * * @param array $input * The array to parse. * @return ApiProblem * A newly constructed problem object. */ public static function fromArray(array $input) : self { $defaultInput = ['title' => null, 'type' => null, 'status' => null, 'detail' => null, 'instance' => null]; $data = $input + $defaultInput; return self::decompile($data); } /** * Decompiles an array into an ApiProblem object. * * @param array $parsed * An array parsed from JSON or XML to turn into an ApiProblem object. * @return ApiProblem * A new ApiProblem object. */ protected static function decompile(array $parsed) : self { // This line is fine as long as the constructor has only optional arguments. That is a requirement // that cannot be enforced in code, but is effectively a requirement of the class. // @phpstan-ignore-next-line $problem = new static(); if (null !== ($title = self::filterStringValue('title', $parsed))) { $problem->setTitle($title); } if (null !== ($type = self::filterStringValue('type', $parsed))) { $problem->setType($type); } if (null !== ($status = self::filterIntValue('status', $parsed))) { $problem->setStatus($status); } if (null !== ($detail = self::filterStringValue('detail', $parsed))) { $problem->setDetail($detail); } if (null !== ($instance = self::filterStringValue('instance', $parsed))) { $problem->setInstance($instance); } // Remove the defined keys. That means whatever is left must be a custom // extension property. unset($parsed['title'], $parsed['type'], $parsed['status'], $parsed['detail'], $parsed['instance']); foreach ($parsed as $key => $value) { $problem[$key] = $value; } return $problem; } /** * Parse the incoming value as non empty string. * The returned value can be used to populate Problem string based properties. * * Skip empty string or missing values. The string 0, however is allowed. * PHP makes this ugly. * The check on string handles XML decompile which may return an empty array. * * @param string|int $key * @param array $arr * * @return string|null */ protected static function filterStringValue($key, array $arr) : ?string { if (!\array_key_exists($key, $arr) || !\is_string($value = $arr[$key])) { return null; } if ($value === '') { return null; } return $value; } /** * Parse the incoming value as integer * The returned value can be used to populate Problem integer based properties. * * If the value can be parse as an integer it is return as one * otherwise null is returned. * * non integer value will all be discarded float included * @see https://3v4l.org/vZjLD * The check on scalar handles XML decompile which may return an empty array. * * @param int|string $key * @param array $arr * * @return int|null */ protected static function filterIntValue($key, array $arr) : ?int { if (!\array_key_exists($key, $arr) || !\is_scalar($value = $arr[$key])) { return null; } $intValue = \intval($value); if (\strval($value) !== \strval($intValue)) { return null; } return $intValue; } /** * Constructs a new ApiProblem. * * @param string $title * A short, human-readable summary of the problem type. It SHOULD NOT * change from occurrence to occurrence of the problem, except for * purposes of localization. * @param string $type * An absolute URI (RFC3986) that identifies the problem type. When * dereferenced, it SHOULD provide human-readable documentation for the * problem type (e.g., using HTML). */ public function __construct(string $title = '', string $type = 'about:blank') { $this->title = $title; $this->type = $type; } /** * Retrieves the title of the problem. * * @return string * The current title. */ public function getTitle() : string { return $this->title; } /** * Sets the title for this problem. * * @param string $title * The title to set. * @return ApiProblem * The invoked object. */ public function setTitle(string $title) : self { $this->title = $title; return $this; } /** * Retrieves the problem type of this problem. * * @return string * The problem type URI of this problem. */ public function getType() : string { return $this->type; } /** * Sets the problem type of this problem. * * @param string $type * The resolvable problem type URI of this problem. * @return ApiProblem * The invoked object. */ public function setType(string $type) : self { $this->type = $type; return $this; } /** * Retrieves the detail information of the problem. * * @return string * The detail of this problem. */ public function getDetail() : string { return $this->detail; } /** * Sets the detail for this problem. * * @param string $detail * The human-readable detail string about this problem. * @return ApiProblem * The invoked object. */ public function setDetail(string $detail) : self { $this->detail = $detail; return $this; } /** * Returns the problem instance URI of this problem. * * @return string * The problem instance URI of this problem. */ public function getInstance() : string { return $this->instance; } /** * Sets the problem instance URI of this problem. * * @param string $instance * An absolute URI that uniquely identifies this problem. It MAY link to * further information about the error, but that is not required. * * @return ApiProblem * The invoked object. */ public function setInstance(string $instance) : self { $this->instance = $instance; return $this; } /** * Returns the current HTTP status code. * * @return int * The current HTTP status code. If not set, it will return 0. */ public function getStatus() : int { return $this->status; } /** * Sets the HTTP status code for this problem. * * It is an error for this value to be set to a different value than the * actual HTTP response code. * * @param int $status * A valid HTTP status code. * @return ApiProblem * The invoked object. */ public function setStatus(int $status) : self { $this->status = $status; return $this; } /** * Renders this problem as JSON. * * @param bool $pretty * Whether or not to pretty-print the JSON string for easier debugging. * @return string * A JSON string representing this problem. */ public function asJson(bool $pretty = \false) : string { $response = $this->compile(); $options = 0; if ($pretty) { $options = \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT; } $json = \json_encode($response, $options); if (\false === $json) { throw JsonEncodeException::fromJsonError(\json_last_error(), $response); } return $json; } /** * Renders this problem as XML. * * @param bool $pretty * Whether or not to pretty-print the XML string for easier debugging. * @return string * An XML string representing this problem. */ public function asXml(bool $pretty = \false) : string { $doc = new \SimpleXMLElement(''); $this->arrayToXml($this->compile(), $doc); /** @var \DOMElement */ $dom = \dom_import_simplexml($doc); if ($pretty) { $dom->ownerDocument->preserveWhiteSpace = \false; $dom->ownerDocument->formatOutput = \true; } return $dom->ownerDocument->saveXML(); } /** * Renders this problem as a native PHP array. * * This is mostly useful for debugging, or for placing * this problem response into, say, a Symfony JsonResponse object. * * @return array * The API problem represented as an array. */ public function asArray() : array { return $this->compile(); } /** * Supports rendering this problem as a JSON using the json_encode() function. * * @return array * The API problem represented as an array for rendering. */ public function jsonSerialize() : array { return $this->compile(); } /** * Compiles the object down to an array format, suitable for serializing. * * @return array * This object, rendered to an array. */ protected function compile() : array { // Start with any extensions, since that's already an array. $response = $this->extensions; // These properties are optional. foreach (['title', 'type', 'status', 'detail', 'instance'] as $key) { // Skip empty string or missing values, as they are optional. // The string or integer 0, however, are allowed. PHP makes // this ugly. if (isset($this->{$key}) && $this->{$key} !== 0 && $this->{$key} !== '') { $response[$key] = $this->{$key}; } } return $response; } /** * Adds a nested array to a SimpleXML element. * * This method was shamelessly coped from the Nocarrier\Hal library: * * @link https://github.com/blongden/hal * * @param array $data * The data to add to the element. * @param \SimpleXMLElement $element * The XML object to which to add data. * @param mixed $parent * Used for internal recursion only. */ protected function arrayToXml(array $data, \SimpleXMLElement $element, $parent = null) : void { foreach ($data as $key => $value) { if (\is_array($value)) { if (!\is_numeric($key)) { if (\count($value) > 0 && isset($value[0])) { $this->arrayToXml($value, $element, $key); } else { $subnode = $element->addChild($key); $this->arrayToXml($value, $subnode, $key); } } else { $subnode = $element->addChild($parent); $this->arrayToXml($value, $subnode, $parent); } } else { if (!\is_numeric($key)) { if ($key[0] === '@') { $element->addAttribute(\substr($key, 1), $value); } elseif ($key === 'value') { $element->{0} = $value; } elseif (\is_bool($value)) { $element->addChild($key, \strval($value)); } else { $element->addChild($key, \htmlspecialchars((string) $value, \ENT_QUOTES)); } } else { $element->addChild($parent, \htmlspecialchars($value, \ENT_QUOTES)); } } } } /** * {@inheritdoc} * @param string $offset */ public function offsetExists($offset) : bool { return \array_key_exists($offset, $this->extensions); } /** * {@inheritdoc} * * @param string $offset * @return mixed * * The proper return type here is `mixed`, which is only available as of 8.0. */ #[\ReturnTypeWillChange] public function &offsetGet($offset) { return $this->extensions[$offset]; } /** * {@inheritdoc} * * @param string $offset * @param mixed $value */ public function offsetSet($offset, $value) : void { $this->extensions[$offset] = $value; } /** * {@inheritdoc} * * @param string $offset */ public function offsetUnset($offset) : void { unset($this->extensions[$offset]); } } # Changelog All notable changes to this project will be documented in this file, in reverse chronological order by release. ## 1.0.1 - 2016-08-06 ### Fixed - Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr - Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr - Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell - For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell ## 1.0.0 - 2015-12-11 Initial stable release; reflects accepted PSR-6 specification PSR Cache ========= This repository holds all interfaces defined by [PSR-6](http://www.php-fig.org/psr/psr-6/). Note that this is not a Cache implementation of its own. It is merely an interface that describes a Cache implementation. See the specification for more details. Copyright (c) 2015 PHP Framework Interoperability Group Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. { "name": "psr\/cache", "description": "Common interface for caching libraries", "keywords": [ "psr", "psr-6", "cache" ], "license": "MIT", "authors": [ { "name": "PHP-FIG", "homepage": "http:\/\/www.php-fig.org\/" } ], "require": { "php": ">=5.3.0" }, "autoload": { "psr-4": { "_ContaoManager\\Psr\\Cache\\": "src\/" } }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } } }=7.2.0" }, "autoload": { "psr-4": { "_ContaoManager\\Psr\\EventDispatcher\\": "src\/" } }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } } }=7.2.0" }, "autoload": { "psr-4": { "_ContaoManager\\Psr\\Container\\": "src\/" } } }logger = $logger; } public function doSomething() { if ($this->logger) { $this->logger->info('Doing work'); } try { $this->doSomethingElse(); } catch (Exception $exception) { $this->logger->error('Oh no!', array('exception' => $exception)); } // do something useful } } ``` You can then pick one of the implementations of the interface to get a logger. If you want to implement the interface, you can require this package and implement `Psr\Log\LoggerInterface` in your code. Please read the [specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) for details. ". * * Example ->error('Foo') would yield "error Foo". * * @return string[] */ public abstract function getLogs(); public function testImplements() { $this->assertInstanceOf('_ContaoManager\\Psr\\Log\\LoggerInterface', $this->getLogger()); } /** * @dataProvider provideLevelsAndMessages */ public function testLogsAtAllLevels($level, $message) { $logger = $this->getLogger(); $logger->{$level}($message, array('user' => 'Bob')); $logger->log($level, $message, array('user' => 'Bob')); $expected = array($level . ' message of level ' . $level . ' with context: Bob', $level . ' message of level ' . $level . ' with context: Bob'); $this->assertEquals($expected, $this->getLogs()); } public function provideLevelsAndMessages() { return array(LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}')); } /** * @expectedException \Psr\Log\InvalidArgumentException */ public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); $logger->log('invalid level', 'Foo'); } public function testContextReplacement() { $logger = $this->getLogger(); $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); $expected = array('info {Message {nothing} Bob Bar a}'); $this->assertEquals($expected, $this->getLogs()); } public function testObjectCastToString() { if (\method_exists($this, 'createPartialMock')) { $dummy = $this->createPartialMock('_ContaoManager\\Psr\\Log\\Test\\DummyTest', array('__toString')); } else { $dummy = $this->getMock('_ContaoManager\\Psr\\Log\\Test\\DummyTest', array('__toString')); } $dummy->expects($this->once())->method('__toString')->will($this->returnValue('DUMMY')); $this->getLogger()->warning($dummy); $expected = array('warning DUMMY'); $this->assertEquals($expected, $this->getLogs()); } public function testContextCanContainAnything() { $closed = \fopen('php://memory', 'r'); \fclose($closed); $context = array('bool' => \true, 'null' => null, 'string' => 'Foo', 'int' => 0, 'float' => 0.5, 'nested' => array('with object' => new DummyTest()), 'object' => new \DateTime(), 'resource' => \fopen('php://memory', 'r'), 'closed' => $closed); $this->getLogger()->warning('Crazy context data', $context); $expected = array('warning Crazy context data'); $this->assertEquals($expected, $this->getLogs()); } public function testContextExceptionKeyCanBeExceptionOrOtherValues() { $logger = $this->getLogger(); $logger->warning('Random message', array('exception' => 'oops')); $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); $expected = array('warning Random message', 'critical Uncaught Exception!'); $this->assertEquals($expected, $this->getLogs()); } } $level, 'message' => $message, 'context' => $context]; $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } public function hasRecords($level) { return isset($this->recordsByLevel[$level]); } public function hasRecord($record, $level) { if (\is_string($record)) { $record = ['message' => $record]; } return $this->hasRecordThatPasses(function ($rec) use($record) { if ($rec['message'] !== $record['message']) { return \false; } if (isset($record['context']) && $rec['context'] !== $record['context']) { return \false; } return \true; }, $level); } public function hasRecordThatContains($message, $level) { return $this->hasRecordThatPasses(function ($rec) use($message) { return \strpos($rec['message'], $message) !== \false; }, $level); } public function hasRecordThatMatches($regex, $level) { return $this->hasRecordThatPasses(function ($rec) use($regex) { return \preg_match($regex, $rec['message']) > 0; }, $level); } public function hasRecordThatPasses(callable $predicate, $level) { if (!isset($this->recordsByLevel[$level])) { return \false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { if (\call_user_func($predicate, $rec, $i)) { return \true; } } return \false; } public function __call($method, $args) { if (\preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = \strtolower($matches[2]); if (\method_exists($this, $genericMethod)) { $args[] = $level; return \call_user_func_array([$this, $genericMethod], $args); } } throw new \BadMethodCallException('Call to undefined method ' . \get_class($this) . '::' . $method . '()'); } public function reset() { $this->records = []; $this->recordsByLevel = []; } } logger = $logger; } } logger) { }` * blocks. */ class NullLogger extends AbstractLogger { /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ public function log($level, $message, array $context = array()) { // noop } } log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * * @return void */ public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * * @return void */ public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * * @return void */ public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * * @return void */ public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param array $context * * @return void */ public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * * @return void */ public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param array $context * * @return void */ public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ public abstract function log($level, $message, array $context = array()); } log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param mixed[] $context * * @return void */ public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param mixed[] $context * * @return void */ public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param mixed[] $context * * @return void */ public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param mixed[] $context * * @return void */ public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param mixed[] $context * * @return void */ public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param mixed[] $context * * @return void */ public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param mixed[] $context * * @return void */ public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } { "name": "psr\/log", "description": "Common interface for logging libraries", "keywords": [ "psr", "psr-3", "log" ], "homepage": "https:\/\/github.com\/php-fig\/log", "license": "MIT", "authors": [ { "name": "PHP-FIG", "homepage": "https:\/\/www.php-fig.org\/" } ], "require": { "php": ">=5.3.0" }, "autoload": { "psr-4": { "_ContaoManager\\Psr\\Log\\": "Psr\/Log\/" } }, "extra": { "branch-alias": { "dev-master": "1.1.x-dev" } } }Copyright (c) 2011-2020 Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### 2.9.2 (2023-10-27) * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804) * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815) * Fixed normalization error when normalizing incomplete classes (#1833) ### 2.9.1 (2023-02-06) * Fixed Logger not being serializable anymore (#1792) ### 2.9.0 (2023-02-05) * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748) * Added support for enum context values in PsrLogMessageProcessor (#1773) * Added graylog2/gelf-php 2.x support (#1747) * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739) * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791) * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758) * Fixed infinite loop detection within Fibers (#1753) * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781) ### 2.8.0 (2022-07-24) * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723) * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733) * Added `GoogleCloudLoggingFormatter` (#1719) * Added support for Predis 2.x (#1732) * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724) * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727) * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720) * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726) * Fixed PHP 8.2 deprecation warnings (#1722) * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678) ### 2.7.0 (2022-06-09) * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682) * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681) * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670) * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677) * Fixed RotatingFileHandler issue when the date format contained slashes (#1671) ### 2.6.0 (2022-05-10) * Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead * Added `SymfonyMailerHandler` (#1663) * Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662) * Added a way to filter/modify stack traces in LineFormatter (#1665) * Fixed UdpSocket not being able to reopen/reconnect after close() * Fixed infinite loops if a Handler is triggering logging while handling log records ### 2.5.0 (2022-04-08) * Added `callType` to IntrospectionProcessor (#1612) * Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651) ### 2.4.0 (2022-03-14) * Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603) * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600) * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637) * Added support for keeping native BSON types as is in MongoDBFormatter (#1620) * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613) * Added support for username/userIcon in SlackWebhookHandler (#1617) * Added extension points to BrowserConsoleHandler (#1593) * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630) * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614) * Fixed a few setter methods not returning `self` (#1609) * Fixed handling of records going over the max Telegram message length (#1616) ### 2.3.5 (2021-10-01) * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592) ### 2.3.4 (2021-09-15) * Fixed support for psr/log 3.x (#1589) ### 2.3.3 (2021-09-14) * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577) * Fixed support for psr/log 2.x (#1587) * Fixed some type annotations ### 2.3.2 (2021-07-23) * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568) ### 2.3.1 (2021-07-14) * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563) * Fixed some `@inheritDoc` annotations having the wrong case ### 2.3.0 (2021-07-05) * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557) * Added ability to customize date format when using JsonFormatter (#1561) * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531) * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540) * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553) ### 2.2.0 (2020-12-14) * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7 * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket) * Added handleBatch support for TelegramBotHandler * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars) * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters * Fixed PHP 8 issues in SyslogUdpHandler * Fixed internal type error when mbstring is missing ### 2.1.1 (2020-07-23) * Fixed removing of json encoding options * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler * Fixed SwiftMailerHandler not accepting email templates with an empty subject * Fixed array access on null in RavenHandler * Fixed unique_id in WebProcessor not being disableable ### 2.1.0 (2020-05-22) * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler * Added tentative support for PHP 8 * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags * Fixed GitProcessor type error when there is no git repo present * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" * Fixed support for relative paths in RotatingFileHandler ### 2.0.2 (2019-12-20) * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter * Fixed formatting of resources in JsonFormatter * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it * Fixed Turkish locale messing up the conversion of level names to their constant values ### 2.0.1 (2019-11-13) * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler * Fixed BrowserConsoleHandler formatting when using multiple styles * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings * Fixed normalization of SoapFault objects containing non-strings as "detail" * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative. ### 2.0.0 (2019-08-30) * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases * Fixed date timezone handling in SyslogUdpHandler ### 2.0.0-beta2 (2019-07-06) * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release * BC Break: PHP 7.2 is now the minimum required PHP version. * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad) * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account * Added support for JsonSerializable when normalizing exceptions * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler * Added SoapFault details to formatted exceptions * Fixed DeduplicationHandler silently failing to start when file could not be opened * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records * Fixed GelfFormatter losing some data when one attachment was too long * Fixed issue in SignalHandler restarting syscalls functionality * Improved performance of LogglyHandler when sending multiple logs in a single request ### 2.0.0-beta1 (2018-12-08) * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release * BC Break: PHP 7.1 is now the minimum required PHP version. * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn` * BC Break: The record timezone is now set per Logger instance and not statically anymore * BC Break: There is no more default handler configured on empty Logger instances * BC Break: ElasticSearchHandler renamed to ElasticaHandler * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability. * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled) * Added timezone and microseconds to the default date format * Added SendGridHandler to use the SendGrid API to send emails * Added LogmaticHandler to use the Logmatic.io API to store log records * Added SqsHandler to send log records to an AWS SQS queue * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files * Added ProcessHandler to write log output to the STDIN of a given process * Added HostnameProcessor that adds the machine's hostname to log records * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler * Fixed many minor issues in various handlers, and probably added a few regressions too ### 1.26.1 (2021-05-28) * Fixed PHP 8.1 deprecation warning ### 1.26.0 (2020-12-14) * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x) ### 1.25.5 (2020-07-23) * Fixed array access on null in RavenHandler * Fixed unique_id in WebProcessor not being disableable ### 1.25.4 (2020-05-22) * Fixed GitProcessor type error when there is no git repo present * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" * Fixed support for relative paths in RotatingFileHandler ### 1.25.3 (2019-12-20) * Fixed formatting of resources in JsonFormatter * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it * Fixed Turkish locale messing up the conversion of level names to their constant values ### 1.25.2 (2019-11-13) * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler * Fixed BrowserConsoleHandler formatting when using multiple styles * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings * Fixed normalization of SoapFault objects containing non-strings as "detail" * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding ### 1.25.1 (2019-09-06) * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too. ### 1.25.0 (2019-09-06) * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though. * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records * Fixed issue in SignalHandler restarting syscalls functionality * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases * Fixed ZendMonitorHandler to work with the latest Zend Server versions * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). ### 1.24.0 (2018-11-05) * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings. * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) * Added a way to log signals being received using Monolog\SignalHandler * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler * Added InsightOpsHandler to migrate users of the LogEntriesHandler * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9 * Added capture of stack traces to ErrorHandler when logging PHP errors * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts * Added forwarding of context info to FluentdFormatter * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example * Added ability to extend/override BrowserConsoleHandler * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility * Dropped official support for HHVM in test builds * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases * Fixed HipChatHandler bug where slack dropped messages randomly * Fixed normalization of objects in Slack handlers * Fixed support for PHP7's Throwable in NewRelicHandler * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory * Fixed table row styling issues in HtmlFormatter * Fixed RavenHandler dropping the message when logging exception * Fixed WhatFailureGroupHandler skipping processors when using handleBatch and implement it where possible * Fixed display of anonymous class names ### 1.23.0 (2017-06-19) * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument * Fixed GelfHandler truncation to be per field and not per message * Fixed compatibility issue with PHP <5.3.6 * Fixed support for headless Chrome in ChromePHPHandler * Fixed support for latest Aws SDK in DynamoDbHandler * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler ### 1.22.1 (2017-03-13) * Fixed lots of minor issues in the new Slack integrations * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces ### 1.22.0 (2016-11-26) * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily * Added MercurialProcessor to add mercurial revision and branch names to log records * Added support for AWS SDK v3 in DynamoDbHandler * Fixed fatal errors occurring when normalizing generators that have been fully consumed * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore * Fixed SyslogUdpHandler to avoid sending empty frames * Fixed a few PHP 7.0 and 7.1 compatibility issues ### 1.21.0 (2016-07-29) * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler * Added information about SoapFault instances in NormalizerFormatter * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level ### 1.20.0 (2016-07-02) * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy * Added StreamHandler::getUrl to retrieve the stream's URL * Added ability to override addRow/addTitle in HtmlFormatter * Added the $context to context information when the ErrorHandler handles a regular php error * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d * Fixed WhatFailureGroupHandler to work with PHP7 throwables * Fixed a few minor bugs ### 1.19.0 (2016-04-12) * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler * Fixed HipChatHandler handling of long messages ### 1.18.2 (2016-04-02) * Fixed ElasticaFormatter to use more precise dates * Fixed GelfMessageFormatter sending too long messages ### 1.18.1 (2016-03-13) * Fixed SlackHandler bug where slack dropped messages randomly * Fixed RedisHandler issue when using with the PHPRedis extension * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension * Fixed BrowserConsoleHandler regression ### 1.18.0 (2016-03-01) * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name * Added FluentdFormatter for the Fluentd unix socket protocol * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed * Added support for replacing context sub-keys using `%context.*%` in LineFormatter * Added support for `payload` context value in RollbarHandler * Added setRelease to RavenHandler to describe the application version, sent with every log * Added support for `fingerprint` context value in RavenHandler * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places ### 1.17.2 (2015-10-14) * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers * Fixed SlackHandler handling to use slack functionalities better * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id * Fixed 5.3 compatibility regression ### 1.17.1 (2015-08-31) * Fixed RollbarHandler triggering PHP notices ### 1.17.0 (2015-08-30) * Added support for `checksum` and `release` context/extra values in RavenHandler * Added better support for exceptions in RollbarHandler * Added UidProcessor::getUid * Added support for showing the resource type in NormalizedFormatter * Fixed IntrospectionProcessor triggering PHP notices ### 1.16.0 (2015-08-09) * Added IFTTTHandler to notify ifttt.com triggers * Added Logger::setHandlers() to allow setting/replacing all handlers * Added $capSize in RedisHandler to cap the log size * Fixed StreamHandler creation of directory to only trigger when the first log write happens * Fixed bug in the handling of curl failures * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler * Fixed missing fatal errors records with handlers that need to be closed to flush log records * Fixed TagProcessor::addTags support for associative arrays ### 1.15.0 (2015-07-12) * Added addTags and setTags methods to change a TagProcessor * Added automatic creation of directories if they are missing for a StreamHandler to open a log file * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used * Fixed HTML/JS escaping in BrowserConsoleHandler * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) ### 1.14.0 (2015-06-19) * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library * Added support for objects implementing __toString in the NormalizerFormatter * Added support for HipChat's v2 API in HipChatHandler * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) * Fixed curl errors being silently suppressed ### 1.13.1 (2015-03-09) * Fixed regression in HipChat requiring a new token to be created ### 1.13.0 (2015-03-05) * Added Registry::hasLogger to check for the presence of a logger instance * Added context.user support to RavenHandler * Added HipChat API v2 support in the HipChatHandler * Added NativeMailerHandler::addParameter to pass params to the mail() process * Added context data to SlackHandler when $includeContextAndExtra is true * Added ability to customize the Swift_Message per-email in SwiftMailerHandler * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided * Fixed serialization of INF and NaN values in Normalizer and LineFormatter ### 1.12.0 (2014-12-29) * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. * Added PsrHandler to forward records to another PSR-3 logger * Added SamplingHandler to wrap around a handler and include only every Nth record * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) * Added exception codes in the output of most formatters * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data * Added $host to HipChatHandler for users of private instances * Added $transactionName to NewRelicHandler and support for a transaction_name context value * Fixed MandrillHandler to avoid outputting API call responses * Fixed some non-standard behaviors in SyslogUdpHandler ### 1.11.0 (2014-09-30) * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails * Added MandrillHandler to send emails via the Mandrillapp.com API * Added SlackHandler to log records to a Slack.com account * Added FleepHookHandler to log records to a Fleep.io account * Added LogglyHandler::addTag to allow adding tags to an existing handler * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing * Added support for PhpAmqpLib in the AmqpHandler * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs * Added support for adding extra fields from $_SERVER in the WebProcessor * Fixed support for non-string values in PrsLogMessageProcessor * Fixed SwiftMailer messages being sent with the wrong date in long running scripts * Fixed minor PHP 5.6 compatibility issues * Fixed BufferHandler::close being called twice ### 1.10.0 (2014-06-04) * Added Logger::getHandlers() and Logger::getProcessors() methods * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached * Added support for extra data in NewRelicHandler * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines ### 1.9.1 (2014-04-24) * Fixed regression in RotatingFileHandler file permissions * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative ### 1.9.0 (2014-04-20) * Added LogEntriesHandler to send logs to a LogEntries account * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes * Added support for table formatting in FirePHPHandler via the table context key * Added a TagProcessor to add tags to records, and support for tags in RavenHandler * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files * Added sound support to the PushoverHandler * Fixed multi-threading support in StreamHandler * Fixed empty headers issue when ChromePHPHandler received no records * Fixed default format of the ErrorLogHandler ### 1.8.0 (2014-03-23) * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler * Added FlowdockHandler to send logs to a Flowdock account * Added RollbarHandler to send logs to a Rollbar account * Added HtmlFormatter to send prettier log emails with colors for each log level * Added GitProcessor to add the current branch/commit to extra record data * Added a Monolog\Registry class to allow easier global access to pre-configured loggers * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement * Added support for HHVM * Added support for Loggly batch uploads * Added support for tweaking the content type and encoding in NativeMailerHandler * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor * Fixed batch request support in GelfHandler ### 1.7.0 (2013-11-14) * Added ElasticSearchHandler to send logs to an Elastic Search server * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB * Added SyslogUdpHandler to send logs to a remote syslogd server * Added LogglyHandler to send logs to a Loggly account * Added $level to IntrospectionProcessor so it only adds backtraces when needed * Added $version to LogstashFormatter to allow using the new v1 Logstash format * Added $appName to NewRelicHandler * Added configuration of Pushover notification retries/expiry * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default * Added chainability to most setters for all handlers * Fixed RavenHandler batch processing so it takes the message from the record with highest priority * Fixed HipChatHandler batch processing so it sends all messages at once * Fixed issues with eAccelerator * Fixed and improved many small things ### 1.6.0 (2013-07-29) * Added HipChatHandler to send logs to a HipChat chat room * Added ErrorLogHandler to send logs to PHP's error_log function * Added NewRelicHandler to send logs to NewRelic's service * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel * Added stack traces output when normalizing exceptions (json output & co) * Added Monolog\Logger::API constant (currently 1) * Added support for ChromePHP's v4.0 extension * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel * Added support for sending messages to multiple users at once with the PushoverHandler * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now * Fixed issue in RotatingFileHandler when an open_basedir restriction is active * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 * Fixed SyslogHandler issue when many were used concurrently with different facilities ### 1.5.0 (2013-04-23) * Added ProcessIdProcessor to inject the PID in log records * Added UidProcessor to inject a unique identifier to all log records of one request/run * Added support for previous exceptions in the LineFormatter exception serialization * Added Monolog\Logger::getLevels() to get all available levels * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle ### 1.4.1 (2013-04-01) * Fixed exception formatting in the LineFormatter to be more minimalistic * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days * Fixed WebProcessor array access so it checks for data presence * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors ### 1.4.0 (2013-02-13) * Added RedisHandler to log to Redis via the Predis library or the phpredis extension * Added ZendMonitorHandler to log to the Zend Server monitor * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor * Added `$useSSL` option to the PushoverHandler which is enabled by default * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously * Fixed header injection capability in the NativeMailHandler ### 1.3.1 (2013-01-11) * Fixed LogstashFormatter to be usable with stream handlers * Fixed GelfMessageFormatter levels on Windows ### 1.3.0 (2013-01-08) * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) * Added PushoverHandler to send mobile notifications * Added CouchDBHandler and DoctrineCouchDBHandler * Added RavenHandler to send data to Sentry servers * Added support for the new MongoClient class in MongoDBHandler * Added microsecond precision to log records' timestamps * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing the oldest entries * Fixed normalization of objects with cyclic references ### 1.2.1 (2012-08-29) * Added new $logopts arg to SyslogHandler to provide custom openlog options * Fixed fatal error in SyslogHandler ### 1.2.0 (2012-08-18) * Added AmqpHandler (for use with AMQP servers) * Added CubeHandler * Added NativeMailerHandler::addHeader() to send custom headers in mails * Added the possibility to specify more than one recipient in NativeMailerHandler * Added the possibility to specify float timeouts in SocketHandler * Added NOTICE and EMERGENCY levels to conform with RFC 5424 * Fixed the log records to use the php default timezone instead of UTC * Fixed BufferHandler not being flushed properly on PHP fatal errors * Fixed normalization of exotic resource types * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog ### 1.1.0 (2012-04-23) * Added Monolog\Logger::isHandling() to check if a handler will handle the given log level * Added ChromePHPHandler * Added MongoDBHandler * Added GelfHandler (for use with Graylog2 servers) * Added SocketHandler (for use with syslog-ng for example) * Added NormalizerFormatter * Added the possibility to change the activation strategy of the FingersCrossedHandler * Added possibility to show microseconds in logs * Added `server` and `referer` to WebProcessor output ### 1.0.2 (2011-10-24) * Fixed bug in IE with large response headers and FirePHPHandler ### 1.0.1 (2011-08-25) * Added MemoryPeakUsageProcessor and MemoryUsageProcessor * Added Monolog\Logger::getName() to get a logger's channel name ### 1.0.0 (2011-07-06) * Added IntrospectionProcessor to get info from where the logger was called * Fixed WebProcessor in CLI ### 1.0.0-RC1 (2011-07-01) * Initial release # Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) [![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) [![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) Monolog sends your logs to files, sockets, inboxes, databases and various web services. See the complete list of handlers below. Special handlers allow you to build advanced logging strategies. This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) interface that you can type-hint against in your own libraries to keep a maximum of interoperability. You can also use it in your applications to make sure you can always use another compatible logger at a later time. As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. Internally Monolog still uses its own level scheme since it predates PSR-3. ## Installation Install the latest version with ```bash $ composer require monolog/monolog ``` ## Basic Usage ```php pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); // add records to the log $log->warning('Foo'); $log->error('Bar'); ``` ## Documentation - [Usage Instructions](doc/01-usage.md) - [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) - [Utility Classes](doc/03-utilities.md) - [Extending Monolog](doc/04-extending.md) - [Log Record Structure](doc/message-structure.md) ## Support Monolog Financially Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek). Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. ## Third Party Packages Third party handlers, formatters and processors are [listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You can also add your own there if you publish one. ## About ### Requirements - Monolog `^2.0` works with PHP 7.2 or above, use Monolog `^1.25` for PHP 5.3+ support. ### Support Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes. ### Submitting bugs and feature requests Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) ### Framework Integrations - Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) can be used very easily with Monolog since it implements the interface. - [Symfony](http://symfony.com) comes out of the box with Monolog. - [Laravel](http://laravel.com/) comes out of the box with Monolog. - [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. - [PPI](https://github.com/ppi/framework) comes out of the box with Monolog. - [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. - [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. - [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. - [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. - [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions. - [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. - [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog. - [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog. - [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins. - [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog. - [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog. - [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module. - [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension. - [Magento](https://magento.com/) comes out of the box with Monolog. ### Author Jordi Boggiano - -
See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project. ### License Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details ### Acknowledgements This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/) library, although most concepts have been adjusted to fit to the PHP world. ### 2.0.0 - `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2` install of Monolog when writing integration code. - Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn`. - DateTime are now formatted with a timezone and microseconds (unless disabled). Various formatters and log output might be affected, which may mess with log parsing in some cases. - The `datetime` in every record array is now a DateTimeImmutable, not that you should have been modifying these anyway. - The timezone is now set per Logger instance and not statically, either via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone should be converted. - `HandlerInterface` has been split off and two new interfaces now exist for more granular controls: `ProcessableHandlerInterface` and `FormattableHandlerInterface`. Handlers not extending `AbstractHandler` should make sure to implement the relevant interfaces. - `HandlerInterface` now requires the `close` method to be implemented. This only impacts you if you implement the interface yourself, but you can extend the new `Monolog\Handler\Handler` base class too. - There is no more default handler configured on empty Logger instances, if you were relying on that you will not get any output anymore, make sure to configure the handler you need. #### LogglyFormatter - The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly. #### AmqpHandler - Log levels are not shortened to 4 characters anymore. e.g. a warning record will be sent using the `warning.channel` routing key instead of `warn.channel` as in 1.x. - The exchange name does not default to 'log' anymore, and it is completely ignored now for the AMQP extension users. Only PHPAmqpLib uses it if provided. #### RotatingFileHandler - The file name format must now contain `{date}` and the date format must be set to one of the predefined FILE_PER_* constants to avoid issues with file rotation. See `setFilenameFormat`. #### LogstashFormatter - Removed Logstash V0 support - Context/extra prefix has been removed in favor of letting users configure the exact key being sent - Context/extra data are now sent as an object instead of single keys #### HipChatHandler - Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead #### SlackbotHandler - Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead #### RavenHandler - Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead #### ElasticSearchHandler - As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been renamed to ElasticaHandler and the new one added as ElasticsearchHandler. { "name": "monolog\/monolog", "description": "Sends your logs to files, sockets, inboxes, databases and various web services", "keywords": [ "log", "logging", "psr-3" ], "homepage": "https:\/\/github.com\/Seldaek\/monolog", "type": "library", "license": "MIT", "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https:\/\/seld.be" } ], "require": { "php": ">=7.2", "psr\/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { "ext-json": "*", "aws\/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine\/couchdb": "~1.0@dev", "elasticsearch\/elasticsearch": "^7 || ^8", "graylog2\/gelf-php": "^1.4.2 || ^2@dev", "guzzlehttp\/guzzle": "^7.4", "guzzlehttp\/psr7": "^2.2", "mongodb\/mongodb": "^1.8", "php-amqplib\/php-amqplib": "~2.4 || ^3", "phpspec\/prophecy": "^1.15", "phpstan\/phpstan": "^0.12.91", "phpunit\/phpunit": "^8.5.14", "predis\/predis": "^1.1 || ^2.0", "rollbar\/rollbar": "^1.3 || ^2 || ^3", "ruflin\/elastica": "^7", "swiftmailer\/swiftmailer": "^5.3|^6.0", "symfony\/mailer": "^5.4 || ^6", "symfony\/mime": "^5.4 || ^6" }, "suggest": { "graylog2\/gelf-php": "Allow sending log messages to a GrayLog2 server", "doctrine\/couchdb": "Allow sending log messages to a CouchDB server", "ruflin\/elastica": "Allow sending log messages to an Elastic Search server", "elasticsearch\/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "php-amqplib\/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", "mongodb\/mongodb": "Allow sending log messages to a MongoDB server (via library)", "aws\/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "rollbar\/rollbar": "Allow sending log messages to Rollbar", "ext-mbstring": "Allow to work properly with unicode symbols", "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", "ext-openssl": "Required to send log messages using SSL" }, "autoload": { "psr-4": { "_ContaoManager\\Monolog\\": "src\/Monolog" } }, "autoload-dev": { "psr-4": { "_ContaoManager\\Monolog\\": "tests\/Monolog" } }, "provide": { "psr\/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "extra": { "branch-alias": { "dev-main": "2.x-dev" } }, "scripts": { "test": "@php vendor\/bin\/phpunit", "phpstan": "@php vendor\/bin\/phpstan analyse" }, "config": { "lock": false, "sort-packages": true, "platform-check": false, "allow-plugins": { "composer\/package-versions-deprecated": true } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; /** * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html * * @author Ricardo Fontanelli */ class SendGridHandler extends MailHandler { /** * The SendGrid API User * @var string */ protected $apiUser; /** * The SendGrid API Key * @var string */ protected $apiKey; /** * The email addresses to which the message will be sent * @var string */ protected $from; /** * The email addresses to which the message will be sent * @var string[] */ protected $to; /** * The subject of the email * @var string */ protected $subject; /** * @param string $apiUser The SendGrid API User * @param string $apiKey The SendGrid API Key * @param string $from The sender of the email * @param string|string[] $to The recipients of the email * @param string $subject The subject of the mail */ public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, $level = Logger::ERROR, bool $bubble = \true) { if (!\extension_loaded('curl')) { throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); } parent::__construct($level, $bubble); $this->apiUser = $apiUser; $this->apiKey = $apiKey; $this->from = $from; $this->to = (array) $to; $this->subject = $subject; } /** * {@inheritDoc} */ protected function send(string $content, array $records) : void { $message = []; $message['api_user'] = $this->apiUser; $message['api_key'] = $this->apiKey; $message['from'] = $this->from; foreach ($this->to as $recipient) { $message['to[]'] = $recipient; } $message['subject'] = $this->subject; $message['date'] = \date('r'); if ($this->isHtmlBody($content)) { $message['html'] = $content; } else { $message['text'] = $content; } $ch = \curl_init(); \curl_setopt($ch, \CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); \curl_setopt($ch, \CURLOPT_POST, 1); \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1); \curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query($message)); Curl\Util::execute($ch, 2); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; /** * Inspired on LogEntriesHandler. * * @author Robert Kaufmann III * @author Gabriel Machado */ class InsightOpsHandler extends SocketHandler { /** * @var string */ protected $logToken; /** * @param string $token Log token supplied by InsightOps * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. * @param bool $useSSL Whether or not SSL encryption should be used * * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing */ public function __construct(string $token, string $region = 'us', bool $useSSL = \true, $level = Logger::DEBUG, bool $bubble = \true, bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { if ($useSSL && !\extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); } $endpoint = $useSSL ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' : $region . '.data.logs.insight.rapid7.com:80'; parent::__construct($endpoint, $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); $this->logToken = $token; } /** * {@inheritDoc} */ protected function generateDataStream(array $record) : string { return $this->logToken . ' ' . $record['formatted']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Used for testing purposes. * * It records all records and gives you access to them for verification. * * @author Jordi Boggiano * * @method bool hasEmergency($record) * @method bool hasAlert($record) * @method bool hasCritical($record) * @method bool hasError($record) * @method bool hasWarning($record) * @method bool hasNotice($record) * @method bool hasInfo($record) * @method bool hasDebug($record) * * @method bool hasEmergencyRecords() * @method bool hasAlertRecords() * @method bool hasCriticalRecords() * @method bool hasErrorRecords() * @method bool hasWarningRecords() * @method bool hasNoticeRecords() * @method bool hasInfoRecords() * @method bool hasDebugRecords() * * @method bool hasEmergencyThatContains($message) * @method bool hasAlertThatContains($message) * @method bool hasCriticalThatContains($message) * @method bool hasErrorThatContains($message) * @method bool hasWarningThatContains($message) * @method bool hasNoticeThatContains($message) * @method bool hasInfoThatContains($message) * @method bool hasDebugThatContains($message) * * @method bool hasEmergencyThatMatches($message) * @method bool hasAlertThatMatches($message) * @method bool hasCriticalThatMatches($message) * @method bool hasErrorThatMatches($message) * @method bool hasWarningThatMatches($message) * @method bool hasNoticeThatMatches($message) * @method bool hasInfoThatMatches($message) * @method bool hasDebugThatMatches($message) * * @method bool hasEmergencyThatPasses($message) * @method bool hasAlertThatPasses($message) * @method bool hasCriticalThatPasses($message) * @method bool hasErrorThatPasses($message) * @method bool hasWarningThatPasses($message) * @method bool hasNoticeThatPasses($message) * @method bool hasInfoThatPasses($message) * @method bool hasDebugThatPasses($message) * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class TestHandler extends AbstractProcessingHandler { /** @var Record[] */ protected $records = []; /** @var array */ protected $recordsByLevel = []; /** @var bool */ private $skipReset = \false; /** * @return array * * @phpstan-return Record[] */ public function getRecords() { return $this->records; } /** * @return void */ public function clear() { $this->records = []; $this->recordsByLevel = []; } /** * @return void */ public function reset() { if (!$this->skipReset) { $this->clear(); } } /** * @return void */ public function setSkipReset(bool $skipReset) { $this->skipReset = $skipReset; } /** * @param string|int $level Logging level value or name * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecords($level) : bool { return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); } /** * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records * @param string|int $level Logging level value or name * * @phpstan-param array{message: string, context?: mixed[]}|string $record * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecord($record, $level) : bool { if (\is_string($record)) { $record = array('message' => $record); } return $this->hasRecordThatPasses(function ($rec) use($record) { if ($rec['message'] !== $record['message']) { return \false; } if (isset($record['context']) && $rec['context'] !== $record['context']) { return \false; } return \true; }, $level); } /** * @param string|int $level Logging level value or name * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecordThatContains(string $message, $level) : bool { return $this->hasRecordThatPasses(function ($rec) use($message) { return \strpos($rec['message'], $message) !== \false; }, $level); } /** * @param string|int $level Logging level value or name * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecordThatMatches(string $regex, $level) : bool { return $this->hasRecordThatPasses(function (array $rec) use($regex) : bool { return \preg_match($regex, $rec['message']) > 0; }, $level); } /** * @param string|int $level Logging level value or name * @return bool * * @psalm-param callable(Record, int): mixed $predicate * @phpstan-param Level|LevelName|LogLevel::* $level */ public function hasRecordThatPasses(callable $predicate, $level) { $level = Logger::toMonologLevel($level); if (!isset($this->recordsByLevel[$level])) { return \false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { if ($predicate($rec, $i)) { return \true; } } return \false; } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } /** * @param string $method * @param mixed[] $args * @return bool */ public function __call($method, $args) { if (\preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = \constant('Monolog\\Logger::' . \strtoupper($matches[2])); $callback = [$this, $genericMethod]; if (\is_callable($callback)) { $args[] = $level; return \call_user_func_array($callback, $args); } } throw new \BadMethodCallException('Call to undefined method ' . \get_class($this) . '::' . $method . '()'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * IFTTTHandler uses cURL to trigger IFTTT Maker actions * * Register a secret key and trigger/event name at https://ifttt.com/maker * * value1 will be the channel from monolog's Logger constructor, * value2 will be the level name (ERROR, WARNING, ..) * value3 will be the log record's message * * @author Nehal Patel */ class IFTTTHandler extends AbstractProcessingHandler { /** @var string */ private $eventName; /** @var string */ private $secretKey; /** * @param string $eventName The name of the IFTTT Maker event that should be triggered * @param string $secretKey A valid IFTTT secret key */ public function __construct(string $eventName, string $secretKey, $level = Logger::ERROR, bool $bubble = \true) { if (!\extension_loaded('curl')) { throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler'); } $this->eventName = $eventName; $this->secretKey = $secretKey; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ public function write(array $record) : void { $postData = ["value1" => $record["channel"], "value2" => $record["level_name"], "value3" => $record["message"]]; $postString = Utils::jsonEncode($postData); $ch = \curl_init(); \curl_setopt($ch, \CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); \curl_setopt($ch, \CURLOPT_POST, \true); \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, \true); \curl_setopt($ch, \CURLOPT_POSTFIELDS, $postString); \curl_setopt($ch, \CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); Curl\Util::execute($ch); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; trait WebRequestRecognizerTrait { /** * Checks if PHP's serving a web request * @return bool */ protected function isWebRequest() : bool { return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; /** * Stores to any socket - uses fsockopen() or pfsockopen(). * * @author Pablo de Leon Belloc * @see http://php.net/manual/en/function.fsockopen.php * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SocketHandler extends AbstractProcessingHandler { /** @var string */ private $connectionString; /** @var float */ private $connectionTimeout; /** @var resource|null */ private $resource; /** @var float */ private $timeout; /** @var float */ private $writingTimeout; /** @var ?int */ private $lastSentBytes = null; /** @var ?int */ private $chunkSize; /** @var bool */ private $persistent; /** @var ?int */ private $errno = null; /** @var ?string */ private $errstr = null; /** @var ?float */ private $lastWritingAt = null; /** * @param string $connectionString Socket connection string * @param bool $persistent Flag to enable/disable persistent connections * @param float $timeout Socket timeout to wait until the request is being aborted * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been * established * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle * * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. */ public function __construct(string $connectionString, $level = Logger::DEBUG, bool $bubble = \true, bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { parent::__construct($level, $bubble); $this->connectionString = $connectionString; if ($connectionTimeout !== null) { $this->validateTimeout($connectionTimeout); } $this->connectionTimeout = $connectionTimeout ?? (float) \ini_get('default_socket_timeout'); $this->persistent = $persistent; $this->validateTimeout($timeout); $this->timeout = $timeout; $this->validateTimeout($writingTimeout); $this->writingTimeout = $writingTimeout; $this->chunkSize = $chunkSize; } /** * Connect (if necessary) and write to the socket * * {@inheritDoc} * * @throws \UnexpectedValueException * @throws \RuntimeException */ protected function write(array $record) : void { $this->connectIfNotConnected(); $data = $this->generateDataStream($record); $this->writeToSocket($data); } /** * We will not close a PersistentSocket instance so it can be reused in other requests. */ public function close() : void { if (!$this->isPersistent()) { $this->closeSocket(); } } /** * Close socket, if open */ public function closeSocket() : void { if (\is_resource($this->resource)) { \fclose($this->resource); $this->resource = null; } } /** * Set socket connection to be persistent. It only has effect before the connection is initiated. */ public function setPersistent(bool $persistent) : self { $this->persistent = $persistent; return $this; } /** * Set connection timeout. Only has effect before we connect. * * @see http://php.net/manual/en/function.fsockopen.php */ public function setConnectionTimeout(float $seconds) : self { $this->validateTimeout($seconds); $this->connectionTimeout = $seconds; return $this; } /** * Set write timeout. Only has effect before we connect. * * @see http://php.net/manual/en/function.stream-set-timeout.php */ public function setTimeout(float $seconds) : self { $this->validateTimeout($seconds); $this->timeout = $seconds; return $this; } /** * Set writing timeout. Only has effect during connection in the writing cycle. * * @param float $seconds 0 for no timeout */ public function setWritingTimeout(float $seconds) : self { $this->validateTimeout($seconds); $this->writingTimeout = $seconds; return $this; } /** * Set chunk size. Only has effect during connection in the writing cycle. */ public function setChunkSize(int $bytes) : self { $this->chunkSize = $bytes; return $this; } /** * Get current connection string */ public function getConnectionString() : string { return $this->connectionString; } /** * Get persistent setting */ public function isPersistent() : bool { return $this->persistent; } /** * Get current connection timeout setting */ public function getConnectionTimeout() : float { return $this->connectionTimeout; } /** * Get current in-transfer timeout */ public function getTimeout() : float { return $this->timeout; } /** * Get current local writing timeout * * @return float */ public function getWritingTimeout() : float { return $this->writingTimeout; } /** * Get current chunk size */ public function getChunkSize() : ?int { return $this->chunkSize; } /** * Check to see if the socket is currently available. * * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. */ public function isConnected() : bool { return \is_resource($this->resource) && !\feof($this->resource); // on TCP - other party can close connection. } /** * Wrapper to allow mocking * * @return resource|false */ protected function pfsockopen() { return @\pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); } /** * Wrapper to allow mocking * * @return resource|false */ protected function fsockopen() { return @\fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); } /** * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-timeout.php * * @return bool */ protected function streamSetTimeout() { $seconds = \floor($this->timeout); $microseconds = \round(($this->timeout - $seconds) * 1000000.0); if (!\is_resource($this->resource)) { throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); } return \stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); } /** * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-chunk-size.php * * @return int|bool */ protected function streamSetChunkSize() { if (!\is_resource($this->resource)) { throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); } if (null === $this->chunkSize) { throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); } return \stream_set_chunk_size($this->resource, $this->chunkSize); } /** * Wrapper to allow mocking * * @return int|bool */ protected function fwrite(string $data) { if (!\is_resource($this->resource)) { throw new \LogicException('fwrite called but $this->resource is not a resource'); } return @\fwrite($this->resource, $data); } /** * Wrapper to allow mocking * * @return mixed[]|bool */ protected function streamGetMetadata() { if (!\is_resource($this->resource)) { throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); } return \stream_get_meta_data($this->resource); } private function validateTimeout(float $value) : void { if ($value < 0) { throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got {$value})"); } } private function connectIfNotConnected() : void { if ($this->isConnected()) { return; } $this->connect(); } /** * @phpstan-param FormattedRecord $record */ protected function generateDataStream(array $record) : string { return (string) $record['formatted']; } /** * @return resource|null */ protected function getResource() { return $this->resource; } private function connect() : void { $this->createSocketResource(); $this->setSocketTimeout(); $this->setStreamChunkSize(); } private function createSocketResource() : void { if ($this->isPersistent()) { $resource = $this->pfsockopen(); } else { $resource = $this->fsockopen(); } if (\is_bool($resource)) { throw new \UnexpectedValueException("Failed connecting to {$this->connectionString} ({$this->errno}: {$this->errstr})"); } $this->resource = $resource; } private function setSocketTimeout() : void { if (!$this->streamSetTimeout()) { throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); } } private function setStreamChunkSize() : void { if ($this->chunkSize && !$this->streamSetChunkSize()) { throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); } } private function writeToSocket(string $data) : void { $length = \strlen($data); $sent = 0; $this->lastSentBytes = $sent; while ($this->isConnected() && $sent < $length) { if (0 == $sent) { $chunk = $this->fwrite($data); } else { $chunk = $this->fwrite(\substr($data, $sent)); } if ($chunk === \false) { throw new \RuntimeException("Could not write to socket"); } $sent += $chunk; $socketInfo = $this->streamGetMetadata(); if (\is_array($socketInfo) && $socketInfo['timed_out']) { throw new \RuntimeException("Write timed-out"); } if ($this->writingIsTimedOut($sent)) { throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent {$sent} of {$length})"); } } if (!$this->isConnected() && $sent < $length) { throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent {$sent} of {$length})"); } } private function writingIsTimedOut(int $sent) : bool { // convert to ms if (0.0 == $this->writingTimeout) { return \false; } if ($sent !== $this->lastSentBytes) { $this->lastWritingAt = \microtime(\true); $this->lastSentBytes = $sent; return \false; } else { \usleep(100); } if (\microtime(\true) - $this->lastWritingAt >= $this->writingTimeout) { $this->closeSocket(); return \true; } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Symfony\Component\Mailer\MailerInterface; use _ContaoManager\Symfony\Component\Mailer\Transport\TransportInterface; use _ContaoManager\Symfony\Component\Mime\Email; /** * SymfonyMailerHandler uses Symfony's Mailer component to send the emails * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger */ class SymfonyMailerHandler extends MailHandler { /** @var MailerInterface|TransportInterface */ protected $mailer; /** @var Email|callable(string, Record[]): Email */ private $emailTemplate; /** * @psalm-param Email|callable(string, Record[]): Email $email * * @param MailerInterface|TransportInterface $mailer The mailer to use * @param callable|Email $email An email template, the subject/body will be replaced */ public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = \true) { parent::__construct($level, $bubble); $this->mailer = $mailer; $this->emailTemplate = $email; } /** * {@inheritDoc} */ protected function send(string $content, array $records) : void { $this->mailer->send($this->buildMessage($content, $records)); } /** * Gets the formatter for the Swift_Message subject. * * @param string|null $format The format of the subject */ protected function getSubjectFormatter(?string $format) : FormatterInterface { return new LineFormatter($format); } /** * Creates instance of Email to be sent * * @param string $content formatted email body to be sent * @param array $records Log records that formed the content * * @phpstan-param Record[] $records */ protected function buildMessage(string $content, array $records) : Email { $message = null; if ($this->emailTemplate instanceof Email) { $message = clone $this->emailTemplate; } elseif (\is_callable($this->emailTemplate)) { $message = ($this->emailTemplate)($content, $records); } if (!$message instanceof Email) { $record = \reset($records); throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); } if ($records) { $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); $message->subject($subjectFormatter->format($this->getHighestRecord($records))); } if ($this->isHtmlBody($content)) { if (null !== ($charset = $message->getHtmlCharset())) { $message->html($content, $charset); } else { $message->html($content); } } else { if (null !== ($charset = $message->getTextCharset())) { $message->text($content, $charset); } else { $message->text($content); } } return $message->date(new \DateTimeImmutable()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; /** * No-op * * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. * This can be used for testing, or to disable a handler when overriding a configuration without * influencing the rest of the stack. * * @author Roel Harbers */ class NoopHandler extends Handler { /** * {@inheritDoc} */ public function isHandling(array $record) : bool { return \true; } /** * {@inheritDoc} */ public function handle(array $record) : bool { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Buffers all records until closing the handler and then pass them as batch. * * This is useful for a MailHandler to send only one mail per request instead of * sending one per log message. * * @author Christophe Coevoet * * @phpstan-import-type Record from \Monolog\Logger */ class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { use ProcessableHandlerTrait; /** @var HandlerInterface */ protected $handler; /** @var int */ protected $bufferSize = 0; /** @var int */ protected $bufferLimit; /** @var bool */ protected $flushOnOverflow; /** @var Record[] */ protected $buffer = []; /** @var bool */ protected $initialized = \false; /** * @param HandlerInterface $handler Handler. * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded */ public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = \true, bool $flushOnOverflow = \false) { parent::__construct($level, $bubble); $this->handler = $handler; $this->bufferLimit = $bufferLimit; $this->flushOnOverflow = $flushOnOverflow; } /** * {@inheritDoc} */ public function handle(array $record) : bool { if ($record['level'] < $this->level) { return \false; } if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors \register_shutdown_function([$this, 'close']); $this->initialized = \true; } if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { if ($this->flushOnOverflow) { $this->flush(); } else { \array_shift($this->buffer); $this->bufferSize--; } } if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } $this->buffer[] = $record; $this->bufferSize++; return \false === $this->bubble; } public function flush() : void { if ($this->bufferSize === 0) { return; } $this->handler->handleBatch($this->buffer); $this->clear(); } public function __destruct() { // suppress the parent behavior since we already have register_shutdown_function() // to call close(), and the reference contained there will prevent this from being // GC'd until the end of the request } /** * {@inheritDoc} */ public function close() : void { $this->flush(); $this->handler->close(); } /** * Clears the buffer without flushing any messages down to the wrapped handler. */ public function clear() : void { $this->bufferSize = 0; $this->buffer = []; } public function reset() { $this->flush(); parent::reset(); $this->resetProcessors(); if ($this->handler instanceof ResettableInterface) { $this->handler->reset(); } } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { if ($this->handler instanceof FormattableHandlerInterface) { $this->handler->setFormatter($formatter); return $this; } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($this->handler) . ' does not support formatters.'); } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { if ($this->handler instanceof FormattableHandlerInterface) { return $this->handler->getFormatter(); } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($this->handler) . ' does not support formatters.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Logs to syslog service. * * usage example: * * $log = new Logger('application'); * $syslog = new SyslogHandler('myfacility', 'local6'); * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); * $syslog->setFormatter($formatter); * $log->pushHandler($syslog); * * @author Sven Paulus */ class SyslogHandler extends AbstractSyslogHandler { /** @var string */ protected $ident; /** @var int */ protected $logopts; /** * @param string $ident * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID */ public function __construct(string $ident, $facility = \LOG_USER, $level = Logger::DEBUG, bool $bubble = \true, int $logopts = \LOG_PID) { parent::__construct($facility, $level, $bubble); $this->ident = $ident; $this->logopts = $logopts; } /** * {@inheritDoc} */ public function close() : void { \closelog(); } /** * {@inheritDoc} */ protected function write(array $record) : void { if (!\openlog($this->ident, $this->logopts, $this->facility)) { throw new \LogicException('Can\'t open syslog for ident "' . $this->ident . '" and facility "' . $this->facility . '"' . Utils::getRecordMessageForException($record)); } \syslog($this->logLevels[$record['level']], (string) $record['formatted']); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Rollbar\RollbarLogger; use Throwable; use _ContaoManager\Monolog\Logger; /** * Sends errors to Rollbar * * If the context data contains a `payload` key, that is used as an array * of payload options to RollbarLogger's log method. * * Rollbar's context info will contain the context + extra keys from the log record * merged, and then on top of that a few keys: * * - level (rollbar level name) * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) * - channel * - datetime (unix timestamp) * * @author Paul Statezny */ class RollbarHandler extends AbstractProcessingHandler { /** * @var RollbarLogger */ protected $rollbarLogger; /** @var string[] */ protected $levelMap = [Logger::DEBUG => 'debug', Logger::INFO => 'info', Logger::NOTICE => 'info', Logger::WARNING => 'warning', Logger::ERROR => 'error', Logger::CRITICAL => 'critical', Logger::ALERT => 'critical', Logger::EMERGENCY => 'critical']; /** * Records whether any log records have been added since the last flush of the rollbar notifier * * @var bool */ private $hasRecords = \false; /** @var bool */ protected $initialized = \false; /** * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token */ public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = \true) { $this->rollbarLogger = $rollbarLogger; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors \register_shutdown_function(array($this, 'close')); $this->initialized = \true; } $context = $record['context']; $context = \array_merge($context, $record['extra'], ['level' => $this->levelMap[$record['level']], 'monolog_level' => $record['level_name'], 'channel' => $record['channel'], 'datetime' => $record['datetime']->format('U')]); if (isset($context['exception']) && $context['exception'] instanceof Throwable) { $exception = $context['exception']; unset($context['exception']); $toLog = $exception; } else { $toLog = $record['message']; } // @phpstan-ignore-next-line $this->rollbarLogger->log($context['level'], $toLog, $context); $this->hasRecords = \true; } public function flush() : void { if ($this->hasRecords) { $this->rollbarLogger->flush(); $this->hasRecords = \false; } } /** * {@inheritDoc} */ public function close() : void { $this->flush(); } /** * {@inheritDoc} */ public function reset() { $this->flush(); parent::reset(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; use _ContaoManager\Monolog\Handler\FingersCrossed\ActivationStrategyInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Psr\Log\LogLevel; /** * Buffers all records until a certain level is reached * * The advantage of this approach is that you don't get any clutter in your log files. * Only requests which actually trigger an error (or whatever your actionLevel is) will be * in the logs, but they will contain all records, not only those above the level threshold. * * You can then have a passthruLevel as well which means that at the end of the request, * even if it did not get activated, it will still send through log records of e.g. at least a * warning level. * * You can find the various activation strategies in the * Monolog\Handler\FingersCrossed\ namespace. * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { use ProcessableHandlerTrait; /** * @var callable|HandlerInterface * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface */ protected $handler; /** @var ActivationStrategyInterface */ protected $activationStrategy; /** @var bool */ protected $buffering = \true; /** @var int */ protected $bufferSize; /** @var Record[] */ protected $buffer = []; /** @var bool */ protected $stopBuffering; /** * @var ?int * @phpstan-var ?Level */ protected $passthruLevel; /** @var bool */ protected $bubble; /** * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). * @param int|string|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) * @param int|string $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered * * @phpstan-param Level|LevelName|LogLevel::* $passthruLevel * @phpstan-param Level|LevelName|LogLevel::*|ActivationStrategyInterface $activationStrategy */ public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = \true, bool $stopBuffering = \true, $passthruLevel = null) { if (null === $activationStrategy) { $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); } // convert simple int activationStrategy to an object if (!$activationStrategy instanceof ActivationStrategyInterface) { $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); } $this->handler = $handler; $this->activationStrategy = $activationStrategy; $this->bufferSize = $bufferSize; $this->bubble = $bubble; $this->stopBuffering = $stopBuffering; if ($passthruLevel !== null) { $this->passthruLevel = Logger::toMonologLevel($passthruLevel); } if (!$this->handler instanceof HandlerInterface && !\is_callable($this->handler)) { throw new \RuntimeException("The given handler (" . \json_encode($this->handler) . ") is not a callable nor a Monolog\\Handler\\HandlerInterface object"); } } /** * {@inheritDoc} */ public function isHandling(array $record) : bool { return \true; } /** * Manually activate this logger regardless of the activation strategy */ public function activate() : void { if ($this->stopBuffering) { $this->buffering = \false; } $this->getHandler(\end($this->buffer) ?: null)->handleBatch($this->buffer); $this->buffer = []; } /** * {@inheritDoc} */ public function handle(array $record) : bool { if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } if ($this->buffering) { $this->buffer[] = $record; if ($this->bufferSize > 0 && \count($this->buffer) > $this->bufferSize) { \array_shift($this->buffer); } if ($this->activationStrategy->isHandlerActivated($record)) { $this->activate(); } } else { $this->getHandler($record)->handle($record); } return \false === $this->bubble; } /** * {@inheritDoc} */ public function close() : void { $this->flushBuffer(); $this->getHandler()->close(); } public function reset() { $this->flushBuffer(); $this->resetProcessors(); if ($this->getHandler() instanceof ResettableInterface) { $this->getHandler()->reset(); } } /** * Clears the buffer without flushing any messages down to the wrapped handler. * * It also resets the handler to its initial buffering state. */ public function clear() : void { $this->buffer = []; $this->reset(); } /** * Resets the state of the handler. Stops forwarding records to the wrapped handler. */ private function flushBuffer() : void { if (null !== $this->passthruLevel) { $level = $this->passthruLevel; $this->buffer = \array_filter($this->buffer, function ($record) use($level) { return $record['level'] >= $level; }); if (\count($this->buffer) > 0) { $this->getHandler(\end($this->buffer))->handleBatch($this->buffer); } } $this->buffer = []; $this->buffering = \true; } /** * Return the nested handler * * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @return HandlerInterface * * @phpstan-param Record $record */ public function getHandler(array $record = null) { if (!$this->handler instanceof HandlerInterface) { $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } } return $this->handler; } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { $handler = $this->getHandler(); if ($handler instanceof FormattableHandlerInterface) { $handler->setFormatter($formatter); return $this; } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($handler) . ' does not support formatters.'); } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { $handler = $this->getHandler(); if ($handler instanceof FormattableHandlerInterface) { return $handler->getFormatter(); } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($handler) . ' does not support formatters.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Handler to only pass log messages when a certain threshold of number of messages is reached. * * This can be useful in cases of processing a batch of data, but you're for example only interested * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? * * Usage example: * * ``` * $log = new Logger('application'); * $handler = new SomeHandler(...) * * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); * * $log->pushHandler($overflow); *``` * * @author Kris Buist */ class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface { /** @var HandlerInterface */ private $handler; /** @var int[] */ private $thresholdMap = [Logger::DEBUG => 0, Logger::INFO => 0, Logger::NOTICE => 0, Logger::WARNING => 0, Logger::ERROR => 0, Logger::CRITICAL => 0, Logger::ALERT => 0, Logger::EMERGENCY => 0]; /** * Buffer of all messages passed to the handler before the threshold was reached * * @var mixed[][] */ private $buffer = []; /** * @param HandlerInterface $handler * @param int[] $thresholdMap Dictionary of logger level => threshold */ public function __construct(HandlerInterface $handler, array $thresholdMap = [], $level = Logger::DEBUG, bool $bubble = \true) { $this->handler = $handler; foreach ($thresholdMap as $thresholdLevel => $threshold) { $this->thresholdMap[$thresholdLevel] = $threshold; } parent::__construct($level, $bubble); } /** * Handles a record. * * All records may be passed to this method, and the handler should discard * those that it does not want to handle. * * The return value of this function controls the bubbling process of the handler stack. * Unless the bubbling is interrupted (by returning true), the Logger class will keep on * calling further handlers in the stack with a given log record. * * {@inheritDoc} */ public function handle(array $record) : bool { if ($record['level'] < $this->level) { return \false; } $level = $record['level']; if (!isset($this->thresholdMap[$level])) { $this->thresholdMap[$level] = 0; } if ($this->thresholdMap[$level] > 0) { // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 $this->thresholdMap[$level]--; $this->buffer[$level][] = $record; return \false === $this->bubble; } if ($this->thresholdMap[$level] == 0) { // This current message is breaking the threshold. Flush the buffer and continue handling the current record foreach ($this->buffer[$level] ?? [] as $buffered) { $this->handler->handle($buffered); } $this->thresholdMap[$level]--; unset($this->buffer[$level]); } $this->handler->handle($record); return \false === $this->bubble; } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { if ($this->handler instanceof FormattableHandlerInterface) { $this->handler->setFormatter($formatter); return $this; } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($this->handler) . ' does not support formatters.'); } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { if ($this->handler instanceof FormattableHandlerInterface) { return $this->handler->getFormatter(); } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($this->handler) . ' does not support formatters.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * This simple wrapper class can be used to extend handlers functionality. * * Example: A custom filtering that can be applied to any handler. * * Inherit from this class and override handle() like this: * * public function handle(array $record) * { * if ($record meets certain conditions) { * return false; * } * return $this->handler->handle($record); * } * * @author Alexey Karapetov */ class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface { /** * @var HandlerInterface */ protected $handler; public function __construct(HandlerInterface $handler) { $this->handler = $handler; } /** * {@inheritDoc} */ public function isHandling(array $record) : bool { return $this->handler->isHandling($record); } /** * {@inheritDoc} */ public function handle(array $record) : bool { return $this->handler->handle($record); } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { $this->handler->handleBatch($records); } /** * {@inheritDoc} */ public function close() : void { $this->handler->close(); } /** * {@inheritDoc} */ public function pushProcessor(callable $callback) : HandlerInterface { if ($this->handler instanceof ProcessableHandlerInterface) { $this->handler->pushProcessor($callback); return $this; } throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** * {@inheritDoc} */ public function popProcessor() : callable { if ($this->handler instanceof ProcessableHandlerInterface) { return $this->handler->popProcessor(); } throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { if ($this->handler instanceof FormattableHandlerInterface) { $this->handler->setFormatter($formatter); return $this; } throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { if ($this->handler instanceof FormattableHandlerInterface) { return $this->handler->getFormatter(); } throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } public function reset() { if ($this->handler instanceof ResettableInterface) { $this->handler->reset(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler\FingersCrossed; /** * Interface for activation strategies for the FingersCrossedHandler. * * @author Johannes M. Schmitt * * @phpstan-import-type Record from \Monolog\Logger */ interface ActivationStrategyInterface { /** * Returns whether the given record activates the handler. * * @phpstan-param Record $record */ public function isHandlerActivated(array $record) : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler\FingersCrossed; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Error level based activation strategy. * * @author Johannes M. Schmitt * * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class ErrorLevelActivationStrategy implements ActivationStrategyInterface { /** * @var Level */ private $actionLevel; /** * @param int|string $actionLevel Level or name or value * * @phpstan-param Level|LevelName|LogLevel::* $actionLevel */ public function __construct($actionLevel) { $this->actionLevel = Logger::toMonologLevel($actionLevel); } public function isHandlerActivated(array $record) : bool { return $record['level'] >= $this->actionLevel; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler\FingersCrossed; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Channel and Error level based monolog activation strategy. Allows to trigger activation * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except * for records of the 'sql' channel; those should trigger activation on level 'WARN'. * * Example: * * * $activationStrategy = new ChannelLevelActivationStrategy( * Logger::CRITICAL, * array( * 'request' => Logger::ALERT, * 'sensitive' => Logger::ERROR, * ) * ); * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); * * * @author Mike Meessen * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class ChannelLevelActivationStrategy implements ActivationStrategyInterface { /** * @var Level */ private $defaultActionLevel; /** * @var array */ private $channelToActionLevel; /** * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any * @param array $channelToActionLevel An array that maps channel names to action levels. * * @phpstan-param array $channelToActionLevel * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel */ public function __construct($defaultActionLevel, array $channelToActionLevel = []) { $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); $this->channelToActionLevel = \array_map('Monolog\\Logger::toMonologLevel', $channelToActionLevel); } /** * @phpstan-param Record $record */ public function isHandlerActivated(array $record) : bool { if (isset($this->channelToActionLevel[$record['channel']])) { return $record['level'] >= $this->channelToActionLevel[$record['channel']]; } return $record['level'] >= $this->defaultActionLevel; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\ResettableInterface; /** * Forwards records to multiple handlers * * @author Lenar Lõhmus * * @phpstan-import-type Record from \Monolog\Logger */ class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface { use ProcessableHandlerTrait; /** @var HandlerInterface[] */ protected $handlers; /** @var bool */ protected $bubble; /** * @param HandlerInterface[] $handlers Array of Handlers. * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ public function __construct(array $handlers, bool $bubble = \true) { foreach ($handlers as $handler) { if (!$handler instanceof HandlerInterface) { throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); } } $this->handlers = $handlers; $this->bubble = $bubble; } /** * {@inheritDoc} */ public function isHandling(array $record) : bool { foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { return \true; } } return \false; } /** * {@inheritDoc} */ public function handle(array $record) : bool { if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { $handler->handle($record); } return \false === $this->bubble; } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { if ($this->processors) { $processed = []; foreach ($records as $record) { $processed[] = $this->processRecord($record); } /** @var Record[] $records */ $records = $processed; } foreach ($this->handlers as $handler) { $handler->handleBatch($records); } } public function reset() { $this->resetProcessors(); foreach ($this->handlers as $handler) { if ($handler instanceof ResettableInterface) { $handler->reset(); } } } public function close() : void { parent::close(); foreach ($this->handlers as $handler) { $handler->close(); } } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { foreach ($this->handlers as $handler) { if ($handler instanceof FormattableHandlerInterface) { $handler->setFormatter($formatter); } } return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Elastic\Elasticsearch\Response\Elasticsearch; use Throwable; use RuntimeException; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\ElasticsearchFormatter; use InvalidArgumentException; use _ContaoManager\Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException; use _ContaoManager\Elasticsearch\Client; use _ContaoManager\Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException; use _ContaoManager\Elastic\Elasticsearch\Client as Client8; /** * Elasticsearch handler * * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html * * Simple usage example: * * $client = \Elasticsearch\ClientBuilder::create() * ->setHosts($hosts) * ->build(); * * $options = array( * 'index' => 'elastic_index_name', * 'type' => 'elastic_doc_type', * ); * $handler = new ElasticsearchHandler($client, $options); * $log = new Logger('application'); * $log->pushHandler($handler); * * @author Avtandil Kikabidze */ class ElasticsearchHandler extends AbstractProcessingHandler { /** * @var Client|Client8 */ protected $client; /** * @var mixed[] Handler config options */ protected $options = []; /** * @var bool */ private $needsType; /** * @param Client|Client8 $client Elasticsearch Client object * @param mixed[] $options Handler configuration */ public function __construct($client, array $options = [], $level = Logger::DEBUG, bool $bubble = \true) { if (!$client instanceof Client && !$client instanceof Client8) { throw new \TypeError('Elasticsearch\\Client or Elastic\\Elasticsearch\\Client instance required'); } parent::__construct($level, $bubble); $this->client = $client; $this->options = \array_merge([ 'index' => 'monolog', // Elastic index name 'type' => '_doc', // Elastic document type 'ignore_error' => \false, ], $options); if ($client instanceof Client8 || $client::VERSION[0] === '7') { $this->needsType = \false; // force the type to _doc for ES8/ES7 $this->options['type'] = '_doc'; } else { $this->needsType = \true; } } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->bulkSend([$record['formatted']]); } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { if ($formatter instanceof ElasticsearchFormatter) { return parent::setFormatter($formatter); } throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); } /** * Getter options * * @return mixed[] */ public function getOptions() : array { return $this->options; } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new ElasticsearchFormatter($this->options['index'], $this->options['type']); } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { $documents = $this->getFormatter()->formatBatch($records); $this->bulkSend($documents); } /** * Use Elasticsearch bulk API to send list of documents * * @param array[] $records Records + _index/_type keys * @throws \RuntimeException */ protected function bulkSend(array $records) : void { try { $params = ['body' => []]; foreach ($records as $record) { $params['body'][] = ['index' => $this->needsType ? ['_index' => $record['_index'], '_type' => $record['_type']] : ['_index' => $record['_index']]]; unset($record['_index'], $record['_type']); $params['body'][] = $record; } /** @var Elasticsearch */ $responses = $this->client->bulk($params); if ($responses['errors'] === \true) { throw $this->createExceptionFromResponses($responses); } } catch (Throwable $e) { if (!$this->options['ignore_error']) { throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); } } } /** * Creates elasticsearch exception from responses array * * Only the first error is converted into an exception. * * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() */ protected function createExceptionFromResponses($responses) : Throwable { foreach ($responses['items'] ?? [] as $item) { if (isset($item['index']['error'])) { return $this->createExceptionFromError($item['index']['error']); } } if (\class_exists(ElasticInvalidArgumentException::class)) { return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); } return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); } /** * Creates elasticsearch exception from error array * * @param mixed[] $error */ protected function createExceptionFromError(array $error) : Throwable { $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; if (\class_exists(ElasticInvalidArgumentException::class)) { return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); } return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Sampling handler * * A sampled event stream can be useful for logging high frequency events in * a production environment where you only need an idea of what is happening * and are not concerned with capturing every occurrence. Since the decision to * handle or not handle a particular event is determined randomly, the * resulting sampled log is not guaranteed to contain 1/N of the events that * occurred in the application, but based on the Law of large numbers, it will * tend to be close to this ratio with a large number of attempts. * * @author Bryan Davis * @author Kunal Mehta * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger */ class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { use ProcessableHandlerTrait; /** * @var HandlerInterface|callable * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface */ protected $handler; /** * @var int $factor */ protected $factor; /** * @psalm-param HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface $handler * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) */ public function __construct($handler, int $factor) { parent::__construct(); $this->handler = $handler; $this->factor = $factor; if (!$this->handler instanceof HandlerInterface && !\is_callable($this->handler)) { throw new \RuntimeException("The given handler (" . \json_encode($this->handler) . ") is not a callable nor a Monolog\\Handler\\HandlerInterface object"); } } public function isHandling(array $record) : bool { return $this->getHandler($record)->isHandling($record); } public function handle(array $record) : bool { if ($this->isHandling($record) && \mt_rand(1, $this->factor) === 1) { if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } $this->getHandler($record)->handle($record); } return \false === $this->bubble; } /** * Return the nested handler * * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @phpstan-param Record|array{level: Level}|null $record * * @return HandlerInterface */ public function getHandler(array $record = null) { if (!$this->handler instanceof HandlerInterface) { $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } } return $this->handler; } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { $handler = $this->getHandler(); if ($handler instanceof FormattableHandlerInterface) { $handler->setFormatter($formatter); return $this; } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($handler) . ' does not support formatters.'); } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { $handler = $this->getHandler(); if ($handler instanceof FormattableHandlerInterface) { return $handler->getFormatter(); } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($handler) . ' does not support formatters.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; /** * Base Handler class providing basic close() support as well as handleBatch * * @author Jordi Boggiano */ abstract class Handler implements HandlerInterface { /** * {@inheritDoc} */ public function handleBatch(array $records) : void { foreach ($records as $record) { $this->handle($record); } } /** * {@inheritDoc} */ public function close() : void { } public function __destruct() { try { $this->close(); } catch (\Throwable $e) { // do nothing } } public function __sleep() { $this->close(); $reflClass = new \ReflectionClass($this); $keys = []; foreach ($reflClass->getProperties() as $reflProp) { if (!$reflProp->isStatic()) { $keys[] = $reflProp->getName(); } } return $keys; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Psr\Log\LogLevel; /** * Base Handler class providing basic level/bubble support * * @author Jordi Boggiano * * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ abstract class AbstractHandler extends Handler implements ResettableInterface { /** * @var int * @phpstan-var Level */ protected $level = Logger::DEBUG; /** @var bool */ protected $bubble = \true; /** * @param int|string $level The minimum logging level at which this handler will be triggered * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG, bool $bubble = \true) { $this->setLevel($level); $this->bubble = $bubble; } /** * {@inheritDoc} */ public function isHandling(array $record) : bool { return $record['level'] >= $this->level; } /** * Sets minimum logging level at which this handler will be triggered. * * @param Level|LevelName|LogLevel::* $level Level or level name * @return self */ public function setLevel($level) : self { $this->level = Logger::toMonologLevel($level); return $this; } /** * Gets minimum logging level at which this handler will be triggered. * * @return int * * @phpstan-return Level */ public function getLevel() : int { return $this->level; } /** * Sets the bubbling behavior. * * @param bool $bubble true means that this handler allows bubbling. * false means that bubbling is not permitted. * @return self */ public function setBubble(bool $bubble) : self { $this->bubble = $bubble; return $this; } /** * Gets the bubbling behavior. * * @return bool true means that this handler allows bubbling. * false means that bubbling is not permitted. */ public function getBubble() : bool { return $this->bubble; } /** * {@inheritDoc} */ public function reset() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Swift_Message; use _ContaoManager\Swift; /** * SwiftMailerHandler uses Swift_Mailer to send the emails * * @author Gyula Sallai * * @phpstan-import-type Record from \Monolog\Logger * @deprecated Since Monolog 2.6. Use SymfonyMailerHandler instead. */ class SwiftMailerHandler extends MailHandler { /** @var \Swift_Mailer */ protected $mailer; /** @var Swift_Message|callable(string, Record[]): Swift_Message */ private $messageTemplate; /** * @psalm-param Swift_Message|callable(string, Record[]): Swift_Message $message * * @param \Swift_Mailer $mailer The mailer to use * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced */ public function __construct(\_ContaoManager\Swift_Mailer $mailer, $message, $level = Logger::ERROR, bool $bubble = \true) { parent::__construct($level, $bubble); @\trigger_error('The SwiftMailerHandler is deprecated since Monolog 2.6. Use SymfonyMailerHandler instead.', \E_USER_DEPRECATED); $this->mailer = $mailer; $this->messageTemplate = $message; } /** * {@inheritDoc} */ protected function send(string $content, array $records) : void { $this->mailer->send($this->buildMessage($content, $records)); } /** * Gets the formatter for the Swift_Message subject. * * @param string|null $format The format of the subject */ protected function getSubjectFormatter(?string $format) : FormatterInterface { return new LineFormatter($format); } /** * Creates instance of Swift_Message to be sent * * @param string $content formatted email body to be sent * @param array $records Log records that formed the content * @return Swift_Message * * @phpstan-param Record[] $records */ protected function buildMessage(string $content, array $records) : Swift_Message { $message = null; if ($this->messageTemplate instanceof Swift_Message) { $message = clone $this->messageTemplate; $message->generateId(); } elseif (\is_callable($this->messageTemplate)) { $message = ($this->messageTemplate)($content, $records); } if (!$message instanceof Swift_Message) { $record = \reset($records); throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); } if ($records) { $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); } $mime = 'text/plain'; if ($this->isHtmlBody($content)) { $mime = 'text/html'; } $message->setBody($content, $mime); /** @phpstan-ignore-next-line */ if (\version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { /** @phpstan-ignore-next-line */ $message->setDate(\time()); } return $message; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Formatter\FlowdockFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Sends notifications through the Flowdock push API * * This must be configured with a FlowdockFormatter instance via setFormatter() * * Notes: * API token - Flowdock API token * * @author Dominik Liebler * @see https://www.flowdock.com/api/push * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 */ class FlowdockHandler extends SocketHandler { /** * @var string */ protected $apiToken; /** * @throws MissingExtensionException if OpenSSL is missing */ public function __construct(string $apiToken, $level = Logger::DEBUG, bool $bubble = \true, bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { if (!\extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); } parent::__construct('ssl://api.flowdock.com:443', $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); $this->apiToken = $apiToken; } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { if (!$formatter instanceof FlowdockFormatter) { throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\\Formatter\\FlowdockFormatter to function correctly'); } return parent::setFormatter($formatter); } /** * Gets the default formatter. */ protected function getDefaultFormatter() : FormatterInterface { throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\\Formatter\\FlowdockFormatter to function correctly'); } /** * {@inheritDoc} */ protected function write(array $record) : void { parent::write($record); $this->closeSocket(); } /** * {@inheritDoc} */ protected function generateDataStream(array $record) : string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } /** * Builds the body of API call * * @phpstan-param FormattedRecord $record */ private function buildContent(array $record) : string { return Utils::jsonEncode($record['formatted']['flowdock']); } /** * Builds the header of the API Call */ private function buildHeader(string $content) : string { $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; $header .= "Host: api.flowdock.com\r\n"; $header .= "Content-Type: application/json\r\n"; $header .= "Content-Length: " . \strlen($content) . "\r\n"; $header .= "\r\n"; return $header; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Psr\Log\LogLevel; /** * Sends notifications through the pushover api to mobile phones * * @author Sebastian Göttschkes * @see https://www.pushover.net/api * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class PushoverHandler extends SocketHandler { /** @var string */ private $token; /** @var array */ private $users; /** @var string */ private $title; /** @var string|int|null */ private $user = null; /** @var int */ private $retry; /** @var int */ private $expire; /** @var int */ private $highPriorityLevel; /** @var int */ private $emergencyLevel; /** @var bool */ private $useFormattedMessage = \false; /** * All parameters that can be sent to Pushover * @see https://pushover.net/api * @var array */ private $parameterNames = ['token' => \true, 'user' => \true, 'message' => \true, 'device' => \true, 'title' => \true, 'url' => \true, 'url_title' => \true, 'priority' => \true, 'timestamp' => \true, 'sound' => \true, 'retry' => \true, 'expire' => \true, 'callback' => \true]; /** * Sounds the api supports by default * @see https://pushover.net/api#sounds * @var string[] */ private $sounds = ['pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', 'persistent', 'echo', 'updown', 'none']; /** * @param string $token Pushover api token * @param string|array $users Pushover user id or array of ids the message will be sent to * @param string|null $title Title sent to the Pushover API * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not * the pushover.net app owner. OpenSSL is required for this option. * @param string|int $highPriorityLevel The minimum logging level at which this handler will start * sending "high priority" requests to the Pushover API * @param string|int $emergencyLevel The minimum logging level at which this handler will start * sending "emergency" requests to the Pushover API * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will * send the same notification to the user. * @param int $expire The expire parameter specifies how many seconds your notification will continue * to be retried for (every retry seconds). * * @phpstan-param string|array $users * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel */ public function __construct(string $token, $users, ?string $title = null, $level = Logger::CRITICAL, bool $bubble = \true, bool $useSSL = \true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, int $retry = 30, int $expire = 25200, bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; parent::__construct($connectionString, $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); $this->token = $token; $this->users = (array) $users; $this->title = $title ?: (string) \gethostname(); $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); $this->retry = $retry; $this->expire = $expire; } protected function generateDataStream(array $record) : string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } /** * @phpstan-param FormattedRecord $record */ private function buildContent(array $record) : string { // Pushover has a limit of 512 characters on title and message combined. $maxMessageLength = 512 - \strlen($this->title); $message = $this->useFormattedMessage ? $record['formatted'] : $record['message']; $message = Utils::substr($message, 0, $maxMessageLength); $timestamp = $record['datetime']->getTimestamp(); $dataArray = ['token' => $this->token, 'user' => $this->user, 'message' => $message, 'title' => $this->title, 'timestamp' => $timestamp]; if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { $dataArray['priority'] = 2; $dataArray['retry'] = $this->retry; $dataArray['expire'] = $this->expire; } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { $dataArray['priority'] = 1; } // First determine the available parameters $context = \array_intersect_key($record['context'], $this->parameterNames); $extra = \array_intersect_key($record['extra'], $this->parameterNames); // Least important info should be merged with subsequent info $dataArray = \array_merge($extra, $context, $dataArray); // Only pass sounds that are supported by the API if (isset($dataArray['sound']) && !\in_array($dataArray['sound'], $this->sounds)) { unset($dataArray['sound']); } return \http_build_query($dataArray); } private function buildHeader(string $content) : string { $header = "POST /1/messages.json HTTP/1.1\r\n"; $header .= "Host: api.pushover.net\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . \strlen($content) . "\r\n"; $header .= "\r\n"; return $header; } protected function write(array $record) : void { foreach ($this->users as $user) { $this->user = $user; parent::write($record); $this->closeSocket(); } $this->user = null; } /** * @param int|string $value * * @phpstan-param Level|LevelName|LogLevel::* $value */ public function setHighPriorityLevel($value) : self { $this->highPriorityLevel = Logger::toMonologLevel($value); return $this; } /** * @param int|string $value * * @phpstan-param Level|LevelName|LogLevel::* $value */ public function setEmergencyLevel($value) : self { $this->emergencyLevel = Logger::toMonologLevel($value); return $this; } /** * Use the formatted message? */ public function useFormattedMessage(bool $value) : self { $this->useFormattedMessage = $value; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Processor\ProcessorInterface; /** * Interface to describe loggers that have processors * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger */ interface ProcessableHandlerInterface { /** * Adds a processor in the stack. * * @psalm-param ProcessorInterface|callable(Record): Record $callback * * @param ProcessorInterface|callable $callback * @return HandlerInterface self */ public function pushProcessor(callable $callback) : HandlerInterface; /** * Removes the processor on top of the stack and returns it. * * @psalm-return ProcessorInterface|callable(Record): Record $callback * * @throws \LogicException In case the processor stack is empty * @return callable|ProcessorInterface */ public function popProcessor() : callable; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; /** * Exception can be thrown if an extension for a handler is missing * * @author Christian Bergau */ class MissingExtensionException extends \Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; /** * Common syslog functionality * * @phpstan-import-type Level from \Monolog\Logger */ abstract class AbstractSyslogHandler extends AbstractProcessingHandler { /** @var int */ protected $facility; /** * Translates Monolog log levels to syslog log priorities. * @var array * @phpstan-var array */ protected $logLevels = [Logger::DEBUG => \LOG_DEBUG, Logger::INFO => \LOG_INFO, Logger::NOTICE => \LOG_NOTICE, Logger::WARNING => \LOG_WARNING, Logger::ERROR => \LOG_ERR, Logger::CRITICAL => \LOG_CRIT, Logger::ALERT => \LOG_ALERT, Logger::EMERGENCY => \LOG_EMERG]; /** * List of valid log facility names. * @var array */ protected $facilities = ['auth' => \LOG_AUTH, 'authpriv' => \LOG_AUTHPRIV, 'cron' => \LOG_CRON, 'daemon' => \LOG_DAEMON, 'kern' => \LOG_KERN, 'lpr' => \LOG_LPR, 'mail' => \LOG_MAIL, 'news' => \LOG_NEWS, 'syslog' => \LOG_SYSLOG, 'user' => \LOG_USER, 'uucp' => \LOG_UUCP]; /** * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant */ public function __construct($facility = \LOG_USER, $level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); if (!\defined('PHP_WINDOWS_VERSION_BUILD')) { $this->facilities['local0'] = \LOG_LOCAL0; $this->facilities['local1'] = \LOG_LOCAL1; $this->facilities['local2'] = \LOG_LOCAL2; $this->facilities['local3'] = \LOG_LOCAL3; $this->facilities['local4'] = \LOG_LOCAL4; $this->facilities['local5'] = \LOG_LOCAL5; $this->facilities['local6'] = \LOG_LOCAL6; $this->facilities['local7'] = \LOG_LOCAL7; } else { $this->facilities['local0'] = 128; // LOG_LOCAL0 $this->facilities['local1'] = 136; // LOG_LOCAL1 $this->facilities['local2'] = 144; // LOG_LOCAL2 $this->facilities['local3'] = 152; // LOG_LOCAL3 $this->facilities['local4'] = 160; // LOG_LOCAL4 $this->facilities['local5'] = 168; // LOG_LOCAL5 $this->facilities['local6'] = 176; // LOG_LOCAL6 $this->facilities['local7'] = 184; // LOG_LOCAL7 } // convert textual description of facility to syslog constant if (\is_string($facility) && \array_key_exists(\strtolower($facility), $this->facilities)) { $facility = $this->facilities[\strtolower($facility)]; } elseif (!\in_array($facility, \array_values($this->facilities), \true)) { throw new \UnexpectedValueException('Unknown facility value "' . $facility . '" given'); } $this->facility = $facility; } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use DateTimeInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Handler\SyslogUdp\UdpSocket; use _ContaoManager\Monolog\Utils; /** * A Handler for logging to a remote syslogd server. * * @author Jesper Skovgaard Nielsen * @author Dominik Kukacka */ class SyslogUdpHandler extends AbstractSyslogHandler { const RFC3164 = 0; const RFC5424 = 1; const RFC5424e = 2; /** @var array */ private $dateFormats = array(self::RFC3164 => 'M d H:i:s', self::RFC5424 => \DateTime::RFC3339, self::RFC5424e => \DateTime::RFC3339_EXTENDED); /** @var UdpSocket */ protected $socket; /** @var string */ protected $ident; /** @var self::RFC* */ protected $rfc; /** * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) * @param int $port Port number, or 0 if $host is a unix socket * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param string $ident Program name or tag for each log message. * @param int $rfc RFC to format the message for. * @throws MissingExtensionException * * @phpstan-param self::RFC* $rfc */ public function __construct(string $host, int $port = 514, $facility = \LOG_USER, $level = Logger::DEBUG, bool $bubble = \true, string $ident = 'php', int $rfc = self::RFC5424) { if (!\extension_loaded('sockets')) { throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); } parent::__construct($facility, $level, $bubble); $this->ident = $ident; $this->rfc = $rfc; $this->socket = new UdpSocket($host, $port); } protected function write(array $record) : void { $lines = $this->splitMessageIntoLines($record['formatted']); $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); foreach ($lines as $line) { $this->socket->write($line, $header); } } public function close() : void { $this->socket->close(); } /** * @param string|string[] $message * @return string[] */ private function splitMessageIntoLines($message) : array { if (\is_array($message)) { $message = \implode("\n", $message); } $lines = \preg_split('/$\\R?^/m', (string) $message, -1, \PREG_SPLIT_NO_EMPTY); if (\false === $lines) { $pcreErrorCode = \preg_last_error(); throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); } return $lines; } /** * Make common syslog header (see rfc5424 or rfc3164) */ protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime) : string { $priority = $severity + $this->facility; if (!($pid = \getmypid())) { $pid = '-'; } if (!($hostname = \gethostname())) { $hostname = '-'; } if ($this->rfc === self::RFC3164) { // see https://github.com/phpstan/phpstan/issues/5348 // @phpstan-ignore-next-line $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); $date = $dateNew->format($this->dateFormats[$this->rfc]); return "<{$priority}>" . $date . " " . $hostname . " " . $this->ident . "[" . $pid . "]: "; } $date = $datetime->format($this->dateFormats[$this->rfc]); return "<{$priority}>1 " . $date . " " . $hostname . " " . $this->ident . " " . $pid . " - - "; } /** * Inject your own socket, mainly used for testing */ public function setSocket(UdpSocket $socket) : self { $this->socket = $socket; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Logger; use function count; use function headers_list; use function stripos; use function trigger_error; use const E_USER_DEPRECATED; /** * Handler sending logs to browser's javascript console with no browser extension required * * @author Olivier Poitrey * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class BrowserConsoleHandler extends AbstractProcessingHandler { /** @var bool */ protected static $initialized = \false; /** @var FormattedRecord[] */ protected static $records = []; protected const FORMAT_HTML = 'html'; protected const FORMAT_JS = 'js'; protected const FORMAT_UNKNOWN = 'unknown'; /** * {@inheritDoc} * * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. * * Example of formatted string: * * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); } /** * {@inheritDoc} */ protected function write(array $record) : void { // Accumulate records static::$records[] = $record; // Register shutdown handler if not already done if (!static::$initialized) { static::$initialized = \true; $this->registerShutdownFunction(); } } /** * Convert records to javascript console commands and send it to the browser. * This method is automatically called on PHP shutdown if output is HTML or Javascript. */ public static function send() : void { $format = static::getResponseFormat(); if ($format === self::FORMAT_UNKNOWN) { return; } if (count(static::$records)) { if ($format === self::FORMAT_HTML) { static::writeOutput(''); } elseif ($format === self::FORMAT_JS) { static::writeOutput(static::generateScript()); } static::resetStatic(); } } public function close() : void { self::resetStatic(); } public function reset() { parent::reset(); self::resetStatic(); } /** * Forget all logged records */ public static function resetStatic() : void { static::$records = []; } /** * Wrapper for register_shutdown_function to allow overriding */ protected function registerShutdownFunction() : void { if (\PHP_SAPI !== 'cli') { \register_shutdown_function(['Monolog\\Handler\\BrowserConsoleHandler', 'send']); } } /** * Wrapper for echo to allow overriding */ protected static function writeOutput(string $str) : void { echo $str; } /** * Checks the format of the response * * If Content-Type is set to application/javascript or text/javascript -> js * If Content-Type is set to text/html, or is unset -> html * If Content-Type is anything else -> unknown * * @return string One of 'js', 'html' or 'unknown' * @phpstan-return self::FORMAT_* */ protected static function getResponseFormat() : string { // Check content type foreach (headers_list() as $header) { if (stripos($header, 'content-type:') === 0) { return static::getResponseFormatFromContentType($header); } } return self::FORMAT_HTML; } /** * @return string One of 'js', 'html' or 'unknown' * @phpstan-return self::FORMAT_* */ protected static function getResponseFormatFromContentType(string $contentType) : string { // This handler only works with HTML and javascript outputs // text/javascript is obsolete in favour of application/javascript, but still used if (stripos($contentType, 'application/javascript') !== \false || stripos($contentType, 'text/javascript') !== \false) { return self::FORMAT_JS; } if (stripos($contentType, 'text/html') !== \false) { return self::FORMAT_HTML; } return self::FORMAT_UNKNOWN; } private static function generateScript() : string { $script = []; foreach (static::$records as $record) { $context = static::dump('Context', $record['context']); $extra = static::dump('Extra', $record['extra']); if (empty($context) && empty($extra)) { $script[] = static::call_array(static::getConsoleMethodForLevel($record['level']), static::handleStyles($record['formatted'])); } else { $script = \array_merge($script, [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))], $context, $extra, [static::call('groupEnd')]); } } return "(function (c) {if (c && c.groupCollapsed) {\n" . \implode("\n", $script) . "\n}})(console);"; } private static function getConsoleMethodForLevel(int $level) : string { return [Logger::DEBUG => 'debug', Logger::INFO => 'info', Logger::NOTICE => 'info', Logger::WARNING => 'warn', Logger::ERROR => 'error', Logger::CRITICAL => 'error', Logger::ALERT => 'error', Logger::EMERGENCY => 'error'][$level] ?? 'log'; } /** * @return string[] */ private static function handleStyles(string $formatted) : array { $args = []; $format = '%c' . $formatted; \preg_match_all('/\\[\\[(.*?)\\]\\]\\{([^}]*)\\}/s', $format, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER); foreach (\array_reverse($matches) as $match) { $args[] = '"font-weight: normal"'; $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); $pos = $match[0][1]; $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + \strlen($match[0][0])); } $args[] = static::quote('font-weight: normal'); $args[] = static::quote($format); return \array_reverse($args); } private static function handleCustomStyles(string $style, string $string) : string { static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; static $labels = []; $style = \preg_replace_callback('/macro\\s*:(.*?)(?:;|$)/', function (array $m) use($string, &$colors, &$labels) { if (\trim($m[1]) === 'autolabel') { // Format the string as a label with consistent auto assigned background color if (!isset($labels[$string])) { $labels[$string] = $colors[count($labels) % count($colors)]; } $color = $labels[$string]; return "background-color: {$color}; color: white; border-radius: 3px; padding: 0 2px 0 2px"; } return $m[1]; }, $style); if (null === $style) { $pcreErrorCode = \preg_last_error(); throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); } return $style; } /** * @param mixed[] $dict * @return mixed[] */ private static function dump(string $title, array $dict) : array { $script = []; $dict = \array_filter($dict); if (empty($dict)) { return $script; } $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); foreach ($dict as $key => $value) { $value = \json_encode($value); if (empty($value)) { $value = static::quote(''); } $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value); } return $script; } private static function quote(string $arg) : string { return '"' . \addcslashes($arg, "\"\n\\") . '"'; } /** * @param mixed $args */ private static function call(...$args) : string { $method = \array_shift($args); if (!\is_string($method)) { throw new \UnexpectedValueException('Expected the first arg to be a string, got: ' . \var_export($method, \true)); } return static::call_array($method, $args); } /** * @param mixed[] $args */ private static function call_array(string $method, array $args) : string { return 'c.' . $method . '(' . \implode(', ', $args) . ');'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Psr\Log\LogLevel; /** * Simple handler wrapper that filters records based on a list of levels * * It can be configured with an exact list of levels to allow, or a min/max level. * * @author Hennadiy Verkh * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { use ProcessableHandlerTrait; /** * Handler or factory callable($record, $this) * * @var callable|HandlerInterface * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface */ protected $handler; /** * Minimum level for logs that are passed to handler * * @var int[] * @phpstan-var array */ protected $acceptedLevels; /** * Whether the messages that are handled can bubble up the stack or not * * @var bool */ protected $bubble; /** * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided * @param int|string $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = \true) { $this->handler = $handler; $this->bubble = $bubble; $this->setAcceptedLevels($minLevelOrList, $maxLevel); if (!$this->handler instanceof HandlerInterface && !\is_callable($this->handler)) { throw new \RuntimeException("The given handler (" . \json_encode($this->handler) . ") is not a callable nor a Monolog\\Handler\\HandlerInterface object"); } } /** * @phpstan-return array */ public function getAcceptedLevels() : array { return \array_flip($this->acceptedLevels); } /** * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array * * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) : self { if (\is_array($minLevelOrList)) { $acceptedLevels = \array_map('Monolog\\Logger::toMonologLevel', $minLevelOrList); } else { $minLevelOrList = Logger::toMonologLevel($minLevelOrList); $maxLevel = Logger::toMonologLevel($maxLevel); $acceptedLevels = \array_values(\array_filter(Logger::getLevels(), function ($level) use($minLevelOrList, $maxLevel) { return $level >= $minLevelOrList && $level <= $maxLevel; })); } $this->acceptedLevels = \array_flip($acceptedLevels); return $this; } /** * {@inheritDoc} */ public function isHandling(array $record) : bool { return isset($this->acceptedLevels[$record['level']]); } /** * {@inheritDoc} */ public function handle(array $record) : bool { if (!$this->isHandling($record)) { return \false; } if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } $this->getHandler($record)->handle($record); return \false === $this->bubble; } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { $filtered = []; foreach ($records as $record) { if ($this->isHandling($record)) { $filtered[] = $record; } } if (\count($filtered) > 0) { $this->getHandler($filtered[\count($filtered) - 1])->handleBatch($filtered); } } /** * Return the nested handler * * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @return HandlerInterface * * @phpstan-param Record $record */ public function getHandler(array $record = null) { if (!$this->handler instanceof HandlerInterface) { $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } } return $this->handler; } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { $handler = $this->getHandler(); if ($handler instanceof FormattableHandlerInterface) { $handler->setFormatter($formatter); return $this; } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($handler) . ' does not support formatters.'); } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { $handler = $this->getHandler(); if ($handler instanceof FormattableHandlerInterface) { return $handler->getFormatter(); } throw new \UnexpectedValueException('The nested handler of type ' . \get_class($handler) . ' does not support formatters.'); } public function reset() { $this->resetProcessors(); if ($this->getHandler() instanceof ResettableInterface) { $this->getHandler()->reset(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Aws\Sqs\SqsClient; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Writes to any sqs queue. * * @author Martijn van Calker */ class SqsHandler extends AbstractProcessingHandler { /** 256 KB in bytes - maximum message size in SQS */ protected const MAX_MESSAGE_SIZE = 262144; /** 100 KB in bytes - head message size for new error log */ protected const HEAD_MESSAGE_SIZE = 102400; /** @var SqsClient */ private $client; /** @var string */ private $queueUrl; public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); $this->client = $sqsClient; $this->queueUrl = $queueUrl; } /** * {@inheritDoc} */ protected function write(array $record) : void { if (!isset($record['formatted']) || 'string' !== \gettype($record['formatted'])) { throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); } $messageBody = $record['formatted']; if (\strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); } $this->client->sendMessage(['QueueUrl' => $this->queueUrl, 'MessageBody' => $messageBody]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use InvalidArgumentException; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Stores logs to files that are rotated every day and a limited number of files are kept. * * This rotation is only intended to be used as a workaround. Using logrotate to * handle the rotation is strongly encouraged when you can use it. * * @author Christophe Coevoet * @author Jordi Boggiano */ class RotatingFileHandler extends StreamHandler { public const FILE_PER_DAY = 'Y-m-d'; public const FILE_PER_MONTH = 'Y-m'; public const FILE_PER_YEAR = 'Y'; /** @var string */ protected $filename; /** @var int */ protected $maxFiles; /** @var bool */ protected $mustRotate; /** @var \DateTimeImmutable */ protected $nextRotation; /** @var string */ protected $filenameFormat; /** @var string */ protected $dateFormat; /** * @param string $filename * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) * @param bool $useLocking Try to lock log file before doing any writes */ public function __construct(string $filename, int $maxFiles = 0, $level = Logger::DEBUG, bool $bubble = \true, ?int $filePermission = null, bool $useLocking = \false) { $this->filename = Utils::canonicalizePath($filename); $this->maxFiles = $maxFiles; $this->nextRotation = new \DateTimeImmutable('tomorrow'); $this->filenameFormat = '{filename}-{date}'; $this->dateFormat = static::FILE_PER_DAY; parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); } /** * {@inheritDoc} */ public function close() : void { parent::close(); if (\true === $this->mustRotate) { $this->rotate(); } } /** * {@inheritDoc} */ public function reset() { parent::reset(); if (\true === $this->mustRotate) { $this->rotate(); } } public function setFilenameFormat(string $filenameFormat, string $dateFormat) : self { if (!\preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { throw new InvalidArgumentException('Invalid date format - format must be one of ' . 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") ' . 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the ' . 'date formats using slashes, underscores and/or dots instead of dashes.'); } if (\substr_count($filenameFormat, '{date}') === 0) { throw new InvalidArgumentException('Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.'); } $this->filenameFormat = $filenameFormat; $this->dateFormat = $dateFormat; $this->url = $this->getTimedFilename(); $this->close(); return $this; } /** * {@inheritDoc} */ protected function write(array $record) : void { // on the first record written, if the log is new, we should rotate (once per day) if (null === $this->mustRotate) { $this->mustRotate = null === $this->url || !\file_exists($this->url); } if ($this->nextRotation <= $record['datetime']) { $this->mustRotate = \true; $this->close(); } parent::write($record); } /** * Rotates the files. */ protected function rotate() : void { // update filename $this->url = $this->getTimedFilename(); $this->nextRotation = new \DateTimeImmutable('tomorrow'); // skip GC of old logs if files are unlimited if (0 === $this->maxFiles) { return; } $logFiles = \glob($this->getGlobPattern()); if (\false === $logFiles) { // failed to glob return; } if ($this->maxFiles >= \count($logFiles)) { // no files to remove return; } // Sorting the files by name to remove the older ones \usort($logFiles, function ($a, $b) { return \strcmp($b, $a); }); foreach (\array_slice($logFiles, $this->maxFiles) as $file) { if (\is_writable($file)) { // suppress errors here as unlink() might fail if two processes // are cleaning up/rotating at the same time \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) : bool { return \false; }); \unlink($file); \restore_error_handler(); } } $this->mustRotate = \false; } protected function getTimedFilename() : string { $fileInfo = \pathinfo($this->filename); $timedFilename = \str_replace(['{filename}', '{date}'], [$fileInfo['filename'], \date($this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat); if (isset($fileInfo['extension'])) { $timedFilename .= '.' . $fileInfo['extension']; } return $timedFilename; } protected function getGlobPattern() : string { $fileInfo = \pathinfo($this->filename); $glob = \str_replace(['{filename}', '{date}'], [$fileInfo['filename'], \str_replace(['Y', 'y', 'm', 'd'], ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], $this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat); if (isset($fileInfo['extension'])) { $glob .= '.' . $fileInfo['extension']; } return $glob; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use Throwable; /** * Forwards records to at most one handler * * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. * * As soon as one handler handles a record successfully, the handling stops there. * * @phpstan-import-type Record from \Monolog\Logger */ class FallbackGroupHandler extends GroupHandler { /** * {@inheritDoc} */ public function handle(array $record) : bool { if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { try { $handler->handle($record); break; } catch (Throwable $e) { // What throwable? } } return \false === $this->bubble; } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { if ($this->processors) { $processed = []; foreach ($records as $record) { $processed[] = $this->processRecord($record); } /** @var Record[] $records */ $records = $processed; } foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); break; } catch (Throwable $e) { // What throwable? } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\LineFormatter; /** * NativeMailerHandler uses the mail() function to send the emails * * @author Christophe Coevoet * @author Mark Garrett */ class NativeMailerHandler extends MailHandler { /** * The email addresses to which the message will be sent * @var string[] */ protected $to; /** * The subject of the email * @var string */ protected $subject; /** * Optional headers for the message * @var string[] */ protected $headers = []; /** * Optional parameters for the message * @var string[] */ protected $parameters = []; /** * The wordwrap length for the message * @var int */ protected $maxColumnWidth; /** * The Content-type for the message * @var string|null */ protected $contentType; /** * The encoding for the message * @var string */ protected $encoding = 'utf-8'; /** * @param string|string[] $to The receiver of the mail * @param string $subject The subject of the mail * @param string $from The sender of the mail * @param int $maxColumnWidth The maximum column width that the message lines will have */ public function __construct($to, string $subject, string $from, $level = Logger::ERROR, bool $bubble = \true, int $maxColumnWidth = 70) { parent::__construct($level, $bubble); $this->to = (array) $to; $this->subject = $subject; $this->addHeader(\sprintf('From: %s', $from)); $this->maxColumnWidth = $maxColumnWidth; } /** * Add headers to the message * * @param string|string[] $headers Custom added headers */ public function addHeader($headers) : self { foreach ((array) $headers as $header) { if (\strpos($header, "\n") !== \false || \strpos($header, "\r") !== \false) { throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); } $this->headers[] = $header; } return $this; } /** * Add parameters to the message * * @param string|string[] $parameters Custom added parameters */ public function addParameter($parameters) : self { $this->parameters = \array_merge($this->parameters, (array) $parameters); return $this; } /** * {@inheritDoc} */ protected function send(string $content, array $records) : void { $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); if ($contentType !== 'text/html') { $content = \wordwrap($content, $this->maxColumnWidth); } $headers = \ltrim(\implode("\r\n", $this->headers) . "\r\n", "\r\n"); $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; if ($contentType === 'text/html' && \false === \strpos($headers, 'MIME-Version:')) { $headers .= 'MIME-Version: 1.0' . "\r\n"; } $subject = $this->subject; if ($records) { $subjectFormatter = new LineFormatter($this->subject); $subject = $subjectFormatter->format($this->getHighestRecord($records)); } $parameters = \implode(' ', $this->parameters); foreach ($this->to as $to) { \mail($to, $subject, $content, $headers, $parameters); } } public function getContentType() : ?string { return $this->contentType; } public function getEncoding() : string { return $this->encoding; } /** * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. */ public function setContentType(string $contentType) : self { if (\strpos($contentType, "\n") !== \false || \strpos($contentType, "\r") !== \false) { throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); } $this->contentType = $contentType; return $this; } public function setEncoding(string $encoding) : self { if (\strpos($encoding, "\n") !== \false || \strpos($encoding, "\r") !== \false) { throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); } $this->encoding = $encoding; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use MongoDB\Driver\BulkWrite; use MongoDB\Driver\Manager; use _ContaoManager\MongoDB\Client; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\MongoDBFormatter; /** * Logs to a MongoDB database. * * Usage example: * * $log = new \Monolog\Logger('application'); * $client = new \MongoDB\Client('mongodb://localhost:27017'); * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); * $log->pushHandler($mongodb); * * The above examples uses the MongoDB PHP library's client class; however, the * MongoDB\Driver\Manager class from ext-mongodb is also supported. */ class MongoDBHandler extends AbstractProcessingHandler { /** @var \MongoDB\Collection */ private $collection; /** @var Client|Manager */ private $manager; /** @var string */ private $namespace; /** * Constructor. * * @param Client|Manager $mongodb MongoDB library or driver client * @param string $database Database name * @param string $collection Collection name */ public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = \true) { if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { throw new \InvalidArgumentException('MongoDB\\Client or MongoDB\\Driver\\Manager instance required'); } if ($mongodb instanceof Client) { $this->collection = $mongodb->selectCollection($database, $collection); } else { $this->manager = $mongodb; $this->namespace = $database . '.' . $collection; } parent::__construct($level, $bubble); } protected function write(array $record) : void { if (isset($this->collection)) { $this->collection->insertOne($record['formatted']); } if (isset($this->manager, $this->namespace)) { $bulk = new BulkWrite(); $bulk->insert($record["formatted"]); $this->manager->executeBulkWrite($this->namespace, $bulk); } } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new MongoDBFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; /** * Stores to STDIN of any process, specified by a command. * * Usage example: *
 * $log = new Logger('myLogger');
 * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
 * 
* * @author Kolja Zuelsdorf */ class ProcessHandler extends AbstractProcessingHandler { /** * Holds the process to receive data on its STDIN. * * @var resource|bool|null */ private $process; /** * @var string */ private $command; /** * @var string|null */ private $cwd; /** * @var resource[] */ private $pipes = []; /** * @var array */ protected const DESCRIPTOR_SPEC = [ 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to 2 => ['pipe', 'w'], ]; /** * @param string $command Command for the process to start. Absolute paths are recommended, * especially if you do not use the $cwd parameter. * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. * @throws \InvalidArgumentException */ public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = \true, ?string $cwd = null) { if ($command === '') { throw new \InvalidArgumentException('The command argument must be a non-empty string.'); } if ($cwd === '') { throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); } parent::__construct($level, $bubble); $this->command = $command; $this->cwd = $cwd; } /** * Writes the record down to the log of the implementing handler * * @throws \UnexpectedValueException */ protected function write(array $record) : void { $this->ensureProcessIsStarted(); $this->writeProcessInput($record['formatted']); $errors = $this->readProcessErrors(); if (empty($errors) === \false) { throw new \UnexpectedValueException(\sprintf('Errors while writing to process: %s', $errors)); } } /** * Makes sure that the process is actually started, and if not, starts it, * assigns the stream pipes, and handles startup errors, if any. */ private function ensureProcessIsStarted() : void { if (\is_resource($this->process) === \false) { $this->startProcess(); $this->handleStartupErrors(); } } /** * Starts the actual process and sets all streams to non-blocking. */ private function startProcess() : void { $this->process = \proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); foreach ($this->pipes as $pipe) { \stream_set_blocking($pipe, \false); } } /** * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. * * @throws \UnexpectedValueException */ private function handleStartupErrors() : void { $selected = $this->selectErrorStream(); if (\false === $selected) { throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); } $errors = $this->readProcessErrors(); if (\is_resource($this->process) === \false || empty($errors) === \false) { throw new \UnexpectedValueException(\sprintf('The process "%s" could not be opened: ' . $errors, $this->command)); } } /** * Selects the STDERR stream. * * @return int|bool */ protected function selectErrorStream() { $empty = []; $errorPipes = [$this->pipes[2]]; return \stream_select($errorPipes, $empty, $empty, 1); } /** * Reads the errors of the process, if there are any. * * @codeCoverageIgnore * @return string Empty string if there are no errors. */ protected function readProcessErrors() : string { return (string) \stream_get_contents($this->pipes[2]); } /** * Writes to the input stream of the opened process. * * @codeCoverageIgnore */ protected function writeProcessInput(string $string) : void { \fwrite($this->pipes[0], $string); } /** * {@inheritDoc} */ public function close() : void { if (\is_resource($this->process)) { foreach ($this->pipes as $pipe) { \fclose($pipe); } \proc_close($this->process); $this->process = null; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; /** * Helper trait for implementing FormattableInterface * * @author Jordi Boggiano */ trait FormattableHandlerTrait { /** * @var ?FormatterInterface */ protected $formatter; /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { $this->formatter = $formatter; return $this; } /** * {@inheritDoc} */ public function getFormatter() : FormatterInterface { if (!$this->formatter) { $this->formatter = $this->getDefaultFormatter(); } return $this->formatter; } /** * Gets the default formatter. * * Overwrite this if the LineFormatter is not a good default for your handler. */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Gelf\PublisherInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\GelfMessageFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Handler to send messages to a Graylog2 (http://www.graylog2.org) server * * @author Matt Lehner * @author Benjamin Zikarsky */ class GelfHandler extends AbstractProcessingHandler { /** * @var PublisherInterface the publisher object that sends the message to the server */ protected $publisher; /** * @param PublisherInterface $publisher a gelf publisher object */ public function __construct(PublisherInterface $publisher, $level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); $this->publisher = $publisher; } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->publisher->publish($record['formatted']); } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new GelfMessageFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\NormalizerFormatter; use _ContaoManager\Monolog\Logger; /** * Handler sending logs to Zend Monitor * * @author Christian Bergau * @author Jason Davis * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class ZendMonitorHandler extends AbstractProcessingHandler { /** * Monolog level / ZendMonitor Custom Event priority map * * @var array */ protected $levelMap = []; /** * @throws MissingExtensionException */ public function __construct($level = Logger::DEBUG, bool $bubble = \true) { if (!\function_exists('_ContaoManager\\zend_monitor_custom_event')) { throw new MissingExtensionException('You must have Zend Server installed with Zend Monitor enabled in order to use this handler'); } //zend monitor constants are not defined if zend monitor is not enabled. $this->levelMap = [Logger::DEBUG => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::INFO => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::NOTICE => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::WARNING => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_WARNING, Logger::ERROR => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::CRITICAL => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::ALERT => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::EMERGENCY => \_ContaoManager\ZEND_MONITOR_EVENT_SEVERITY_ERROR]; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->writeZendMonitorCustomEvent(Logger::getLevelName($record['level']), $record['message'], $record['formatted'], $this->levelMap[$record['level']]); } /** * Write to Zend Monitor Events * @param string $type Text displayed in "Class Name (custom)" field * @param string $message Text displayed in "Error String" * @param array $formatted Displayed in Custom Variables tab * @param int $severity Set the event severity level (-1,0,1) * * @phpstan-param FormattedRecord $formatted */ protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity) : void { zend_monitor_custom_event($type, $message, $formatted, $severity); } /** * {@inheritDoc} */ public function getDefaultFormatter() : FormatterInterface { return new NormalizerFormatter(); } /** * @return array */ public function getLevelMap() : array { return $this->levelMap; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\PhpConsole\Connector; use _ContaoManager\PhpConsole\Handler as VendorPhpConsoleHandler; use _ContaoManager\PhpConsole\Helper; /** * Monolog handler for Google Chrome extension "PHP Console" * * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely * * Usage: * 1. Install Google Chrome extension [now dead and removed from the chrome store] * 2. See overview https://github.com/barbushin/php-console#overview * 3. Install PHP Console library https://github.com/barbushin/php-console#installation * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) * * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); * \Monolog\ErrorHandler::register($logger); * echo $undefinedVar; * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); * PC::debug($_SERVER); // PHP Console debugger for any type of vars * * @author Sergey Barbushin https://www.linkedin.com/in/barbushin * * @phpstan-import-type Record from \Monolog\Logger * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 */ class PHPConsoleHandler extends AbstractProcessingHandler { /** @var array */ private $options = [ 'enabled' => \true, // bool Is PHP Console server enabled 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled 'useOwnErrorsHandler' => \false, // bool Enable errors handling 'useOwnExceptionsHandler' => \false, // bool Enable exceptions handling 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths 'registerHelper' => \true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') 'serverEncoding' => null, // string|null Server internal encoding 'headersLimit' => null, // int|null Set headers size limit for your web-server 'password' => null, // string|null Protect PHP Console connection by password 'enableSslOnlyMode' => \false, // bool Force connection by SSL for clients with PHP Console installed 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') 'enableEvalListener' => \false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) 'dumperDetectCallbacks' => \false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON 'detectDumpTraceAndSource' => \false, // bool Autodetect and append trace data to debug 'dataStorage' => null, ]; /** @var Connector */ private $connector; /** * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) * @throws \RuntimeException */ public function __construct(array $options = [], ?Connector $connector = null, $level = Logger::DEBUG, bool $bubble = \true) { if (!\class_exists('_ContaoManager\\PhpConsole\\Connector')) { throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); } parent::__construct($level, $bubble); $this->options = $this->initOptions($options); $this->connector = $this->initConnector($connector); } /** * @param array $options * * @return array */ private function initOptions(array $options) : array { $wrongOptions = \array_diff(\array_keys($options), \array_keys($this->options)); if ($wrongOptions) { throw new \RuntimeException('Unknown options: ' . \implode(', ', $wrongOptions)); } return \array_replace($this->options, $options); } private function initConnector(?Connector $connector = null) : Connector { if (!$connector) { if ($this->options['dataStorage']) { Connector::setPostponeStorage($this->options['dataStorage']); } $connector = Connector::getInstance(); } if ($this->options['registerHelper'] && !Helper::isRegistered()) { Helper::register(); } if ($this->options['enabled'] && $connector->isActiveClient()) { if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { $handler = VendorPhpConsoleHandler::getInstance(); $handler->setHandleErrors($this->options['useOwnErrorsHandler']); $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); $handler->start(); } if ($this->options['sourcesBasePath']) { $connector->setSourcesBasePath($this->options['sourcesBasePath']); } if ($this->options['serverEncoding']) { $connector->setServerEncoding($this->options['serverEncoding']); } if ($this->options['password']) { $connector->setPassword($this->options['password']); } if ($this->options['enableSslOnlyMode']) { $connector->enableSslOnlyMode(); } if ($this->options['ipMasks']) { $connector->setAllowedIpMasks($this->options['ipMasks']); } if ($this->options['headersLimit']) { $connector->setHeadersLimit($this->options['headersLimit']); } if ($this->options['detectDumpTraceAndSource']) { $connector->getDebugDispatcher()->detectTraceAndSource = \true; } $dumper = $connector->getDumper(); $dumper->levelLimit = $this->options['dumperLevelLimit']; $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; if ($this->options['enableEvalListener']) { $connector->startEvalRequestsListener(); } } return $connector; } public function getConnector() : Connector { return $this->connector; } /** * @return array */ public function getOptions() : array { return $this->options; } public function handle(array $record) : bool { if ($this->options['enabled'] && $this->connector->isActiveClient()) { return parent::handle($record); } return !$this->bubble; } /** * Writes the record down to the log of the implementing handler */ protected function write(array $record) : void { if ($record['level'] < Logger::NOTICE) { $this->handleDebugRecord($record); } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { $this->handleExceptionRecord($record); } else { $this->handleErrorRecord($record); } } /** * @phpstan-param Record $record */ private function handleDebugRecord(array $record) : void { $tags = $this->getRecordTags($record); $message = $record['message']; if ($record['context']) { $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(\array_filter($record['context'])), null, \true); } $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); } /** * @phpstan-param Record $record */ private function handleExceptionRecord(array $record) : void { $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); } /** * @phpstan-param Record $record */ private function handleErrorRecord(array $record) : void { $context = $record['context']; $this->connector->getErrorsDispatcher()->dispatchError($context['code'] ?? null, $context['message'] ?? $record['message'], $context['file'] ?? null, $context['line'] ?? null, $this->options['classesPartialsTraceIgnore']); } /** * @phpstan-param Record $record * @return string */ private function getRecordTags(array &$record) { $tags = null; if (!empty($record['context'])) { $context =& $record['context']; foreach ($this->options['debugTagsKeysInContext'] as $key) { if (!empty($context[$key])) { $tags = $context[$key]; if ($key === 0) { \array_shift($context); } else { unset($context[$key]); } break; } } } return $tags ?: \strtolower($record['level_name']); } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter('%message%'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Stores to PHP error_log() handler. * * @author Elan Ruusamäe */ class ErrorLogHandler extends AbstractProcessingHandler { public const OPERATING_SYSTEM = 0; public const SAPI = 4; /** @var int */ protected $messageType; /** @var bool */ protected $expandNewlines; /** * @param int $messageType Says where the error should go. * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries */ public function __construct(int $messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, bool $bubble = \true, bool $expandNewlines = \false) { parent::__construct($level, $bubble); if (\false === \in_array($messageType, self::getAvailableTypes(), \true)) { $message = \sprintf('The given message type "%s" is not supported', \print_r($messageType, \true)); throw new \InvalidArgumentException($message); } $this->messageType = $messageType; $this->expandNewlines = $expandNewlines; } /** * @return int[] With all available types */ public static function getAvailableTypes() : array { return [self::OPERATING_SYSTEM, self::SAPI]; } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); } /** * {@inheritDoc} */ protected function write(array $record) : void { if (!$this->expandNewlines) { \error_log((string) $record['formatted'], $this->messageType); return; } $lines = \preg_split('{[\\r\\n]+}', (string) $record['formatted']); if ($lines === \false) { $pcreErrorCode = \preg_last_error(); throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); } foreach ($lines as $line) { \error_log($line, $this->messageType); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; /** * @author Robert Kaufmann III */ class LogEntriesHandler extends SocketHandler { /** * @var string */ protected $logToken; /** * @param string $token Log token supplied by LogEntries * @param bool $useSSL Whether or not SSL encryption should be used. * @param string $host Custom hostname to send the data to if needed * * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing */ public function __construct(string $token, bool $useSSL = \true, $level = Logger::DEBUG, bool $bubble = \true, string $host = 'data.logentries.com', bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { if ($useSSL && !\extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); } $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; parent::__construct($endpoint, $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); $this->logToken = $token; } /** * {@inheritDoc} */ protected function generateDataStream(array $record) : string { return $this->logToken . ' ' . $record['formatted']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LogmaticFormatter; /** * @author Julien Breux */ class LogmaticHandler extends SocketHandler { /** * @var string */ private $logToken; /** * @var string */ private $hostname; /** * @var string */ private $appname; /** * @param string $token Log token supplied by Logmatic. * @param string $hostname Host name supplied by Logmatic. * @param string $appname Application name supplied by Logmatic. * @param bool $useSSL Whether or not SSL encryption should be used. * * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing */ public function __construct(string $token, string $hostname = '', string $appname = '', bool $useSSL = \true, $level = Logger::DEBUG, bool $bubble = \true, bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { if ($useSSL && !\extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); } $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; $endpoint .= '/v1/'; parent::__construct($endpoint, $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); $this->logToken = $token; $this->hostname = $hostname; $this->appname = $appname; } /** * {@inheritDoc} */ protected function generateDataStream(array $record) : string { return $this->logToken . ' ' . $record['formatted']; } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { $formatter = new LogmaticFormatter(); if (!empty($this->hostname)) { $formatter->setHostname($this->hostname); } if (!empty($this->appname)) { $formatter->setAppname($this->appname); } return $formatter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Simple handler wrapper that deduplicates log records across multiple requests * * It also includes the BufferHandler functionality and will buffer * all messages until the end of the request or flush() is called. * * This works by storing all log records' messages above $deduplicationLevel * to the file specified by $deduplicationStore. When further logs come in at the end of the * request (or when flush() is called), all those above $deduplicationLevel are checked * against the existing stored logs. If they match and the timestamps in the stored log is * not older than $time seconds, the new log record is discarded. If no log record is new, the * whole data set is discarded. * * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers * that send messages to people, to avoid spamming with the same message over and over in case of * a major component failure like a database server being down which makes all requests fail in the * same way. * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger */ class DeduplicationHandler extends BufferHandler { /** * @var string */ protected $deduplicationStore; /** * @var Level */ protected $deduplicationLevel; /** * @var int */ protected $time; /** * @var bool */ private $gc = \false; /** * @param HandlerInterface $handler Handler. * @param string $deduplicationStore The file/path where the deduplication log should be kept * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel */ public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = \true) { parent::__construct($handler, 0, Logger::DEBUG, $bubble, \false); $this->deduplicationStore = $deduplicationStore === null ? \sys_get_temp_dir() . '/monolog-dedup-' . \substr(\md5(__FILE__), 0, 20) . '.log' : $deduplicationStore; $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); $this->time = $time; } public function flush() : void { if ($this->bufferSize === 0) { return; } $passthru = null; foreach ($this->buffer as $record) { if ($record['level'] >= $this->deduplicationLevel) { $passthru = $passthru || !$this->isDuplicate($record); if ($passthru) { $this->appendRecord($record); } } } // default of null is valid as well as if no record matches duplicationLevel we just pass through if ($passthru === \true || $passthru === null) { $this->handler->handleBatch($this->buffer); } $this->clear(); if ($this->gc) { $this->collectLogs(); } } /** * @phpstan-param Record $record */ private function isDuplicate(array $record) : bool { if (!\file_exists($this->deduplicationStore)) { return \false; } $store = \file($this->deduplicationStore, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); if (!\is_array($store)) { return \false; } $yesterday = \time() - 86400; $timestampValidity = $record['datetime']->getTimestamp() - $this->time; $expectedMessage = \preg_replace('{[\\r\\n].*}', '', $record['message']); for ($i = \count($store) - 1; $i >= 0; $i--) { list($timestamp, $level, $message) = \explode(':', $store[$i], 3); if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { return \true; } if ($timestamp < $yesterday) { $this->gc = \true; } } return \false; } private function collectLogs() : void { if (!\file_exists($this->deduplicationStore)) { return; } $handle = \fopen($this->deduplicationStore, 'rw+'); if (!$handle) { throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); } \flock($handle, \LOCK_EX); $validLogs = []; $timestampValidity = \time() - $this->time; while (!\feof($handle)) { $log = \fgets($handle); if ($log && \substr($log, 0, 10) >= $timestampValidity) { $validLogs[] = $log; } } \ftruncate($handle, 0); \rewind($handle); foreach ($validLogs as $log) { \fwrite($handle, $log); } \flock($handle, \LOCK_UN); \fclose($handle); $this->gc = \false; } /** * @phpstan-param Record $record */ private function appendRecord(array $record) : void { \file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . \preg_replace('{[\\r\\n].*}', '', $record['message']) . "\n", \FILE_APPEND); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\WildfireFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. * * @author Eric Clemmons (@ericclemmons) * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FirePHPHandler extends AbstractProcessingHandler { use WebRequestRecognizerTrait; /** * WildFire JSON header message format */ protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; /** * FirePHP structure for parsing messages & their presentation */ protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; /** * Must reference a "known" plugin, otherwise headers won't display in FirePHP */ protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; /** * Header prefix for Wildfire to recognize & parse headers */ protected const HEADER_PREFIX = 'X-Wf'; /** * Whether or not Wildfire vendor-specific headers have been generated & sent yet * @var bool */ protected static $initialized = \false; /** * Shared static message index between potentially multiple handlers * @var int */ protected static $messageIndex = 1; /** @var bool */ protected static $sendHeaders = \true; /** * Base header creation function used by init headers & record headers * * @param array $meta Wildfire Plugin, Protocol & Structure Indexes * @param string $message Log message * * @return array Complete header string ready for the client as key and message as value * * @phpstan-return non-empty-array */ protected function createHeader(array $meta, string $message) : array { $header = \sprintf('%s-%s', static::HEADER_PREFIX, \join('-', $meta)); return [$header => $message]; } /** * Creates message header from record * * @return array * * @phpstan-return non-empty-array * * @see createHeader() * * @phpstan-param FormattedRecord $record */ protected function createRecordHeader(array $record) : array { // Wildfire is extensible to support multiple protocols & plugins in a single request, // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. return $this->createHeader([1, 1, 1, self::$messageIndex++], $record['formatted']); } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new WildfireFormatter(); } /** * Wildfire initialization headers to enable message parsing * * @see createHeader() * @see sendHeader() * * @return array */ protected function getInitHeaders() : array { // Initial payload consists of required headers for Wildfire return \array_merge($this->createHeader(['Protocol', 1], static::PROTOCOL_URI), $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI)); } /** * Send header string to the client */ protected function sendHeader(string $header, string $content) : void { if (!\headers_sent() && self::$sendHeaders) { \header(\sprintf('%s: %s', $header, $content)); } } /** * Creates & sends header for a record, ensuring init headers have been sent prior * * @see sendHeader() * @see sendInitHeaders() */ protected function write(array $record) : void { if (!self::$sendHeaders || !$this->isWebRequest()) { return; } // WildFire-specific headers must be sent prior to any messages if (!self::$initialized) { self::$initialized = \true; self::$sendHeaders = $this->headersAccepted(); if (!self::$sendHeaders) { return; } foreach ($this->getInitHeaders() as $header => $content) { $this->sendHeader($header, $content); } } $header = $this->createRecordHeader($record); if (\trim(\current($header)) !== '') { $this->sendHeader(\key($header), \current($header)); } } /** * Verifies if the headers are accepted by the current user agent */ protected function headersAccepted() : bool { if (!empty($_SERVER['HTTP_USER_AGENT']) && \preg_match('{\\bFirePHP/\\d+\\.\\d+\\b}', $_SERVER['HTTP_USER_AGENT'])) { return \true; } return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler\Curl; use CurlHandle; /** * This class is marked as internal and it is not under the BC promise of the package. * * @internal */ final class Util { /** @var array */ private static $retriableErrorCodes = [\CURLE_COULDNT_RESOLVE_HOST, \CURLE_COULDNT_CONNECT, \CURLE_HTTP_NOT_FOUND, \CURLE_READ_ERROR, \CURLE_OPERATION_TIMEOUTED, \CURLE_HTTP_POST_ERROR, \CURLE_SSL_CONNECT_ERROR]; /** * Executes a CURL request with optional retries and exception on failure * * @param resource|CurlHandle $ch curl handler * @param int $retries * @param bool $closeAfterDone * @return bool|string @see curl_exec */ public static function execute($ch, int $retries = 5, bool $closeAfterDone = \true) { while ($retries--) { $curlResponse = \curl_exec($ch); if ($curlResponse === \false) { $curlErrno = \curl_errno($ch); if (\false === \in_array($curlErrno, self::$retriableErrorCodes, \true) || !$retries) { $curlError = \curl_error($ch); if ($closeAfterDone) { \curl_close($ch); } throw new \RuntimeException(\sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); } continue; } if ($closeAfterDone) { \curl_close($ch); } return $curlResponse; } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\NormalizerFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Doctrine\CouchDB\CouchDBClient; /** * CouchDB handler for Doctrine CouchDB ODM * * @author Markus Bachmann */ class DoctrineCouchDBHandler extends AbstractProcessingHandler { /** @var CouchDBClient */ private $client; public function __construct(CouchDBClient $client, $level = Logger::DEBUG, bool $bubble = \true) { $this->client = $client; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->client->postDocument($record['formatted']); } protected function getDefaultFormatter() : FormatterInterface { return new NormalizerFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Aws\Sdk; use _ContaoManager\Aws\DynamoDb\DynamoDbClient; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Aws\DynamoDb\Marshaler; use _ContaoManager\Monolog\Formatter\ScalarFormatter; use _ContaoManager\Monolog\Logger; /** * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) * * @link https://github.com/aws/aws-sdk-php/ * @author Andrew Lawson */ class DynamoDbHandler extends AbstractProcessingHandler { public const DATE_FORMAT = 'Y-m-d\\TH:i:s.uO'; /** * @var DynamoDbClient */ protected $client; /** * @var string */ protected $table; /** * @var int */ protected $version; /** * @var Marshaler */ protected $marshaler; public function __construct(DynamoDbClient $client, string $table, $level = Logger::DEBUG, bool $bubble = \true) { /** @phpstan-ignore-next-line */ if (\defined('Aws\\Sdk::VERSION') && \version_compare(Sdk::VERSION, '3.0', '>=')) { $this->version = 3; $this->marshaler = new Marshaler(); } else { $this->version = 2; } $this->client = $client; $this->table = $table; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { $filtered = $this->filterEmptyFields($record['formatted']); if ($this->version === 3) { $formatted = $this->marshaler->marshalItem($filtered); } else { /** @phpstan-ignore-next-line */ $formatted = $this->client->formatAttributes($filtered); } $this->client->putItem(['TableName' => $this->table, 'Item' => $formatted]); } /** * @param mixed[] $record * @return mixed[] */ protected function filterEmptyFields(array $record) : array { return \array_filter($record, function ($value) { return !empty($value) || \false === $value || 0 === $value; }); } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new ScalarFormatter(self::DATE_FORMAT); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Interface to describe loggers that have a formatter * * @author Jordi Boggiano */ interface FormattableHandlerInterface { /** * Sets the formatter. * * @param FormatterInterface $formatter * @return HandlerInterface self */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface; /** * Gets the formatter. * * @return FormatterInterface */ public function getFormatter() : FormatterInterface; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; /** * Sends the message to a Redis Pub/Sub channel using PUBLISH * * usage example: * * $log = new Logger('application'); * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Logger::WARNING); * $log->pushHandler($redis); * * @author Gaëtan Faugère */ class RedisPubSubHandler extends AbstractProcessingHandler { /** @var \Predis\Client<\Predis\Client>|\Redis */ private $redisClient; /** @var string */ private $channelKey; /** * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance * @param string $key The channel key to publish records to */ public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = \true) { if (!($redis instanceof \_ContaoManager\Predis\Client || $redis instanceof \Redis)) { throw new \InvalidArgumentException('Predis\\Client or Redis instance required'); } $this->redisClient = $redis; $this->channelKey = $key; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->redisClient->publish($this->channelKey, $record["formatted"]); } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\HtmlFormatter; /** * Base class for all mail handlers * * @author Gyula Sallai * * @phpstan-import-type Record from \Monolog\Logger */ abstract class MailHandler extends AbstractProcessingHandler { /** * {@inheritDoc} */ public function handleBatch(array $records) : void { $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { continue; } /** @var Record $message */ $message = $this->processRecord($record); $messages[] = $message; } if (!empty($messages)) { $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); } } /** * Send a mail with the given content * * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content * * @phpstan-param Record[] $records */ protected abstract function send(string $content, array $records) : void; /** * {@inheritDoc} */ protected function write(array $record) : void { $this->send((string) $record['formatted'], [$record]); } /** * @phpstan-param non-empty-array $records * @phpstan-return Record */ protected function getHighestRecord(array $records) : array { $highestRecord = null; foreach ($records as $record) { if ($highestRecord === null || $highestRecord['level'] < $record['level']) { $highestRecord = $record; } } return $highestRecord; } protected function isHtmlBody(string $body) : bool { return ($body[0] ?? null) === '<'; } /** * Gets the default formatter. * * @return FormatterInterface */ protected function getDefaultFormatter() : FormatterInterface { return new HtmlFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Stores to any stream resource * * Can be used to store into php://stderr, remote and local files, etc. * * @author Jordi Boggiano * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class StreamHandler extends AbstractProcessingHandler { /** @const int */ protected const MAX_CHUNK_SIZE = 2147483647; /** @const int 10MB */ protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; /** @var int */ protected $streamChunkSize; /** @var resource|null */ protected $stream; /** @var ?string */ protected $url = null; /** @var ?string */ private $errorMessage = null; /** @var ?int */ protected $filePermission; /** @var bool */ protected $useLocking; /** @var true|null */ private $dirCreated = null; /** * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) * @param bool $useLocking Try to lock log file before doing any writes * * @throws \InvalidArgumentException If stream is not a resource or string */ public function __construct($stream, $level = Logger::DEBUG, bool $bubble = \true, ?int $filePermission = null, bool $useLocking = \false) { parent::__construct($level, $bubble); if (($phpMemoryLimit = Utils::expandIniShorthandBytes(\ini_get('memory_limit'))) !== \false) { if ($phpMemoryLimit > 0) { // use max 10% of allowed memory for the chunk size, and at least 100KB $this->streamChunkSize = \min(static::MAX_CHUNK_SIZE, \max((int) ($phpMemoryLimit / 10), 100 * 1024)); } else { // memory is unlimited, set to the default 10MB $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; } } else { // no memory limit information, set to the default 10MB $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; } if (\is_resource($stream)) { $this->stream = $stream; \stream_set_chunk_size($this->stream, $this->streamChunkSize); } elseif (\is_string($stream)) { $this->url = Utils::canonicalizePath($stream); } else { throw new \InvalidArgumentException('A stream must either be a resource or a string.'); } $this->filePermission = $filePermission; $this->useLocking = $useLocking; } /** * {@inheritDoc} */ public function close() : void { if ($this->url && \is_resource($this->stream)) { \fclose($this->stream); } $this->stream = null; $this->dirCreated = null; } /** * Return the currently active stream if it is open * * @return resource|null */ public function getStream() { return $this->stream; } /** * Return the stream URL if it was configured with a URL and not an active resource * * @return string|null */ public function getUrl() : ?string { return $this->url; } /** * @return int */ public function getStreamChunkSize() : int { return $this->streamChunkSize; } /** * {@inheritDoc} */ protected function write(array $record) : void { if (!\is_resource($this->stream)) { $url = $this->url; if (null === $url || '' === $url) { throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); } $this->createDir($url); $this->errorMessage = null; \set_error_handler([$this, 'customErrorHandler']); try { $stream = \fopen($url, 'a'); if ($this->filePermission !== null) { @\chmod($url, $this->filePermission); } } finally { \restore_error_handler(); } if (!\is_resource($stream)) { $this->stream = null; throw new \UnexpectedValueException(\sprintf('The stream or file "%s" could not be opened in append mode: ' . $this->errorMessage, $url) . Utils::getRecordMessageForException($record)); } \stream_set_chunk_size($stream, $this->streamChunkSize); $this->stream = $stream; } $stream = $this->stream; if (!\is_resource($stream)) { throw new \LogicException('No stream was opened yet' . Utils::getRecordMessageForException($record)); } if ($this->useLocking) { // ignoring errors here, there's not much we can do about them \flock($stream, \LOCK_EX); } $this->streamWrite($stream, $record); if ($this->useLocking) { \flock($stream, \LOCK_UN); } } /** * Write to stream * @param resource $stream * @param array $record * * @phpstan-param FormattedRecord $record */ protected function streamWrite($stream, array $record) : void { \fwrite($stream, (string) $record['formatted']); } private function customErrorHandler(int $code, string $msg) : bool { $this->errorMessage = \preg_replace('{^(fopen|mkdir)\\(.*?\\): }', '', $msg); return \true; } private function getDirFromStream(string $stream) : ?string { $pos = \strpos($stream, '://'); if ($pos === \false) { return \dirname($stream); } if ('file://' === \substr($stream, 0, 7)) { return \dirname(\substr($stream, 7)); } return null; } private function createDir(string $url) : void { // Do not try to create dir if it has already been tried. if ($this->dirCreated) { return; } $dir = $this->getDirFromStream($url); if (null !== $dir && !\is_dir($dir)) { $this->errorMessage = null; \set_error_handler([$this, 'customErrorHandler']); $status = \mkdir($dir, 0777, \true); \restore_error_handler(); if (\false === $status && !\is_dir($dir) && \strpos((string) $this->errorMessage, 'File exists') === \false) { throw new \UnexpectedValueException(\sprintf('There is no existing directory at "%s" and it could not be created: ' . $this->errorMessage, $dir)); } } $this->dirCreated = \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LogglyFormatter; use function array_key_exists; use CurlHandle; /** * Sends errors to Loggly. * * @author Przemek Sobstel * @author Adam Pancutt * @author Gregory Barchard */ class LogglyHandler extends AbstractProcessingHandler { protected const HOST = 'logs-01.loggly.com'; protected const ENDPOINT_SINGLE = 'inputs'; protected const ENDPOINT_BATCH = 'bulk'; /** * Caches the curl handlers for every given endpoint. * * @var resource[]|CurlHandle[] */ protected $curlHandlers = []; /** @var string */ protected $token; /** @var string[] */ protected $tag = []; /** * @param string $token API token supplied by Loggly * * @throws MissingExtensionException If the curl extension is missing */ public function __construct(string $token, $level = Logger::DEBUG, bool $bubble = \true) { if (!\extension_loaded('curl')) { throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler'); } $this->token = $token; parent::__construct($level, $bubble); } /** * Loads and returns the shared curl handler for the given endpoint. * * @param string $endpoint * * @return resource|CurlHandle */ protected function getCurlHandler(string $endpoint) { if (!array_key_exists($endpoint, $this->curlHandlers)) { $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); } return $this->curlHandlers[$endpoint]; } /** * Starts a fresh curl session for the given endpoint and returns its handler. * * @param string $endpoint * * @return resource|CurlHandle */ private function loadCurlHandle(string $endpoint) { $url = \sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); $ch = \curl_init(); \curl_setopt($ch, \CURLOPT_URL, $url); \curl_setopt($ch, \CURLOPT_POST, \true); \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, \true); return $ch; } /** * @param string[]|string $tag */ public function setTag($tag) : self { $tag = !empty($tag) ? $tag : []; $this->tag = \is_array($tag) ? $tag : [$tag]; return $this; } /** * @param string[]|string $tag */ public function addTag($tag) : self { if (!empty($tag)) { $tag = \is_array($tag) ? $tag : [$tag]; $this->tag = \array_unique(\array_merge($this->tag, $tag)); } return $this; } protected function write(array $record) : void { $this->send($record["formatted"], static::ENDPOINT_SINGLE); } public function handleBatch(array $records) : void { $level = $this->level; $records = \array_filter($records, function ($record) use($level) { return $record['level'] >= $level; }); if ($records) { $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); } } protected function send(string $data, string $endpoint) : void { $ch = $this->getCurlHandler($endpoint); $headers = ['Content-Type: application/json']; if (!empty($this->tag)) { $headers[] = 'X-LOGGLY-TAG: ' . \implode(',', $this->tag); } \curl_setopt($ch, \CURLOPT_POSTFIELDS, $data); \curl_setopt($ch, \CURLOPT_HTTPHEADER, $headers); Curl\Util::execute($ch, 5, \false); } protected function getDefaultFormatter() : FormatterInterface { return new LogglyFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Handler\Slack\SlackRecord; /** * Sends notifications through Slack Webhooks * * @author Haralan Dobrev * @see https://api.slack.com/incoming-webhooks */ class SlackWebhookHandler extends AbstractProcessingHandler { /** * Slack Webhook token * @var string */ private $webhookUrl; /** * Instance of the SlackRecord util class preparing data for Slack API. * @var SlackRecord */ private $slackRecord; /** * @param string $webhookUrl Slack Webhook URL * @param string|null $channel Slack channel (encoded ID or name) * @param string|null $username Name of a bot * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) * @param string|null $iconEmoji The emoji name to use (or null) * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style * @param bool $includeContextAndExtra Whether the attachment should include context and extra data * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] */ public function __construct(string $webhookUrl, ?string $channel = null, ?string $username = null, bool $useAttachment = \true, ?string $iconEmoji = null, bool $useShortAttachment = \false, bool $includeContextAndExtra = \false, $level = Logger::CRITICAL, bool $bubble = \true, array $excludeFields = array()) { if (!\extension_loaded('curl')) { throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler'); } parent::__construct($level, $bubble); $this->webhookUrl = $webhookUrl; $this->slackRecord = new SlackRecord($channel, $username, $useAttachment, $iconEmoji, $useShortAttachment, $includeContextAndExtra, $excludeFields); } public function getSlackRecord() : SlackRecord { return $this->slackRecord; } public function getWebhookUrl() : string { return $this->webhookUrl; } /** * {@inheritDoc} */ protected function write(array $record) : void { $postData = $this->slackRecord->getSlackData($record); $postString = Utils::jsonEncode($postData); $ch = \curl_init(); $options = array(\CURLOPT_URL => $this->webhookUrl, \CURLOPT_POST => \true, \CURLOPT_RETURNTRANSFER => \true, \CURLOPT_HTTPHEADER => array('Content-type: application/json'), \CURLOPT_POSTFIELDS => $postString); if (\defined('CURLOPT_SAFE_UPLOAD')) { $options[\CURLOPT_SAFE_UPLOAD] = \true; } \curl_setopt_array($ch, $options); Curl\Util::execute($ch); } public function setFormatter(FormatterInterface $formatter) : HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); return $this; } public function getFormatter() : FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); return $formatter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Elastica\Document; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\ElasticaFormatter; use _ContaoManager\Monolog\Logger; use _ContaoManager\Elastica\Client; use _ContaoManager\Elastica\Exception\ExceptionInterface; /** * Elastic Search handler * * Usage example: * * $client = new \Elastica\Client(); * $options = array( * 'index' => 'elastic_index_name', * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 * ); * $handler = new ElasticaHandler($client, $options); * $log = new Logger('application'); * $log->pushHandler($handler); * * @author Jelle Vink */ class ElasticaHandler extends AbstractProcessingHandler { /** * @var Client */ protected $client; /** * @var mixed[] Handler config options */ protected $options = []; /** * @param Client $client Elastica Client object * @param mixed[] $options Handler configuration */ public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); $this->client = $client; $this->options = \array_merge([ 'index' => 'monolog', // Elastic index name 'type' => 'record', // Elastic document type 'ignore_error' => \false, ], $options); } /** * {@inheritDoc} */ protected function write(array $record) : void { $this->bulkSend([$record['formatted']]); } /** * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { if ($formatter instanceof ElasticaFormatter) { return parent::setFormatter($formatter); } throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); } /** * @return mixed[] */ public function getOptions() : array { return $this->options; } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new ElasticaFormatter($this->options['index'], $this->options['type']); } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { $documents = $this->getFormatter()->formatBatch($records); $this->bulkSend($documents); } /** * Use Elasticsearch bulk API to send list of documents * * @param Document[] $documents * * @throws \RuntimeException */ protected function bulkSend(array $documents) : void { try { $this->client->addDocuments($documents); } catch (ExceptionInterface $e) { if (!$this->options['ignore_error']) { throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; /** * Forwards records to multiple handlers suppressing failures of each handler * and continuing through to give every handler a chance to succeed. * * @author Craig D'Amelio * * @phpstan-import-type Record from \Monolog\Logger */ class WhatFailureGroupHandler extends GroupHandler { /** * {@inheritDoc} */ public function handle(array $record) : bool { if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { try { $handler->handle($record); } catch (\Throwable $e) { // What failure? } } return \false === $this->bubble; } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { if ($this->processors) { $processed = array(); foreach ($records as $record) { $processed[] = $this->processRecord($record); } /** @var Record[] $records */ $records = $processed; } foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); } catch (\Throwable $e) { // What failure? } } } /** * {@inheritDoc} */ public function close() : void { foreach ($this->handlers as $handler) { try { $handler->close(); } catch (\Throwable $e) { // What failure? } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\ChromePHPFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) * * This also works out of the box with Firefox 43+ * * @author Christophe Coevoet * * @phpstan-import-type Record from \Monolog\Logger */ class ChromePHPHandler extends AbstractProcessingHandler { use WebRequestRecognizerTrait; /** * Version of the extension */ protected const VERSION = '4.0'; /** * Header name */ protected const HEADER_NAME = 'X-ChromeLogger-Data'; /** * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) */ protected const USER_AGENT_REGEX = '{\\b(?:Chrome/\\d+(?:\\.\\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\\d|\\d{3,})(?:\\.\\d)*)\\b}'; /** @var bool */ protected static $initialized = \false; /** * Tracks whether we sent too much data * * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending * * @var bool */ protected static $overflowed = \false; /** @var mixed[] */ protected static $json = ['version' => self::VERSION, 'columns' => ['label', 'log', 'backtrace', 'type'], 'rows' => []]; /** @var bool */ protected static $sendHeaders = \true; public function __construct($level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); if (!\function_exists('json_encode')) { throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); } } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { if (!$this->isWebRequest()) { return; } $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { continue; } /** @var Record $message */ $message = $this->processRecord($record); $messages[] = $message; } if (!empty($messages)) { $messages = $this->getFormatter()->formatBatch($messages); self::$json['rows'] = \array_merge(self::$json['rows'], $messages); $this->send(); } } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new ChromePHPFormatter(); } /** * Creates & sends header for a record * * @see sendHeader() * @see send() */ protected function write(array $record) : void { if (!$this->isWebRequest()) { return; } self::$json['rows'][] = $record['formatted']; $this->send(); } /** * Sends the log header * * @see sendHeader() */ protected function send() : void { if (self::$overflowed || !self::$sendHeaders) { return; } if (!self::$initialized) { self::$initialized = \true; self::$sendHeaders = $this->headersAccepted(); if (!self::$sendHeaders) { return; } self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; } $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~\JSON_UNESCAPED_UNICODE, \true); $data = \base64_encode($json); if (\strlen($data) > 3 * 1024) { self::$overflowed = \true; $record = ['message' => 'Incomplete logs, chrome header size limit reached', 'context' => [], 'level' => Logger::WARNING, 'level_name' => Logger::getLevelName(Logger::WARNING), 'channel' => 'monolog', 'datetime' => new \DateTimeImmutable(), 'extra' => []]; self::$json['rows'][\count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~\JSON_UNESCAPED_UNICODE, \true); $data = \base64_encode($json); } if (\trim($data) !== '') { $this->sendHeader(static::HEADER_NAME, $data); } } /** * Send header string to the client */ protected function sendHeader(string $header, string $content) : void { if (!\headers_sent() && self::$sendHeaders) { \header(\sprintf('%s: %s', $header, $content)); } } /** * Verifies if the headers are accepted by the current user agent */ protected function headersAccepted() : bool { if (empty($_SERVER['HTTP_USER_AGENT'])) { return \false; } return \preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler\Slack; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Formatter\NormalizerFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Slack record utility helping to log to Slack webhooks or API. * * @author Greg Kedzierski * @author Haralan Dobrev * @see https://api.slack.com/incoming-webhooks * @see https://api.slack.com/docs/message-attachments * * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler * @phpstan-import-type Record from \Monolog\Logger */ class SlackRecord { public const COLOR_DANGER = 'danger'; public const COLOR_WARNING = 'warning'; public const COLOR_GOOD = 'good'; public const COLOR_DEFAULT = '#e3e4e6'; /** * Slack channel (encoded ID or name) * @var string|null */ private $channel; /** * Name of a bot * @var string|null */ private $username; /** * User icon e.g. 'ghost', 'http://example.com/user.png' * @var string|null */ private $userIcon; /** * Whether the message should be added to Slack as attachment (plain text otherwise) * @var bool */ private $useAttachment; /** * Whether the the context/extra messages added to Slack as attachments are in a short style * @var bool */ private $useShortAttachment; /** * Whether the attachment should include context and extra data * @var bool */ private $includeContextAndExtra; /** * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] * @var string[] */ private $excludeFields; /** * @var ?FormatterInterface */ private $formatter; /** * @var NormalizerFormatter */ private $normalizerFormatter; /** * @param string[] $excludeFields */ public function __construct(?string $channel = null, ?string $username = null, bool $useAttachment = \true, ?string $userIcon = null, bool $useShortAttachment = \false, bool $includeContextAndExtra = \false, array $excludeFields = array(), FormatterInterface $formatter = null) { $this->setChannel($channel)->setUsername($username)->useAttachment($useAttachment)->setUserIcon($userIcon)->useShortAttachment($useShortAttachment)->includeContextAndExtra($includeContextAndExtra)->excludeFields($excludeFields)->setFormatter($formatter); if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); } } /** * Returns required data in format that Slack * is expecting. * * @phpstan-param FormattedRecord $record * @phpstan-return mixed[] */ public function getSlackData(array $record) : array { $dataArray = array(); $record = $this->removeExcludedFields($record); if ($this->username) { $dataArray['username'] = $this->username; } if ($this->channel) { $dataArray['channel'] = $this->channel; } if ($this->formatter && !$this->useAttachment) { /** @phpstan-ignore-next-line */ $message = $this->formatter->format($record); } else { $message = $record['message']; } if ($this->useAttachment) { $attachment = array('fallback' => $message, 'text' => $message, 'color' => $this->getAttachmentColor($record['level']), 'fields' => array(), 'mrkdwn_in' => array('fields'), 'ts' => $record['datetime']->getTimestamp(), 'footer' => $this->username, 'footer_icon' => $this->userIcon); if ($this->useShortAttachment) { $attachment['title'] = $record['level_name']; } else { $attachment['title'] = 'Message'; $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); } if ($this->includeContextAndExtra) { foreach (array('extra', 'context') as $key) { if (empty($record[$key])) { continue; } if ($this->useShortAttachment) { $attachment['fields'][] = $this->generateAttachmentField((string) $key, $record[$key]); } else { // Add all extra fields as individual fields in attachment $attachment['fields'] = \array_merge($attachment['fields'], $this->generateAttachmentFields($record[$key])); } } } $dataArray['attachments'] = array($attachment); } else { $dataArray['text'] = $message; } if ($this->userIcon) { if (\filter_var($this->userIcon, \FILTER_VALIDATE_URL)) { $dataArray['icon_url'] = $this->userIcon; } else { $dataArray['icon_emoji'] = ":{$this->userIcon}:"; } } return $dataArray; } /** * Returns a Slack message attachment color associated with * provided level. */ public function getAttachmentColor(int $level) : string { switch (\true) { case $level >= Logger::ERROR: return static::COLOR_DANGER; case $level >= Logger::WARNING: return static::COLOR_WARNING; case $level >= Logger::INFO: return static::COLOR_GOOD; default: return static::COLOR_DEFAULT; } } /** * Stringifies an array of key/value pairs to be used in attachment fields * * @param mixed[] $fields */ public function stringify(array $fields) : string { /** @var Record $fields */ $normalized = $this->normalizerFormatter->format($fields); $hasSecondDimension = \count(\array_filter($normalized, 'is_array')); $hasNonNumericKeys = !\count(\array_filter(\array_keys($normalized), 'is_numeric')); return $hasSecondDimension || $hasNonNumericKeys ? Utils::jsonEncode($normalized, \JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS) : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); } /** * Channel used by the bot when posting * * @param ?string $channel * * @return static */ public function setChannel(?string $channel = null) : self { $this->channel = $channel; return $this; } /** * Username used by the bot when posting * * @param ?string $username * * @return static */ public function setUsername(?string $username = null) : self { $this->username = $username; return $this; } public function useAttachment(bool $useAttachment = \true) : self { $this->useAttachment = $useAttachment; return $this; } public function setUserIcon(?string $userIcon = null) : self { $this->userIcon = $userIcon; if (\is_string($userIcon)) { $this->userIcon = \trim($userIcon, ':'); } return $this; } public function useShortAttachment(bool $useShortAttachment = \false) : self { $this->useShortAttachment = $useShortAttachment; return $this; } public function includeContextAndExtra(bool $includeContextAndExtra = \false) : self { $this->includeContextAndExtra = $includeContextAndExtra; if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); } return $this; } /** * @param string[] $excludeFields */ public function excludeFields(array $excludeFields = []) : self { $this->excludeFields = $excludeFields; return $this; } public function setFormatter(?FormatterInterface $formatter = null) : self { $this->formatter = $formatter; return $this; } /** * Generates attachment field * * @param string|mixed[] $value * * @return array{title: string, value: string, short: false} */ private function generateAttachmentField(string $title, $value) : array { $value = \is_array($value) ? \sprintf('```%s```', \substr($this->stringify($value), 0, 1990)) : $value; return array('title' => \ucfirst($title), 'value' => $value, 'short' => \false); } /** * Generates a collection of attachment fields from array * * @param mixed[] $data * * @return array */ private function generateAttachmentFields(array $data) : array { /** @var Record $data */ $normalized = $this->normalizerFormatter->format($data); $fields = array(); foreach ($normalized as $key => $value) { $fields[] = $this->generateAttachmentField((string) $key, $value); } return $fields; } /** * Get a copy of record with fields excluded according to $this->excludeFields * * @phpstan-param FormattedRecord $record * * @return mixed[] */ private function removeExcludedFields(array $record) : array { foreach ($this->excludeFields as $field) { $keys = \explode('.', $field); $node =& $record; $lastKey = \end($keys); foreach ($keys as $key) { if (!isset($node[$key])) { break; } if ($lastKey === $key) { unset($node[$key]); break; } $node =& $node[$key]; } } return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Proxies log messages to an existing PSR-3 compliant logger. * * If a formatter is configured, the formatter's output MUST be a string and the * formatted message will be fed to the wrapped PSR logger instead of the original * log record's message. * * @author Michael Moussa */ class PsrHandler extends AbstractHandler implements FormattableHandlerInterface { /** * PSR-3 compliant logger * * @var LoggerInterface */ protected $logger; /** * @var FormatterInterface|null */ protected $formatter; /** * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied */ public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); $this->logger = $logger; } /** * {@inheritDoc} */ public function handle(array $record) : bool { if (!$this->isHandling($record)) { return \false; } if ($this->formatter) { $formatted = $this->formatter->format($record); $this->logger->log(\strtolower($record['level_name']), (string) $formatted, $record['context']); } else { $this->logger->log(\strtolower($record['level_name']), $record['message'], $record['context']); } return \false === $this->bubble; } /** * Sets the formatter. * * @param FormatterInterface $formatter */ public function setFormatter(FormatterInterface $formatter) : HandlerInterface { $this->formatter = $formatter; return $this; } /** * Gets the formatter. * * @return FormatterInterface */ public function getFormatter() : FormatterInterface { if (!$this->formatter) { throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); } return $this->formatter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Formatter\NormalizerFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Class to record a log on a NewRelic application. * Enabling New Relic High Security mode may prevent capture of useful information. * * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted'] * * @see https://docs.newrelic.com/docs/agents/php-agent * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security */ class NewRelicHandler extends AbstractProcessingHandler { /** * Name of the New Relic application that will receive logs from this handler. * * @var ?string */ protected $appName; /** * Name of the current transaction * * @var ?string */ protected $transactionName; /** * Some context and extra data is passed into the handler as arrays of values. Do we send them as is * (useful if we are using the API), or explode them for display on the NewRelic RPM website? * * @var bool */ protected $explodeArrays; /** * {@inheritDoc} * * @param string|null $appName * @param bool $explodeArrays * @param string|null $transactionName */ public function __construct($level = Logger::ERROR, bool $bubble = \true, ?string $appName = null, bool $explodeArrays = \false, ?string $transactionName = null) { parent::__construct($level, $bubble); $this->appName = $appName; $this->explodeArrays = $explodeArrays; $this->transactionName = $transactionName; } /** * {@inheritDoc} */ protected function write(array $record) : void { if (!$this->isNewRelicEnabled()) { throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); } if ($appName = $this->getAppName($record['context'])) { $this->setNewRelicAppName($appName); } if ($transactionName = $this->getTransactionName($record['context'])) { $this->setNewRelicTransactionName($transactionName); unset($record['formatted']['context']['transaction_name']); } if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { \newrelic_notice_error($record['message'], $record['context']['exception']); unset($record['formatted']['context']['exception']); } else { \newrelic_notice_error($record['message']); } if (isset($record['formatted']['context']) && \is_array($record['formatted']['context'])) { foreach ($record['formatted']['context'] as $key => $parameter) { if (\is_array($parameter) && $this->explodeArrays) { foreach ($parameter as $paramKey => $paramValue) { $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); } } else { $this->setNewRelicParameter('context_' . $key, $parameter); } } } if (isset($record['formatted']['extra']) && \is_array($record['formatted']['extra'])) { foreach ($record['formatted']['extra'] as $key => $parameter) { if (\is_array($parameter) && $this->explodeArrays) { foreach ($parameter as $paramKey => $paramValue) { $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); } } else { $this->setNewRelicParameter('extra_' . $key, $parameter); } } } } /** * Checks whether the NewRelic extension is enabled in the system. * * @return bool */ protected function isNewRelicEnabled() : bool { return \extension_loaded('newrelic'); } /** * Returns the appname where this log should be sent. Each log can override the default appname, set in this * handler's constructor, by providing the appname in it's context. * * @param mixed[] $context */ protected function getAppName(array $context) : ?string { if (isset($context['appname'])) { return $context['appname']; } return $this->appName; } /** * Returns the name of the current transaction. Each log can override the default transaction name, set in this * handler's constructor, by providing the transaction_name in it's context * * @param mixed[] $context */ protected function getTransactionName(array $context) : ?string { if (isset($context['transaction_name'])) { return $context['transaction_name']; } return $this->transactionName; } /** * Sets the NewRelic application that should receive this log. */ protected function setNewRelicAppName(string $appName) : void { \newrelic_set_appname($appName); } /** * Overwrites the name of the current transaction */ protected function setNewRelicTransactionName(string $transactionName) : void { \newrelic_name_transaction($transactionName); } /** * @param string $key * @param mixed $value */ protected function setNewRelicParameter(string $key, $value) : void { if (null === $value || \is_scalar($value)) { \newrelic_add_custom_parameter($key, $value); } else { \newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, \true)); } } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new NormalizerFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Monolog\Processor\ProcessorInterface; /** * Helper trait for implementing ProcessableInterface * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger */ trait ProcessableHandlerTrait { /** * @var callable[] * @phpstan-var array */ protected $processors = []; /** * {@inheritDoc} */ public function pushProcessor(callable $callback) : HandlerInterface { \array_unshift($this->processors, $callback); return $this; } /** * {@inheritDoc} */ public function popProcessor() : callable { if (!$this->processors) { throw new \LogicException('You tried to pop from an empty processor stack.'); } return \array_shift($this->processors); } /** * Processes a record. * * @phpstan-param Record $record * @phpstan-return Record */ protected function processRecord(array $record) : array { foreach ($this->processors as $processor) { $record = $processor($record); } return $record; } protected function resetProcessors() : void { foreach ($this->processors as $processor) { if ($processor instanceof ResettableInterface) { $processor->reset(); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; /** * Interface that all Monolog Handlers must implement * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger */ interface HandlerInterface { /** * Checks whether the given record will be handled by this handler. * * This is mostly done for performance reasons, to avoid calling processors for nothing. * * Handlers should still check the record levels within handle(), returning false in isHandling() * is no guarantee that handle() will not be called, and isHandling() might not be called * for a given record. * * @param array $record Partial log record containing only a level key * * @return bool * * @phpstan-param array{level: Level} $record */ public function isHandling(array $record) : bool; /** * Handles a record. * * All records may be passed to this method, and the handler should discard * those that it does not want to handle. * * The return value of this function controls the bubbling process of the handler stack. * Unless the bubbling is interrupted (by returning true), the Logger class will keep on * calling further handlers in the stack with a given log record. * * @param array $record The record to handle * @return bool true means that this handler handled the record, and that bubbling is not permitted. * false means the record was either not processed or that this handler allows bubbling. * * @phpstan-param Record $record */ public function handle(array $record) : bool; /** * Handles a set of records at once. * * @param array $records The records to handle (an array of record arrays) * * @phpstan-param Record[] $records */ public function handleBatch(array $records) : void; /** * Closes the handler. * * Ends a log cycle and frees all resources used by the handler. * * Closing a Handler means flushing all buffers and freeing any open resources/handles. * * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) * and ideally handlers should be able to reopen themselves on handle() after they have been closed. * * This is useful at the end of a request and will be called automatically when the object * is destroyed if you extend Monolog\Handler\Handler. * * If you are thinking of calling this method yourself, most likely you should be * calling ResettableInterface::reset instead. Have a look. */ public function close() : void; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; use _ContaoManager\Monolog\Handler\Slack\SlackRecord; /** * Sends notifications through Slack API * * @author Greg Kedzierski * @see https://api.slack.com/ * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SlackHandler extends SocketHandler { /** * Slack API token * @var string */ private $token; /** * Instance of the SlackRecord util class preparing data for Slack API. * @var SlackRecord */ private $slackRecord; /** * @param string $token Slack API token * @param string $channel Slack channel (encoded ID or name) * @param string|null $username Name of a bot * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) * @param string|null $iconEmoji The emoji name to use (or null) * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style * @param bool $includeContextAndExtra Whether the attachment should include context and extra data * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] * @throws MissingExtensionException If no OpenSSL PHP extension configured */ public function __construct(string $token, string $channel, ?string $username = null, bool $useAttachment = \true, ?string $iconEmoji = null, $level = Logger::CRITICAL, bool $bubble = \true, bool $useShortAttachment = \false, bool $includeContextAndExtra = \false, array $excludeFields = array(), bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { if (!\extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); } parent::__construct('ssl://slack.com:443', $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); $this->slackRecord = new SlackRecord($channel, $username, $useAttachment, $iconEmoji, $useShortAttachment, $includeContextAndExtra, $excludeFields); $this->token = $token; } public function getSlackRecord() : SlackRecord { return $this->slackRecord; } public function getToken() : string { return $this->token; } /** * {@inheritDoc} */ protected function generateDataStream(array $record) : string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } /** * Builds the body of API call * * @phpstan-param FormattedRecord $record */ private function buildContent(array $record) : string { $dataArray = $this->prepareContentData($record); return \http_build_query($dataArray); } /** * @phpstan-param FormattedRecord $record * @return string[] */ protected function prepareContentData(array $record) : array { $dataArray = $this->slackRecord->getSlackData($record); $dataArray['token'] = $this->token; if (!empty($dataArray['attachments'])) { $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); } return $dataArray; } /** * Builds the header of the API Call */ private function buildHeader(string $content) : string { $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; $header .= "Host: slack.com\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . \strlen($content) . "\r\n"; $header .= "\r\n"; return $header; } /** * {@inheritDoc} */ protected function write(array $record) : void { parent::write($record); $this->finalizeWrite(); } /** * Finalizes the request by reading some bytes and then closing the socket * * If we do not read some but close the socket too early, slack sometimes * drops the request entirely. */ protected function finalizeWrite() : void { $res = $this->getResource(); if (\is_resource($res)) { @\fread($res, 2048); } $this->closeSocket(); } public function setFormatter(FormatterInterface $formatter) : HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); return $this; } public function getFormatter() : FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); return $formatter; } /** * Channel used by the bot when posting */ public function setChannel(string $channel) : self { $this->slackRecord->setChannel($channel); return $this; } /** * Username used by the bot when posting */ public function setUsername(string $username) : self { $this->slackRecord->setUsername($username); return $this; } public function useAttachment(bool $useAttachment) : self { $this->slackRecord->useAttachment($useAttachment); return $this; } public function setIconEmoji(string $iconEmoji) : self { $this->slackRecord->setUserIcon($iconEmoji); return $this; } public function useShortAttachment(bool $useShortAttachment) : self { $this->slackRecord->useShortAttachment($useShortAttachment); return $this; } public function includeContextAndExtra(bool $includeContextAndExtra) : self { $this->slackRecord->includeContextAndExtra($includeContextAndExtra); return $this; } /** * @param string[] $excludeFields */ public function excludeFields(array $excludeFields) : self { $this->slackRecord->excludeFields($excludeFields); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Logger; /** * Sends logs to Fleep.io using Webhook integrations * * You'll need a Fleep.io account to use this handler. * * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation * @author Ando Roots * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FleepHookHandler extends SocketHandler { protected const FLEEP_HOST = 'fleep.io'; protected const FLEEP_HOOK_URI = '/hook/'; /** * @var string Webhook token (specifies the conversation where logs are sent) */ protected $token; /** * Construct a new Fleep.io Handler. * * For instructions on how to create a new web hook in your conversations * see https://fleep.io/integrations/webhooks/ * * @param string $token Webhook token * @throws MissingExtensionException */ public function __construct(string $token, $level = Logger::DEBUG, bool $bubble = \true, bool $persistent = \false, float $timeout = 0.0, float $writingTimeout = 10.0, ?float $connectionTimeout = null, ?int $chunkSize = null) { if (!\extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); } $this->token = $token; $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; parent::__construct($connectionString, $level, $bubble, $persistent, $timeout, $writingTimeout, $connectionTimeout, $chunkSize); } /** * Returns the default formatter to use with this handler * * Overloaded to remove empty context and extra arrays from the end of the log message. * * @return LineFormatter */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter(null, null, \true, \true); } /** * Handles a log record */ public function write(array $record) : void { parent::write($record); $this->closeSocket(); } /** * {@inheritDoc} */ protected function generateDataStream(array $record) : string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } /** * Builds the header of the API Call */ private function buildHeader(string $content) : string { $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; $header .= "Host: " . static::FLEEP_HOST . "\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . \strlen($content) . "\r\n"; $header .= "\r\n"; return $header; } /** * Builds the body of API call * * @phpstan-param FormattedRecord $record */ private function buildContent(array $record) : string { $dataArray = ['message' => $record['formatted']]; return \http_build_query($dataArray); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Blackhole * * Any record it can handle will be thrown away. This can be used * to put on top of an existing stack to override it temporarily. * * @author Jordi Boggiano * * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class NullHandler extends Handler { /** * @var int */ private $level; /** * @param string|int $level The minimum logging level at which this handler will be triggered * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** * {@inheritDoc} */ public function isHandling(array $record) : bool { return $record['level'] >= $this->level; } /** * {@inheritDoc} */ public function handle(array $record) : bool { return $record['level'] >= $this->level; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\JsonFormatter; use _ContaoManager\PhpAmqpLib\Message\AMQPMessage; use _ContaoManager\PhpAmqpLib\Channel\AMQPChannel; use AMQPExchange; /** * @phpstan-import-type Record from \Monolog\Logger */ class AmqpHandler extends AbstractProcessingHandler { /** * @var AMQPExchange|AMQPChannel $exchange */ protected $exchange; /** @var array */ private $extraAttributes = []; /** * @return array */ public function getExtraAttributes() : array { return $this->extraAttributes; } /** * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension) * * @param array $extraAttributes One of content_type, content_encoding, * message_id, user_id, app_id, delivery_mode, * priority, timestamp, expiration, type * or reply_to, headers. * @return AmqpHandler */ public function setExtraAttributes(array $extraAttributes) : self { $this->extraAttributes = $extraAttributes; return $this; } /** * @var string */ protected $exchangeName; /** * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only */ public function __construct($exchange, ?string $exchangeName = null, $level = Logger::DEBUG, bool $bubble = \true) { if ($exchange instanceof AMQPChannel) { $this->exchangeName = (string) $exchangeName; } elseif (!$exchange instanceof AMQPExchange) { throw new \InvalidArgumentException('PhpAmqpLib\\Channel\\AMQPChannel or AMQPExchange instance required'); } elseif ($exchangeName) { @\trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', \E_USER_DEPRECATED); } $this->exchange = $exchange; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { $data = $record["formatted"]; $routingKey = $this->getRoutingKey($record); if ($this->exchange instanceof AMQPExchange) { $attributes = ['delivery_mode' => 2, 'content_type' => 'application/json']; if ($this->extraAttributes) { $attributes = \array_merge($attributes, $this->extraAttributes); } $this->exchange->publish($data, $routingKey, 0, $attributes); } else { $this->exchange->basic_publish($this->createAmqpMessage($data), $this->exchangeName, $routingKey); } } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { if ($this->exchange instanceof AMQPExchange) { parent::handleBatch($records); return; } foreach ($records as $record) { if (!$this->isHandling($record)) { continue; } /** @var Record $record */ $record = $this->processRecord($record); $data = $this->getFormatter()->format($record); $this->exchange->batch_basic_publish($this->createAmqpMessage($data), $this->exchangeName, $this->getRoutingKey($record)); } $this->exchange->publish_batch(); } /** * Gets the routing key for the AMQP exchange * * @phpstan-param Record $record */ protected function getRoutingKey(array $record) : string { $routingKey = \sprintf('%s.%s', $record['level_name'], $record['channel']); return \strtolower($routingKey); } private function createAmqpMessage(string $data) : AMQPMessage { $attributes = ['delivery_mode' => 2, 'content_type' => 'application/json']; if ($this->extraAttributes) { $attributes = \array_merge($attributes, $this->extraAttributes); } return new AMQPMessage($data, $attributes); } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, \false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use RuntimeException; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Handler send logs to Telegram using Telegram Bot API. * * How to use: * 1) Create telegram bot with https://telegram.me/BotFather * 2) Create a telegram channel where logs will be recorded. * 3) Add created bot from step 1 to the created channel from step 2. * * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler * * @link https://core.telegram.org/bots/api * * @author Mazur Alexandr * * @phpstan-import-type Record from \Monolog\Logger */ class TelegramBotHandler extends AbstractProcessingHandler { private const BOT_API = 'https://api.telegram.org/bot'; /** * The available values of parseMode according to the Telegram api documentation */ private const AVAILABLE_PARSE_MODES = ['HTML', 'MarkdownV2', 'Markdown']; /** * The maximum number of characters allowed in a message according to the Telegram api documentation */ private const MAX_MESSAGE_LENGTH = 4096; /** * Telegram bot access token provided by BotFather. * Create telegram bot with https://telegram.me/BotFather and use access token from it. * @var string */ private $apiKey; /** * Telegram channel name. * Since to start with '@' symbol as prefix. * @var string */ private $channel; /** * The kind of formatting that is used for the message. * See available options at https://core.telegram.org/bots/api#formatting-options * or in AVAILABLE_PARSE_MODES * @var ?string */ private $parseMode; /** * Disables link previews for links in the message. * @var ?bool */ private $disableWebPagePreview; /** * Sends the message silently. Users will receive a notification with no sound. * @var ?bool */ private $disableNotification; /** * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. * False - truncates a message that is too long. * @var bool */ private $splitLongMessages; /** * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). * @var bool */ private $delayBetweenMessages; /** * @param string $apiKey Telegram bot access token provided by BotFather * @param string $channel Telegram channel name * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API * @throws MissingExtensionException */ public function __construct(string $apiKey, string $channel, $level = Logger::DEBUG, bool $bubble = \true, string $parseMode = null, bool $disableWebPagePreview = null, bool $disableNotification = null, bool $splitLongMessages = \false, bool $delayBetweenMessages = \false) { if (!\extension_loaded('curl')) { throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); } parent::__construct($level, $bubble); $this->apiKey = $apiKey; $this->channel = $channel; $this->setParseMode($parseMode); $this->disableWebPagePreview($disableWebPagePreview); $this->disableNotification($disableNotification); $this->splitLongMessages($splitLongMessages); $this->delayBetweenMessages($delayBetweenMessages); } public function setParseMode(string $parseMode = null) : self { if ($parseMode !== null && !\in_array($parseMode, self::AVAILABLE_PARSE_MODES)) { throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . \implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); } $this->parseMode = $parseMode; return $this; } public function disableWebPagePreview(bool $disableWebPagePreview = null) : self { $this->disableWebPagePreview = $disableWebPagePreview; return $this; } public function disableNotification(bool $disableNotification = null) : self { $this->disableNotification = $disableNotification; return $this; } /** * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. * False - truncates a message that is too long. * @param bool $splitLongMessages * @return $this */ public function splitLongMessages(bool $splitLongMessages = \false) : self { $this->splitLongMessages = $splitLongMessages; return $this; } /** * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). * @param bool $delayBetweenMessages * @return $this */ public function delayBetweenMessages(bool $delayBetweenMessages = \false) : self { $this->delayBetweenMessages = $delayBetweenMessages; return $this; } /** * {@inheritDoc} */ public function handleBatch(array $records) : void { /** @var Record[] $messages */ $messages = []; foreach ($records as $record) { if (!$this->isHandling($record)) { continue; } if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } $messages[] = $record; } if (!empty($messages)) { $this->send((string) $this->getFormatter()->formatBatch($messages)); } } /** * @inheritDoc */ protected function write(array $record) : void { $this->send($record['formatted']); } /** * Send request to @link https://api.telegram.org/bot on SendMessage action. * @param string $message */ protected function send(string $message) : void { $messages = $this->handleMessageLength($message); foreach ($messages as $key => $msg) { if ($this->delayBetweenMessages && $key > 0) { \sleep(1); } $this->sendCurl($msg); } } protected function sendCurl(string $message) : void { $ch = \curl_init(); $url = self::BOT_API . $this->apiKey . '/SendMessage'; \curl_setopt($ch, \CURLOPT_URL, $url); \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, \true); \curl_setopt($ch, \CURLOPT_SSL_VERIFYPEER, \true); \curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query(['text' => $message, 'chat_id' => $this->channel, 'parse_mode' => $this->parseMode, 'disable_web_page_preview' => $this->disableWebPagePreview, 'disable_notification' => $this->disableNotification])); $result = Curl\Util::execute($ch); if (!\is_string($result)) { throw new RuntimeException('Telegram API error. Description: No response'); } $result = \json_decode($result, \true); if ($result['ok'] === \false) { throw new RuntimeException('Telegram API error. Description: ' . $result['description']); } } /** * Handle a message that is too long: truncates or splits into several * @param string $message * @return string[] */ private function handleMessageLength(string $message) : array { $truncatedMarker = ' (...truncated)'; if (!$this->splitLongMessages && \strlen($message) > self::MAX_MESSAGE_LENGTH) { return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - \strlen($truncatedMarker)) . $truncatedMarker]; } return \str_split($message, self::MAX_MESSAGE_LENGTH); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Swift; use _ContaoManager\Swift_Message; /** * MandrillHandler uses cURL to send the emails to the Mandrill API * * @author Adam Nicholson */ class MandrillHandler extends MailHandler { /** @var Swift_Message */ protected $message; /** @var string */ protected $apiKey; /** * @psalm-param Swift_Message|callable(): Swift_Message $message * * @param string $apiKey A valid Mandrill API key * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced */ public function __construct(string $apiKey, $message, $level = Logger::ERROR, bool $bubble = \true) { parent::__construct($level, $bubble); if (!$message instanceof Swift_Message && \is_callable($message)) { $message = $message(); } if (!$message instanceof Swift_Message) { throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); } $this->message = $message; $this->apiKey = $apiKey; } /** * {@inheritDoc} */ protected function send(string $content, array $records) : void { $mime = 'text/plain'; if ($this->isHtmlBody($content)) { $mime = 'text/html'; } $message = clone $this->message; $message->setBody($content, $mime); /** @phpstan-ignore-next-line */ if (\version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { /** @phpstan-ignore-next-line */ $message->setDate(\time()); } $ch = \curl_init(); \curl_setopt($ch, \CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); \curl_setopt($ch, \CURLOPT_POST, 1); \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1); \curl_setopt($ch, \CURLOPT_POSTFIELDS, \http_build_query(['key' => $this->apiKey, 'raw_message' => (string) $message, 'async' => \false])); Curl\Util::execute($ch); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; /** * Base Handler class providing the Handler structure, including processors and formatters * * Classes extending it should (in most cases) only implement write($record) * * @author Jordi Boggiano * @author Christophe Coevoet * * @phpstan-import-type LevelName from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type Record from \Monolog\Logger * @phpstan-type FormattedRecord array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[], formatted: mixed} */ abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { use ProcessableHandlerTrait; use FormattableHandlerTrait; /** * {@inheritDoc} */ public function handle(array $record) : bool { if (!$this->isHandling($record)) { return \false; } if ($this->processors) { /** @var Record $record */ $record = $this->processRecord($record); } $record['formatted'] = $this->getFormatter()->format($record); $this->write($record); return \false === $this->bubble; } /** * Writes the record down to the log of the implementing handler * * @phpstan-param FormattedRecord $record */ protected abstract function write(array $record) : void; /** * @return void */ public function reset() { parent::reset(); $this->resetProcessors(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\JsonFormatter; use _ContaoManager\Monolog\Logger; /** * CouchDB handler * * @author Markus Bachmann */ class CouchDBHandler extends AbstractProcessingHandler { /** @var mixed[] */ private $options; /** * @param mixed[] $options */ public function __construct(array $options = [], $level = Logger::DEBUG, bool $bubble = \true) { $this->options = \array_merge(['host' => 'localhost', 'port' => 5984, 'dbname' => 'logger', 'username' => null, 'password' => null], $options); parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { $basicAuth = null; if ($this->options['username']) { $basicAuth = \sprintf('%s:%s@', $this->options['username'], $this->options['password']); } $url = 'http://' . $basicAuth . $this->options['host'] . ':' . $this->options['port'] . '/' . $this->options['dbname']; $context = \stream_context_create(['http' => ['method' => 'POST', 'content' => $record['formatted'], 'ignore_errors' => \true, 'max_redirects' => 0, 'header' => 'Content-type: application/json']]); if (\false === @\file_get_contents($url, \false, $context)) { throw new \RuntimeException(\sprintf('Could not connect to %s', $url)); } } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, \false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; /** * Logs to a Redis key using rpush * * usage example: * * $log = new Logger('application'); * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); * $log->pushHandler($redis); * * @author Thomas Tourlourat * * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class RedisHandler extends AbstractProcessingHandler { /** @var \Predis\Client<\Predis\Client>|\Redis */ private $redisClient; /** @var string */ private $redisKey; /** @var int */ protected $capSize; /** * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance * @param string $key The key name to push records to * @param int $capSize Number of entries to limit list size to, 0 = unlimited */ public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = \true, int $capSize = 0) { if (!($redis instanceof \_ContaoManager\Predis\Client || $redis instanceof \Redis)) { throw new \InvalidArgumentException('Predis\\Client or Redis instance required'); } $this->redisClient = $redis; $this->redisKey = $key; $this->capSize = $capSize; parent::__construct($level, $bubble); } /** * {@inheritDoc} */ protected function write(array $record) : void { if ($this->capSize) { $this->writeCapped($record); } else { $this->redisClient->rpush($this->redisKey, $record["formatted"]); } } /** * Write and cap the collection * Writes the record to the redis list and caps its * * @phpstan-param FormattedRecord $record */ protected function writeCapped(array $record) : void { if ($this->redisClient instanceof \Redis) { $mode = \defined('\\Redis::MULTI') ? \Redis::MULTI : 1; $this->redisClient->multi($mode)->rpush($this->redisKey, $record["formatted"])->ltrim($this->redisKey, -$this->capSize, -1)->exec(); } else { $redisKey = $this->redisKey; $capSize = $this->capSize; $this->redisClient->transaction(function ($tx) use($record, $redisKey, $capSize) { $tx->rpush($redisKey, $record["formatted"]); $tx->ltrim($redisKey, -$capSize, -1); }); } } /** * {@inheritDoc} */ protected function getDefaultFormatter() : FormatterInterface { return new LineFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Logs to Cube. * * @link https://github.com/square/cube/wiki * @author Wan Chen * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 */ class CubeHandler extends AbstractProcessingHandler { /** @var resource|\Socket|null */ private $udpConnection = null; /** @var resource|\CurlHandle|null */ private $httpConnection = null; /** @var string */ private $scheme; /** @var string */ private $host; /** @var int */ private $port; /** @var string[] */ private $acceptedSchemes = ['http', 'udp']; /** * Create a Cube handler * * @throws \UnexpectedValueException when given url is not a valid url. * A valid url must consist of three parts : protocol://host:port * Only valid protocols used by Cube are http and udp */ public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = \true) { $urlInfo = \parse_url($url); if ($urlInfo === \false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { throw new \UnexpectedValueException('URL "' . $url . '" is not valid'); } if (!\in_array($urlInfo['scheme'], $this->acceptedSchemes)) { throw new \UnexpectedValueException('Invalid protocol (' . $urlInfo['scheme'] . ').' . ' Valid options are ' . \implode(', ', $this->acceptedSchemes)); } $this->scheme = $urlInfo['scheme']; $this->host = $urlInfo['host']; $this->port = (int) $urlInfo['port']; parent::__construct($level, $bubble); } /** * Establish a connection to an UDP socket * * @throws \LogicException when unable to connect to the socket * @throws MissingExtensionException when there is no socket extension */ protected function connectUdp() : void { if (!\extension_loaded('sockets')) { throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); } $udpConnection = \socket_create(\AF_INET, \SOCK_DGRAM, 0); if (\false === $udpConnection) { throw new \LogicException('Unable to create a socket'); } $this->udpConnection = $udpConnection; if (!\socket_connect($this->udpConnection, $this->host, $this->port)) { throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); } } /** * Establish a connection to an http server * * @throws \LogicException when unable to connect to the socket * @throws MissingExtensionException when no curl extension */ protected function connectHttp() : void { if (!\extension_loaded('curl')) { throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); } $httpConnection = \curl_init('http://' . $this->host . ':' . $this->port . '/1.0/event/put'); if (\false === $httpConnection) { throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); } $this->httpConnection = $httpConnection; \curl_setopt($this->httpConnection, \CURLOPT_CUSTOMREQUEST, "POST"); \curl_setopt($this->httpConnection, \CURLOPT_RETURNTRANSFER, \true); } /** * {@inheritDoc} */ protected function write(array $record) : void { $date = $record['datetime']; $data = ['time' => $date->format('Y-m-d\\TH:i:s.uO')]; unset($record['datetime']); if (isset($record['context']['type'])) { $data['type'] = $record['context']['type']; unset($record['context']['type']); } else { $data['type'] = $record['channel']; } $data['data'] = $record['context']; $data['data']['level'] = $record['level']; if ($this->scheme === 'http') { $this->writeHttp(Utils::jsonEncode($data)); } else { $this->writeUdp(Utils::jsonEncode($data)); } } private function writeUdp(string $data) : void { if (!$this->udpConnection) { $this->connectUdp(); } \socket_send($this->udpConnection, $data, \strlen($data), 0); } private function writeHttp(string $data) : void { if (!$this->httpConnection) { $this->connectHttp(); } if (null === $this->httpConnection) { throw new \LogicException('No connection could be established'); } \curl_setopt($this->httpConnection, \CURLOPT_POSTFIELDS, '[' . $data . ']'); \curl_setopt($this->httpConnection, \CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Content-Length: ' . \strlen('[' . $data . ']')]); Curl\Util::execute($this->httpConnection, 5, \false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Handler\SyslogUdp; use _ContaoManager\Monolog\Utils; use Socket; class UdpSocket { protected const DATAGRAM_MAX_LENGTH = 65023; /** @var string */ protected $ip; /** @var int */ protected $port; /** @var resource|Socket|null */ protected $socket = null; public function __construct(string $ip, int $port = 514) { $this->ip = $ip; $this->port = $port; } /** * @param string $line * @param string $header * @return void */ public function write($line, $header = "") { $this->send($this->assembleMessage($line, $header)); } public function close() : void { if (\is_resource($this->socket) || $this->socket instanceof Socket) { \socket_close($this->socket); $this->socket = null; } } /** * @return resource|Socket */ protected function getSocket() { if (null !== $this->socket) { return $this->socket; } $domain = \AF_INET; $protocol = \SOL_UDP; // Check if we are using unix sockets. if ($this->port === 0) { $domain = \AF_UNIX; $protocol = \IPPROTO_IP; } $this->socket = \socket_create($domain, \SOCK_DGRAM, $protocol) ?: null; if (null === $this->socket) { throw new \RuntimeException('The UdpSocket to ' . $this->ip . ':' . $this->port . ' could not be opened via socket_create'); } return $this->socket; } protected function send(string $chunk) : void { \socket_sendto($this->getSocket(), $chunk, \strlen($chunk), $flags = 0, $this->ip, $this->port); } protected function assembleMessage(string $line, string $header) : string { $chunkSize = static::DATAGRAM_MAX_LENGTH - \strlen($header); return $header . Utils::substr($line, 0, $chunkSize); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; use DateTimeZone; use _ContaoManager\Monolog\Handler\HandlerInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Psr\Log\InvalidArgumentException; use _ContaoManager\Psr\Log\LogLevel; use Throwable; use Stringable; /** * Monolog log channel * * It contains a stack of Handlers and a stack of Processors, * and uses them to store records that are added to it. * * @author Jordi Boggiano * * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY' * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} */ class Logger implements LoggerInterface, ResettableInterface { /** * Detailed debug information */ public const DEBUG = 100; /** * Interesting events * * Examples: User logs in, SQL logs. */ public const INFO = 200; /** * Uncommon events */ public const NOTICE = 250; /** * Exceptional occurrences that are not errors * * Examples: Use of deprecated APIs, poor use of an API, * undesirable things that are not necessarily wrong. */ public const WARNING = 300; /** * Runtime errors */ public const ERROR = 400; /** * Critical conditions * * Example: Application component unavailable, unexpected exception. */ public const CRITICAL = 500; /** * Action must be taken immediately * * Example: Entire website down, database unavailable, etc. * This should trigger the SMS alerts and wake you up. */ public const ALERT = 550; /** * Urgent alert. */ public const EMERGENCY = 600; /** * Monolog API version * * This is only bumped when API breaks are done and should * follow the major version of the library * * @var int */ public const API = 2; /** * This is a static variable and not a constant to serve as an extension point for custom levels * * @var array $levels Logging levels with the levels as key * * @phpstan-var array $levels Logging levels with the levels as key */ protected static $levels = [self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', self::WARNING => 'WARNING', self::ERROR => 'ERROR', self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY']; /** * Mapping between levels numbers defined in RFC 5424 and Monolog ones * * @phpstan-var array $rfc_5424_levels */ private const RFC_5424_LEVELS = [7 => self::DEBUG, 6 => self::INFO, 5 => self::NOTICE, 4 => self::WARNING, 3 => self::ERROR, 2 => self::CRITICAL, 1 => self::ALERT, 0 => self::EMERGENCY]; /** * @var string */ protected $name; /** * The handler stack * * @var HandlerInterface[] */ protected $handlers; /** * Processors that will process all log records * * To process records of a single handler instead, add the processor on that specific handler * * @var callable[] */ protected $processors; /** * @var bool */ protected $microsecondTimestamps = \true; /** * @var DateTimeZone */ protected $timezone; /** * @var callable|null */ protected $exceptionHandler; /** * @var int Keeps track of depth to prevent infinite logging loops */ private $logDepth = 0; /** * @var \WeakMap<\Fiber, int>|null Keeps track of depth inside fibers to prevent infinite logging loops */ private $fiberLogDepth; /** * @var bool Whether to detect infinite logging loops * * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this */ private $detectCycles = \true; /** * @psalm-param array $processors * * @param string $name The logging channel, a simple descriptive name that is attached to all log records * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. * @param callable[] $processors Optional array of processors * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used */ public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null) { $this->name = $name; $this->setHandlers($handlers); $this->processors = $processors; $this->timezone = $timezone ?: new DateTimeZone(\date_default_timezone_get() ?: 'UTC'); if (\PHP_VERSION_ID >= 80100) { // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412 /** @var \WeakMap<\Fiber, int> $fiberLogDepth */ $fiberLogDepth = new \WeakMap(); $this->fiberLogDepth = $fiberLogDepth; } } public function getName() : string { return $this->name; } /** * Return a new cloned instance with the name changed */ public function withName(string $name) : self { $new = clone $this; $new->name = $name; return $new; } /** * Pushes a handler on to the stack. */ public function pushHandler(HandlerInterface $handler) : self { \array_unshift($this->handlers, $handler); return $this; } /** * Pops a handler from the stack * * @throws \LogicException If empty handler stack */ public function popHandler() : HandlerInterface { if (!$this->handlers) { throw new \LogicException('You tried to pop from an empty handler stack.'); } return \array_shift($this->handlers); } /** * Set handlers, replacing all existing ones. * * If a map is passed, keys will be ignored. * * @param HandlerInterface[] $handlers */ public function setHandlers(array $handlers) : self { $this->handlers = []; foreach (\array_reverse($handlers) as $handler) { $this->pushHandler($handler); } return $this; } /** * @return HandlerInterface[] */ public function getHandlers() : array { return $this->handlers; } /** * Adds a processor on to the stack. */ public function pushProcessor(callable $callback) : self { \array_unshift($this->processors, $callback); return $this; } /** * Removes the processor on top of the stack and returns it. * * @throws \LogicException If empty processor stack * @return callable */ public function popProcessor() : callable { if (!$this->processors) { throw new \LogicException('You tried to pop from an empty processor stack.'); } return \array_shift($this->processors); } /** * @return callable[] */ public function getProcessors() : array { return $this->processors; } /** * Control the use of microsecond resolution timestamps in the 'datetime' * member of new records. * * As of PHP7.1 microseconds are always included by the engine, so * there is no performance penalty and Monolog 2 enabled microseconds * by default. This function lets you disable them though in case you want * to suppress microseconds from the output. * * @param bool $micro True to use microtime() to create timestamps */ public function useMicrosecondTimestamps(bool $micro) : self { $this->microsecondTimestamps = $micro; return $this; } public function useLoggingLoopDetection(bool $detectCycles) : self { $this->detectCycles = $detectCycles; return $this; } /** * Adds a log record. * * @param int $level The logging level (a Monolog or RFC 5424 level) * @param string $message The log message * @param mixed[] $context The log context * @param DateTimeImmutable $datetime Optional log date to log into the past or future * @return bool Whether the record has been processed * * @phpstan-param Level $level */ public function addRecord(int $level, string $message, array $context = [], DateTimeImmutable $datetime = null) : bool { if (isset(self::RFC_5424_LEVELS[$level])) { $level = self::RFC_5424_LEVELS[$level]; } if ($this->detectCycles) { if (\PHP_VERSION_ID >= 80100 && ($fiber = \Fiber::getCurrent())) { $this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0; $logDepth = ++$this->fiberLogDepth[$fiber]; } else { $logDepth = ++$this->logDepth; } } else { $logDepth = 0; } if ($logDepth === 3) { $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); return \false; } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above return \false; } try { $record = null; foreach ($this->handlers as $handler) { if (null === $record) { // skip creating the record as long as no handler is going to handle it if (!$handler->isHandling(['level' => $level])) { continue; } $levelName = static::getLevelName($level); $record = ['message' => $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, 'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), 'extra' => []]; try { foreach ($this->processors as $processor) { $record = $processor($record); } } catch (Throwable $e) { $this->handleException($e, $record); return \true; } } // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted try { if (\true === $handler->handle($record)) { break; } } catch (Throwable $e) { $this->handleException($e, $record); return \true; } } } finally { if ($this->detectCycles) { if (isset($fiber)) { $this->fiberLogDepth[$fiber]--; } else { $this->logDepth--; } } } return null !== $record; } /** * Ends a log cycle and frees all resources used by handlers. * * Closing a Handler means flushing all buffers and freeing any open resources/handles. * Handlers that have been closed should be able to accept log records again and re-open * themselves on demand, but this may not always be possible depending on implementation. * * This is useful at the end of a request and will be called automatically on every handler * when they get destructed. */ public function close() : void { foreach ($this->handlers as $handler) { $handler->close(); } } /** * Ends a log cycle and resets all handlers and processors to their initial state. * * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal * state, and getting it back to a state in which it can receive log records again. * * This is useful in case you want to avoid logs leaking between two requests or jobs when you * have a long running process like a worker or an application server serving multiple requests * in one process. */ public function reset() : void { foreach ($this->handlers as $handler) { if ($handler instanceof ResettableInterface) { $handler->reset(); } } foreach ($this->processors as $processor) { if ($processor instanceof ResettableInterface) { $processor->reset(); } } } /** * Gets all supported logging levels. * * @return array Assoc array with human-readable level names => level codes. * @phpstan-return array */ public static function getLevels() : array { return \array_flip(static::$levels); } /** * Gets the name of the logging level. * * @throws \Psr\Log\InvalidArgumentException If level is not defined * * @phpstan-param Level $level * @phpstan-return LevelName */ public static function getLevelName(int $level) : string { if (!isset(static::$levels[$level])) { throw new InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . \implode(', ', \array_keys(static::$levels))); } return static::$levels[$level]; } /** * Converts PSR-3 levels to Monolog ones if necessary * * @param string|int $level Level number (monolog) or name (PSR-3) * @throws \Psr\Log\InvalidArgumentException If level is not defined * * @phpstan-param Level|LevelName|LogLevel::* $level * @phpstan-return Level */ public static function toMonologLevel($level) : int { if (\is_string($level)) { if (\is_numeric($level)) { /** @phpstan-ignore-next-line */ return \intval($level); } // Contains chars of all log levels and avoids using strtoupper() which may have // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) $upper = \strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); if (\defined(__CLASS__ . '::' . $upper)) { return \constant(__CLASS__ . '::' . $upper); } throw new InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . \implode(', ', \array_keys(static::$levels) + static::$levels)); } if (!\is_int($level)) { throw new InvalidArgumentException('Level "' . \var_export($level, \true) . '" is not defined, use one of: ' . \implode(', ', \array_keys(static::$levels) + static::$levels)); } return $level; } /** * Checks whether the Logger has a handler that listens on the given level * * @phpstan-param Level $level */ public function isHandling(int $level) : bool { $record = ['level' => $level]; foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { return \true; } } return \false; } /** * Set a custom exception handler that will be called if adding a new record fails * * The callable will receive an exception object and the record that failed to be logged */ public function setExceptionHandler(?callable $callback) : self { $this->exceptionHandler = $callback; return $this; } public function getExceptionHandler() : ?callable { return $this->exceptionHandler; } /** * Adds a log record at an arbitrary level. * * This method allows for compatibility with common interfaces. * * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) * @param string|Stringable $message The log message * @param mixed[] $context The log context * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function log($level, $message, array $context = []) : void { if (!\is_int($level) && !\is_string($level)) { throw new \InvalidArgumentException('$level is expected to be a string or int'); } if (isset(self::RFC_5424_LEVELS[$level])) { $level = self::RFC_5424_LEVELS[$level]; } $level = static::toMonologLevel($level); $this->addRecord($level, (string) $message, $context); } /** * Adds a log record at the DEBUG level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function debug($message, array $context = []) : void { $this->addRecord(static::DEBUG, (string) $message, $context); } /** * Adds a log record at the INFO level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function info($message, array $context = []) : void { $this->addRecord(static::INFO, (string) $message, $context); } /** * Adds a log record at the NOTICE level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function notice($message, array $context = []) : void { $this->addRecord(static::NOTICE, (string) $message, $context); } /** * Adds a log record at the WARNING level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function warning($message, array $context = []) : void { $this->addRecord(static::WARNING, (string) $message, $context); } /** * Adds a log record at the ERROR level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function error($message, array $context = []) : void { $this->addRecord(static::ERROR, (string) $message, $context); } /** * Adds a log record at the CRITICAL level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function critical($message, array $context = []) : void { $this->addRecord(static::CRITICAL, (string) $message, $context); } /** * Adds a log record at the ALERT level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function alert($message, array $context = []) : void { $this->addRecord(static::ALERT, (string) $message, $context); } /** * Adds a log record at the EMERGENCY level. * * This method allows for compatibility with common interfaces. * * @param string|Stringable $message The log message * @param mixed[] $context The log context */ public function emergency($message, array $context = []) : void { $this->addRecord(static::EMERGENCY, (string) $message, $context); } /** * Sets the timezone to be used for the timestamp of log records. */ public function setTimezone(DateTimeZone $tz) : self { $this->timezone = $tz; return $this; } /** * Returns the timezone to be used for the timestamp of log records. */ public function getTimezone() : DateTimeZone { return $this->timezone; } /** * Delegates exception management to the custom exception handler, * or throws the exception if no custom handler is set. * * @param array $record * @phpstan-param Record $record */ protected function handleException(Throwable $e, array $record) : void { if (!$this->exceptionHandler) { throw $e; } ($this->exceptionHandler)($e, $record); } /** * @return array */ public function __serialize() : array { return ['name' => $this->name, 'handlers' => $this->handlers, 'processors' => $this->processors, 'microsecondTimestamps' => $this->microsecondTimestamps, 'timezone' => $this->timezone, 'exceptionHandler' => $this->exceptionHandler, 'logDepth' => $this->logDepth, 'detectCycles' => $this->detectCycles]; } /** * @param array $data */ public function __unserialize(array $data) : void { foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) { if (isset($data[$property])) { $this->{$property} = $data[$property]; } } if (\PHP_VERSION_ID >= 80100) { // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412 /** @var \WeakMap<\Fiber, int> $fiberLogDepth */ $fiberLogDepth = new \WeakMap(); $this->fiberLogDepth = $fiberLogDepth; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Attribute; /** * A reusable attribute to help configure a class or a method as a processor. * * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer. * * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if * needed and manually pushed to the loggers and to the processable handlers. */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AsMonologProcessor { /** @var string|null */ public $channel = null; /** @var string|null */ public $handler = null; /** @var string|null */ public $method = null; /** * @param string|null $channel The logging channel the processor should be pushed to. * @param string|null $handler The handler the processor should be pushed to. * @param string|null $method The method that processes the records (if the attribute is used at the class level). */ public function __construct(?string $channel = null, ?string $handler = null, ?string $method = null) { $this->channel = $channel; $this->handler = $handler; $this->method = $method; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; use DateTimeZone; /** * Overrides default json encoding of date time objects * * @author Menno Holtkamp * @author Jordi Boggiano */ class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable { /** * @var bool */ private $useMicroseconds; public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) { $this->useMicroseconds = $useMicroseconds; parent::__construct('now', $timezone); } public function jsonSerialize() : string { if ($this->useMicroseconds) { return $this->format('Y-m-d\\TH:i:s.uP'); } return $this->format('Y-m-d\\TH:i:sP'); } public function __toString() : string { return $this->jsonSerialize(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Test; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\DateTimeImmutable; use _ContaoManager\Monolog\Formatter\FormatterInterface; /** * Lets you easily generate log records and a dummy formatter for testing purposes * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger * * @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677 */ class TestCase extends \_ContaoManager\PHPUnit\Framework\TestCase { public function tearDown() : void { parent::tearDown(); if (isset($this->handler)) { unset($this->handler); } } /** * @param mixed[] $context * * @return array Record * * @phpstan-param Level $level * @phpstan-return Record */ protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []) : array { return ['message' => (string) $message, 'context' => $context, 'level' => $level, 'level_name' => Logger::getLevelName($level), 'channel' => 'test', 'datetime' => new DateTimeImmutable(\true), 'extra' => []]; } /** * @phpstan-return Record[] */ protected function getMultipleRecords() : array { return [$this->getRecord(Logger::DEBUG, 'debug message 1'), $this->getRecord(Logger::DEBUG, 'debug message 2'), $this->getRecord(Logger::INFO, 'information'), $this->getRecord(Logger::WARNING, 'warning'), $this->getRecord(Logger::ERROR, 'error')]; } protected function getIdentityFormatter() : FormatterInterface { $formatter = $this->createMock(FormatterInterface::class); $formatter->expects($this->any())->method('format')->will($this->returnCallback(function ($record) { return $record['message']; })); return $formatter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; use InvalidArgumentException; /** * Monolog log registry * * Allows to get `Logger` instances in the global scope * via static method calls on this class. * * * $application = new Monolog\Logger('application'); * $api = new Monolog\Logger('api'); * * Monolog\Registry::addLogger($application); * Monolog\Registry::addLogger($api); * * function testLogger() * { * Monolog\Registry::api()->error('Sent to $api Logger instance'); * Monolog\Registry::application()->error('Sent to $application Logger instance'); * } * * * @author Tomas Tatarko */ class Registry { /** * List of all loggers in the registry (by named indexes) * * @var Logger[] */ private static $loggers = []; /** * Adds new logging channel to the registry * * @param Logger $logger Instance of the logging channel * @param string|null $name Name of the logging channel ($logger->getName() by default) * @param bool $overwrite Overwrite instance in the registry if the given name already exists? * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists * @return void */ public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = \false) { $name = $name ?: $logger->getName(); if (isset(self::$loggers[$name]) && !$overwrite) { throw new InvalidArgumentException('Logger with the given name already exists'); } self::$loggers[$name] = $logger; } /** * Checks if such logging channel exists by name or instance * * @param string|Logger $logger Name or logger instance */ public static function hasLogger($logger) : bool { if ($logger instanceof Logger) { $index = \array_search($logger, self::$loggers, \true); return \false !== $index; } return isset(self::$loggers[$logger]); } /** * Removes instance from registry by name or instance * * @param string|Logger $logger Name or logger instance */ public static function removeLogger($logger) : void { if ($logger instanceof Logger) { if (\false !== ($idx = \array_search($logger, self::$loggers, \true))) { unset(self::$loggers[$idx]); } } else { unset(self::$loggers[$logger]); } } /** * Clears the registry */ public static function clear() : void { self::$loggers = []; } /** * Gets Logger instance from the registry * * @param string $name Name of the requested Logger instance * @throws \InvalidArgumentException If named Logger instance is not in the registry */ public static function getInstance($name) : Logger { if (!isset(self::$loggers[$name])) { throw new InvalidArgumentException(\sprintf('Requested "%s" logger instance is not in the registry', $name)); } return self::$loggers[$name]; } /** * Gets Logger instance from the registry via static method call * * @param string $name Name of the requested Logger instance * @param mixed[] $arguments Arguments passed to static method call * @throws \InvalidArgumentException If named Logger instance is not in the registry * @return Logger Requested instance of Logger */ public static function __callStatic($name, $arguments) { return self::getInstance($name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Injects value of gethostname in all records */ class HostnameProcessor implements ProcessorInterface { /** @var string */ private static $host; public function __construct() { self::$host = (string) \gethostname(); } /** * {@inheritDoc} */ public function __invoke(array $record) : array { $record['extra']['hostname'] = self::$host; return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; use _ContaoManager\Monolog\Utils; /** * Processes a record's message according to PSR-3 rules * * It replaces {foo} with the value from $context['foo'] * * @author Jordi Boggiano */ class PsrLogMessageProcessor implements ProcessorInterface { public const SIMPLE_DATE = "Y-m-d\\TH:i:s.uP"; /** @var string|null */ private $dateFormat; /** @var bool */ private $removeUsedContextFields; /** * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset */ public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = \false) { $this->dateFormat = $dateFormat; $this->removeUsedContextFields = $removeUsedContextFields; } /** * {@inheritDoc} */ public function __invoke(array $record) : array { if (\false === \strpos($record['message'], '{')) { return $record; } $replacements = []; foreach ($record['context'] as $key => $val) { $placeholder = '{' . $key . '}'; if (\strpos($record['message'], $placeholder) === \false) { continue; } if (\is_null($val) || \is_scalar($val) || \is_object($val) && \method_exists($val, "__toString")) { $replacements[$placeholder] = $val; } elseif ($val instanceof \DateTimeInterface) { if (!$this->dateFormat && $val instanceof \_ContaoManager\Monolog\DateTimeImmutable) { // handle monolog dates using __toString if no specific dateFormat was asked for // so that it follows the useMicroseconds flag $replacements[$placeholder] = (string) $val; } else { $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); } } elseif ($val instanceof \UnitEnum) { $replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name; } elseif (\is_object($val)) { $replacements[$placeholder] = '[object ' . Utils::getClass($val) . ']'; } elseif (\is_array($val)) { $replacements[$placeholder] = 'array' . Utils::jsonEncode($val, null, \true); } else { $replacements[$placeholder] = '[' . \gettype($val) . ']'; } if ($this->removeUsedContextFields) { unset($record['context'][$key]); } } $record['message'] = \strtr($record['message'], $replacements); return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Injects url/method and remote IP of the current web request in all records * * @author Jordi Boggiano */ class WebProcessor implements ProcessorInterface { /** * @var array|\ArrayAccess */ protected $serverData; /** * Default fields * * Array is structured as [key in record.extra => key in $serverData] * * @var array */ protected $extraFields = ['url' => 'REQUEST_URI', 'ip' => 'REMOTE_ADDR', 'http_method' => 'REQUEST_METHOD', 'server' => 'SERVER_NAME', 'referrer' => 'HTTP_REFERER', 'user_agent' => 'HTTP_USER_AGENT']; /** * @param array|\ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data */ public function __construct($serverData = null, array $extraFields = null) { if (null === $serverData) { $this->serverData =& $_SERVER; } elseif (\is_array($serverData) || $serverData instanceof \ArrayAccess) { $this->serverData = $serverData; } else { throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); } $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; if (isset($this->serverData['UNIQUE_ID'])) { $this->extraFields['unique_id'] = 'UNIQUE_ID'; $defaultEnabled[] = 'unique_id'; } if (null === $extraFields) { $extraFields = $defaultEnabled; } if (isset($extraFields[0])) { foreach (\array_keys($this->extraFields) as $fieldName) { if (!\in_array($fieldName, $extraFields)) { unset($this->extraFields[$fieldName]); } } } else { $this->extraFields = $extraFields; } } /** * {@inheritDoc} */ public function __invoke(array $record) : array { // skip processing if for some reason request data // is not present (CLI or wonky SAPIs) if (!isset($this->serverData['REQUEST_URI'])) { return $record; } $record['extra'] = $this->appendExtraFields($record['extra']); return $record; } public function addExtraField(string $extraName, string $serverName) : self { $this->extraFields[$extraName] = $serverName; return $this; } /** * @param mixed[] $extra * @return mixed[] */ private function appendExtraFields(array $extra) : array { foreach ($this->extraFields as $extraName => $serverName) { $extra[$extraName] = $this->serverData[$serverName] ?? null; } return $extra; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Injects memory_get_peak_usage in all records * * @see Monolog\Processor\MemoryProcessor::__construct() for options * @author Rob Jensen */ class MemoryPeakUsageProcessor extends MemoryProcessor { /** * {@inheritDoc} */ public function __invoke(array $record) : array { $usage = \memory_get_peak_usage($this->realUsage); if ($this->useFormatting) { $usage = $this->formatBytes($usage); } $record['extra']['memory_peak_usage'] = $usage; return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Adds value of getmypid into records * * @author Andreas Hörnicke */ class ProcessIdProcessor implements ProcessorInterface { /** * {@inheritDoc} */ public function __invoke(array $record) : array { $record['extra']['process_id'] = \getmypid(); return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Injects Hg branch and Hg revision number in all records * * @author Jonathan A. Schweder * * @phpstan-import-type LevelName from \Monolog\Logger * @phpstan-import-type Level from \Monolog\Logger */ class MercurialProcessor implements ProcessorInterface { /** @var Level */ private $level; /** @var array{branch: string, revision: string}|array|null */ private static $cache = null; /** * @param int|string $level The minimum logging level at which this Processor will be triggered * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** * {@inheritDoc} */ public function __invoke(array $record) : array { // return if the level is not high enough if ($record['level'] < $this->level) { return $record; } $record['extra']['hg'] = self::getMercurialInfo(); return $record; } /** * @return array{branch: string, revision: string}|array */ private static function getMercurialInfo() : array { if (self::$cache) { return self::$cache; } $result = \explode(' ', \trim(`hg id -nb`)); if (\count($result) >= 3) { return self::$cache = ['branch' => $result[1], 'revision' => $result[2]]; } return self::$cache = []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Adds a tags array into record * * @author Martijn Riemers */ class TagProcessor implements ProcessorInterface { /** @var string[] */ private $tags; /** * @param string[] $tags */ public function __construct(array $tags = []) { $this->setTags($tags); } /** * @param string[] $tags */ public function addTags(array $tags = []) : self { $this->tags = \array_merge($this->tags, $tags); return $this; } /** * @param string[] $tags */ public function setTags(array $tags = []) : self { $this->tags = $tags; return $this; } /** * {@inheritDoc} */ public function __invoke(array $record) : array { $record['extra']['tags'] = $this->tags; return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Injects Git branch and Git commit SHA in all records * * @author Nick Otter * @author Jordi Boggiano * * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class GitProcessor implements ProcessorInterface { /** @var int */ private $level; /** @var array{branch: string, commit: string}|array|null */ private static $cache = null; /** * @param string|int $level The minimum logging level at which this Processor will be triggered * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** * {@inheritDoc} */ public function __invoke(array $record) : array { // return if the level is not high enough if ($record['level'] < $this->level) { return $record; } $record['extra']['git'] = self::getGitInfo(); return $record; } /** * @return array{branch: string, commit: string}|array */ private static function getGitInfo() : array { if (self::$cache) { return self::$cache; } $branches = `git branch -v --no-abbrev`; if ($branches && \preg_match('{^\\* (.+?)\\s+([a-f0-9]{40})(?:\\s|$)}m', $branches, $matches)) { return self::$cache = ['branch' => $matches[1], 'commit' => $matches[2]]; } return self::$cache = []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Some methods that are common for all memory processors * * @author Rob Jensen */ abstract class MemoryProcessor implements ProcessorInterface { /** * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. */ protected $realUsage; /** * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) */ protected $useFormatting; /** * @param bool $realUsage Set this to true to get the real size of memory allocated from system. * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) */ public function __construct(bool $realUsage = \true, bool $useFormatting = \true) { $this->realUsage = $realUsage; $this->useFormatting = $useFormatting; } /** * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is * * @param int $bytes * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int */ protected function formatBytes(int $bytes) { if (!$this->useFormatting) { return $bytes; } if ($bytes > 1024 * 1024) { return \round($bytes / 1024 / 1024, 2) . ' MB'; } elseif ($bytes > 1024) { return \round($bytes / 1024, 2) . ' KB'; } return $bytes . ' B'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; use _ContaoManager\Monolog\Logger; use _ContaoManager\Psr\Log\LogLevel; /** * Injects line/file:class/function where the log message came from * * Warning: This only works if the handler processes the logs directly. * If you put the processor on a handler that is behind a FingersCrossedHandler * for example, the processor will only be called once the trigger level is reached, * and all the log records will have the same file/line/.. data from the call that * triggered the FingersCrossedHandler. * * @author Jordi Boggiano * * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class IntrospectionProcessor implements ProcessorInterface { /** @var int */ private $level; /** @var string[] */ private $skipClassesPartials; /** @var int */ private $skipStackFramesCount; /** @var string[] */ private $skipFunctions = ['call_user_func', 'call_user_func_array']; /** * @param string|int $level The minimum logging level at which this Processor will be triggered * @param string[] $skipClassesPartials * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) { $this->level = Logger::toMonologLevel($level); $this->skipClassesPartials = \array_merge(['Monolog\\'], $skipClassesPartials); $this->skipStackFramesCount = $skipStackFramesCount; } /** * {@inheritDoc} */ public function __invoke(array $record) : array { // return if the level is not high enough if ($record['level'] < $this->level) { return $record; } $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); // skip first since it's always the current method \array_shift($trace); // the call_user_func call is also skipped \array_shift($trace); $i = 0; while ($this->isTraceClassOrSkippedFunction($trace, $i)) { if (isset($trace[$i]['class'])) { foreach ($this->skipClassesPartials as $part) { if (\strpos($trace[$i]['class'], $part) !== \false) { $i++; continue 2; } } } elseif (\in_array($trace[$i]['function'], $this->skipFunctions)) { $i++; continue; } break; } $i += $this->skipStackFramesCount; // we should have the call source now $record['extra'] = \array_merge($record['extra'], ['file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, 'callType' => isset($trace[$i]['type']) ? $trace[$i]['type'] : null, 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null]); return $record; } /** * @param array[] $trace */ private function isTraceClassOrSkippedFunction(array $trace, int $index) : bool { if (!isset($trace[$index])) { return \false; } return isset($trace[$index]['class']) || \in_array($trace[$index]['function'], $this->skipFunctions); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; use _ContaoManager\Monolog\ResettableInterface; /** * Adds a unique identifier into records * * @author Simon Mönch */ class UidProcessor implements ProcessorInterface, ResettableInterface { /** @var string */ private $uid; public function __construct(int $length = 7) { if ($length > 32 || $length < 1) { throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); } $this->uid = $this->generateUid($length); } /** * {@inheritDoc} */ public function __invoke(array $record) : array { $record['extra']['uid'] = $this->uid; return $record; } public function getUid() : string { return $this->uid; } public function reset() { $this->uid = $this->generateUid(\strlen($this->uid)); } private function generateUid(int $length) : string { return \substr(\bin2hex(\random_bytes((int) \ceil($length / 2))), 0, $length); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * An optional interface to allow labelling Monolog processors. * * @author Nicolas Grekas * * @phpstan-import-type Record from \Monolog\Logger */ interface ProcessorInterface { /** * @return array The processed record * * @phpstan-param Record $record * @phpstan-return Record */ public function __invoke(array $record); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Processor; /** * Injects memory_get_usage in all records * * @see Monolog\Processor\MemoryProcessor::__construct() for options * @author Rob Jensen */ class MemoryUsageProcessor extends MemoryProcessor { /** * {@inheritDoc} */ public function __invoke(array $record) : array { $usage = \memory_get_usage($this->realUsage); if ($this->useFormatting) { $usage = $this->formatBytes($usage); } $record['extra']['memory_usage'] = $usage; return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; /** * Handler or Processor implementing this interface will be reset when Logger::reset() is called. * * Resetting ends a log cycle gets them back to their initial state. * * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal * state, and getting it back to a state in which it can receive log records again. * * This is useful in case you want to avoid logs leaking between two requests or jobs when you * have a long running process like a worker or an application server serving multiple requests * in one process. * * @author Grégoire Pineau */ interface ResettableInterface { /** * @return void */ public function reset(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Psr\Log\LogLevel; use ReflectionExtension; /** * Monolog POSIX signal handler * * @author Robert Gust-Bardon * * @phpstan-import-type Level from \Monolog\Logger * @phpstan-import-type LevelName from \Monolog\Logger */ class SignalHandler { /** @var LoggerInterface */ private $logger; /** @var array SIG_DFL, SIG_IGN or previous callable */ private $previousSignalHandler = []; /** @var array */ private $signalLevelMap = []; /** @var array */ private $signalRestartSyscalls = []; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } /** * @param int|string $level Level or level name * @param bool $callPrevious * @param bool $restartSyscalls * @param bool|null $async * @return $this * * @phpstan-param Level|LevelName|LogLevel::* $level */ public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = \true, bool $restartSyscalls = \true, ?bool $async = \true) : self { if (!\extension_loaded('pcntl') || !\function_exists('pcntl_signal')) { return $this; } $level = Logger::toMonologLevel($level); if ($callPrevious) { $handler = \pcntl_signal_get_handler($signo); $this->previousSignalHandler[$signo] = $handler; } else { unset($this->previousSignalHandler[$signo]); } $this->signalLevelMap[$signo] = $level; $this->signalRestartSyscalls[$signo] = $restartSyscalls; if ($async !== null) { \pcntl_async_signals($async); } \pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); return $this; } /** * @param mixed $siginfo */ public function handleSignal(int $signo, $siginfo = null) : void { static $signals = []; if (!$signals && \extension_loaded('pcntl')) { $pcntl = new ReflectionExtension('pcntl'); // HHVM 3.24.2 returns an empty array. foreach ($pcntl->getConstants() ?: \get_defined_constants(\true)['Core'] as $name => $value) { if (\substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && \is_int($value)) { $signals[$value] = $name; } } } $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; $signal = $signals[$signo] ?? $signo; $context = $siginfo ?? []; $this->logger->log($level, \sprintf('Program received signal %s', $signal), $context); if (!isset($this->previousSignalHandler[$signo])) { return; } if ($this->previousSignalHandler[$signo] === \SIG_DFL) { if (\extension_loaded('pcntl') && \function_exists('pcntl_signal') && \function_exists('pcntl_sigprocmask') && \function_exists('pcntl_signal_dispatch') && \extension_loaded('posix') && \function_exists('posix_getpid') && \function_exists('posix_kill')) { $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? \true; \pcntl_signal($signo, \SIG_DFL, $restartSyscalls); \pcntl_sigprocmask(\SIG_UNBLOCK, [$signo], $oldset); \posix_kill(\posix_getpid(), $signo); \pcntl_signal_dispatch(); \pcntl_sigprocmask(\SIG_SETMASK, $oldset); \pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); } } elseif (\is_callable($this->previousSignalHandler[$signo])) { $this->previousSignalHandler[$signo]($signo, $siginfo); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; /** * Encodes message information into JSON in a format compatible with Logmatic. * * @author Julien Breux */ class LogmaticFormatter extends JsonFormatter { protected const MARKERS = ["sourcecode", "php"]; /** * @var string */ protected $hostname = ''; /** * @var string */ protected $appname = ''; public function setHostname(string $hostname) : self { $this->hostname = $hostname; return $this; } public function setAppname(string $appname) : self { $this->appname = $appname; return $this; } /** * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. * * @see http://doc.logmatic.io/docs/basics-to-send-data * @see \Monolog\Formatter\JsonFormatter::format() */ public function format(array $record) : string { if (!empty($this->hostname)) { $record["hostname"] = $this->hostname; } if (!empty($this->appname)) { $record["appname"] = $this->appname; } $record["@marker"] = static::MARKERS; return parent::format($record); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Elastica\Document; /** * Format a log message into an Elastica Document * * @author Jelle Vink * * @phpstan-import-type Record from \Monolog\Logger */ class ElasticaFormatter extends NormalizerFormatter { /** * @var string Elastic search index name */ protected $index; /** * @var ?string Elastic search document type */ protected $type; /** * @param string $index Elastic Search index name * @param ?string $type Elastic Search document type, deprecated as of Elastica 7 */ public function __construct(string $index, ?string $type) { // elasticsearch requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\\TH:i:s.uP'); $this->index = $index; $this->type = $type; } /** * {@inheritDoc} */ public function format(array $record) { $record = parent::format($record); return $this->getDocument($record); } public function getIndex() : string { return $this->index; } /** * @deprecated since Elastica 7 type has no effect */ public function getType() : string { /** @phpstan-ignore-next-line */ return $this->type; } /** * Convert a log message into an Elastica Document * * @phpstan-param Record $record */ protected function getDocument(array $record) : Document { $document = new Document(); $document->setData($record); if (\method_exists($document, 'setType')) { /** @phpstan-ignore-next-line */ $document->setType($this->type); } $document->setIndex($this->index); return $document; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\Utils; /** * Formats incoming records into a one-line string * * This is especially useful for logging to files * * @author Jordi Boggiano * @author Christophe Coevoet */ class LineFormatter extends NormalizerFormatter { public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; /** @var string */ protected $format; /** @var bool */ protected $allowInlineLineBreaks; /** @var bool */ protected $ignoreEmptyContextAndExtra; /** @var bool */ protected $includeStacktraces; /** @var ?callable */ protected $stacktracesParser; /** * @param string|null $format The format of the message * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries * @param bool $ignoreEmptyContextAndExtra */ public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = \false, bool $ignoreEmptyContextAndExtra = \false, bool $includeStacktraces = \false) { $this->format = $format === null ? static::SIMPLE_FORMAT : $format; $this->allowInlineLineBreaks = $allowInlineLineBreaks; $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; $this->includeStacktraces($includeStacktraces); parent::__construct($dateFormat); } public function includeStacktraces(bool $include = \true, ?callable $parser = null) : self { $this->includeStacktraces = $include; if ($this->includeStacktraces) { $this->allowInlineLineBreaks = \true; $this->stacktracesParser = $parser; } return $this; } public function allowInlineLineBreaks(bool $allow = \true) : self { $this->allowInlineLineBreaks = $allow; return $this; } public function ignoreEmptyContextAndExtra(bool $ignore = \true) : self { $this->ignoreEmptyContextAndExtra = $ignore; return $this; } /** * {@inheritDoc} */ public function format(array $record) : string { $vars = parent::format($record); $output = $this->format; foreach ($vars['extra'] as $var => $val) { if (\false !== \strpos($output, '%extra.' . $var . '%')) { $output = \str_replace('%extra.' . $var . '%', $this->stringify($val), $output); unset($vars['extra'][$var]); } } foreach ($vars['context'] as $var => $val) { if (\false !== \strpos($output, '%context.' . $var . '%')) { $output = \str_replace('%context.' . $var . '%', $this->stringify($val), $output); unset($vars['context'][$var]); } } if ($this->ignoreEmptyContextAndExtra) { if (empty($vars['context'])) { unset($vars['context']); $output = \str_replace('%context%', '', $output); } if (empty($vars['extra'])) { unset($vars['extra']); $output = \str_replace('%extra%', '', $output); } } foreach ($vars as $var => $val) { if (\false !== \strpos($output, '%' . $var . '%')) { $output = \str_replace('%' . $var . '%', $this->stringify($val), $output); } } // remove leftover %extra.xxx% and %context.xxx% if any if (\false !== \strpos($output, '%')) { $output = \preg_replace('/%(?:extra|context)\\..+?%/', '', $output); if (null === $output) { $pcreErrorCode = \preg_last_error(); throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); } } return $output; } public function formatBatch(array $records) : string { $message = ''; foreach ($records as $record) { $message .= $this->format($record); } return $message; } /** * @param mixed $value */ public function stringify($value) : string { return $this->replaceNewlines($this->convertToString($value)); } protected function normalizeException(\Throwable $e, int $depth = 0) : string { $str = $this->formatException($e); if ($previous = $e->getPrevious()) { do { $depth++; if ($depth > $this->maxNormalizeDepth) { $str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; break; } $str .= "\n[previous exception] " . $this->formatException($previous); } while ($previous = $previous->getPrevious()); } return $str; } /** * @param mixed $data */ protected function convertToString($data) : string { if (null === $data || \is_bool($data)) { return \var_export($data, \true); } if (\is_scalar($data)) { return (string) $data; } return $this->toJson($data, \true); } protected function replaceNewlines(string $str) : string { if ($this->allowInlineLineBreaks) { if (0 === \strpos($str, '{')) { $str = \preg_replace('/(?getCode(); if ($e instanceof \SoapFault) { if (isset($e->faultcode)) { $str .= ' faultcode: ' . $e->faultcode; } if (isset($e->faultactor)) { $str .= ' faultactor: ' . $e->faultactor; } if (isset($e->detail)) { if (\is_string($e->detail)) { $str .= ' detail: ' . $e->detail; } elseif (\is_object($e->detail) || \is_array($e->detail)) { $str .= ' detail: ' . $this->toJson($e->detail, \true); } } } $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')'; if ($this->includeStacktraces) { $str .= $this->stacktracesParser($e); } return $str; } private function stacktracesParser(\Throwable $e) : string { $trace = $e->getTraceAsString(); if ($this->stacktracesParser) { $trace = $this->stacktracesParserCustom($trace); } return "\n[stacktrace]\n" . $trace . "\n"; } private function stacktracesParserCustom(string $trace) : string { return \implode("\n", \array_filter(\array_map($this->stacktracesParser, \explode("\n", $trace)))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use Throwable; /** * Encodes whatever record data is passed to it as json * * This can be useful to log to databases or remote APIs * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger */ class JsonFormatter extends NormalizerFormatter { public const BATCH_MODE_JSON = 1; public const BATCH_MODE_NEWLINES = 2; /** @var self::BATCH_MODE_* */ protected $batchMode; /** @var bool */ protected $appendNewline; /** @var bool */ protected $ignoreEmptyContextAndExtra; /** @var bool */ protected $includeStacktraces = \false; /** * @param self::BATCH_MODE_* $batchMode */ public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = \true, bool $ignoreEmptyContextAndExtra = \false, bool $includeStacktraces = \false) { $this->batchMode = $batchMode; $this->appendNewline = $appendNewline; $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; $this->includeStacktraces = $includeStacktraces; parent::__construct(); } /** * The batch mode option configures the formatting style for * multiple records. By default, multiple records will be * formatted as a JSON-encoded array. However, for * compatibility with some API endpoints, alternative styles * are available. */ public function getBatchMode() : int { return $this->batchMode; } /** * True if newlines are appended to every formatted record */ public function isAppendingNewlines() : bool { return $this->appendNewline; } /** * {@inheritDoc} */ public function format(array $record) : string { $normalized = $this->normalize($record); if (isset($normalized['context']) && $normalized['context'] === []) { if ($this->ignoreEmptyContextAndExtra) { unset($normalized['context']); } else { $normalized['context'] = new \stdClass(); } } if (isset($normalized['extra']) && $normalized['extra'] === []) { if ($this->ignoreEmptyContextAndExtra) { unset($normalized['extra']); } else { $normalized['extra'] = new \stdClass(); } } return $this->toJson($normalized, \true) . ($this->appendNewline ? "\n" : ''); } /** * {@inheritDoc} */ public function formatBatch(array $records) : string { switch ($this->batchMode) { case static::BATCH_MODE_NEWLINES: return $this->formatBatchNewlines($records); case static::BATCH_MODE_JSON: default: return $this->formatBatchJson($records); } } /** * @return self */ public function includeStacktraces(bool $include = \true) : self { $this->includeStacktraces = $include; return $this; } /** * Return a JSON-encoded array of records. * * @phpstan-param Record[] $records */ protected function formatBatchJson(array $records) : string { return $this->toJson($this->normalize($records), \true); } /** * Use new lines to separate records instead of a * JSON-encoded array. * * @phpstan-param Record[] $records */ protected function formatBatchNewlines(array $records) : string { $instance = $this; $oldNewline = $this->appendNewline; $this->appendNewline = \false; \array_walk($records, function (&$value, $key) use($instance) { $value = $instance->format($value); }); $this->appendNewline = $oldNewline; return \implode("\n", $records); } /** * Normalizes given $data. * * @param mixed $data * * @return mixed */ protected function normalize($data, int $depth = 0) { if ($depth > $this->maxNormalizeDepth) { return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; } if (\is_array($data)) { $normalized = []; $count = 1; foreach ($data as $key => $value) { if ($count++ > $this->maxNormalizeItemCount) { $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items (' . \count($data) . ' total), aborting normalization'; break; } $normalized[$key] = $this->normalize($value, $depth + 1); } return $normalized; } if (\is_object($data)) { if ($data instanceof \DateTimeInterface) { return $this->formatDate($data); } if ($data instanceof Throwable) { return $this->normalizeException($data, $depth); } // if the object has specific json serializability we want to make sure we skip the __toString treatment below if ($data instanceof \JsonSerializable) { return $data; } if (\method_exists($data, '__toString')) { return $data->__toString(); } return $data; } if (\is_resource($data)) { return parent::normalize($data); } return $data; } /** * Normalizes given exception with or without its own stack trace based on * `includeStacktraces` property. * * {@inheritDoc} */ protected function normalizeException(Throwable $e, int $depth = 0) : array { $data = parent::normalizeException($e, $depth); if (!$this->includeStacktraces) { unset($data['trace']); } return $data; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; /** * formats the record to be used in the FlowdockHandler * * @author Dominik Liebler * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 */ class FlowdockFormatter implements FormatterInterface { /** * @var string */ private $source; /** * @var string */ private $sourceEmail; public function __construct(string $source, string $sourceEmail) { $this->source = $source; $this->sourceEmail = $sourceEmail; } /** * {@inheritDoc} * * @return mixed[] */ public function format(array $record) : array { $tags = ['#logs', '#' . \strtolower($record['level_name']), '#' . $record['channel']]; foreach ($record['extra'] as $value) { $tags[] = '#' . $value; } $subject = \sprintf('in %s: %s - %s', $this->source, $record['level_name'], $this->getShortMessage($record['message'])); $record['flowdock'] = ['source' => $this->source, 'from_address' => $this->sourceEmail, 'subject' => $subject, 'content' => $record['message'], 'tags' => $tags, 'project' => $this->source]; return $record; } /** * {@inheritDoc} * * @return mixed[][] */ public function formatBatch(array $records) : array { $formatted = []; foreach ($records as $record) { $formatted[] = $this->format($record); } return $formatted; } public function getShortMessage(string $message) : string { static $hasMbString; if (null === $hasMbString) { $hasMbString = \function_exists('mb_strlen'); } $maxLength = 45; if ($hasMbString) { if (\mb_strlen($message, 'UTF-8') > $maxLength) { $message = \mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; } } else { if (\strlen($message) > $maxLength) { $message = \substr($message, 0, $maxLength - 4) . ' ...'; } } return $message; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; /** * Interface for formatters * * @author Jordi Boggiano * * @phpstan-import-type Record from \Monolog\Logger */ interface FormatterInterface { /** * Formats a log record. * * @param array $record A record to format * @return mixed The formatted record * * @phpstan-param Record $record */ public function format(array $record); /** * Formats a set of log records. * * @param array $records A set of records to format * @return mixed The formatted set of records * * @phpstan-param Record[] $records */ public function formatBatch(array $records); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; /** * Serializes a log message to Logstash Event Format * * @see https://www.elastic.co/products/logstash * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java * * @author Tim Mower */ class LogstashFormatter extends NormalizerFormatter { /** * @var string the name of the system for the Logstash log message, used to fill the @source field */ protected $systemName; /** * @var string an application name for the Logstash log message, used to fill the @type field */ protected $applicationName; /** * @var string the key for 'extra' fields from the Monolog record */ protected $extraKey; /** * @var string the key for 'context' fields from the Monolog record */ protected $contextKey; /** * @param string $applicationName The application that sends the data, used as the "type" field of logstash * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra * @param string $contextKey The key for context keys inside logstash "fields", defaults to context */ public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') { // logstash requires a ISO 8601 format date with optional millisecond precision. parent::__construct('Y-m-d\\TH:i:s.uP'); $this->systemName = $systemName === null ? (string) \gethostname() : $systemName; $this->applicationName = $applicationName; $this->extraKey = $extraKey; $this->contextKey = $contextKey; } /** * {@inheritDoc} */ public function format(array $record) : string { $record = parent::format($record); if (empty($record['datetime'])) { $record['datetime'] = \gmdate('c'); } $message = ['@timestamp' => $record['datetime'], '@version' => 1, 'host' => $this->systemName]; if (isset($record['message'])) { $message['message'] = $record['message']; } if (isset($record['channel'])) { $message['type'] = $record['channel']; $message['channel'] = $record['channel']; } if (isset($record['level_name'])) { $message['level'] = $record['level_name']; } if (isset($record['level'])) { $message['monolog_level'] = $record['level']; } if ($this->applicationName) { $message['type'] = $this->applicationName; } if (!empty($record['extra'])) { $message[$this->extraKey] = $record['extra']; } if (!empty($record['context'])) { $message[$this->contextKey] = $record['context']; } return $this->toJson($message) . "\n"; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; /** * Encodes message information into JSON in a format compatible with Loggly. * * @author Adam Pancutt */ class LogglyFormatter extends JsonFormatter { /** * Overrides the default batch mode to new lines for compatibility with the * Loggly bulk API. */ public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = \false) { parent::__construct($batchMode, $appendNewline); } /** * Appends the 'timestamp' parameter for indexing by Loggly. * * @see https://www.loggly.com/docs/automated-parsing/#json * @see \Monolog\Formatter\JsonFormatter::format() */ public function format(array $record) : string { if (isset($record["datetime"]) && $record["datetime"] instanceof \DateTimeInterface) { $record["timestamp"] = $record["datetime"]->format("Y-m-d\\TH:i:s.uO"); unset($record["datetime"]); } return parent::format($record); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use DateTimeInterface; /** * Format a log message into an Elasticsearch record * * @author Avtandil Kikabidze */ class ElasticsearchFormatter extends NormalizerFormatter { /** * @var string Elasticsearch index name */ protected $index; /** * @var string Elasticsearch record type */ protected $type; /** * @param string $index Elasticsearch index name * @param string $type Elasticsearch record type */ public function __construct(string $index, string $type) { // Elasticsearch requires an ISO 8601 format date with optional millisecond precision. parent::__construct(DateTimeInterface::ISO8601); $this->index = $index; $this->type = $type; } /** * {@inheritDoc} */ public function format(array $record) { $record = parent::format($record); return $this->getDocument($record); } /** * Getter index * * @return string */ public function getIndex() : string { return $this->index; } /** * Getter type * * @return string */ public function getType() : string { return $this->type; } /** * Convert a log message into an Elasticsearch record * * @param mixed[] $record Log message * @return mixed[] */ protected function getDocument(array $record) : array { $record['_index'] = $this->index; $record['_type'] = $this->type; return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; /** * Formats data into an associative array of scalar values. * Objects and arrays will be JSON encoded. * * @author Andrew Lawson */ class ScalarFormatter extends NormalizerFormatter { /** * {@inheritDoc} * * @phpstan-return array $record */ public function format(array $record) : array { $result = []; foreach ($record as $key => $value) { $result[$key] = $this->normalizeValue($value); } return $result; } /** * @param mixed $value * @return scalar|null */ protected function normalizeValue($value) { $normalized = $this->normalize($value); if (\is_array($normalized)) { return $this->toJson($normalized, \true); } return $normalized; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use MongoDB\BSON\Type; use MongoDB\BSON\UTCDateTime; use _ContaoManager\Monolog\Utils; /** * Formats a record for use with the MongoDBHandler. * * @author Florian Plattner */ class MongoDBFormatter implements FormatterInterface { /** @var bool */ private $exceptionTraceAsString; /** @var int */ private $maxNestingLevel; /** @var bool */ private $isLegacyMongoExt; /** * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings */ public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = \true) { $this->maxNestingLevel = \max($maxNestingLevel, 0); $this->exceptionTraceAsString = $exceptionTraceAsString; $this->isLegacyMongoExt = \extension_loaded('mongodb') && \version_compare((string) \phpversion('mongodb'), '1.1.9', '<='); } /** * {@inheritDoc} * * @return mixed[] */ public function format(array $record) : array { /** @var mixed[] $res */ $res = $this->formatArray($record); return $res; } /** * {@inheritDoc} * * @return array */ public function formatBatch(array $records) : array { $formatted = []; foreach ($records as $key => $record) { $formatted[$key] = $this->format($record); } return $formatted; } /** * @param mixed[] $array * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" */ protected function formatArray(array $array, int $nestingLevel = 0) { if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { return '[...]'; } foreach ($array as $name => $value) { if ($value instanceof \DateTimeInterface) { $array[$name] = $this->formatDate($value, $nestingLevel + 1); } elseif ($value instanceof \Throwable) { $array[$name] = $this->formatException($value, $nestingLevel + 1); } elseif (\is_array($value)) { $array[$name] = $this->formatArray($value, $nestingLevel + 1); } elseif (\is_object($value) && !$value instanceof Type) { $array[$name] = $this->formatObject($value, $nestingLevel + 1); } } return $array; } /** * @param mixed $value * @return mixed[]|string */ protected function formatObject($value, int $nestingLevel) { $objectVars = \get_object_vars($value); $objectVars['class'] = Utils::getClass($value); return $this->formatArray($objectVars, $nestingLevel); } /** * @return mixed[]|string */ protected function formatException(\Throwable $exception, int $nestingLevel) { $formattedException = ['class' => Utils::getClass($exception), 'message' => $exception->getMessage(), 'code' => (int) $exception->getCode(), 'file' => $exception->getFile() . ':' . $exception->getLine()]; if ($this->exceptionTraceAsString === \true) { $formattedException['trace'] = $exception->getTraceAsString(); } else { $formattedException['trace'] = $exception->getTrace(); } return $this->formatArray($formattedException, $nestingLevel); } protected function formatDate(\DateTimeInterface $value, int $nestingLevel) : UTCDateTime { if ($this->isLegacyMongoExt) { return $this->legacyGetMongoDbDateTime($value); } return $this->getMongoDbDateTime($value); } private function getMongoDbDateTime(\DateTimeInterface $value) : UTCDateTime { return new UTCDateTime((int) \floor((float) $value->format('U.u') * 1000)); } /** * This is needed to support MongoDB Driver v1.19 and below * * See https://github.com/mongodb/mongo-php-driver/issues/426 * * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted */ private function legacyGetMongoDbDateTime(\DateTimeInterface $value) : UTCDateTime { $milliseconds = \floor((float) $value->format('U.u') * 1000); $milliseconds = \PHP_INT_SIZE == 8 ? (int) $milliseconds : (string) $milliseconds; // @phpstan-ignore-next-line return new UTCDateTime($milliseconds); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\Logger; /** * Formats a log message according to the ChromePHP array format * * @author Christophe Coevoet */ class ChromePHPFormatter implements FormatterInterface { /** * Translates Monolog log levels to Wildfire levels. * * @var array */ private $logLevels = [Logger::DEBUG => 'log', Logger::INFO => 'info', Logger::NOTICE => 'info', Logger::WARNING => 'warn', Logger::ERROR => 'error', Logger::CRITICAL => 'error', Logger::ALERT => 'error', Logger::EMERGENCY => 'error']; /** * {@inheritDoc} */ public function format(array $record) { // Retrieve the line and file if set and remove them from the formatted extra $backtrace = 'unknown'; if (isset($record['extra']['file'], $record['extra']['line'])) { $backtrace = $record['extra']['file'] . ' : ' . $record['extra']['line']; unset($record['extra']['file'], $record['extra']['line']); } $message = ['message' => $record['message']]; if ($record['context']) { $message['context'] = $record['context']; } if ($record['extra']) { $message['extra'] = $record['extra']; } if (\count($message) === 1) { $message = \reset($message); } return [$record['channel'], $message, $backtrace, $this->logLevels[$record['level']]]; } /** * {@inheritDoc} */ public function formatBatch(array $records) { $formatted = []; foreach ($records as $record) { $formatted[] = $this->format($record); } return $formatted; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\Utils; /** * Class FluentdFormatter * * Serializes a log message to Fluentd unix socket protocol * * Fluentd config: * * * type unix * path /var/run/td-agent/td-agent.sock * * * Monolog setup: * * $logger = new Monolog\Logger('fluent.tag'); * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); * $logger->pushHandler($fluentHandler); * * @author Andrius Putna */ class FluentdFormatter implements FormatterInterface { /** * @var bool $levelTag should message level be a part of the fluentd tag */ protected $levelTag = \false; public function __construct(bool $levelTag = \false) { if (!\function_exists('json_encode')) { throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); } $this->levelTag = $levelTag; } public function isUsingLevelsInTag() : bool { return $this->levelTag; } public function format(array $record) : string { $tag = $record['channel']; if ($this->levelTag) { $tag .= '.' . \strtolower($record['level_name']); } $message = ['message' => $record['message'], 'context' => $record['context'], 'extra' => $record['extra']]; if (!$this->levelTag) { $message['level'] = $record['level']; $message['level_name'] = $record['level_name']; } return Utils::jsonEncode([$tag, $record['datetime']->getTimestamp(), $message]); } public function formatBatch(array $records) : string { $message = ''; foreach ($records as $record) { $message .= $this->format($record); } return $message; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\Logger; use _ContaoManager\Gelf\Message; use _ContaoManager\Monolog\Utils; /** * Serializes a log message to GELF * @see http://docs.graylog.org/en/latest/pages/gelf.html * * @author Matt Lehner * * @phpstan-import-type Level from \Monolog\Logger */ class GelfMessageFormatter extends NormalizerFormatter { protected const DEFAULT_MAX_LENGTH = 32766; /** * @var string the name of the system for the Gelf log message */ protected $systemName; /** * @var string a prefix for 'extra' fields from the Monolog record (optional) */ protected $extraPrefix; /** * @var string a prefix for 'context' fields from the Monolog record (optional) */ protected $contextPrefix; /** * @var int max length per field */ protected $maxLength; /** * @var int */ private $gelfVersion = 2; /** * Translates Monolog log levels to Graylog2 log priorities. * * @var array * * @phpstan-var array */ private $logLevels = [Logger::DEBUG => 7, Logger::INFO => 6, Logger::NOTICE => 5, Logger::WARNING => 4, Logger::ERROR => 3, Logger::CRITICAL => 2, Logger::ALERT => 1, Logger::EMERGENCY => 0]; public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null) { if (!\class_exists(Message::class)) { throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter'); } parent::__construct('U.u'); $this->systemName = \is_null($systemName) || $systemName === '' ? (string) \gethostname() : $systemName; $this->extraPrefix = \is_null($extraPrefix) ? '' : $extraPrefix; $this->contextPrefix = $contextPrefix; $this->maxLength = \is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; if (\method_exists(Message::class, 'setFacility')) { $this->gelfVersion = 1; } } /** * {@inheritDoc} */ public function format(array $record) : Message { $context = $extra = []; if (isset($record['context'])) { /** @var mixed[] $context */ $context = parent::normalize($record['context']); } if (isset($record['extra'])) { /** @var mixed[] $extra */ $extra = parent::normalize($record['extra']); } if (!isset($record['datetime'], $record['message'], $record['level'])) { throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, ' . \var_export($record, \true) . ' given'); } $message = new Message(); $message->setTimestamp($record['datetime'])->setShortMessage((string) $record['message'])->setHost($this->systemName)->setLevel($this->logLevels[$record['level']]); // message length + system name length + 200 for padding / metadata $len = 200 + \strlen((string) $record['message']) + \strlen($this->systemName); if ($len > $this->maxLength) { $message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength)); } if ($this->gelfVersion === 1) { if (isset($record['channel'])) { $message->setFacility($record['channel']); } if (isset($extra['line'])) { $message->setLine($extra['line']); unset($extra['line']); } if (isset($extra['file'])) { $message->setFile($extra['file']); unset($extra['file']); } } else { $message->setAdditional('facility', $record['channel']); } foreach ($extra as $key => $val) { $val = \is_scalar($val) || null === $val ? $val : $this->toJson($val); $len = \strlen($this->extraPrefix . $key . $val); if ($len > $this->maxLength) { $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); continue; } $message->setAdditional($this->extraPrefix . $key, $val); } foreach ($context as $key => $val) { $val = \is_scalar($val) || null === $val ? $val : $this->toJson($val); $len = \strlen($this->contextPrefix . $key . $val); if ($len > $this->maxLength) { $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); continue; } $message->setAdditional($this->contextPrefix . $key, $val); } if ($this->gelfVersion === 1) { /** @phpstan-ignore-next-line */ if (null === $message->getFile() && isset($context['exception']['file'])) { if (\preg_match("/^(.+):([0-9]+)\$/", $context['exception']['file'], $matches)) { $message->setFile($matches[1]); $message->setLine($matches[2]); } } } return $message; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\Logger; /** * Serializes a log message according to Wildfire's header requirements * * @author Eric Clemmons (@ericclemmons) * @author Christophe Coevoet * @author Kirill chEbba Chebunin * * @phpstan-import-type Level from \Monolog\Logger */ class WildfireFormatter extends NormalizerFormatter { /** * Translates Monolog log levels to Wildfire levels. * * @var array */ private $logLevels = [Logger::DEBUG => 'LOG', Logger::INFO => 'INFO', Logger::NOTICE => 'INFO', Logger::WARNING => 'WARN', Logger::ERROR => 'ERROR', Logger::CRITICAL => 'ERROR', Logger::ALERT => 'ERROR', Logger::EMERGENCY => 'ERROR']; /** * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format */ public function __construct(?string $dateFormat = null) { parent::__construct($dateFormat); // http headers do not like non-ISO-8559-1 characters $this->removeJsonEncodeOption(\JSON_UNESCAPED_UNICODE); } /** * {@inheritDoc} * * @return string */ public function format(array $record) : string { // Retrieve the line and file if set and remove them from the formatted extra $file = $line = ''; if (isset($record['extra']['file'])) { $file = $record['extra']['file']; unset($record['extra']['file']); } if (isset($record['extra']['line'])) { $line = $record['extra']['line']; unset($record['extra']['line']); } /** @var mixed[] $record */ $record = $this->normalize($record); $message = ['message' => $record['message']]; $handleError = \false; if ($record['context']) { $message['context'] = $record['context']; $handleError = \true; } if ($record['extra']) { $message['extra'] = $record['extra']; $handleError = \true; } if (\count($message) === 1) { $message = \reset($message); } if (isset($record['context']['table'])) { $type = 'TABLE'; $label = $record['channel'] . ': ' . $record['message']; $message = $record['context']['table']; } else { $type = $this->logLevels[$record['level']]; $label = $record['channel']; } // Create JSON object describing the appearance of the message in the console $json = $this->toJson([['Type' => $type, 'File' => $file, 'Line' => $line, 'Label' => $label], $message], $handleError); // The message itself is a serialization of the above JSON object + it's length return \sprintf('%d|%s|', \strlen($json), $json); } /** * {@inheritDoc} * * @phpstan-return never */ public function formatBatch(array $records) { throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); } /** * {@inheritDoc} * * @return null|scalar|array|object */ protected function normalize($data, int $depth = 0) { if (\is_object($data) && !$data instanceof \DateTimeInterface) { return $data; } return parent::normalize($data, $depth); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use DateTimeInterface; use _ContaoManager\Monolog\LogRecord; /** * Encodes message information into JSON in a format compatible with Cloud logging. * * @see https://cloud.google.com/logging/docs/structured-logging * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry * * @author Luís Cobucci */ final class GoogleCloudLoggingFormatter extends JsonFormatter { /** {@inheritdoc} **/ public function format(array $record) : string { // Re-key level for GCP logging $record['severity'] = $record['level_name']; $record['time'] = $record['datetime']->format(DateTimeInterface::RFC3339_EXTENDED); // Remove keys that are not used by GCP unset($record['level'], $record['level_name'], $record['datetime']); return parent::format($record); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\DateTimeImmutable; use _ContaoManager\Monolog\Utils; use Throwable; /** * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets * * @author Jordi Boggiano */ class NormalizerFormatter implements FormatterInterface { public const SIMPLE_DATE = "Y-m-d\\TH:i:sP"; /** @var string */ protected $dateFormat; /** @var int */ protected $maxNormalizeDepth = 9; /** @var int */ protected $maxNormalizeItemCount = 1000; /** @var int */ private $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS; /** * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format */ public function __construct(?string $dateFormat = null) { $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; if (!\function_exists('json_encode')) { throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); } } /** * {@inheritDoc} * * @param mixed[] $record */ public function format(array $record) { return $this->normalize($record); } /** * {@inheritDoc} */ public function formatBatch(array $records) { foreach ($records as $key => $record) { $records[$key] = $this->format($record); } return $records; } public function getDateFormat() : string { return $this->dateFormat; } public function setDateFormat(string $dateFormat) : self { $this->dateFormat = $dateFormat; return $this; } /** * The maximum number of normalization levels to go through */ public function getMaxNormalizeDepth() : int { return $this->maxNormalizeDepth; } public function setMaxNormalizeDepth(int $maxNormalizeDepth) : self { $this->maxNormalizeDepth = $maxNormalizeDepth; return $this; } /** * The maximum number of items to normalize per level */ public function getMaxNormalizeItemCount() : int { return $this->maxNormalizeItemCount; } public function setMaxNormalizeItemCount(int $maxNormalizeItemCount) : self { $this->maxNormalizeItemCount = $maxNormalizeItemCount; return $this; } /** * Enables `json_encode` pretty print. */ public function setJsonPrettyPrint(bool $enable) : self { if ($enable) { $this->jsonEncodeOptions |= \JSON_PRETTY_PRINT; } else { $this->jsonEncodeOptions &= ~\JSON_PRETTY_PRINT; } return $this; } /** * @param mixed $data * @return null|scalar|array */ protected function normalize($data, int $depth = 0) { if ($depth > $this->maxNormalizeDepth) { return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; } if (null === $data || \is_scalar($data)) { if (\is_float($data)) { if (\is_infinite($data)) { return ($data > 0 ? '' : '-') . 'INF'; } if (\is_nan($data)) { return 'NaN'; } } return $data; } if (\is_array($data)) { $normalized = []; $count = 1; foreach ($data as $key => $value) { if ($count++ > $this->maxNormalizeItemCount) { $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items (' . \count($data) . ' total), aborting normalization'; break; } $normalized[$key] = $this->normalize($value, $depth + 1); } return $normalized; } if ($data instanceof \DateTimeInterface) { return $this->formatDate($data); } if (\is_object($data)) { if ($data instanceof Throwable) { return $this->normalizeException($data, $depth); } if ($data instanceof \JsonSerializable) { /** @var null|scalar|array $value */ $value = $data->jsonSerialize(); } elseif (\get_class($data) === '__PHP_Incomplete_Class') { $accessor = new \ArrayObject($data); $value = (string) $accessor['__PHP_Incomplete_Class_Name']; } elseif (\method_exists($data, '__toString')) { /** @var string $value */ $value = $data->__toString(); } else { // the rest is normalized by json encoding and decoding it /** @var null|scalar|array $value */ $value = \json_decode($this->toJson($data, \true), \true); } return [Utils::getClass($data) => $value]; } if (\is_resource($data)) { return \sprintf('[resource(%s)]', \get_resource_type($data)); } return '[unknown(' . \gettype($data) . ')]'; } /** * @return mixed[] */ protected function normalizeException(Throwable $e, int $depth = 0) { if ($depth > $this->maxNormalizeDepth) { return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; } if ($e instanceof \JsonSerializable) { return (array) $e->jsonSerialize(); } $data = ['class' => Utils::getClass($e), 'message' => $e->getMessage(), 'code' => (int) $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine()]; if ($e instanceof \SoapFault) { if (isset($e->faultcode)) { $data['faultcode'] = $e->faultcode; } if (isset($e->faultactor)) { $data['faultactor'] = $e->faultactor; } if (isset($e->detail)) { if (\is_string($e->detail)) { $data['detail'] = $e->detail; } elseif (\is_object($e->detail) || \is_array($e->detail)) { $data['detail'] = $this->toJson($e->detail, \true); } } } $trace = $e->getTrace(); foreach ($trace as $frame) { if (isset($frame['file'])) { $data['trace'][] = $frame['file'] . ':' . $frame['line']; } } if ($previous = $e->getPrevious()) { $data['previous'] = $this->normalizeException($previous, $depth + 1); } return $data; } /** * Return the JSON representation of a value * * @param mixed $data * @throws \RuntimeException if encoding fails and errors are not ignored * @return string if encoding fails and ignoreErrors is true 'null' is returned */ protected function toJson($data, bool $ignoreErrors = \false) : string { return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); } /** * @return string */ protected function formatDate(\DateTimeInterface $date) { // in case the date format isn't custom then we defer to the custom DateTimeImmutable // formatting logic, which will pick the right format based on whether useMicroseconds is on if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { return (string) $date; } return $date->format($this->dateFormat); } public function addJsonEncodeOption(int $option) : self { $this->jsonEncodeOptions |= $option; return $this; } public function removeJsonEncodeOption(int $option) : self { $this->jsonEncodeOptions &= ~$option; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog\Formatter; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Utils; /** * Formats incoming records into an HTML table * * This is especially useful for html email logging * * @author Tiago Brito */ class HtmlFormatter extends NormalizerFormatter { /** * Translates Monolog log levels to html color priorities. * * @var array */ protected $logLevels = [Logger::DEBUG => '#CCCCCC', Logger::INFO => '#28A745', Logger::NOTICE => '#17A2B8', Logger::WARNING => '#FFC107', Logger::ERROR => '#FD7E14', Logger::CRITICAL => '#DC3545', Logger::ALERT => '#821722', Logger::EMERGENCY => '#000000']; /** * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format */ public function __construct(?string $dateFormat = null) { parent::__construct($dateFormat); } /** * Creates an HTML table row * * @param string $th Row header content * @param string $td Row standard cell content * @param bool $escapeTd false if td content must not be html escaped */ protected function addRow(string $th, string $td = ' ', bool $escapeTd = \true) : string { $th = \htmlspecialchars($th, \ENT_NOQUOTES, 'UTF-8'); if ($escapeTd) { $td = '
' . \htmlspecialchars($td, \ENT_NOQUOTES, 'UTF-8') . '
'; } return "\n{$th}:\n" . $td . "\n"; } /** * Create a HTML h1 tag * * @param string $title Text to be in the h1 * @param int $level Error level * @return string */ protected function addTitle(string $title, int $level) : string { $title = \htmlspecialchars($title, \ENT_NOQUOTES, 'UTF-8'); return '

' . $title . '

'; } /** * Formats a log record. * * @return string The formatted record */ public function format(array $record) : string { $output = $this->addTitle($record['level_name'], $record['level']); $output .= ''; $output .= $this->addRow('Message', (string) $record['message']); $output .= $this->addRow('Time', $this->formatDate($record['datetime'])); $output .= $this->addRow('Channel', $record['channel']); if ($record['context']) { $embeddedTable = '
'; foreach ($record['context'] as $key => $value) { $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); } $embeddedTable .= '
'; $output .= $this->addRow('Context', $embeddedTable, \false); } if ($record['extra']) { $embeddedTable = ''; foreach ($record['extra'] as $key => $value) { $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); } $embeddedTable .= '
'; $output .= $this->addRow('Extra', $embeddedTable, \false); } return $output . ''; } /** * Formats a set of log records. * * @return string The formatted set of records */ public function formatBatch(array $records) : string { $message = ''; foreach ($records as $record) { $message .= $this->format($record); } return $message; } /** * @param mixed $data */ protected function convertToString($data) : string { if (null === $data || \is_scalar($data)) { return (string) $data; } $data = $this->normalize($data); return Utils::jsonEncode($data, \JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, \true); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; use ArrayAccess; /** * Monolog log record interface for forward compatibility with Monolog 3.0 * * This is just present in Monolog 2.4+ to allow interoperable code to be written against * both versions by type-hinting arguments as `array|\Monolog\LogRecord $record` * * Do not rely on this interface for other purposes, and do not implement it. * * @author Jordi Boggiano * @template-extends \ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', mixed> * @phpstan-import-type Record from Logger */ interface LogRecord extends \ArrayAccess { /** * @phpstan-return Record */ public function toArray() : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Psr\Log\LogLevel; /** * Monolog error handler * * A facility to enable logging of runtime errors, exceptions and fatal errors. * * Quick setup: ErrorHandler::register($logger); * * @author Jordi Boggiano */ class ErrorHandler { /** @var LoggerInterface */ private $logger; /** @var ?callable */ private $previousExceptionHandler = null; /** @var array an array of class name to LogLevel::* constant mapping */ private $uncaughtExceptionLevelMap = []; /** @var callable|true|null */ private $previousErrorHandler = null; /** @var array an array of E_* constant to LogLevel::* constant mapping */ private $errorLevelMap = []; /** @var bool */ private $handleOnlyReportedErrors = \true; /** @var bool */ private $hasFatalErrorHandler = \false; /** @var LogLevel::* */ private $fatalLevel = LogLevel::ALERT; /** @var ?string */ private $reservedMemory = null; /** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */ private $lastFatalData = null; /** @var int[] */ private static $fatalErrors = [\E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR]; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } /** * Registers a new ErrorHandler for a given Logger * * By default it will handle errors, exceptions and fatal errors * * @param LoggerInterface $logger * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling * @return ErrorHandler */ public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null) : self { /** @phpstan-ignore-next-line */ $handler = new static($logger); if ($errorLevelMap !== \false) { $handler->registerErrorHandler($errorLevelMap); } if ($exceptionLevelMap !== \false) { $handler->registerExceptionHandler($exceptionLevelMap); } if ($fatalLevel !== \false) { $handler->registerFatalHandler($fatalLevel); } return $handler; } /** * @param array $levelMap an array of class name to LogLevel::* constant mapping * @return $this */ public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = \true) : self { $prev = \set_exception_handler(function (\Throwable $e) : void { $this->handleException($e); }); $this->uncaughtExceptionLevelMap = $levelMap; foreach ($this->defaultExceptionLevelMap() as $class => $level) { if (!isset($this->uncaughtExceptionLevelMap[$class])) { $this->uncaughtExceptionLevelMap[$class] = $level; } } if ($callPrevious && $prev) { $this->previousExceptionHandler = $prev; } return $this; } /** * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping * @return $this */ public function registerErrorHandler(array $levelMap = [], bool $callPrevious = \true, int $errorTypes = -1, bool $handleOnlyReportedErrors = \true) : self { $prev = \set_error_handler([$this, 'handleError'], $errorTypes); $this->errorLevelMap = \array_replace($this->defaultErrorLevelMap(), $levelMap); if ($callPrevious) { $this->previousErrorHandler = $prev ?: \true; } else { $this->previousErrorHandler = null; } $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; return $this; } /** * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done */ public function registerFatalHandler($level = null, int $reservedMemorySize = 20) : self { \register_shutdown_function([$this, 'handleFatalError']); $this->reservedMemory = \str_repeat(' ', 1024 * $reservedMemorySize); $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; $this->hasFatalErrorHandler = \true; return $this; } /** * @return array */ protected function defaultExceptionLevelMap() : array { return ['ParseError' => LogLevel::CRITICAL, 'Throwable' => LogLevel::ERROR]; } /** * @return array */ protected function defaultErrorLevelMap() : array { return [\E_ERROR => LogLevel::CRITICAL, \E_WARNING => LogLevel::WARNING, \E_PARSE => LogLevel::ALERT, \E_NOTICE => LogLevel::NOTICE, \E_CORE_ERROR => LogLevel::CRITICAL, \E_CORE_WARNING => LogLevel::WARNING, \E_COMPILE_ERROR => LogLevel::ALERT, \E_COMPILE_WARNING => LogLevel::WARNING, \E_USER_ERROR => LogLevel::ERROR, \E_USER_WARNING => LogLevel::WARNING, \E_USER_NOTICE => LogLevel::NOTICE, \E_STRICT => LogLevel::NOTICE, \E_RECOVERABLE_ERROR => LogLevel::ERROR, \E_DEPRECATED => LogLevel::NOTICE, \E_USER_DEPRECATED => LogLevel::NOTICE]; } /** * @phpstan-return never */ private function handleException(\Throwable $e) : void { $level = LogLevel::ERROR; foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { if ($e instanceof $class) { $level = $candidate; break; } } $this->logger->log($level, \sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), ['exception' => $e]); if ($this->previousExceptionHandler) { ($this->previousExceptionHandler)($e); } if (!\headers_sent() && \in_array(\strtolower((string) \ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], \true)) { \http_response_code(500); } exit(255); } /** * @private * * @param mixed[] $context */ public function handleError(int $code, string $message, string $file = '', int $line = 0, ?array $context = []) : bool { if ($this->handleOnlyReportedErrors && !(\error_reporting() & $code)) { return \false; } // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries if (!$this->hasFatalErrorHandler || !\in_array($code, self::$fatalErrors, \true)) { $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; $this->logger->log($level, self::codeToString($code) . ': ' . $message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); } else { $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); \array_shift($trace); // Exclude handleError from trace $this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace]; } if ($this->previousErrorHandler === \true) { return \false; } elseif ($this->previousErrorHandler) { return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context); } return \true; } /** * @private */ public function handleFatalError() : void { $this->reservedMemory = ''; if (\is_array($this->lastFatalData)) { $lastError = $this->lastFatalData; } else { $lastError = \error_get_last(); } if ($lastError && \in_array($lastError['type'], self::$fatalErrors, \true)) { $trace = $lastError['trace'] ?? null; $this->logger->log($this->fatalLevel, 'Fatal Error (' . self::codeToString($lastError['type']) . '): ' . $lastError['message'], ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace]); if ($this->logger instanceof Logger) { foreach ($this->logger->getHandlers() as $handler) { $handler->close(); } } } } /** * @param int $code */ private static function codeToString($code) : string { switch ($code) { case \E_ERROR: return 'E_ERROR'; case \E_WARNING: return 'E_WARNING'; case \E_PARSE: return 'E_PARSE'; case \E_NOTICE: return 'E_NOTICE'; case \E_CORE_ERROR: return 'E_CORE_ERROR'; case \E_CORE_WARNING: return 'E_CORE_WARNING'; case \E_COMPILE_ERROR: return 'E_COMPILE_ERROR'; case \E_COMPILE_WARNING: return 'E_COMPILE_WARNING'; case \E_USER_ERROR: return 'E_USER_ERROR'; case \E_USER_WARNING: return 'E_USER_WARNING'; case \E_USER_NOTICE: return 'E_USER_NOTICE'; case \E_STRICT: return 'E_STRICT'; case \E_RECOVERABLE_ERROR: return 'E_RECOVERABLE_ERROR'; case \E_DEPRECATED: return 'E_DEPRECATED'; case \E_USER_DEPRECATED: return 'E_USER_DEPRECATED'; } return 'Unknown PHP error'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Monolog; final class Utils { const DEFAULT_JSON_FLAGS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION | \JSON_INVALID_UTF8_SUBSTITUTE | \JSON_PARTIAL_OUTPUT_ON_ERROR; public static function getClass(object $object) : string { $class = \get_class($object); if (\false === ($pos = \strpos($class, "@anonymous\x00"))) { return $class; } if (\false === ($parent = \get_parent_class($class))) { return \substr($class, 0, $pos + 10); } return $parent . '@anonymous'; } public static function substr(string $string, int $start, ?int $length = null) : string { if (\extension_loaded('mbstring')) { return \mb_strcut($string, $start, $length); } return \substr($string, $start, null === $length ? \strlen($string) : $length); } /** * Makes sure if a relative path is passed in it is turned into an absolute path * * @param string $streamUrl stream URL or path without protocol */ public static function canonicalizePath(string $streamUrl) : string { $prefix = ''; if ('file://' === \substr($streamUrl, 0, 7)) { $streamUrl = \substr($streamUrl, 7); $prefix = 'file://'; } // other type of stream, not supported if (\false !== \strpos($streamUrl, '://')) { return $streamUrl; } // already absolute if (\substr($streamUrl, 0, 1) === '/' || \substr($streamUrl, 1, 1) === ':' || \substr($streamUrl, 0, 2) === '\\\\') { return $prefix . $streamUrl; } $streamUrl = \getcwd() . '/' . $streamUrl; return $prefix . $streamUrl; } /** * Return the JSON representation of a value * * @param mixed $data * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null * @throws \RuntimeException if encoding fails and errors are not ignored * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null */ public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = \false) : string { if (null === $encodeFlags) { $encodeFlags = self::DEFAULT_JSON_FLAGS; } if ($ignoreErrors) { $json = @\json_encode($data, $encodeFlags); if (\false === $json) { return 'null'; } return $json; } $json = \json_encode($data, $encodeFlags); if (\false === $json) { $json = self::handleJsonError(\json_last_error(), $data); } return $json; } /** * Handle a json_encode failure. * * If the failure is due to invalid string encoding, try to clean the * input and encode again. If the second encoding attempt fails, the * initial error is not encoding related or the input can't be cleaned then * raise a descriptive exception. * * @param int $code return code of json_last_error function * @param mixed $data data that was meant to be encoded * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION * @throws \RuntimeException if failure can't be corrected * @return string JSON encoded data after error correction */ public static function handleJsonError(int $code, $data, ?int $encodeFlags = null) : string { if ($code !== \JSON_ERROR_UTF8) { self::throwEncodeError($code, $data); } if (\is_string($data)) { self::detectAndCleanUtf8($data); } elseif (\is_array($data)) { \array_walk_recursive($data, array('Monolog\\Utils', 'detectAndCleanUtf8')); } else { self::throwEncodeError($code, $data); } if (null === $encodeFlags) { $encodeFlags = self::DEFAULT_JSON_FLAGS; } $json = \json_encode($data, $encodeFlags); if ($json === \false) { self::throwEncodeError(\json_last_error(), $data); } return $json; } /** * @internal */ public static function pcreLastErrorMessage(int $code) : string { if (\PHP_VERSION_ID >= 80000) { return \preg_last_error_msg(); } $constants = \get_defined_constants(\true)['pcre']; $constants = \array_filter($constants, function ($key) { return \substr($key, -6) == '_ERROR'; }, \ARRAY_FILTER_USE_KEY); $constants = \array_flip($constants); return $constants[$code] ?? 'UNDEFINED_ERROR'; } /** * Throws an exception according to a given code with a customized message * * @param int $code return code of json_last_error function * @param mixed $data data that was meant to be encoded * @throws \RuntimeException * * @return never */ private static function throwEncodeError(int $code, $data) : void { switch ($code) { case \JSON_ERROR_DEPTH: $msg = 'Maximum stack depth exceeded'; break; case \JSON_ERROR_STATE_MISMATCH: $msg = 'Underflow or the modes mismatch'; break; case \JSON_ERROR_CTRL_CHAR: $msg = 'Unexpected control character found'; break; case \JSON_ERROR_UTF8: $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; break; default: $msg = 'Unknown error'; } throw new \RuntimeException('JSON encoding failed: ' . $msg . '. Encoding: ' . \var_export($data, \true)); } /** * Detect invalid UTF-8 string characters and convert to valid UTF-8. * * Valid UTF-8 input will be left unmodified, but strings containing * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed * original encoding of ISO-8859-15. This conversion may result in * incorrect output if the actual encoding was not ISO-8859-15, but it * will be clean UTF-8 output and will not rely on expensive and fragile * detection algorithms. * * Function converts the input in place in the passed variable so that it * can be used as a callback for array_walk_recursive. * * @param mixed $data Input to check and convert if needed, passed by ref */ private static function detectAndCleanUtf8(&$data) : void { if (\is_string($data) && !\preg_match('//u', $data)) { $data = \preg_replace_callback('/[\\x80-\\xFF]+/', function ($m) { return \function_exists('mb_convert_encoding') ? \mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : \utf8_encode($m[0]); }, $data); if (!\is_string($data)) { $pcreErrorCode = \preg_last_error(); throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); } $data = \str_replace(['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], $data); } } /** * Converts a string with a valid 'memory_limit' format, to bytes. * * @param string|false $val * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. */ public static function expandIniShorthandBytes($val) { if (!\is_string($val)) { return \false; } // support -1 if ((int) $val < 0) { return (int) $val; } if (!\preg_match('/^\\s*(?\\d+)(?:\\.\\d+)?\\s*(?[gmk]?)\\s*$/i', $val, $match)) { return \false; } $val = (int) $match['val']; switch (\strtolower($match['unit'] ?? '')) { case 'g': $val *= 1024; case 'm': $val *= 1024; case 'k': $val *= 1024; } return $val; } /** * @param array $record */ public static function getRecordMessageForException(array $record) : string { $context = ''; $extra = ''; try { if ($record['context']) { $context = "\nContext: " . \json_encode($record['context']); } if ($record['extra']) { $extra = "\nExtra: " . \json_encode($record['extra']); } } catch (\Throwable $e) { // noop } return "\nThe exception occurred while attempting to log: " . $record['message'] . $context . $extra; } } The MIT License (MIT) Copyright (c) 2016 Studio 24 Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tests/src/ # Rotate Simple file rotation utility which rotates and removes old files or folders, useful where you cannot use logrotate (e.g. a Windows system) or you want to rotate or delete files based on a timestamp or date contained in the filename. ## Installation ```sh composer require studio24/rotate ``` ## Usage You can use Rotate in two modes: rotate (renames files and removes oldest files) or delete (deletes files according to a pattern). Import at the top of your PHP script via: ```sh use studio24\Rotate\Rotate; ``` ### Setting the filename format to match Both Rotate and Delete pass in the filename pattern you want to match for in the constructor or via the `setFilenameFormat()` method. This can be used to match a single file, files matching a pattern, or files with a datetime within the filename pattern. Rotate only works on files. Delete can also delete folders and recursively deletes any child files in that folder. #### Matches on leaf elements Please note files are matched on the last leaf element. All files in the parent folder are scanned, files (and folders for Delete) are checked to see whether they match the specified pattern. For example passing `path/to/*.log` will search for all files ending .log in the folder `path/to`. #### Filename patterns The following patterns are supported when matching files or folders: * `debug.log` - exact match for a file called debug.log * `*` - matches any string, for example `*.log` matches all files ending .log * `{Ymd}` - matches time segment in a file, for example `order.{Ymd}.log` matches a file in the format order.20160401.log #### Datetime formats For datetime formats, any date format supported by [DateTime::createFromFormat](http://php.net/datetime.createfromformat) is allowed excluding the Timezone identifier `e` and whitespace and separator characters. ### Deleting folders recursively You can also delete folders and all child files that match. This is, however, dangerous so by default no folders can be deleted. You need to explicitly add folder paths that are safe to delete via `Delete::addSafeRecursiveDeletePath($path)`. When using this function you need to use the full absolute path. Use `realpath()` to expand full paths if you need to. For example, if you want to delete all folders in `/var/www/test/staging/data/logs/` that are 1+ months old: ``` $rotate = new Delete('/var/www/test/staging/data/logs/old-logs/*'); $rotate->addSafeRecursiveDeletePath('/var/www/test/staging/data/logs/'); $files = $rotate->deleteByFilenameTime('1 month'); ``` The above code would allow you to delete folders within `/var/www/test/staging/data/logs/` only. ### Rotate Rotate log files in a similar manner to logrotate. The following example rotates the file debug.log, this renames debug.log to debug.log.1, debug.log.1 to debug.log.2, debug.log.2 to debug.log.3 and so on. It keeps 10 copies, so it deletes debug.log.10 and renames debug.log.9 to debug.log.10. ```sh use studio24\Rotate\Rotate; $rotate = new Rotate('path/to/debug.log'); $rotate->run(); ``` #### How many copies to keep Rotate keeps 10 copies of files by default, you can change this via: ``` $rotate->keep(20); ``` #### Rotated based on filesize You can only rotate files when they reach a certain filesize, rather than automatically rotate each time the `$rotate->run()` method is run. ``` $rotate->size("12MB"); ``` ### Delete Deletes files based on modification time, datetime in the filename, or based on a custom callback function. #### Time-based Deletes files based on the modification time. For example, to delete all JPG files in a folder over 3 months old: ```sh use studio24\Rotate\Delete; $rotate = new Delete('path/to/images/*.jpg'); $deletedFiles = $rotate->deleteByFileModifiedDate('3 months'); ``` The `deleteByFileModifiedDate()` method accepts either a valid DateInterval object or a relative date format as specified on [Relative Formats](http://php.net/manual/en/datetime.formats.relative.php). #### Time format in filename Deletes files based on the datetime in the filename. For example, to delete all order logfiles with a date in their filename over 3 months old: ```sh use studio24\Rotate\Delete; $rotate = new Delete('path/to/logs/orders.YYYYMMDD.log'); $deletedFiles = $rotate->deleteByFilenameTime('3 months'); ``` #### Based on a custom callback Deletes files based on a custom callback function, this is useful if you need to perform more complex code to assess whether a file should be deleted. For example, to delete all image files called 1000.jpg and below: ```sh use studio24\Rotate\Delete; use studio24\Rotate\DirectoryIterator; $rotate = new Delete('path/to/logs/*.jpg'); $deletedFiles = $rotate->deleteByCallback(function(DirectoryIterator $file){ if ($file->getBasename() <= 1000) { return true; } return false; }); ``` ##### DirectoryIterator The callback function must accept one parameter `$file`, which is of type `\studio24\Rotate\DirectoryIterator`. This iterator has the following methods in addition to the normal [SPL DirectoryIterator](http://php.net/DirectoryIterator). * `isMatch()` - whether the current file matches the filename patter we are looking for * `hasDate()` - whether the current file has a datetime within the filename * `getFilenameDate()` - return the datetime from the current file (as a DateTime object) ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. ## Credits - [Simon R Jones](https://github.com/simonrjones){ "name": "studio24\/rotate", "type": "library", "description": "File rotation utility which rotates and removes old files", "keywords": [ "rotate", "logrotate", "delete files" ], "homepage": "https:\/\/github.com\/studio24\/rotate", "license": "MIT", "authors": [ { "name": "Simon R Jones", "email": "hello@studio24.net", "homepage": "http:\/\/simonrjones.net", "role": "Developer" } ], "require": { "php": ">=5.6.0" }, "autoload": { "psr-4": { "_ContaoManager\\studio24\\Rotate\\": "src\/" } }, "require-dev": { "phpunit\/phpunit": "^5.2" } }keepNumber = $number; } /** * Return number of old copies to keep * * @return int */ public function getKeepNumber() { return $this->keepNumber; } /** * Set the filesize to rotate files on * * @param string $size Define as an number with a string suffix indicating the unit measurement, e.g. 5MB * @throws RotateException */ public function size($size) { if (!\preg_match('/^(\\d+)\\s?(B|KB|MB|GB)$/i', $size, $m)) { throw new RotateException("You must define size in the format 10B|KB|MB|GB"); } if ($m[1] === 0) { throw new RotateException("You must define a non-zero size to rotate files on"); } switch (\strtoupper($m[2])) { case 'B': $this->sizeToRotate = $m[1]; break; case 'KB': $this->sizeToRotate = $m[1] * 1024; break; case 'MB': $this->sizeToRotate = $m[1] * 1024 * 1024; break; case 'GB': $this->sizeToRotate = $m[1] * 1024 * 1024 * 1024; break; } } /** * Return filesize to rotate files on (in bytes) * * @return int */ public function getSizeToRotate() { return $this->sizeToRotate; } /** * Have we defined a filesize to rotate on? * * @return bool */ public function hasSizeToRotate() { return \is_int($this->getSizeToRotate()) && $this->getSizeToRotate() !== 0; } /** * Run the file rotation * * @return array Array of files which have been rotated * @throws FilenameFormatException * @throws RotateException */ public function run() { if (!$this->hasFilenameFormat()) { throw new FilenameFormatException('You must set a filename format to match files against'); } $rotated = []; $dir = new DirectoryIterator($this->getFilenameFormat()->getPath()); $dir->setFilenameFormat($this->getFilenameFormat()); foreach ($dir as $file) { if ($file->isFile() && $file->isMatch()) { // Skip if rotate size specified and initial matched file doesn't exceed this if ($this->hasSizeToRotate()) { if ($file->getSize() < $this->getSizeToRotate()) { continue; } } // Rotate files for ($x = $this->keepNumber; $x--; $x > 0) { $fileToRotate = $file->getPath() . '/' . $file->getRotatedFilename($x); if (!\file_exists($fileToRotate)) { continue; } if ($x === $this->keepNumber) { if (!$this->isDryRun()) { if (!\unlink($fileToRotate)) { throw new RotateException('Cannot delete file: ' . $file->getRotatedFilename($x)); } } $rotated[] = $fileToRotate; } else { if (!$this->isDryRun()) { if (!\rename($fileToRotate, $file->getPath() . '/' . $file->getRotatedFilename($x + 1))) { throw new RotateException('Cannot rotate file: ' . $file->getRotatedFilename($x)); } } $rotated[] = $fileToRotate; } } if (!$this->isDryRun()) { if (!\rename($file->getPath() . '/' . $file->getBasename(), $file->getPath() . '/' . $file->getRotatedFilename(1))) { throw new RotateException('Cannot rotate file: ' . $file->getBasename()); } } $rotated[] = $file->getPath() . '/' . $file->getBasename(); } } return $rotated; } } path = \dirname($filenameFormat); if (!\is_dir($this->path) || !\is_readable($this->path)) { throw new FilenameFormatException("Directory path does not exist or is not readable at: " . \strip_tags($filenameFormat)); } $this->filenamePattern = \basename($filenameFormat); $this->filenameRegex = $this->extractRegex($this->filenamePattern); } /** * Extract regex pattern from filename pattern * * * matches any string, for example *.log matches all files ending .log * {Ymd} = matches time segment in a file, for example order.{Ymd}.log matches a file in the format order.20160401.log * Any date format supported by DateTime::createFromFormat is allowed (excluding the Timezone identifier 'e' and whitespace and separator characters) * * @param string $filename * @return string Regex pattern for matching files (with the ungreedy modifier set) * @throws FilenameFormatException */ public function extractRegex($filename) { if (\strpos('/', $filename) !== \false) { throw new FilenameFormatException("Filename part cannot contain '/' character"); } $escape = ['/\\./' => '\\.', '/\\+/' => '\\+', '/\\:/' => '\\:', '/\\-/' => '\\-']; $pattern = \preg_replace(\array_keys($escape), \array_values($escape), $filename); $replacements = ['/\\*/' => '.+']; $pattern = \preg_replace(\array_keys($replacements), \array_values($replacements), $pattern); if (\preg_match('/{([^}]+)}/', $filename, $m)) { $dateFormat = $m[1]; $validDateFormat = 'djDlSzFMmnYyaAghGHisuOPTU'; if (!empty(\array_diff(\str_split($dateFormat), \str_split($validDateFormat)))) { throw new FilenameFormatException("Date format is not valid: {$dateFormat}"); } $this->dateFormat = $dateFormat; $pattern = \preg_replace('/{[^}]+}/', '([^.]+)', $pattern); } return '/^' . $pattern . '$/'; } /** * Return path to folder we're looking for files in * * @return string */ public function getPath() { return $this->path; } /** * Return filename pattern to match files * * @return string */ public function getFilenamePattern() { return $this->filenamePattern; } /** * Return regex to match files * * @return string */ public function getFilenameRegex() { return $this->filenameRegex; } /** * Does the filename pattern contain a date format? * * @return bool */ public function hasDateFormat() { return $this->dateFormat !== null; } /** * Return the date format * * @return string */ public function getDateFormat() { return $this->dateFormat; } } setFilenameFormat($filenameFormat); } } /** * Set the filename format we're matching * * @param string $filenameFormat Filename format to match files against */ public function setFilenameFormat($filenameFormat) { $this->filenameFormat = new FilenameFormat($filenameFormat); } /** * Return the filename format we're matching * * @return FilenameFormat */ public function getFilenameFormat() { return $this->filenameFormat; } /** * Does this object have a valid filename format set? * * @return bool */ public function hasFilenameFormat() { return $this->filenameFormat instanceof FilenameFormat; } /** * Set the dry run flag, which means we don't actually delete any files * * @param $dryRun */ public function setDryRun($dryRun) { $this->dryRun = (bool) $dryRun; } /** * Are we in dry-run mode? * * @return bool */ public function isDryRun() { return $this->dryRun; } } now = $dateTime; } /** * Return current time * * Defaults to current date, midnight * * @return DateTime */ public function getNow() { if (!$this->now instanceof DateTime) { $now = new DateTime(); $this->now = new DateTime($now->format('Y-m-d') . ' 00:00:00'); } return $this->now; } /** * Add a folder path it is safe to perform recursive delete on * * If you don't do this you cannot recursively delete a folder, this is for safety! * * @param string $path Folder path * @throws RotateException */ public function addSafeRecursiveDeletePath($path) { if (!\file_exists($path) || !\is_dir($path)) { throw new RotateException("Cannot set path as a safe recursive delete path since does not exist or is not a folder"); } $this->safeRecursiveDeletePaths[] = $path; } /** * Delete a file or a folder containing files * * If you try to delete a folder containing files, you must add the path via addSafeRecursiveDeletePath() * * @param $path * @return array Array of deleted files * @throws RotateException */ protected function delete($path) { if (\is_dir($path)) { return $this->recursiveDeleteFolder($path); } else { if (!\unlink($path)) { throw new RotateException('Cannot delete file: ' . $path); } return [$path]; } } /** * Recursively delete a folder and its contents * * Warning: this can be dangerous, you must add paths that are safe to perform recursive deletion on via addSafeRecursiveDeletePath() * otherwise this function will fail * * @param $folderPath * @return array Array of deleted files * @throws RotateException */ protected function recursiveDeleteFolder($folderPath) { $deleted = []; $folderPath = \realpath($folderPath); if (!\is_dir($folderPath)) { throw new RotateException("Path {$path} is not a folder"); } $safeToDelete = \false; foreach ($this->safeRecursiveDeletePaths as $safePath) { if (\preg_match('!^' . \preg_quote($safePath, '!') . '.+$!', $folderPath)) { $safeToDelete = \true; } } if (!$safeToDelete) { throw new RotateException("It is not safe to perform a recursive delete on path: {$folderPath}"); } $dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST); foreach ($dir as $file) { if (!$this->isDryRun()) { if ($file->isDir()) { if (!\rmdir($file->getPathname())) { throw new RotateException('Cannot delete folder: ' . $file->getPathname()); } } else { if (!\unlink($file->getPathname())) { throw new RotateException('Cannot delete file: ' . $file->getPathname()); } } } $deleted[] = $file->getPathname(); } if (!$this->isDryRun()) { \rmdir($folderPath); $deleted[] = $folderPath; } unset($dir, $file); return $deleted; } /** * Delete files by the filename time stored within the filename itself * * For example delete all files with a filename date pattern of payment.2016-01-01.log over 3 months old * * @param mixed $timePeriod DateInterval object, or time interval supported by DateInterval::createFromDateString, e.g. 3 months * @return array Array of deleted files * @throws FilenameFormatException * @throws RotateException */ public function deleteByFilenameTime($timePeriod) { if (!$this->hasFilenameFormat()) { throw new FilenameFormatException('You must set a filename format to match files against'); } $deleted = []; if ($timePeriod instanceof DateInterval) { $interval = $timePeriod; } else { $interval = DateInterval::createFromDateString($timePeriod); } $oldestDate = clone $this->getNow(); $oldestDate = $oldestDate->sub($interval); $dir = new DirectoryIterator($this->getFilenameFormat()->getPath()); $dir->setFilenameFormat($this->getFilenameFormat()); foreach ($dir as $file) { if (!$file->isDot() && $file->isMatch()) { if ($file->getFilenameDate() < $oldestDate) { if (!$this->isDryRun()) { $results = $this->delete($file->getPathname()); $deleted = \array_merge($deleted, $results); } else { $deleted[] = $file->getPathname(); } } } } return $deleted; } /** * Delete files by the last modified time of the filename * * For example delete all files over 3 months old. * * @param mixed $timePeriod DateInterval object, or time interval supported by DateInterval::createFromDateString, e.g. 3 months * @return array Array of deleted files * @throws FilenameFormatException * @throws RotateException */ public function deleteByFileModifiedDate($timePeriod) { if (!$this->hasFilenameFormat()) { throw new FilenameFormatException('You must set a filename format to match files against'); } $deleted = []; if ($timePeriod instanceof DateInterval) { $interval = $timePeriod; } else { $interval = DateInterval::createFromDateString($timePeriod); } $oldestDate = clone $this->getNow(); $oldestDate = $oldestDate->sub($interval); $dir = new DirectoryIterator($this->getFilenameFormat()->getPath()); $dir->setFilenameFormat($this->getFilenameFormat()); foreach ($dir as $file) { if (!$file->isDot() && $file->isMatch()) { $fileDate = new DateTime(); $fileDate->setTimestamp($file->getMTime()); if ($fileDate < $oldestDate) { if (!$this->isDryRun()) { $results = $this->delete($file->getPathname()); $deleted = \array_merge($deleted, $results); } else { $deleted[] = $file->getPathname(); } } } } return $deleted; } /** * Delete files by a custom callback function * * For example, do a database lookup on the order ID stored within the filename to ensure the order has been completed, if so delete the file. * * Callback function must accept one parameter (DirectoryIterator $file) and must return true (delete file) or false (do not delete file) * * Example to delete all files over 5Mb in size: * $callback = function(DirectoryIterator $file) { * if ($file->getSize() > 5 * 1024 * 1024) { * return true; * } else { * return false; * } * } * * @param callable $callback * @return array Array of deleted files * @throws FilenameFormatException * @throws RotateException */ public function deleteByCallback(callable $callback) { if (!$this->hasFilenameFormat()) { throw new FilenameFormatException('You must set a filename format to match files against'); } $deleted = []; $dir = new DirectoryIterator($this->getFilenameFormat()->getPath()); $dir->setFilenameFormat($this->getFilenameFormat()); foreach ($dir as $file) { if (!$file->isDot() && $file->isMatch()) { if ($callback($file)) { if (!$this->isDryRun()) { $results = $this->delete($file->getPathname()); $deleted = \array_merge($deleted, $results); } else { $deleted[] = $file->getPathname(); } } } } return $deleted; } } filenameFormat = $filenameFormat; } /** * Is the current filename a match for the filename format we're looking for? * * @return bool */ public function isMatch() { return (bool) \preg_match($this->filenameFormat->getFilenameRegex(), $this->getFilename()); } /** * Does the filename format we're looking for contain a date? * * @return bool */ public function hasDate() { return $this->filenameFormat->hasDateFormat(); } /** * Return date contained within the current filename * * @return DateTime or false on failure */ public function getFilenameDate() { if (!$this->hasDate()) { return \false; } if (\preg_match($this->filenameFormat->getFilenameRegex(), $this->getFilename(), $m)) { return \DateTime::createFromFormat($this->filenameFormat->getDateFormat(), $m[1]); } return \false; } /** * Return rotated filename * * @param int $num Rotation number * @return string */ public function getRotatedFilename($num) { return $this->getBasename() . '.' . $num; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollector; /** * @author Laurent VOULLEMIER */ abstract class AbstractDataCollector extends DataCollector implements TemplateAwareDataCollectorInterface { public function getName() : string { return static::class; } public function reset() : void { $this->data = []; } public static function getTemplate() : ?string { return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DataCollector; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\RedirectController; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouterDataCollector; /** * @author Fabien Potencier * * @final */ class RouterDataCollector extends BaseRouterDataCollector { public function guessRoute(Request $request, $controller) { if (\is_array($controller)) { $controller = $controller[0]; } if ($controller instanceof RedirectController) { return $request->attributes->get('_route'); } return parent::guessRoute($request, $controller); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; /** * @author Laurent VOULLEMIER */ interface TemplateAwareDataCollectorInterface extends DataCollectorInterface { public static function getTemplate() : ?string; } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\PHPUnit\Framework\TestCase; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * KernelTestCase is the base class for tests needing a Kernel. * * @author Fabien Potencier */ abstract class KernelTestCase extends TestCase { use MailerAssertionsTrait; protected static $class; /** * @var KernelInterface */ protected static $kernel; /** * @var ContainerInterface * * @deprecated since Symfony 5.3, use static::getContainer() instead */ protected static $container; protected static $booted = \false; protected function tearDown() : void { static::ensureKernelShutdown(); static::$class = null; static::$kernel = null; static::$booted = \false; } /** * @return string * * @throws \RuntimeException * @throws \LogicException */ protected static function getKernelClass() { if (!isset($_SERVER['KERNEL_CLASS']) && !isset($_ENV['KERNEL_CLASS'])) { throw new \LogicException(\sprintf('You must set the KERNEL_CLASS environment variable to the fully-qualified class name of your Kernel in phpunit.xml / phpunit.xml.dist or override the "%1$s::createKernel()" or "%1$s::getKernelClass()" method.', static::class)); } if (!\class_exists($class = $_ENV['KERNEL_CLASS'] ?? $_SERVER['KERNEL_CLASS'])) { throw new \RuntimeException(\sprintf('Class "%s" doesn\'t exist or cannot be autoloaded. Check that the KERNEL_CLASS value in phpunit.xml matches the fully-qualified class name of your Kernel or override the "%s::createKernel()" method.', $class, static::class)); } return $class; } /** * Boots the Kernel for this test. * * @return KernelInterface */ protected static function bootKernel(array $options = []) { static::ensureKernelShutdown(); $kernel = static::createKernel($options); $kernel->boot(); static::$kernel = $kernel; static::$booted = \true; $container = static::$kernel->getContainer(); static::$container = $container->has('test.service_container') ? $container->get('test.service_container') : $container; return static::$kernel; } /** * Provides a dedicated test container with access to both public and private * services. The container will not include private services that have been * inlined or removed. Private services will be removed when they are not * used by other services. * * Using this method is the best way to get a container from your test code. */ protected static function getContainer() : ContainerInterface { if (!static::$booted) { static::bootKernel(); } try { return self::$kernel->getContainer()->get('test.service_container'); } catch (ServiceNotFoundException $e) { throw new \LogicException('Could not find service "test.service_container". Try updating the "framework.test" config to "true".', 0, $e); } } /** * Creates a Kernel. * * Available options: * * * environment * * debug * * @return KernelInterface */ protected static function createKernel(array $options = []) { if (null === static::$class) { static::$class = static::getKernelClass(); } if (isset($options['environment'])) { $env = $options['environment']; } elseif (isset($_ENV['APP_ENV'])) { $env = $_ENV['APP_ENV']; } elseif (isset($_SERVER['APP_ENV'])) { $env = $_SERVER['APP_ENV']; } else { $env = 'test'; } if (isset($options['debug'])) { $debug = $options['debug']; } elseif (isset($_ENV['APP_DEBUG'])) { $debug = $_ENV['APP_DEBUG']; } elseif (isset($_SERVER['APP_DEBUG'])) { $debug = $_SERVER['APP_DEBUG']; } else { $debug = \true; } return new static::$class($env, $debug); } /** * Shuts the kernel down if it was used in the test - called by the tearDown method by default. */ protected static function ensureKernelShutdown() { if (null !== static::$kernel) { static::$kernel->boot(); $container = static::$kernel->getContainer(); static::$kernel->shutdown(); static::$booted = \false; if ($container instanceof ResetInterface) { $container->reset(); } } static::$container = null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\PHPUnit\Framework\Constraint\LogicalAnd; use _ContaoManager\PHPUnit\Framework\Constraint\LogicalNot; use _ContaoManager\Symfony\Component\DomCrawler\Crawler; use _ContaoManager\Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint; use _ContaoManager\Symfony\Component\DomCrawler\Test\Constraint\CrawlerSelectorExists; /** * Ideas borrowed from Laravel Dusk's assertions. * * @see https://laravel.com/docs/5.7/dusk#available-assertions */ trait DomCrawlerAssertionsTrait { public static function assertSelectorExists(string $selector, string $message = '') : void { self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message); } public static function assertSelectorNotExists(string $selector, string $message = '') : void { self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); } public static function assertSelectorTextContains(string $selector, string $text, string $message = '') : void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(new DomCrawlerConstraint\CrawlerSelectorExists($selector), new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)), $message); } public static function assertSelectorTextSame(string $selector, string $text, string $message = '') : void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(new DomCrawlerConstraint\CrawlerSelectorExists($selector), new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text)), $message); } public static function assertSelectorTextNotContains(string $selector, string $text, string $message = '') : void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(new DomCrawlerConstraint\CrawlerSelectorExists($selector), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text))), $message); } public static function assertPageTitleSame(string $expectedTitle, string $message = '') : void { self::assertSelectorTextSame('title', $expectedTitle, $message); } public static function assertPageTitleContains(string $expectedTitle, string $message = '') : void { self::assertSelectorTextContains('title', $expectedTitle, $message); } public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = '') : void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"{$fieldName}\"]"), new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"{$fieldName}\"]", 'value', $expectedValue)), $message); } public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = '') : void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"{$fieldName}\"]"), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"{$fieldName}\"]", 'value', $expectedValue))), $message); } public static function assertCheckboxChecked(string $fieldName, string $message = '') : void { self::assertThat(self::getCrawler(), new CrawlerSelectorExists("input[name=\"{$fieldName}\"]:checked"), $message); } public static function assertCheckboxNotChecked(string $fieldName, string $message = '') : void { self::assertThat(self::getCrawler(), new LogicalNot(new CrawlerSelectorExists("input[name=\"{$fieldName}\"]:checked")), $message); } public static function assertFormValue(string $formSelector, string $fieldName, string $value, string $message = '') : void { $node = self::getCrawler()->filter($formSelector); self::assertNotEmpty($node, \sprintf('Form "%s" not found.', $formSelector)); $values = $node->form()->getValues(); self::assertArrayHasKey($fieldName, $values, $message ?: \sprintf('Field "%s" not found in form "%s".', $fieldName, $formSelector)); self::assertSame($value, $values[$fieldName]); } public static function assertNoFormValue(string $formSelector, string $fieldName, string $message = '') : void { $node = self::getCrawler()->filter($formSelector); self::assertNotEmpty($node, \sprintf('Form "%s" not found.', $formSelector)); $values = $node->form()->getValues(); self::assertArrayNotHasKey($fieldName, $values, $message ?: \sprintf('Field "%s" has a value in form "%s".', $fieldName, $formSelector)); } private static function getCrawler() : Crawler { if (!($crawler = self::getClient()->getCrawler())) { static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?'); } return $crawler; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\PHPUnit\Framework\Constraint\LogicalNot; use _ContaoManager\Symfony\Component\Mailer\Event\MessageEvent; use _ContaoManager\Symfony\Component\Mailer\Event\MessageEvents; use _ContaoManager\Symfony\Component\Mailer\Test\Constraint as MailerConstraint; use _ContaoManager\Symfony\Component\Mime\RawMessage; use _ContaoManager\Symfony\Component\Mime\Test\Constraint as MimeConstraint; trait MailerAssertionsTrait { public static function assertEmailCount(int $count, ?string $transport = null, string $message = '') : void { self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport), $message); } public static function assertQueuedEmailCount(int $count, ?string $transport = null, string $message = '') : void { self::assertThat(self::getMessageMailerEvents(), new MailerConstraint\EmailCount($count, $transport, \true), $message); } public static function assertEmailIsQueued(MessageEvent $event, string $message = '') : void { self::assertThat($event, new MailerConstraint\EmailIsQueued(), $message); } public static function assertEmailIsNotQueued(MessageEvent $event, string $message = '') : void { self::assertThat($event, new LogicalNot(new MailerConstraint\EmailIsQueued()), $message); } public static function assertEmailAttachmentCount(RawMessage $email, int $count, string $message = '') : void { self::assertThat($email, new MimeConstraint\EmailAttachmentCount($count), $message); } public static function assertEmailTextBodyContains(RawMessage $email, string $text, string $message = '') : void { self::assertThat($email, new MimeConstraint\EmailTextBodyContains($text), $message); } public static function assertEmailTextBodyNotContains(RawMessage $email, string $text, string $message = '') : void { self::assertThat($email, new LogicalNot(new MimeConstraint\EmailTextBodyContains($text)), $message); } public static function assertEmailHtmlBodyContains(RawMessage $email, string $text, string $message = '') : void { self::assertThat($email, new MimeConstraint\EmailHtmlBodyContains($text), $message); } public static function assertEmailHtmlBodyNotContains(RawMessage $email, string $text, string $message = '') : void { self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHtmlBodyContains($text)), $message); } public static function assertEmailHasHeader(RawMessage $email, string $headerName, string $message = '') : void { self::assertThat($email, new MimeConstraint\EmailHasHeader($headerName), $message); } public static function assertEmailNotHasHeader(RawMessage $email, string $headerName, string $message = '') : void { self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHasHeader($headerName)), $message); } public static function assertEmailHeaderSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '') : void { self::assertThat($email, new MimeConstraint\EmailHeaderSame($headerName, $expectedValue), $message); } public static function assertEmailHeaderNotSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '') : void { self::assertThat($email, new LogicalNot(new MimeConstraint\EmailHeaderSame($headerName, $expectedValue)), $message); } public static function assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '') : void { self::assertThat($email, new MimeConstraint\EmailAddressContains($headerName, $expectedValue), $message); } /** * @return MessageEvent[] */ public static function getMailerEvents(?string $transport = null) : array { return self::getMessageMailerEvents()->getEvents($transport); } public static function getMailerEvent(int $index = 0, ?string $transport = null) : ?MessageEvent { return self::getMailerEvents($transport)[$index] ?? null; } /** * @return RawMessage[] */ public static function getMailerMessages(?string $transport = null) : array { return self::getMessageMailerEvents()->getMessages($transport); } public static function getMailerMessage(int $index = 0, ?string $transport = null) : ?RawMessage { return self::getMailerMessages($transport)[$index] ?? null; } private static function getMessageMailerEvents() : MessageEvents { $container = static::getContainer(); if ($container->has('mailer.message_logger_listener')) { return $container->get('mailer.message_logger_listener')->getEvents(); } if ($container->has('mailer.logger_message_listener')) { return $container->get('mailer.logger_message_listener')->getEvents(); } static::fail('A client must have Mailer enabled to make email assertions. Did you forget to require symfony/mailer?'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\LogicalAnd; use _ContaoManager\PHPUnit\Framework\Constraint\LogicalNot; use _ContaoManager\PHPUnit\Framework\ExpectationFailedException; use _ContaoManager\Symfony\Component\BrowserKit\AbstractBrowser; use _ContaoManager\Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint; /** * Ideas borrowed from Laravel Dusk's assertions. * * @see https://laravel.com/docs/5.7/dusk#available-assertions */ trait BrowserKitAssertionsTrait { public static function assertResponseIsSuccessful(string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful(), $message); } public static function assertResponseStatusCodeSame(int $expectedCode, string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message); } public static function assertResponseFormatSame(?string $expectedFormat, string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); } public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '') : void { $constraint = new ResponseConstraint\ResponseIsRedirected(); if ($expectedLocation) { $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation)); } if ($expectedCode) { $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode)); } self::assertThatForResponse($constraint, $message); } public static function assertResponseHasHeader(string $headerName, string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseHasHeader($headerName), $message); } public static function assertResponseNotHasHeader(string $headerName, string $message = '') : void { self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message); } public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message); } public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = '') : void { self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message); } public static function assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message); } public static function assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '') : void { self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message); } public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = '') : void { self::assertThatForResponse(LogicalAnd::fromConstraints(new ResponseConstraint\ResponseHasCookie($name, $path, $domain), new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain)), $message); } public static function assertResponseIsUnprocessable(string $message = '') : void { self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable(), $message); } public static function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '') : void { self::assertThatForClient(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message); } public static function assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '') : void { self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); } public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = \false, string $path = '/', ?string $domain = null, string $message = '') : void { self::assertThatForClient(LogicalAnd::fromConstraints(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain)), $message); } public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = '') : void { self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message); } public static function assertRouteSame(string $expectedRoute, array $parameters = [], string $message = '') : void { $constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute); $constraints = []; foreach ($parameters as $key => $value) { $constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value); } if ($constraints) { $constraint = LogicalAnd::fromConstraints($constraint, ...$constraints); } self::assertThat(self::getRequest(), $constraint, $message); } public static function assertThatForResponse(Constraint $constraint, string $message = '') : void { try { self::assertThat(self::getResponse(), $constraint, $message); } catch (ExpectationFailedException $exception) { if (($serverExceptionMessage = self::getResponse()->headers->get('X-Debug-Exception')) && ($serverExceptionFile = self::getResponse()->headers->get('X-Debug-Exception-File'))) { $serverExceptionFile = \explode(':', $serverExceptionFile); $exception->__construct($exception->getMessage(), $exception->getComparisonFailure(), new \ErrorException(\rawurldecode($serverExceptionMessage), 0, 1, \rawurldecode($serverExceptionFile[0]), $serverExceptionFile[1]), $exception->getPrevious()); } throw $exception; } } public static function assertThatForClient(Constraint $constraint, string $message = '') : void { self::assertThat(self::getClient(), $constraint, $message); } private static function getClient(?AbstractBrowser $newClient = null) : ?AbstractBrowser { static $client; if (0 < \func_num_args()) { return $client = $newClient; } if (!$client instanceof AbstractBrowser) { static::fail(\sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__)); } return $client; } private static function getResponse() : Response { if (!($response = self::getClient()->getResponse())) { static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?'); } return $response; } private static function getRequest() : Request { if (!($request = self::getClient()->getRequest())) { static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?'); } return $request; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * A very limited token that is used to login in tests using the KernelBrowser. * * @author Wouter de Jong */ class TestBrowserToken extends AbstractToken { private $firewallName; public function __construct(array $roles = [], ?UserInterface $user = null, string $firewallName = 'main') { parent::__construct($roles); if (null !== $user) { $this->setUser($user); } $this->firewallName = $firewallName; } public function getFirewallName() : string { return $this->firewallName; } public function getCredentials() { return null; } public function __serialize() : array { return [$this->firewallName, parent::__serialize()]; } public function __unserialize(array $data) : void { [$this->firewallName, $parentData] = $data; parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\Symfony\Bundle\FrameworkBundle\KernelBrowser; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; /** * WebTestCase is the base class for functional tests. * * @author Fabien Potencier */ abstract class WebTestCase extends KernelTestCase { use WebTestAssertionsTrait; protected function tearDown() : void { parent::tearDown(); self::getClient(null); } /** * Creates a KernelBrowser. * * @param array $options An array of options to pass to the createKernel method * @param array $server An array of server parameters * * @return KernelBrowser */ protected static function createClient(array $options = [], array $server = []) { if (static::$booted) { throw new \LogicException(\sprintf('Booting the kernel before calling "%s()" is not supported, the kernel should only be booted once.', __METHOD__)); } $kernel = static::bootKernel($options); try { $client = $kernel->getContainer()->get('test.client'); } catch (ServiceNotFoundException $e) { if (\class_exists(KernelBrowser::class)) { throw new \LogicException('You cannot create the client used in functional tests if the "framework.test" config is not set to true.'); } throw new \LogicException('You cannot create the client used in functional tests if the BrowserKit component is not available. Try running "composer require symfony/browser-kit".'); } $client->setServerParameters($server); return self::getClient($client); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; trait WebTestAssertionsTrait { use BrowserKitAssertionsTrait; use DomCrawlerAssertionsTrait; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Test; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * A special container used in tests. This gives access to both public and * private services. The container will not include private services that has * been inlined or removed. Private services will be removed when they are not * used by other services. * * @author Nicolas Grekas * * @internal */ class TestContainer extends Container { private $kernel; private $privateServicesLocatorId; public function __construct(KernelInterface $kernel, string $privateServicesLocatorId) { $this->kernel = $kernel; $this->privateServicesLocatorId = $privateServicesLocatorId; } /** * {@inheritdoc} */ public function compile() { $this->getPublicContainer()->compile(); } /** * {@inheritdoc} */ public function isCompiled() : bool { return $this->getPublicContainer()->isCompiled(); } /** * {@inheritdoc} */ public function getParameterBag() : ParameterBagInterface { return $this->getPublicContainer()->getParameterBag(); } /** * {@inheritdoc} * * @return array|bool|float|int|string|\UnitEnum|null */ public function getParameter(string $name) { return $this->getPublicContainer()->getParameter($name); } /** * {@inheritdoc} */ public function hasParameter(string $name) : bool { return $this->getPublicContainer()->hasParameter($name); } /** * {@inheritdoc} */ public function setParameter(string $name, $value) { $this->getPublicContainer()->setParameter($name, $value); } /** * {@inheritdoc} */ public function set(string $id, $service) { $this->getPublicContainer()->set($id, $service); } /** * {@inheritdoc} */ public function has(string $id) : bool { return $this->getPublicContainer()->has($id) || $this->getPrivateContainer()->has($id); } /** * {@inheritdoc} */ public function get(string $id, int $invalidBehavior = 1) : ?object { return $this->getPrivateContainer()->has($id) ? $this->getPrivateContainer()->get($id) : $this->getPublicContainer()->get($id, $invalidBehavior); } /** * {@inheritdoc} */ public function initialized(string $id) : bool { return $this->getPublicContainer()->initialized($id); } /** * {@inheritdoc} */ public function reset() { // ignore the call } /** * {@inheritdoc} */ public function getServiceIds() : array { return $this->getPublicContainer()->getServiceIds(); } /** * {@inheritdoc} */ public function getRemovedIds() : array { return $this->getPublicContainer()->getRemovedIds(); } private function getPublicContainer() : Container { if (null === ($container = $this->kernel->getContainer())) { throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); } return $container; } private function getPrivateContainer() : ContainerInterface { return $this->getPublicContainer()->get($this->privateServicesLocatorId); } } CHANGELOG ========= 5.4 --- * Add `set_locale_from_accept_language` config option to automatically set the request locale based on the `Accept-Language` HTTP request header and the `framework.enabled_locales` config option * Add `set_content_language_from_locale` config option to automatically set the `Content-Language` HTTP response header based on the Request locale * Deprecate the `framework.translator.enabled_locales`, use `framework.enabled_locales` instead * Add autowiring alias for `HttpCache\StoreInterface` * Add the ability to enable the profiler using a request query parameter, body parameter or attribute * Deprecate the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead * Deprecate the public `profiler` service to private * Deprecate `get()`, `has()`, `getDoctrine()`, and `dispatchMessage()` in `AbstractController`, use method/constructor injection instead * Deprecate the `cache.adapter.doctrine` service * Add support for resetting container services after each messenger message * Add `configureContainer()`, `configureRoutes()`, `getConfigDir()` and `getBundlesPath()` to `MicroKernelTrait` * Add support for configuring log level, and status code by exception class * Bind the `default_context` parameter onto serializer's encoders and normalizers * Add support for `statusCode` default parameter when loading a template directly from route using the `Symfony\Bundle\FrameworkBundle\Controller\TemplateController` controller * Deprecate `translation:update` command, use `translation:extract` instead * Add `PhpStanExtractor` support for the PropertyInfo component * Add `cache.adapter.doctrine_dbal` service to replace `cache.adapter.pdo` when a Doctrine DBAL connection is used. 5.3 --- * Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead * Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead * Deprecate the `session` service and the `SessionInterface` alias, use the `Request::getSession()` or the new `RequestStack::getSession()` methods instead * Add `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code * Add support for configuring PHP error level to log levels * Add the `dispatcher` option to `debug:event-dispatcher` * Add the `event_dispatcher.dispatcher` tag * Add `assertResponseFormatSame()` in `BrowserKitAssertionsTrait` * Add support for configuring UUID factory services * Add tag `assets.package` to register asset packages * Add support to use a PSR-6 compatible cache for Doctrine annotations * Deprecate all other values than "none", "php_array" and "file" for `framework.annotation.cache` * Add `KernelTestCase::getContainer()` as the best way to get a container in tests * Rename the container parameter `profiler_listener.only_master_requests` to `profiler_listener.only_main_requests` * Add service `fragment.uri_generator` to generate the URI of a fragment * Deprecate registering workflow services as public * Deprecate option `--xliff-version` of the `translation:update` command, use e.g. `--format=xlf20` instead * Deprecate option `--output-format` of the `translation:update` command, use e.g. `--format=xlf20` instead 5.2.0 ----- * Added `framework.http_cache` configuration tree * Added `framework.trusted_proxies` and `framework.trusted_headers` configuration options * Deprecated the public `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`, `cache_clearer`, `filesystem` and `validator` services to private. * Added `TemplateAwareDataCollectorInterface` and `AbstractDataCollector` to simplify custom data collector creation and leverage autoconfiguration * Add `cache.adapter.redis_tag_aware` tag to use `RedisCacheAwareAdapter` * added `framework.http_client.retry_failing` configuration tree * added `assertCheckboxChecked()` and `assertCheckboxNotChecked()` in `WebTestCase` * added `assertFormValue()` and `assertNoFormValue()` in `WebTestCase` * Added "--as-tree=3" option to `translation:update` command to dump messages as a tree-like structure. The given value defines the level where to switch to inline YAML * Deprecated the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead. 5.1.0 ----- * Removed `--no-backup` option from `translation:update` command (broken since `5.0.0`) * Added link to source for controllers registered as named services * Added link to source on controller on `router:match`/`debug:router` (when `framework.ide` is configured) * Added the `framework.router.default_uri` configuration option to configure the default `RequestContext` * Made `MicroKernelTrait::configureContainer()` compatible with `ContainerConfigurator` * Added a new `mailer.message_bus` option to configure or disable the message bus to use to send mails. * Added flex-compatible default implementation for `MicroKernelTrait::registerBundles()` * Deprecated passing a `RouteCollectionBuilder` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead * The `TemplateController` now accepts context argument * Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0 * Added tag `routing.expression_language_function` to define functions available in route conditions * Added `debug:container --deprecations` option to see compile-time deprecations. * Made `BrowserKitAssertionsTrait` report the original error message in case of a failure * Added ability for `config:dump-reference` and `debug:config` to dump and debug kernel container extension configuration. * Deprecated `session.attribute_bag` service and `session.flash_bag` service. 5.0.0 ----- * Removed support to load translation resources from the legacy directories `src/Resources/translations/` and `src/Resources//translations/` * Removed `ControllerNameParser`. * Removed `ResolveControllerNameSubscriber` * Removed support for `bundle:controller:action` to reference controllers. Use `serviceOrFqcn::method` instead * Removed support for PHP templating, use Twig instead * Removed `Controller`, use `AbstractController` instead * Removed `Client`, use `KernelBrowser` instead * Removed `ContainerAwareCommand`, use dependency injection instead * Removed the `validation.strict_email` option, use `validation.email_validation_mode` instead * Removed the `cache.app.simple` service and its corresponding PSR-16 autowiring alias * Removed cache-related compiler passes and `RequestDataCollector` * Removed the `translator.selector` and `session.save_listener` services * Removed `SecurityUserValueResolver`, use `UserValueResolver` instead * Removed `routing.loader.service`. * Service route loaders must be tagged with `routing.route_loader`. * Added `slugger` service and `SluggerInterface` alias * Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services. * Removed the `router.cache_class_prefix` parameter. 4.4.0 ----- * Added `lint:container` command to check that services wiring matches type declarations * Added `MailerAssertionsTrait` * Deprecated support for `templating` engine in `TemplateController`, use Twig instead * Deprecated the `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()` * Deprecated the `controller_name_converter` and `resolve_controller_name_subscriber` services * The `ControllerResolver` and `DelegatingLoader` classes have been marked as `final` * Added support for configuring chained cache pools * Deprecated calling `WebTestCase::createClient()` while a kernel has been booted, ensure the kernel is shut down before calling the method * Deprecated `routing.loader.service`, use `routing.loader.container` instead. * Not tagging service route loaders with `routing.route_loader` has been deprecated. * Overriding the methods `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` without the `void` return-type is deprecated. * Added new `error_controller` configuration to handle system exceptions * Added sort option for `translation:update` command. * [BC Break] The `framework.messenger.routing.senders` config key is not deeply merged anymore. * Added `secrets:*` commands to deal with secrets seamlessly. * Made `framework.session.handler_id` accept a DSN * Marked the `RouterDataCollector` class as `@final`. * [BC Break] The `framework.messenger.buses..middleware` config key is not deeply merged anymore. * Moved `MailerAssertionsTrait` in `KernelTestCase` 4.3.0 ----- * Deprecated the `framework.templating` option, configure the Twig bundle instead. * Added `WebTestAssertionsTrait` (included by default in `WebTestCase`) * Renamed `Client` to `KernelBrowser` * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will be mandatory in 5.0. * Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead * Added the ability to specify a custom `serializer` option for each transport under`framework.messenger.transports`. * Added the `RegisterLocaleAwareServicesPass` and configured the `LocaleAwareListener` * [BC Break] When using Messenger, the default transport changed from using Symfony's serializer service to use `PhpSerializer`, which uses PHP's native `serialize()` and `unserialize()` functions. To use the original serialization method, set the `framework.messenger.default_serializer` config option to `messenger.transport.symfony_serializer`. Or set the `serializer` option under one specific `transport`. * [BC Break] The `framework.messenger.serializer` config key changed to `framework.messenger.default_serializer`, which holds the string service id and `framework.messenger.symfony_serializer`, which configures the options if you're using Symfony's serializer. * [BC Break] Removed the `framework.messenger.routing.send_and_handle` configuration. Instead of setting it to true, configure a `SyncTransport` and route messages to it. * Added information about deprecated aliases in `debug:autowiring` * Added php ini session options `sid_length` and `sid_bits_per_character` to the `session` section of the configuration * Added support for Translator paths, Twig paths in translation commands. * Added support for PHP files with translations in translation commands. * Added support for boolean container parameters within routes. * Added the `messenger:setup-transports` command to setup messenger transports * Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`. * Added `framework.property_access.throw_exception_on_invalid_property_path` config option. * Added `cache:pool:list` command to list all available cache pools. 4.2.0 ----- * Added a `AbstractController::addLink()` method to add Link headers to the current response * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) * Allowed configuring PDO-based cache pools via a new `cache.adapter.pdo` abstract service * Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead * Deprecated processing of services tagged `security.expression_language_provider` in favor of a new `AddExpressionLanguageProvidersPass` in SecurityBundle. * Deprecated the `Symfony\Bundle\FrameworkBundle\Controller\Controller` class in favor of `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. * Enabled autoconfiguration for `Psr\Log\LoggerAwareInterface` * Added new "auto" mode for `framework.session.cookie_secure` to turn it on when HTTPS is used * Removed the `framework.messenger.encoder` and `framework.messenger.decoder` options. Use the `framework.messenger.serializer.id` option to replace the Messenger serializer. * Deprecated the `ContainerAwareCommand` class in favor of `Symfony\Component\Console\Command\Command` * Made `debug:container` and `debug:autowiring` ignore backslashes in service ids * Deprecated the `Templating\Helper\TranslatorHelper::transChoice()` method, use the `trans()` one instead with a `%count%` parameter * Deprecated `CacheCollectorPass`. Use `Symfony\Component\Cache\DependencyInjection\CacheCollectorPass` instead. * Deprecated `CachePoolClearerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass` instead. * Deprecated `CachePoolPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPass` instead. * Deprecated `CachePoolPrunerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass` instead. * Deprecated support for legacy translations directories `src/Resources/translations/` and `src/Resources//translations/`, use `translations/` instead. * Deprecated support for the legacy directory structure in `translation:update` and `debug:translation` commands. 4.1.0 ----- * Allowed to pass an optional `LoggerInterface $logger` instance to the `Router` * Added a new `parameter_bag` service with related autowiring aliases to access parameters as-a-service * Allowed the `Router` to work with any PSR-11 container * Added option in workflow dump command to label graph with a custom label * Using a `RouterInterface` that does not implement the `WarmableInterface` is deprecated. * Warming up a router in `RouterCacheWarmer` that does not implement the `WarmableInterface` is deprecated and will not be supported anymore in 5.0. * The `RequestDataCollector` class has been deprecated. Use the `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector` class instead. * The `RedirectController` class allows for 307/308 HTTP status codes * Deprecated `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn` is either the service ID or the FQCN of the controller. * Deprecated `Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser` * The `container.service_locator` tag of `ServiceLocator`s is now autoconfigured. * Add the ability to search a route in `debug:router`. * Add the ability to use SameSite cookies for sessions. 4.0.0 ----- * The default `type` option of the `framework.workflows.*` configuration entries is `state_machine` * removed `AddConsoleCommandPass`, `AddConstraintValidatorsPass`, `AddValidatorInitializersPass`, `CompilerDebugDumpPass`, `ConfigCachePass`, `ControllerArgumentValueResolverPass`, `FormPass`, `PropertyInfoPass`, `RoutingResolverPass`, `SerializerPass`, `ValidateWorkflowsPass` * made `Translator::__construct()` `$defaultLocale` argument required * removed `SessionListener`, `TestSessionListener` * Removed `cache:clear` warmup part along with the `--no-optional-warmers` option * Removed core form types services registration when unnecessary * Removed `framework.serializer.cache` option and `serializer.mapping.cache.apc`, `serializer.mapping.cache.doctrine.apc` services * Removed `ConstraintValidatorFactory` * Removed class parameters related to routing * Removed absolute template paths support in the template name parser * Removed support of the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`. * Removed the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods. * Removed the "framework.validation.cache" configuration option. Configure the "cache.validator" service under "framework.cache.pools" instead. * Removed `PhpStringTokenParser`, use `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead. * Removed `PhpExtractor`, use `Symfony\Component\Translation\Extractor\PhpExtractor` instead. * Removed the `use_strict_mode` session option, it's is now enabled by default 3.4.0 ----- * Added `translator.default_path` option and parameter * Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated * Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore, but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead * Always register a minimalist logger that writes in `stderr` * Deprecated `profiler.matcher` option * Added support for `EventSubscriberInterface` on `MicroKernelTrait` * Removed `doctrine/cache` from the list of required dependencies in `composer.json` * Deprecated `validator.mapping.cache.doctrine.apc` service * The `symfony/stopwatch` dependency has been removed, require it via `composer require symfony/stopwatch` in your `dev` environment. * Deprecated using the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`. * Deprecated the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods. * Deprecated `AddCacheClearerPass`, use tagged iterator arguments instead. * Deprecated `AddCacheWarmerPass`, use tagged iterator arguments instead. * Deprecated `TranslationDumperPass`, use `Symfony\Component\Translation\DependencyInjection\TranslationDumperPass` instead * Deprecated `TranslationExtractorPass`, use `Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass` instead * Deprecated `TranslatorPass`, use `Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead * Added `command` attribute to the `console.command` tag which takes the command name as value, using it makes the command lazy * Added `cache:pool:prune` command to allow manual stale cache item pruning of supported PSR-6 and PSR-16 cache pool implementations * Deprecated `Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader`, use `Symfony\Component\Translation\Reader\TranslationReader` instead * Deprecated `translation.loader` service, use `translation.reader` instead * `AssetsInstallCommand::__construct()` now takes an instance of `Symfony\Component\Filesystem\Filesystem` as first argument * `CacheClearCommand::__construct()` now takes an instance of `Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface` as first argument * `CachePoolClearCommand::__construct()` now takes an instance of `Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer` as first argument * `EventDispatcherDebugCommand::__construct()` now takes an instance of `Symfony\Component\EventDispatcher\EventDispatcherInterface` as first argument * `RouterDebugCommand::__construct()` now takes an instance of `Symfony\Component\Routing\RouterInterface` as first argument * `RouterMatchCommand::__construct()` now takes an instance of `Symfony\Component\Routing\RouterInterface` as first argument * `TranslationDebugCommand::__construct()` now takes an instance of `Symfony\Component\Translation\TranslatorInterface` as first argument * `TranslationUpdateCommand::__construct()` now takes an instance of `Symfony\Component\Translation\TranslatorInterface` as first argument * `AssetsInstallCommand`, `CacheClearCommand`, `CachePoolClearCommand`, `EventDispatcherDebugCommand`, `RouterDebugCommand`, `RouterMatchCommand`, `TranslationDebugCommand`, `TranslationUpdateCommand`, `XliffLintCommand` and `YamlLintCommand` classes have been marked as final * Added `asset.request_context.base_path` and `asset.request_context.secure` parameters to provide a default request context in case the stack is empty (similar to `router.request_context.*` parameters) * Display environment variables managed by `Dotenv` in `AboutCommand` 3.3.0 ----- * Not defining the `type` option of the `framework.workflows.*` configuration entries is deprecated. The default value will be `state_machine` in Symfony 4.0. * Deprecated the `CompilerDebugDumpPass` class * Deprecated the "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter * Added a new version strategy option called "json_manifest_path" that allows you to use the `JsonManifestVersionStrategy`. * Added `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. It provides the same helpers as the `Controller` class, but does not allow accessing the dependency injection container, in order to encourage explicit dependency declarations. * Added support for the `controller.service_arguments` tag, for injecting services into controllers' actions * Changed default configuration for assets/forms/validation/translation/serialization/csrf from `canBeEnabled()` to `canBeDisabled()` when Flex is used * The server:* commands and their associated router files were moved to WebServerBundle * Translation related services are not loaded anymore when the `framework.translator` option is disabled. * Added `GlobalVariables::getToken()` * Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass`. Use `Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass` instead. * Added configurable paths for validation files * Deprecated `SerializerPass`, use `Symfony\Component\Serializer\DependencyInjection\SerializerPass` instead * Deprecated `FormPass`, use `Symfony\Component\Form\DependencyInjection\FormPass` instead * Deprecated `SessionListener` * Deprecated `TestSessionListener` * Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass`. Use tagged iterator arguments instead. * Deprecated `PropertyInfoPass`, use `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` instead * Deprecated `ControllerArgumentValueResolverPass`. Use `Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass` instead * Deprecated `RoutingResolverPass`, use `Symfony\Component\Routing\DependencyInjection\RoutingResolverPass` instead * [BC BREAK] The `server:run`, `server:start`, `server:stop` and `server:status` console commands have been moved to a dedicated bundle. Require `symfony/web-server-bundle` in your composer.json and register `Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them. * Added `$defaultLocale` as 3rd argument of `Translator::__construct()` making `Translator` works with any PSR-11 container * Added `framework.serializer.mapping` config option allowing to define custom serialization mapping files and directories * Deprecated `AddValidatorInitializersPass`, use `Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass` instead * Deprecated `AddConstraintValidatorsPass`, use `Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass` instead * Deprecated `ValidateWorkflowsPass`, use `Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass` instead * Deprecated `ConstraintValidatorFactory`, use `Symfony\Component\Validator\ContainerConstraintValidatorFactory` instead. * Deprecated `PhpStringTokenParser`, use `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead. * Deprecated `PhpExtractor`, use `Symfony\Component\Translation\Extractor\PhpExtractor` instead. 3.2.0 ----- * Removed `doctrine/annotations` from the list of required dependencies in `composer.json` * Removed `symfony/security-core` and `symfony/security-csrf` from the list of required dependencies in `composer.json` * Removed `symfony/templating` from the list of required dependencies in `composer.json` * Removed `symfony/translation` from the list of required dependencies in `composer.json` * Removed `symfony/asset` from the list of required dependencies in `composer.json` * The `Resources/public/images/*` files have been removed. * The `Resources/public/css/*.css` files have been removed (they are now inlined in TwigBundle). * Added possibility to prioritize form type extensions with `'priority'` attribute on tags `form.type_extension` 3.1.0 ----- * Added `Controller::json` to simplify creating JSON responses when using the Serializer component * Deprecated absolute template paths support in the template name parser * Deprecated using core form types without dependencies as services * Added `Symfony\Component\HttpHernel\DataCollector\RequestDataCollector::onKernelResponse()` * Added `Symfony\Bundle\FrameworkBundle\DataCollector\RequestDataCollector` * The `framework.serializer.cache` option and the service `serializer.mapping.cache.apc` have been deprecated. APCu should now be automatically used when available. 3.0.0 ----- * removed `validator.api` parameter * removed `alias` option of the `form.type` tag 2.8.0 ----- * Deprecated the `alias` option of the `form.type_extension` tag in favor of the `extended_type`/`extended-type` option * Deprecated the `alias` option of the `form.type` tag * Deprecated the Shell 2.7.0 ----- * Added possibility to extract translation messages from a file or files besides extracting from a directory * Added `TranslationsCacheWarmer` to create catalogues at warmup 2.6.0 ----- * Added helper commands (`server:start`, `server:stop` and `server:status`) to control the built-in web server in the background * Added `Controller::isCsrfTokenValid` helper * Added configuration for the PropertyAccess component * Added `Controller::redirectToRoute` helper * Added `Controller::addFlash` helper * Added `Controller::isGranted` helper * Added `Controller::denyAccessUnlessGranted` helper * Deprecated `app.security` in twig as `app.user` and `is_granted()` are already available 2.5.0 ----- * Added `translation:debug` command * Added `--no-backup` option to `translation:update` command * Added `config:debug` command * Added `yaml:lint` command * Deprecated the `RouterApacheDumperCommand` which will be removed in Symfony 3.0. 2.4.0 ----- * allowed multiple IP addresses in profiler matcher settings * added stopwatch helper to time templates with the WebProfilerBundle * added service definition for "security.secure_random" service * added service definitions for the new Security CSRF sub-component 2.3.0 ----- * [BC BREAK] added a way to disable the profiler (when disabling the profiler, it is now completely removed) To get the same "disabled" behavior as before, set `enabled` to `true` and `collect` to `false` * [BC BREAK] the `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass` was moved to `Component\HttpKernel\DependencyInjection\RegisterListenersPass` * added ControllerNameParser::build() which converts a controller short notation (a:b:c) to a class::method notation * added possibility to run PHP built-in server in production environment * added possibility to load the serializer component in the service container * added route debug information when using the `router:match` command * added `TimedPhpEngine` * added `--clean` option to the `translation:update` command * added `http_method_override` option * added support for default templates per render tag * added FormHelper::form(), FormHelper::start() and FormHelper::end() * deprecated FormHelper::enctype() in favor of FormHelper::start() * RedirectController actions now receive the Request instance via the method signature. 2.2.0 ----- * added a new `uri_signer` service to help sign URIs * deprecated `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` and `Symfony\Bundle\FrameworkBundle\HttpKernel::forward()` * deprecated the `Symfony\Bundle\FrameworkBundle\HttpKernel` class in favor of `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` * added support for adding new HTTP content rendering strategies (like ESI and Hinclude) in the DIC via the `kernel.fragment_renderer` tag * [BC BREAK] restricted the `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method to only accept URIs or ControllerReference instances * `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method signature changed and the first argument must now be a URI or a ControllerReference instance (the `generateInternalUri()` method was removed) * The internal routes (`Resources/config/routing/internal.xml`) have been removed and replaced with a listener (`Symfony\Component\HttpKernel\EventListener\FragmentListener`) * The `render` method of the `actions` templating helper signature and arguments changed * replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver * replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher * added Client::enableProfiler() * a new parameter has been added to the DIC: `router.request_context.base_url` You can customize it for your functional tests or for generating URLs with the right base URL when your are in the CLI context. * added support for default templates per render tag 2.1.0 ----- * moved the translation files to the Form and Validator components * changed the default extension for XLIFF files from .xliff to .xlf * moved Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher to Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher * moved Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher to Symfony\Component\EventDispatcher\ContainerAwareTraceableEventDispatcher * added a router:match command * added a config:dump-reference command * added a server:run command * added kernel.event_subscriber tag * added a way to create relative symlinks when running assets:install command (--relative option) * added Controller::getUser() * [BC BREAK] assets_base_urls and base_urls merging strategy has changed * changed the default profiler storage to use the filesystem instead of SQLite * added support for placeholders in route defaults and requirements (replaced by the value set in the service container) * added Filesystem component as a dependency * added support for hinclude (use ``standalone: 'js'`` in render tag) * session options: lifetime, path, domain, secure, httponly were deprecated. Prefixed versions should now be used instead: cookie_lifetime, cookie_path, cookie_domain, cookie_secure, cookie_httponly * [BC BREAK] following session options: 'lifetime', 'path', 'domain', 'secure', 'httponly' are now prefixed with cookie_ when dumped to the container * Added `handler_id` configuration under `session` key to represent `session.handler` service, defaults to `session.handler.native_file`. * Added `gc_maxlifetime`, `gc_probability`, and `gc_divisor` to session configuration. This means session garbage collection has a `gc_probability`/`gc_divisor` chance of being run. The `gc_maxlifetime` defines how long a session can idle for. It is different from cookie lifetime which declares how long a cookie can be stored on the remote client. * Removed 'auto_start' configuration parameter from session config. The session will start on demand. * [BC BREAK] TemplateNameParser::parseFromFilename() has been moved to a dedicated parser: TemplateFilenameParser::parse(). * [BC BREAK] Kernel parameters are replaced by their value wherever they appear in Route patterns, requirements and defaults. Use '%%' as the escaped value for '%'. * [BC BREAK] Switched behavior of flash messages to expire flash messages on retrieval using Symfony\Component\HttpFoundation\Session\Flash\FlashBag as opposed to on next pageload regardless of whether they are displayed or not. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Translation; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\Config\Resource\DirectoryResource; use _ContaoManager\Symfony\Component\Config\Resource\FileExistenceResource; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use _ContaoManager\Symfony\Component\Translation\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Translation\Formatter\MessageFormatterInterface; use _ContaoManager\Symfony\Component\Translation\Translator as BaseTranslator; /** * @author Fabien Potencier */ class Translator extends BaseTranslator implements WarmableInterface { protected $container; protected $loaderIds; protected $options = ['cache_dir' => null, 'debug' => \false, 'resource_files' => [], 'scanned_directories' => [], 'cache_vary' => []]; /** * @var list */ private $resourceLocales; /** * Holds parameters from addResource() calls so we can defer the actual * parent::addResource() calls until initialize() is executed. * * @var array[] */ private $resources = []; /** * @var string[][] */ private $resourceFiles; /** * @var string[] */ private $scannedDirectories; /** * @var string[] */ private $enabledLocales; /** * Constructor. * * Available options: * * * cache_dir: The cache directory (or null to disable caching) * * debug: Whether to enable debugging or not (false by default) * * resource_files: List of translation resources available grouped by locale. * * cache_vary: An array of data that is serialized to generate the cached catalogue name. * * @throws InvalidArgumentException */ public function __construct(ContainerInterface $container, MessageFormatterInterface $formatter, string $defaultLocale, array $loaderIds = [], array $options = [], array $enabledLocales = []) { $this->container = $container; $this->loaderIds = $loaderIds; $this->enabledLocales = $enabledLocales; // check option names if ($diff = \array_diff(\array_keys($options), \array_keys($this->options))) { throw new InvalidArgumentException(\sprintf('The Translator does not support the following options: \'%s\'.', \implode('\', \'', $diff))); } $this->options = \array_merge($this->options, $options); $this->resourceLocales = \array_keys($this->options['resource_files']); $this->resourceFiles = $this->options['resource_files']; $this->scannedDirectories = $this->options['scanned_directories']; parent::__construct($defaultLocale, $formatter, $this->options['cache_dir'], $this->options['debug'], $this->options['cache_vary']); } /** * {@inheritdoc} * * @return string[] */ public function warmUp(string $cacheDir) { // skip warmUp when translator doesn't use cache if (null === $this->options['cache_dir']) { return []; } $localesToWarmUp = $this->enabledLocales ?: \array_merge($this->getFallbackLocales(), [$this->getLocale()], $this->resourceLocales); foreach (\array_unique($localesToWarmUp) as $locale) { // reset catalogue in case it's already loaded during the dump of the other locales. if (isset($this->catalogues[$locale])) { unset($this->catalogues[$locale]); } $this->loadCatalogue($locale); } return []; } public function addResource(string $format, $resource, string $locale, ?string $domain = null) { if ($this->resourceFiles) { $this->addResourceFiles(); } $this->resources[] = [$format, $resource, $locale, $domain]; } /** * {@inheritdoc} */ protected function initializeCatalogue(string $locale) { $this->initialize(); parent::initializeCatalogue($locale); } /** * @internal */ protected function doLoadCatalogue(string $locale) : void { parent::doLoadCatalogue($locale); foreach ($this->scannedDirectories as $directory) { $resourceClass = \file_exists($directory) ? DirectoryResource::class : FileExistenceResource::class; $this->catalogues[$locale]->addResource(new $resourceClass($directory)); } } protected function initialize() { if ($this->resourceFiles) { $this->addResourceFiles(); } foreach ($this->resources as $params) { [$format, $resource, $locale, $domain] = $params; parent::addResource($format, $resource, $locale, $domain); } $this->resources = []; foreach ($this->loaderIds as $id => $aliases) { foreach ($aliases as $alias) { $this->addLoader($alias, $this->container->get($id)); } } } private function addResourceFiles() : void { $filesByLocale = $this->resourceFiles; $this->resourceFiles = []; foreach ($filesByLocale as $files) { foreach ($files as $file) { // filename is domain.locale.format $fileNameParts = \explode('.', \basename($file)); $format = \array_pop($fileNameParts); $locale = \array_pop($fileNameParts); $domain = \implode('.', $fileNameParts); $this->addResource($format, $file, $locale, $domain); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets; use _ContaoManager\Symfony\Component\DependencyInjection\EnvVarLoaderInterface; use _ContaoManager\Symfony\Component\VarExporter\VarExporter; /** * @author Tobias Schultze * @author Jérémy Derussé * @author Nicolas Grekas * * @internal */ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface { private $encryptionKey; private $decryptionKey; private $pathPrefix; private $secretsDir; /** * @param string|\Stringable|null $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault * or null to store generated keys in the provided $secretsDir */ public function __construct(string $secretsDir, $decryptionKey = null) { if (null !== $decryptionKey && !\is_string($decryptionKey) && !(\is_object($decryptionKey) && \method_exists($decryptionKey, '__toString'))) { throw new \TypeError(\sprintf('Decryption key should be a string or an object that implements the __toString() method, "%s" given.', \get_debug_type($decryptionKey))); } $this->pathPrefix = \rtrim(\strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR . \basename($secretsDir) . '.'; $this->decryptionKey = $decryptionKey; $this->secretsDir = $secretsDir; } public function generateKeys(bool $override = \false) : bool { $this->lastMessage = null; if (null === $this->encryptionKey && '' !== ($this->decryptionKey = (string) $this->decryptionKey)) { $this->lastMessage = 'Cannot generate keys when a decryption key has been provided while instantiating the vault.'; return \false; } try { $this->loadKeys(); } catch (\RuntimeException $e) { // ignore failures to load keys } if ('' !== $this->decryptionKey && !\is_file($this->pathPrefix . 'encrypt.public.php')) { $this->export('encrypt.public', $this->encryptionKey); } if (!$override && null !== $this->encryptionKey) { $this->lastMessage = \sprintf('Sodium keys already exist at "%s*.{public,private}" and won\'t be overridden.', $this->getPrettyPath($this->pathPrefix)); return \false; } $this->decryptionKey = \sodium_crypto_box_keypair(); $this->encryptionKey = \sodium_crypto_box_publickey($this->decryptionKey); $this->export('encrypt.public', $this->encryptionKey); $this->export('decrypt.private', $this->decryptionKey); $this->lastMessage = \sprintf('Sodium keys have been generated at "%s*.public/private.php".', $this->getPrettyPath($this->pathPrefix)); return \true; } public function seal(string $name, string $value) : void { $this->lastMessage = null; $this->validateName($name); $this->loadKeys(); $filename = $this->getFilename($name); $this->export($filename, \sodium_crypto_box_seal($value, $this->encryptionKey ?? \sodium_crypto_box_publickey($this->decryptionKey))); $list = $this->list(); $list[$name] = null; \uksort($list, 'strnatcmp'); \file_put_contents($this->pathPrefix . 'list.php', \sprintf("lastMessage = \sprintf('Secret "%s" encrypted in "%s"; you can commit it.', $name, $this->getPrettyPath(\dirname($this->pathPrefix) . \DIRECTORY_SEPARATOR)); } public function reveal(string $name) : ?string { $this->lastMessage = null; $this->validateName($name); $filename = $this->getFilename($name); if (!\is_file($file = $this->pathPrefix . $filename . '.php')) { $this->lastMessage = \sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix) . \DIRECTORY_SEPARATOR)); return null; } if (!\function_exists('sodium_crypto_box_seal')) { $this->lastMessage = \sprintf('Secret "%s" cannot be revealed as the "sodium" PHP extension missing. Try running "composer require paragonie/sodium_compat" if you cannot enable the extension."', $name); return null; } $this->loadKeys(); if ('' === $this->decryptionKey) { $this->lastMessage = \sprintf('Secret "%s" cannot be revealed as no decryption key was found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix) . \DIRECTORY_SEPARATOR)); return null; } if (\false === ($value = \sodium_crypto_box_seal_open(include $file, $this->decryptionKey))) { $this->lastMessage = \sprintf('Secret "%s" cannot be revealed as the wrong decryption key was provided for "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix) . \DIRECTORY_SEPARATOR)); return null; } return $value; } public function remove(string $name) : bool { $this->lastMessage = null; $this->validateName($name); $filename = $this->getFilename($name); if (!\is_file($file = $this->pathPrefix . $filename . '.php')) { $this->lastMessage = \sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix) . \DIRECTORY_SEPARATOR)); return \false; } $list = $this->list(); unset($list[$name]); \file_put_contents($this->pathPrefix . 'list.php', \sprintf("lastMessage = \sprintf('Secret "%s" removed from "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix) . \DIRECTORY_SEPARATOR)); return @\unlink($file) || !\file_exists($file); } public function list(bool $reveal = \false) : array { $this->lastMessage = null; if (!\is_file($file = $this->pathPrefix . 'list.php')) { return []; } $secrets = (include $file); if (!$reveal) { return $secrets; } foreach ($secrets as $name => $value) { $secrets[$name] = $this->reveal($name); } return $secrets; } public function loadEnvVars() : array { return $this->list(\true); } private function loadKeys() : void { if (!\function_exists('sodium_crypto_box_seal')) { throw new \LogicException('The "sodium" PHP extension is required to deal with secrets. Alternatively, try running "composer require paragonie/sodium_compat" if you cannot enable the extension.".'); } if (null !== $this->encryptionKey || '' !== ($this->decryptionKey = (string) $this->decryptionKey)) { return; } if (\is_file($this->pathPrefix . 'decrypt.private.php')) { $this->decryptionKey = (string) (include $this->pathPrefix . 'decrypt.private.php'); } if (\is_file($this->pathPrefix . 'encrypt.public.php')) { $this->encryptionKey = (string) (include $this->pathPrefix . 'encrypt.public.php'); } elseif ('' !== $this->decryptionKey) { $this->encryptionKey = \sodium_crypto_box_publickey($this->decryptionKey); } else { throw new \RuntimeException(\sprintf('Encryption key not found in "%s".', \dirname($this->pathPrefix))); } } private function export(string $filename, string $data) : void { $b64 = 'decrypt.private' === $filename ? '// SYMFONY_DECRYPTION_SECRET=' . \base64_encode($data) . "\n" : ''; $name = \basename($this->pathPrefix . $filename); $data = \str_replace('%', '\\x', \rawurlencode($data)); $data = \sprintf("createSecretsDir(); if (\false === \file_put_contents($this->pathPrefix . $filename . '.php', $data, \LOCK_EX)) { $e = \error_get_last(); throw new \ErrorException($e['message'] ?? 'Failed to write secrets data.', 0, $e['type'] ?? \E_USER_WARNING); } } private function createSecretsDir() : void { if ($this->secretsDir && !\is_dir($this->secretsDir) && !@\mkdir($this->secretsDir, 0777, \true) && !\is_dir($this->secretsDir)) { throw new \RuntimeException(\sprintf('Unable to create the secrets directory (%s).', $this->secretsDir)); } $this->secretsDir = null; } private function getFilename(string $name) : string { // The MD5 hash allows making secrets case-sensitive. The filename is not enough on Windows. return $name . '.' . \substr(\md5($name), 0, 6); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets; /** * @author Nicolas Grekas * * @internal */ class DotenvVault extends AbstractVault { private $dotenvFile; public function __construct(string $dotenvFile) { $this->dotenvFile = \strtr($dotenvFile, '/', \DIRECTORY_SEPARATOR); } public function generateKeys(bool $override = \false) : bool { $this->lastMessage = 'The dotenv vault doesn\'t encrypt secrets thus doesn\'t need keys.'; return \false; } public function seal(string $name, string $value) : void { $this->lastMessage = null; $this->validateName($name); $v = \str_replace("'", "'\\''", $value); $content = \is_file($this->dotenvFile) ? \file_get_contents($this->dotenvFile) : ''; $content = \preg_replace("/^{$name}=((\\\\'|'[^']++')++|.*)/m", "{$name}='{$v}'", $content, -1, $count); if (!$count) { $content .= "{$name}='{$v}'\n"; } \file_put_contents($this->dotenvFile, $content); $this->lastMessage = \sprintf('Secret "%s" %s in "%s".', $name, $count ? 'added' : 'updated', $this->getPrettyPath($this->dotenvFile)); } public function reveal(string $name) : ?string { $this->lastMessage = null; $this->validateName($name); $v = \is_string($_SERVER[$name] ?? null) && !\str_starts_with($name, 'HTTP_') ? $_SERVER[$name] : $_ENV[$name] ?? null; if (null === $v) { $this->lastMessage = \sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); return null; } return $v; } public function remove(string $name) : bool { $this->lastMessage = null; $this->validateName($name); $content = \is_file($this->dotenvFile) ? \file_get_contents($this->dotenvFile) : ''; $content = \preg_replace("/^{$name}=((\\\\'|'[^']++')++|.*)\n?/m", '', $content, -1, $count); if ($count) { \file_put_contents($this->dotenvFile, $content); $this->lastMessage = \sprintf('Secret "%s" removed from file "%s".', $name, $this->getPrettyPath($this->dotenvFile)); return \true; } $this->lastMessage = \sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); return \false; } public function list(bool $reveal = \false) : array { $this->lastMessage = null; $secrets = []; foreach ($_ENV as $k => $v) { if (\preg_match('/^\\w+$/D', $k)) { $secrets[$k] = $reveal ? $v : null; } } foreach ($_SERVER as $k => $v) { if (\is_string($v) && \preg_match('/^\\w+$/D', $k)) { $secrets[$k] = $reveal ? $v : null; } } return $secrets; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets; /** * @author Nicolas Grekas * * @internal */ abstract class AbstractVault { protected $lastMessage; public function getLastMessage() : ?string { return $this->lastMessage; } public abstract function generateKeys(bool $override = \false) : bool; public abstract function seal(string $name, string $value) : void; public abstract function reveal(string $name) : ?string; public abstract function remove(string $name) : bool; public abstract function list(bool $reveal = \false) : array; protected function validateName(string $name) : void { if (!\preg_match('/^\\w++$/D', $name)) { throw new \LogicException(\sprintf('Invalid secret name "%s": only "word" characters are allowed.', $name)); } } protected function getPrettyPath(string $path) { return \str_replace(\getcwd() . \DIRECTORY_SEPARATOR, '', $path); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ('cli' !== \PHP_SAPI) { throw new \Exception('This script must be run from the command line.'); } require \dirname(__DIR__, 6) . '/vendor/autoload.php'; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\UnusedTagsPassUtils; $target = \dirname(__DIR__, 2) . '/DependencyInjection/Compiler/UnusedTagsPass.php'; $contents = \file_get_contents($target); $contents = \preg_replace('{private const KNOWN_TAGS = \\[(.+?)\\];}sm', "private const KNOWN_TAGS = [\n '" . \implode("',\n '", UnusedTagsPassUtils::getDefinedTags()) . "',\n ];", $contents); \file_put_contents($target, $contents); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\SurrogateListener; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\Esi; return static function (ContainerConfigurator $container) { $container->services()->set('esi', Esi::class)->set('esi_listener', SurrogateListener::class)->args([service('esi')->ignoreOnInvalid()])->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Translation\Translator; use _ContaoManager\Symfony\Component\Translation\Dumper\CsvFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\IcuResFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\IniFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\JsonFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\MoFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\PhpFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\PoFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\QtFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\XliffFileDumper; use _ContaoManager\Symfony\Component\Translation\Dumper\YamlFileDumper; use _ContaoManager\Symfony\Component\Translation\Extractor\ChainExtractor; use _ContaoManager\Symfony\Component\Translation\Extractor\ExtractorInterface; use _ContaoManager\Symfony\Component\Translation\Extractor\PhpExtractor; use _ContaoManager\Symfony\Component\Translation\Formatter\MessageFormatter; use _ContaoManager\Symfony\Component\Translation\Loader\CsvFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\IcuDatFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\IcuResFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\IniFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\JsonFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\MoFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\PoFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\QtFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\XliffFileLoader; use _ContaoManager\Symfony\Component\Translation\Loader\YamlFileLoader; use _ContaoManager\Symfony\Component\Translation\LoggingTranslator; use _ContaoManager\Symfony\Component\Translation\Reader\TranslationReader; use _ContaoManager\Symfony\Component\Translation\Reader\TranslationReaderInterface; use _ContaoManager\Symfony\Component\Translation\Writer\TranslationWriter; use _ContaoManager\Symfony\Component\Translation\Writer\TranslationWriterInterface; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; return static function (ContainerConfigurator $container) { $container->services()->set('translator.default', Translator::class)->args([abstract_arg('translation loaders locator'), service('translator.formatter'), param('kernel.default_locale'), abstract_arg('translation loaders ids'), ['cache_dir' => param('kernel.cache_dir') . '/translations', 'debug' => param('kernel.debug')], abstract_arg('enabled locales')])->call('setConfigCacheFactory', [service('config_cache_factory')])->tag('kernel.locale_aware')->alias(TranslatorInterface::class, 'translator')->set('translator.logging', LoggingTranslator::class)->args([service('translator.logging.inner'), service('logger')])->tag('monolog.logger', ['channel' => 'translation'])->set('translator.formatter.default', MessageFormatter::class)->args([service('identity_translator')])->set('translation.loader.php', PhpFileLoader::class)->tag('translation.loader', ['alias' => 'php'])->set('translation.loader.yml', YamlFileLoader::class)->tag('translation.loader', ['alias' => 'yaml', 'legacy-alias' => 'yml'])->set('translation.loader.xliff', XliffFileLoader::class)->tag('translation.loader', ['alias' => 'xlf', 'legacy-alias' => 'xliff'])->set('translation.loader.po', PoFileLoader::class)->tag('translation.loader', ['alias' => 'po'])->set('translation.loader.mo', MoFileLoader::class)->tag('translation.loader', ['alias' => 'mo'])->set('translation.loader.qt', QtFileLoader::class)->tag('translation.loader', ['alias' => 'ts'])->set('translation.loader.csv', CsvFileLoader::class)->tag('translation.loader', ['alias' => 'csv'])->set('translation.loader.res', IcuResFileLoader::class)->tag('translation.loader', ['alias' => 'res'])->set('translation.loader.dat', IcuDatFileLoader::class)->tag('translation.loader', ['alias' => 'dat'])->set('translation.loader.ini', IniFileLoader::class)->tag('translation.loader', ['alias' => 'ini'])->set('translation.loader.json', JsonFileLoader::class)->tag('translation.loader', ['alias' => 'json'])->set('translation.dumper.php', PhpFileDumper::class)->tag('translation.dumper', ['alias' => 'php'])->set('translation.dumper.xliff', XliffFileDumper::class)->tag('translation.dumper', ['alias' => 'xlf'])->set('translation.dumper.po', PoFileDumper::class)->tag('translation.dumper', ['alias' => 'po'])->set('translation.dumper.mo', MoFileDumper::class)->tag('translation.dumper', ['alias' => 'mo'])->set('translation.dumper.yml', YamlFileDumper::class)->tag('translation.dumper', ['alias' => 'yml'])->set('translation.dumper.yaml', YamlFileDumper::class)->args(['yaml'])->tag('translation.dumper', ['alias' => 'yaml'])->set('translation.dumper.qt', QtFileDumper::class)->tag('translation.dumper', ['alias' => 'ts'])->set('translation.dumper.csv', CsvFileDumper::class)->tag('translation.dumper', ['alias' => 'csv'])->set('translation.dumper.ini', IniFileDumper::class)->tag('translation.dumper', ['alias' => 'ini'])->set('translation.dumper.json', JsonFileDumper::class)->tag('translation.dumper', ['alias' => 'json'])->set('translation.dumper.res', IcuResFileDumper::class)->tag('translation.dumper', ['alias' => 'res'])->set('translation.extractor.php', PhpExtractor::class)->tag('translation.extractor', ['alias' => 'php'])->set('translation.reader', TranslationReader::class)->alias(TranslationReaderInterface::class, 'translation.reader')->set('translation.extractor', ChainExtractor::class)->alias(ExtractorInterface::class, 'translation.extractor')->set('translation.writer', TranslationWriter::class)->alias(TranslationWriterInterface::class, 'translation.writer')->set('translation.warmer', TranslationsCacheWarmer::class)->args([service(ContainerInterface::class)])->tag('container.service_subscriber', ['id' => 'translator'])->tag('kernel.cache_warmer'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; use _ContaoManager\Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; use _ContaoManager\Symfony\Component\Serializer\Encoder\CsvEncoder; use _ContaoManager\Symfony\Component\Serializer\Encoder\DecoderInterface; use _ContaoManager\Symfony\Component\Serializer\Encoder\EncoderInterface; use _ContaoManager\Symfony\Component\Serializer\Encoder\JsonEncoder; use _ContaoManager\Symfony\Component\Serializer\Encoder\XmlEncoder; use _ContaoManager\Symfony\Component\Serializer\Encoder\YamlEncoder; use _ContaoManager\Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use _ContaoManager\Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; use _ContaoManager\Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use _ContaoManager\Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use _ContaoManager\Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use _ContaoManager\Symfony\Component\Serializer\Mapping\Loader\LoaderChain; use _ContaoManager\Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use _ContaoManager\Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use _ContaoManager\Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\DataUriNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use _ContaoManager\Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\NormalizerInterface; use _ContaoManager\Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\ProblemNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\UidNormalizer; use _ContaoManager\Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use _ContaoManager\Symfony\Component\Serializer\Serializer; use _ContaoManager\Symfony\Component\Serializer\SerializerInterface; return static function (ContainerConfigurator $container) { $container->parameters()->set('serializer.mapping.cache.file', '%kernel.cache_dir%/serialization.php'); $container->services()->set('serializer', Serializer::class)->public()->args([[], []])->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->alias(SerializerInterface::class, 'serializer')->alias(NormalizerInterface::class, 'serializer')->alias(DenormalizerInterface::class, 'serializer')->alias(EncoderInterface::class, 'serializer')->alias(DecoderInterface::class, 'serializer')->alias('serializer.property_accessor', 'property_accessor')->set('serializer.mapping.class_discriminator_resolver', ClassDiscriminatorFromClassMetadata::class)->args([service('serializer.mapping.class_metadata_factory')])->alias(ClassDiscriminatorResolverInterface::class, 'serializer.mapping.class_discriminator_resolver')->set('serializer.normalizer.constraint_violation_list', ConstraintViolationListNormalizer::class)->args([1 => service('serializer.name_converter.metadata_aware')])->autowire(\true)->tag('serializer.normalizer', ['priority' => -915])->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class)->args([service('serializer.normalizer.property')])->tag('serializer.normalizer', ['priority' => -915])->set('serializer.normalizer.datetimezone', DateTimeZoneNormalizer::class)->tag('serializer.normalizer', ['priority' => -915])->set('serializer.normalizer.dateinterval', DateIntervalNormalizer::class)->tag('serializer.normalizer', ['priority' => -915])->set('serializer.normalizer.data_uri', DataUriNormalizer::class)->args([service('mime_types')->nullOnInvalid()])->tag('serializer.normalizer', ['priority' => -920])->set('serializer.normalizer.datetime', DateTimeNormalizer::class)->tag('serializer.normalizer', ['priority' => -910])->set('serializer.normalizer.json_serializable', JsonSerializableNormalizer::class)->args([null, null])->tag('serializer.normalizer', ['priority' => -950])->set('serializer.normalizer.problem', ProblemNormalizer::class)->args([param('kernel.debug')])->tag('serializer.normalizer', ['priority' => -890])->set('serializer.denormalizer.unwrapping', UnwrappingDenormalizer::class)->args([service('serializer.property_accessor')])->tag('serializer.normalizer', ['priority' => 1000])->set('serializer.normalizer.uid', UidNormalizer::class)->tag('serializer.normalizer', ['priority' => -890])->set('serializer.normalizer.form_error', FormErrorNormalizer::class)->tag('serializer.normalizer', ['priority' => -915])->set('serializer.normalizer.object', ObjectNormalizer::class)->args([service('serializer.mapping.class_metadata_factory'), service('serializer.name_converter.metadata_aware'), service('serializer.property_accessor'), service('property_info')->ignoreOnInvalid(), service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), null])->tag('serializer.normalizer', ['priority' => -1000])->alias(ObjectNormalizer::class, 'serializer.normalizer.object')->set('serializer.normalizer.property', PropertyNormalizer::class)->args([service('serializer.mapping.class_metadata_factory'), service('serializer.name_converter.metadata_aware'), service('property_info')->ignoreOnInvalid(), service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), null])->alias(PropertyNormalizer::class, 'serializer.normalizer.property')->set('serializer.denormalizer.array', ArrayDenormalizer::class)->tag('serializer.normalizer', ['priority' => -990])->set('serializer.mapping.chain_loader', LoaderChain::class)->args([[]])->set('serializer.mapping.class_metadata_factory', ClassMetadataFactory::class)->args([service('serializer.mapping.chain_loader')])->alias(ClassMetadataFactoryInterface::class, 'serializer.mapping.class_metadata_factory')->set('serializer.mapping.cache_warmer', SerializerCacheWarmer::class)->args([abstract_arg('The serializer metadata loaders'), param('serializer.mapping.cache.file')])->tag('kernel.cache_warmer')->set('serializer.mapping.cache.symfony', CacheItemPoolInterface::class)->factory([PhpArrayAdapter::class, 'create'])->args([param('serializer.mapping.cache.file'), service('cache.serializer')])->set('serializer.mapping.cache_class_metadata_factory', CacheClassMetadataFactory::class)->decorate('serializer.mapping.class_metadata_factory')->args([service('serializer.mapping.cache_class_metadata_factory.inner'), service('serializer.mapping.cache.symfony')])->set('serializer.encoder.xml', XmlEncoder::class)->tag('serializer.encoder')->set('serializer.encoder.json', JsonEncoder::class)->args([null, null])->tag('serializer.encoder')->set('serializer.encoder.yaml', YamlEncoder::class)->args([null, null])->tag('serializer.encoder')->set('serializer.encoder.csv', CsvEncoder::class)->tag('serializer.encoder')->set('serializer.name_converter.camel_case_to_snake_case', CamelCaseToSnakeCaseNameConverter::class)->set('serializer.name_converter.metadata_aware', MetadataAwareNameConverter::class)->args([service('serializer.mapping.class_metadata_factory')])->set('property_info.serializer_extractor', SerializerExtractor::class)->args([service('serializer.mapping.class_metadata_factory')])->tag('property_info.list_extractor', ['priority' => -999])->alias('error_renderer', 'error_renderer.serializer')->alias('error_renderer.serializer', 'error_handler.error_renderer.serializer')->set('error_handler.error_renderer.serializer', SerializerErrorRenderer::class)->args([service('serializer'), inline_service()->factory([SerializerErrorRenderer::class, 'getPreferredFormat'])->args([service('request_stack')]), service('error_renderer.html'), inline_service()->factory([HtmlErrorRenderer::class, 'isDebug'])->args([service('request_stack'), param('kernel.debug')])]); if (\interface_exists(\BackedEnum::class)) { $container->services()->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class)->tag('serializer.normalizer', ['priority' => -915]); } }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Form\Extension\DataCollector\FormDataCollector; use _ContaoManager\Symfony\Component\Form\Extension\DataCollector\FormDataExtractor; use _ContaoManager\Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy; use _ContaoManager\Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension; use _ContaoManager\Symfony\Component\Form\ResolvedFormTypeFactory; return static function (ContainerConfigurator $container) { $container->services()->set('form.resolved_type_factory', ResolvedTypeFactoryDataCollectorProxy::class)->args([inline_service(ResolvedFormTypeFactory::class), service('data_collector.form')])->set('form.type_extension.form.data_collector', DataCollectorTypeExtension::class)->args([service('data_collector.form')])->tag('form.type_extension')->set('data_collector.form.extractor', FormDataExtractor::class)->set('data_collector.form', FormDataCollector::class)->args([service('data_collector.form.extractor')])->tag('data_collector', ['template' => '@WebProfiler/Collector/form.html.twig', 'id' => 'form', 'priority' => 310]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; use _ContaoManager\Symfony\Component\Validator\Constraints\EmailValidator; use _ContaoManager\Symfony\Component\Validator\Constraints\ExpressionValidator; use _ContaoManager\Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; use _ContaoManager\Symfony\Component\Validator\ContainerConstraintValidatorFactory; use _ContaoManager\Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use _ContaoManager\Symfony\Component\Validator\Validation; use _ContaoManager\Symfony\Component\Validator\Validator\ValidatorInterface; use _ContaoManager\Symfony\Component\Validator\ValidatorBuilder; return static function (ContainerConfigurator $container) { $container->parameters()->set('validator.mapping.cache.file', param('kernel.cache_dir') . '/validation.php'); $container->services()->set('validator', ValidatorInterface::class)->public()->factory([service('validator.builder'), 'getValidator'])->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->alias(ValidatorInterface::class, 'validator')->set('validator.builder', ValidatorBuilder::class)->factory([Validation::class, 'createValidatorBuilder'])->call('setConstraintValidatorFactory', [service('validator.validator_factory')])->call('setTranslator', [service('translator')->ignoreOnInvalid()])->call('setTranslationDomain', [param('validator.translation_domain')])->alias('validator.mapping.class_metadata_factory', 'validator')->set('validator.mapping.cache_warmer', ValidatorCacheWarmer::class)->args([service('validator.builder'), param('validator.mapping.cache.file')])->tag('kernel.cache_warmer')->set('validator.mapping.cache.adapter', PhpArrayAdapter::class)->factory([PhpArrayAdapter::class, 'create'])->args([param('validator.mapping.cache.file'), service('cache.validator')])->set('validator.validator_factory', ContainerConstraintValidatorFactory::class)->args([abstract_arg('Constraint validators locator')])->set('validator.expression', ExpressionValidator::class)->args([service('validator.expression_language')->nullOnInvalid()])->tag('validator.constraint_validator', ['alias' => 'validator.expression'])->set('validator.expression_language', ExpressionLanguage::class)->args([service('cache.validator_expression_language')->nullOnInvalid()])->set('cache.validator_expression_language')->parent('cache.system')->tag('cache.pool')->set('validator.email', EmailValidator::class)->args([abstract_arg('Default mode')])->tag('validator.constraint_validator', ['alias' => EmailValidator::class])->set('validator.not_compromised_password', NotCompromisedPasswordValidator::class)->args([service('http_client')->nullOnInvalid(), param('kernel.charset'), \false])->tag('validator.constraint_validator', ['alias' => NotCompromisedPasswordValidator::class])->set('validator.property_info_loader', PropertyInfoLoader::class)->args([service('property_info'), service('property_info'), service('property_info')])->tag('validator.auto_mapper'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Doctrine\Common\Annotations\AnnotationReader; use _ContaoManager\Doctrine\Common\Annotations\AnnotationRegistry; use _ContaoManager\Doctrine\Common\Annotations\PsrCachedReader; use _ContaoManager\Doctrine\Common\Annotations\Reader; use _ContaoManager\Doctrine\Common\Cache\Psr6\DoctrineProvider; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\FilesystemAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; return static function (ContainerConfigurator $container) { $container->services()->set('annotations.reader', AnnotationReader::class)->call('addGlobalIgnoredName', ['required', service('annotations.dummy_registry')->nullOnInvalid()])->set('annotations.dummy_registry', AnnotationRegistry::class)->call('registerUniqueLoader', ['class_exists'])->set('annotations.cached_reader', PsrCachedReader::class)->args([service('annotations.reader'), inline_service(ArrayAdapter::class), abstract_arg('Debug-Flag')])->tag('annotations.cached_reader')->tag('container.do_not_inline')->set('annotations.filesystem_cache_adapter', FilesystemAdapter::class)->args(['', 0, abstract_arg('Cache-Directory')])->set('annotations.filesystem_cache', DoctrineProvider::class)->factory([DoctrineProvider::class, 'wrap'])->args([service('annotations.filesystem_cache_adapter')])->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"')->set('annotations.cache_warmer', AnnotationsCacheWarmer::class)->args([service('annotations.reader'), param('kernel.cache_dir') . '/annotations.php', '#^Symfony\\\\(?:Component\\\\HttpKernel\\\\|Bundle\\\\FrameworkBundle\\\\Controller\\\\(?!.*Controller$))#', param('kernel.debug')])->set('annotations.cache_adapter', PhpArrayAdapter::class)->factory([PhpArrayAdapter::class, 'create'])->args([param('kernel.cache_dir') . '/annotations.php', service('cache.annotations')])->tag('container.hot_path')->set('annotations.cache', DoctrineProvider::class)->factory([DoctrineProvider::class, 'wrap'])->args([service('annotations.cache_adapter')])->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"')->alias('annotation_reader', 'annotations.reader')->alias(Reader::class, 'annotation_reader'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\ProfilerListener; use _ContaoManager\Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; use _ContaoManager\Symfony\Component\HttpKernel\Profiler\Profiler; return static function (ContainerConfigurator $container) { $container->services()->set('profiler', Profiler::class)->public()->args([service('profiler.storage'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'profiler'])->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.4'])->set('profiler.storage', FileProfilerStorage::class)->args([param('profiler.storage.dsn')])->set('profiler_listener', ProfilerListener::class)->args([service('profiler'), service('request_stack'), null, param('profiler_listener.only_exceptions'), param('profiler_listener.only_main_requests')])->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Mailer\EventListener\EnvelopeListener; use _ContaoManager\Symfony\Component\Mailer\EventListener\MessageListener; use _ContaoManager\Symfony\Component\Mailer\EventListener\MessageLoggerListener; use _ContaoManager\Symfony\Component\Mailer\Mailer; use _ContaoManager\Symfony\Component\Mailer\MailerInterface; use _ContaoManager\Symfony\Component\Mailer\Messenger\MessageHandler; use _ContaoManager\Symfony\Component\Mailer\Transport; use _ContaoManager\Symfony\Component\Mailer\Transport\TransportInterface; use _ContaoManager\Symfony\Component\Mailer\Transport\Transports; return static function (ContainerConfigurator $container) { $container->services()->set('mailer.mailer', Mailer::class)->args([service('mailer.transports'), abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid()])->alias('mailer', 'mailer.mailer')->alias(MailerInterface::class, 'mailer.mailer')->set('mailer.transports', Transports::class)->factory([service('mailer.transport_factory'), 'fromStrings'])->args([abstract_arg('transports')])->set('mailer.transport_factory', Transport::class)->args([tagged_iterator('mailer.transport_factory')])->set('mailer.default_transport', TransportInterface::class)->factory([service('mailer.transport_factory'), 'fromString'])->args([abstract_arg('env(MAILER_DSN)')])->alias(TransportInterface::class, 'mailer.default_transport')->set('mailer.messenger.message_handler', MessageHandler::class)->args([service('mailer.transports')])->tag('messenger.message_handler')->set('mailer.envelope_listener', EnvelopeListener::class)->args([abstract_arg('sender'), abstract_arg('recipients')])->tag('kernel.event_subscriber')->set('mailer.message_listener', MessageListener::class)->args([abstract_arg('headers')])->tag('kernel.event_subscriber')->set('mailer.logger_message_listener', MessageLoggerListener::class)->tag('kernel.event_subscriber')->tag('kernel.reset', ['method' => 'reset'])->deprecate('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "mailer.message_logger_listener" instead.')->set('mailer.message_logger_listener', MessageLoggerListener::class)->tag('kernel.event_subscriber')->tag('kernel.reset', ['method' => 'reset']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\FragmentListener; return static function (ContainerConfigurator $container) { $container->services()->set('fragment.listener', FragmentListener::class)->args([service('uri_signer'), param('fragment.path')])->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Translation\DataCollector\TranslationDataCollector; use _ContaoManager\Symfony\Component\Translation\DataCollectorTranslator; return static function (ContainerConfigurator $container) { $container->services()->set('translator.data_collector', DataCollectorTranslator::class)->args([service('translator.data_collector.inner')])->set('data_collector.translation', TranslationDataCollector::class)->args([service('translator.data_collector')])->tag('data_collector', ['template' => '@WebProfiler/Collector/translation.html.twig', 'id' => 'translation', 'priority' => 275]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Notifier\DataCollector\NotificationDataCollector; return static function (ContainerConfigurator $container) { $container->services()->set('notifier.data_collector', NotificationDataCollector::class)->args([service('notifier.logger_notification_listener')])->tag('data_collector', ['template' => '@WebProfiler/Collector/notifier.html.twig', 'id' => 'notifier']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\RateLimiter\RateLimiterFactory; return static function (ContainerConfigurator $container) { $container->services()->set('cache.rate_limiter')->parent('cache.app')->tag('cache.pool')->set('limiter', RateLimiterFactory::class)->abstract()->args([abstract_arg('config'), abstract_arg('storage'), null]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Lock\LockFactory; use _ContaoManager\Symfony\Component\Lock\Store\CombinedStore; use _ContaoManager\Symfony\Component\Lock\Strategy\ConsensusStrategy; return static function (ContainerConfigurator $container) { $container->services()->set('lock.store.combined.abstract', CombinedStore::class)->abstract()->args([abstract_arg('List of stores'), service('lock.strategy.majority')])->set('lock.strategy.majority', ConsensusStrategy::class)->set('lock.factory.abstract', LockFactory::class)->abstract()->args([abstract_arg('Store')])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'lock']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Translation\IdentityTranslator; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; return static function (ContainerConfigurator $container) { $container->services()->set('translator', IdentityTranslator::class)->public()->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->alias(TranslatorInterface::class, 'translator')->set('identity_translator', IdentityTranslator::class); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Psr\Http\Client\ClientInterface; use _ContaoManager\Psr\Http\Message\ResponseFactoryInterface; use _ContaoManager\Psr\Http\Message\StreamFactoryInterface; use _ContaoManager\Symfony\Component\HttpClient\HttpClient; use _ContaoManager\Symfony\Component\HttpClient\HttplugClient; use _ContaoManager\Symfony\Component\HttpClient\Psr18Client; use _ContaoManager\Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use _ContaoManager\Symfony\Contracts\HttpClient\HttpClientInterface; return static function (ContainerConfigurator $container) { $container->services()->set('http_client', HttpClientInterface::class)->factory([HttpClient::class, 'create'])->args([ [], // default options abstract_arg('max host connections'), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'http_client'])->tag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore'])->tag('http_client.client')->alias(HttpClientInterface::class, 'http_client')->set('psr18.http_client', Psr18Client::class)->args([service('http_client'), service(ResponseFactoryInterface::class)->ignoreOnInvalid(), service(StreamFactoryInterface::class)->ignoreOnInvalid()])->alias(ClientInterface::class, 'psr18.http_client')->set(\_ContaoManager\Http\Client\HttpClient::class, HttplugClient::class)->args([service('http_client'), service(ResponseFactoryInterface::class)->ignoreOnInvalid(), service(StreamFactoryInterface::class)->ignoreOnInvalid()])->set('http_client.abstract_retry_strategy', GenericRetryStrategy::class)->abstract()->args([abstract_arg('http codes'), abstract_arg('delay ms'), abstract_arg('multiplier'), abstract_arg('max delay ms'), abstract_arg('jitter')]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Mailer\DataCollector\MessageDataCollector; return static function (ContainerConfigurator $container) { $container->services()->set('mailer.data_collector', MessageDataCollector::class)->args([service('mailer.message_logger_listener')])->tag('data_collector', ['template' => '@WebProfiler/Collector/mailer.html.twig', 'id' => 'mailer']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; use _ContaoManager\Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; use _ContaoManager\Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use _ContaoManager\Symfony\Component\Config\ResourceCheckerConfigCacheFactory; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker; use _ContaoManager\Symfony\Component\DependencyInjection\EnvVarProcessor; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ReverseContainer; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcher; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherInterfaceComponentAlias; use _ContaoManager\Symfony\Component\Filesystem\Filesystem; use _ContaoManager\Symfony\Component\Form\FormEvents; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\UrlHelper; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; use _ContaoManager\Symfony\Component\HttpKernel\Config\FileLocator; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\LocaleAwareListener; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\Store; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\StoreInterface; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernel; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\UriSigner; use _ContaoManager\Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner; use _ContaoManager\Symfony\Component\Runtime\Runner\Symfony\ResponseRunner; use _ContaoManager\Symfony\Component\Runtime\SymfonyRuntime; use _ContaoManager\Symfony\Component\String\LazyString; use _ContaoManager\Symfony\Component\String\Slugger\AsciiSlugger; use _ContaoManager\Symfony\Component\String\Slugger\SluggerInterface; use _ContaoManager\Symfony\Component\Workflow\WorkflowEvents; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; return static function (ContainerConfigurator $container) { // this parameter is used at compile time in RegisterListenersPass $container->parameters()->set('event_dispatcher.event_aliases', \array_merge(\class_exists(ConsoleEvents::class) ? ConsoleEvents::ALIASES : [], \class_exists(FormEvents::class) ? FormEvents::ALIASES : [], KernelEvents::ALIASES, \class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [])); $container->services()->set('parameter_bag', ContainerBag::class)->args([service('service_container')])->alias(ContainerBagInterface::class, 'parameter_bag')->alias(ParameterBagInterface::class, 'parameter_bag')->set('event_dispatcher', EventDispatcher::class)->public()->tag('container.hot_path')->tag('event_dispatcher.dispatcher', ['name' => 'event_dispatcher'])->alias(EventDispatcherInterfaceComponentAlias::class, 'event_dispatcher')->alias(EventDispatcherInterface::class, 'event_dispatcher')->set('http_kernel', HttpKernel::class)->public()->args([service('event_dispatcher'), service('controller_resolver'), service('request_stack'), service('argument_resolver')])->tag('container.hot_path')->tag('container.preload', ['class' => HttpKernelRunner::class])->tag('container.preload', ['class' => ResponseRunner::class])->tag('container.preload', ['class' => SymfonyRuntime::class])->alias(HttpKernelInterface::class, 'http_kernel')->set('request_stack', RequestStack::class)->public()->alias(RequestStack::class, 'request_stack')->set('http_cache', HttpCache::class)->args([service('kernel'), service('http_cache.store'), service('esi')->nullOnInvalid(), abstract_arg('options')])->tag('container.hot_path')->set('http_cache.store', Store::class)->args([param('kernel.cache_dir') . '/http_cache'])->alias(StoreInterface::class, 'http_cache.store')->set('url_helper', UrlHelper::class)->args([service('request_stack'), service('router')->ignoreOnInvalid()])->alias(UrlHelper::class, 'url_helper')->set('cache_warmer', CacheWarmerAggregate::class)->public()->args([tagged_iterator('kernel.cache_warmer'), param('kernel.debug'), \sprintf('%s/%sDeprecations.log', param('kernel.build_dir'), param('kernel.container_class'))])->tag('container.no_preload')->set('cache_clearer', ChainCacheClearer::class)->public()->args([tagged_iterator('kernel.cache_clearer')])->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->set('kernel')->synthetic()->public()->alias(KernelInterface::class, 'kernel')->set('filesystem', Filesystem::class)->public()->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->alias(Filesystem::class, 'filesystem')->set('file_locator', FileLocator::class)->args([service('kernel')])->alias(FileLocator::class, 'file_locator')->set('uri_signer', UriSigner::class)->args([param('kernel.secret')])->alias(UriSigner::class, 'uri_signer')->set('config_cache_factory', ResourceCheckerConfigCacheFactory::class)->args([tagged_iterator('config_cache.resource_checker')])->set('dependency_injection.config.container_parameters_resource_checker', ContainerParametersResourceChecker::class)->args([service('service_container')])->tag('config_cache.resource_checker', ['priority' => -980])->set('config.resource.self_checking_resource_checker', SelfCheckingResourceChecker::class)->tag('config_cache.resource_checker', ['priority' => -990])->set('services_resetter', ServicesResetter::class)->public()->set('reverse_container', ReverseContainer::class)->args([service('service_container'), service_locator([])])->alias(ReverseContainer::class, 'reverse_container')->set('locale_aware_listener', LocaleAwareListener::class)->args([ [], // locale aware services service('request_stack'), ])->tag('kernel.event_subscriber')->set('container.env_var_processor', EnvVarProcessor::class)->args([service('service_container'), tagged_iterator('container.env_var_loader')])->tag('container.env_var_processor')->set('slugger', AsciiSlugger::class)->args([param('kernel.default_locale')])->tag('kernel.locale_aware')->alias(SluggerInterface::class, 'slugger')->set('container.getenv', \Closure::class)->factory([\Closure::class, 'fromCallable'])->args([[service('service_container'), 'getEnv']])->tag('routing.expression_language_function', ['function' => 'env'])->set('container.env', LazyString::class)->abstract()->factory([LazyString::class, 'fromCallable'])->args([service('container.getenv')])->set('config_builder.warmer', ConfigBuilderCacheWarmer::class)->args([service(KernelInterface::class), service('logger')->nullOnInvalid()])->tag('kernel.cache_warmer'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\AbstractAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\ApcuAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\DoctrineAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\FilesystemAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\MemcachedAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PdoAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ProxyAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\RedisAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\RedisTagAwareAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\TagAwareAdapter; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Messenger\EarlyExpirationHandler; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; use _ContaoManager\Symfony\Contracts\Cache\TagAwareCacheInterface; return static function (ContainerConfigurator $container) { $container->services()->set('cache.app')->parent('cache.adapter.filesystem')->public()->tag('cache.pool', ['clearer' => 'cache.app_clearer'])->set('cache.app.taggable', TagAwareAdapter::class)->args([service('cache.app')])->set('cache.system')->parent('cache.adapter.system')->public()->tag('cache.pool')->set('cache.validator')->parent('cache.system')->private()->tag('cache.pool')->set('cache.serializer')->parent('cache.system')->private()->tag('cache.pool')->set('cache.annotations')->parent('cache.system')->private()->tag('cache.pool')->set('cache.property_info')->parent('cache.system')->private()->tag('cache.pool')->set('cache.messenger.restart_workers_signal')->parent('cache.app')->private()->tag('cache.pool')->set('cache.adapter.system', AdapterInterface::class)->abstract()->factory([AbstractAdapter::class, 'createSystemCache'])->args([ '', // namespace 0, // default lifetime abstract_arg('version'), \sprintf('%s/pools/system', param('kernel.cache_dir')), service('logger')->ignoreOnInvalid(), ])->tag('cache.pool', ['clearer' => 'cache.system_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.apcu', ApcuAdapter::class)->abstract()->args([ '', // namespace 0, // default lifetime abstract_arg('version'), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.doctrine', DoctrineAdapter::class)->abstract()->args([ abstract_arg('Doctrine provider service'), '', // namespace 0, ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['provider' => 'cache.default_doctrine_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id%" service inherits from "cache.adapter.doctrine" which is deprecated.')->set('cache.adapter.filesystem', FilesystemAdapter::class)->abstract()->args([ '', // namespace 0, // default lifetime \sprintf('%s/pools/app', param('kernel.cache_dir')), service('cache.default_marshaller')->ignoreOnInvalid(), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.psr6', ProxyAdapter::class)->abstract()->args([ abstract_arg('PSR-6 provider service'), '', // namespace 0, ])->tag('cache.pool', ['provider' => 'cache.default_psr6_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->set('cache.adapter.redis', RedisAdapter::class)->abstract()->args([ abstract_arg('Redis connection service'), '', // namespace 0, // default lifetime service('cache.default_marshaller')->ignoreOnInvalid(), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['provider' => 'cache.default_redis_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.redis_tag_aware', RedisTagAwareAdapter::class)->abstract()->args([ abstract_arg('Redis connection service'), '', // namespace 0, // default lifetime service('cache.default_marshaller')->ignoreOnInvalid(), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['provider' => 'cache.default_redis_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.memcached', MemcachedAdapter::class)->abstract()->args([ abstract_arg('Memcached connection service'), '', // namespace 0, // default lifetime service('cache.default_marshaller')->ignoreOnInvalid(), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['provider' => 'cache.default_memcached_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.doctrine_dbal', DoctrineDbalAdapter::class)->abstract()->args([ abstract_arg('DBAL connection service'), '', // namespace 0, // default lifetime [], // table options service('cache.default_marshaller')->ignoreOnInvalid(), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['provider' => 'cache.default_doctrine_dbal_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.pdo', PdoAdapter::class)->abstract()->args([ abstract_arg('PDO connection service'), '', // namespace 0, // default lifetime [], // table options service('cache.default_marshaller')->ignoreOnInvalid(), ])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['provider' => 'cache.default_pdo_provider', 'clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.adapter.array', ArrayAdapter::class)->abstract()->args([0])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset'])->tag('monolog.logger', ['channel' => 'cache'])->set('cache.default_marshaller', DefaultMarshaller::class)->args([ null, // use igbinary_serialize() when available '%kernel.debug%', ])->set('cache.early_expiration_handler', EarlyExpirationHandler::class)->args([service('reverse_container')])->tag('messenger.message_handler')->set('cache.default_clearer', Psr6CacheClearer::class)->args([[]])->set('cache.system_clearer')->parent('cache.default_clearer')->public()->set('cache.global_clearer')->parent('cache.default_clearer')->public()->alias('cache.app_clearer', 'cache.default_clearer')->public()->alias(CacheItemPoolInterface::class, 'cache.app')->alias(AdapterInterface::class, 'cache.app')->deprecate('symfony/framework-bundle', '5.4', \sprintf('The "%%alias_id%%" alias is deprecated, use "%s" instead.', CacheItemPoolInterface::class))->alias(CacheInterface::class, 'cache.app')->alias(TagAwareCacheInterface::class, 'cache.app.taggable'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Session\DeprecatedSessionFactory; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Session\ServiceSessionFactory; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionFactory; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\MarshallingSessionHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerFactory; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorageFactory; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorageFactory; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\SessionListener; return static function (ContainerConfigurator $container) { $container->parameters()->set('session.metadata.storage_key', '_sf2_meta'); $container->services()->set('.session.do-not-use', Session::class)->factory([service('session.factory'), 'createSession'])->set('session.factory', SessionFactory::class)->args([service('request_stack'), service('session.storage.factory'), [service('session_listener'), 'onSessionUsage']])->set('session.storage.factory.native', NativeSessionStorageFactory::class)->args([param('session.storage.options'), service('session.handler'), inline_service(MetadataBag::class)->args([param('session.metadata.storage_key'), param('session.metadata.update_threshold')]), \false])->set('session.storage.factory.php_bridge', PhpBridgeSessionStorageFactory::class)->args([service('session.handler'), inline_service(MetadataBag::class)->args([param('session.metadata.storage_key'), param('session.metadata.update_threshold')]), \false])->set('session.storage.factory.mock_file', MockFileSessionStorageFactory::class)->args([param('kernel.cache_dir') . '/sessions', 'MOCKSESSID', inline_service(MetadataBag::class)->args([param('session.metadata.storage_key'), param('session.metadata.update_threshold')])])->set('session.storage.factory.service', ServiceSessionFactory::class)->args([service('session.storage')])->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.native", "session.storage.factory.php_bridge" or "session.storage.factory.mock_file" instead.')->set('.session.deprecated', SessionInterface::class)->factory([inline_service(DeprecatedSessionFactory::class)->args([service('request_stack')]), 'getSession'])->alias(SessionInterface::class, '.session.do-not-use')->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.')->alias(SessionStorageInterface::class, 'session.storage')->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use "session.storage.factory" instead.')->alias(\SessionHandlerInterface::class, 'session.handler')->set('session.storage.metadata_bag', MetadataBag::class)->args([param('session.metadata.storage_key'), param('session.metadata.update_threshold')])->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, create your own "session.storage.factory" instead.')->set('session.storage.native', NativeSessionStorage::class)->args([param('session.storage.options'), service('session.handler'), service('session.storage.metadata_bag')])->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.native" instead.')->set('session.storage.php_bridge', PhpBridgeSessionStorage::class)->args([service('session.handler'), service('session.storage.metadata_bag')])->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.php_bridge" instead.')->set('session.flash_bag', FlashBag::class)->factory([service('.session.do-not-use'), 'getFlashBag'])->deprecate('symfony/framework-bundle', '5.1', 'The "%service_id%" service is deprecated, use "$session->getFlashBag()" instead.')->alias(FlashBagInterface::class, 'session.flash_bag')->set('session.attribute_bag', AttributeBag::class)->factory([service('.session.do-not-use'), 'getBag'])->args(['attributes'])->deprecate('symfony/framework-bundle', '5.1', 'The "%service_id%" service is deprecated, use "$session->getAttributeBag()" instead.')->set('session.storage.mock_file', MockFileSessionStorage::class)->args([param('kernel.cache_dir') . '/sessions', 'MOCKSESSID', service('session.storage.metadata_bag')])->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.mock_file" instead.')->set('session.handler.native', StrictSessionHandler::class)->args([inline_service(\SessionHandler::class)])->set('session.handler.native_file', StrictSessionHandler::class)->args([inline_service(NativeFileSessionHandler::class)->args([param('session.save_path')])])->set('session.abstract_handler', AbstractSessionHandler::class)->factory([SessionHandlerFactory::class, 'createHandler'])->args([abstract_arg('A string or a connection object')])->set('session_listener', SessionListener::class)->args([service_locator(['session_factory' => service('session.factory')->ignoreOnInvalid(), 'session' => service('.session.do-not-use')->ignoreOnInvalid(), 'initialized_session' => service('.session.do-not-use')->ignoreOnUninitialized(), 'logger' => service('logger')->ignoreOnInvalid(), 'session_collector' => service('data_collector.request.session_collector')->ignoreOnInvalid()]), param('kernel.debug'), param('session.storage.options')])->tag('kernel.event_subscriber')->tag('kernel.reset', ['method' => 'reset'])->alias('session.storage.filesystem', 'session.storage.mock_file')->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use "session.storage.factory.mock_file" instead.')->set('session.marshaller', IdentityMarshaller::class)->set('session.marshalling_handler', MarshallingSessionHandler::class)->decorate('session.handler')->args([service('session.marshalling_handler.inner'), service('session.marshaller')]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Transport\AbstractTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Transport\NativeTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Transport\NullTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Transport\SendmailTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory; return static function (ContainerConfigurator $container) { $container->services()->set('mailer.transport_factory.abstract', AbstractTransportFactory::class)->abstract()->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid(), service('logger')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'mailer'])->set('mailer.transport_factory.amazon', SesTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.gmail', GmailTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.mailjet', MailjetTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.mailgun', MailgunTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.postmark', PostmarkTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.sendgrid', SendgridTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.null', NullTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.sendmail', SendmailTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.sendinblue', SendinblueTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.ohmysmtp', OhMySmtpTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory')->set('mailer.transport_factory.smtp', EsmtpTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory', ['priority' => -100])->set('mailer.transport_factory.native', NativeTransportFactory::class)->parent('mailer.transport_factory.abstract')->tag('mailer.transport_factory'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; use _ContaoManager\Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; use _ContaoManager\Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; use _ContaoManager\Symfony\Component\Translation\Provider\NullProviderFactory; use _ContaoManager\Symfony\Component\Translation\Provider\TranslationProviderCollection; use _ContaoManager\Symfony\Component\Translation\Provider\TranslationProviderCollectionFactory; return static function (ContainerConfigurator $container) { $container->services()->set('translation.provider_collection', TranslationProviderCollection::class)->factory([service('translation.provider_collection_factory'), 'fromConfig'])->args([[]])->set('translation.provider_collection_factory', TranslationProviderCollectionFactory::class)->args([tagged_iterator('translation.provider_factory'), []])->set('translation.provider_factory.null', NullProviderFactory::class)->tag('translation.provider_factory')->set('translation.provider_factory.crowdin', CrowdinProviderFactory::class)->args([service('http_client'), service('logger'), param('kernel.default_locale'), service('translation.loader.xliff'), service('translation.dumper.xliff')])->tag('translation.provider_factory')->set('translation.provider_factory.loco', LocoProviderFactory::class)->args([service('http_client'), service('logger'), param('kernel.default_locale'), service('translation.loader.xliff')])->tag('translation.provider_factory')->set('translation.provider_factory.lokalise', LokaliseProviderFactory::class)->args([service('http_client'), service('logger'), param('kernel.default_locale'), service('translation.loader.xliff')])->tag('translation.provider_factory'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\TraceableControllerResolver; use _ContaoManager\Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; return static function (ContainerConfigurator $container) { $container->services()->set('debug.event_dispatcher', TraceableEventDispatcher::class)->decorate('event_dispatcher')->args([service('debug.event_dispatcher.inner'), service('debug.stopwatch'), service('logger')->nullOnInvalid(), service('request_stack')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'event'])->tag('kernel.reset', ['method' => 'reset'])->set('debug.controller_resolver', TraceableControllerResolver::class)->decorate('controller_resolver')->args([service('debug.controller_resolver.inner'), service('debug.stopwatch')])->set('debug.argument_resolver', TraceableArgumentResolver::class)->decorate('argument_resolver')->args([service('debug.argument_resolver.inner'), service('debug.stopwatch')])->set('argument_resolver.not_tagged_controller', NotTaggedControllerValueResolver::class)->args([abstract_arg('Controller argument, set in FrameworkExtension')])->tag('controller.argument_value_resolver', ['priority' => -200]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; return static function (ContainerConfigurator $container) { $container->parameters()->set('fragment.renderer.hinclude.global_template', null)->set('fragment.path', '/_fragment'); $container->services()->set('fragment.handler', LazyLoadingFragmentHandler::class)->args([abstract_arg('fragment renderer locator'), service('request_stack'), param('kernel.debug')])->set('fragment.uri_generator', FragmentUriGenerator::class)->args([param('fragment.path'), service('uri_signer'), service('request_stack')])->alias(FragmentUriGeneratorInterface::class, 'fragment.uri_generator')->set('fragment.renderer.inline', InlineFragmentRenderer::class)->args([service('http_kernel'), service('event_dispatcher')])->call('setFragmentPath', [param('fragment.path')])->tag('kernel.fragment_renderer', ['alias' => 'inline'])->set('fragment.renderer.hinclude', HIncludeFragmentRenderer::class)->args([service('twig')->nullOnInvalid(), service('uri_signer'), param('fragment.renderer.hinclude.global_template')])->call('setFragmentPath', [param('fragment.path')])->set('fragment.renderer.esi', EsiFragmentRenderer::class)->args([service('esi')->nullOnInvalid(), service('fragment.renderer.inline'), service('uri_signer')])->call('setFragmentPath', [param('fragment.path')])->tag('kernel.fragment_renderer', ['alias' => 'esi'])->set('fragment.renderer.ssi', SsiFragmentRenderer::class)->args([service('ssi')->nullOnInvalid(), service('fragment.renderer.inline'), service('uri_signer')])->call('setFragmentPath', [param('fragment.path')])->tag('kernel.fragment_renderer', ['alias' => 'ssi']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; return static function (ContainerConfigurator $container) { $container->services()->set('web_link.add_link_header_listener', AddLinkHeaderListener::class)->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\RedirectController; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\TemplateController; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing\Router; use _ContaoManager\Symfony\Component\Config\Loader\LoaderResolver; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\RouterListener; use _ContaoManager\Symfony\Component\Routing\Generator\CompiledUrlGenerator; use _ContaoManager\Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Routing\Loader\ContainerLoader; use _ContaoManager\Symfony\Component\Routing\Loader\DirectoryLoader; use _ContaoManager\Symfony\Component\Routing\Loader\GlobFileLoader; use _ContaoManager\Symfony\Component\Routing\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\Routing\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\Routing\Loader\YamlFileLoader; use _ContaoManager\Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; use _ContaoManager\Symfony\Component\Routing\Matcher\ExpressionLanguageProvider; use _ContaoManager\Symfony\Component\Routing\Matcher\UrlMatcherInterface; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Routing\RequestContextAwareInterface; use _ContaoManager\Symfony\Component\Routing\RouterInterface; return static function (ContainerConfigurator $container) { $container->parameters()->set('router.request_context.host', 'localhost')->set('router.request_context.scheme', 'http')->set('router.request_context.base_url', ''); $container->services()->set('routing.resolver', LoaderResolver::class)->set('routing.loader.xml', XmlFileLoader::class)->args([service('file_locator'), '%kernel.environment%'])->tag('routing.loader')->set('routing.loader.yml', YamlFileLoader::class)->args([service('file_locator'), '%kernel.environment%'])->tag('routing.loader')->set('routing.loader.php', PhpFileLoader::class)->args([service('file_locator'), '%kernel.environment%'])->tag('routing.loader')->set('routing.loader.glob', GlobFileLoader::class)->args([service('file_locator'), '%kernel.environment%'])->tag('routing.loader')->set('routing.loader.directory', DirectoryLoader::class)->args([service('file_locator'), '%kernel.environment%'])->tag('routing.loader')->set('routing.loader.container', ContainerLoader::class)->args([tagged_locator('routing.route_loader'), '%kernel.environment%'])->tag('routing.loader')->set('routing.loader', DelegatingLoader::class)->public()->args([ service('routing.resolver'), [], // Default options [], ])->set('router.default', Router::class)->args([service(ContainerInterface::class), param('router.resource'), ['cache_dir' => param('kernel.cache_dir'), 'debug' => param('kernel.debug'), 'generator_class' => CompiledUrlGenerator::class, 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, 'matcher_class' => RedirectableCompiledUrlMatcher::class, 'matcher_dumper_class' => CompiledUrlMatcherDumper::class], service('router.request_context')->ignoreOnInvalid(), service('parameter_bag')->ignoreOnInvalid(), service('logger')->ignoreOnInvalid(), param('kernel.default_locale')])->call('setConfigCacheFactory', [service('config_cache_factory')])->tag('monolog.logger', ['channel' => 'router'])->tag('container.service_subscriber', ['id' => 'routing.loader'])->alias('router', 'router.default')->public()->alias(RouterInterface::class, 'router')->alias(UrlGeneratorInterface::class, 'router')->alias(UrlMatcherInterface::class, 'router')->alias(RequestContextAwareInterface::class, 'router')->set('router.request_context', RequestContext::class)->factory([RequestContext::class, 'fromUri'])->args([param('router.request_context.base_url'), param('router.request_context.host'), param('router.request_context.scheme'), param('request_listener.http_port'), param('request_listener.https_port')])->call('setParameter', ['_functions', service('router.expression_language_provider')->ignoreOnInvalid()])->alias(RequestContext::class, 'router.request_context')->set('router.expression_language_provider', ExpressionLanguageProvider::class)->args([tagged_locator('routing.expression_language_function', 'function')])->tag('routing.expression_language_provider')->set('router.cache_warmer', RouterCacheWarmer::class)->args([service(ContainerInterface::class)])->tag('container.service_subscriber', ['id' => 'router'])->tag('kernel.cache_warmer')->set('router_listener', RouterListener::class)->args([service('router'), service('request_stack'), service('router.request_context')->ignoreOnInvalid(), service('logger')->ignoreOnInvalid(), param('kernel.project_dir'), param('kernel.debug')])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'request'])->set(RedirectController::class)->public()->args([service('router'), inline_service('int')->factory([service('router.request_context'), 'getHttpPort']), inline_service('int')->factory([service('router.request_context'), 'getHttpsPort'])])->set(TemplateController::class)->args([service('twig')->ignoreOnInvalid()])->public(); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bridge\Monolog\Handler\NotifierHandler; use _ContaoManager\Symfony\Component\Notifier\Channel\BrowserChannel; use _ContaoManager\Symfony\Component\Notifier\Channel\ChannelPolicy; use _ContaoManager\Symfony\Component\Notifier\Channel\ChatChannel; use _ContaoManager\Symfony\Component\Notifier\Channel\EmailChannel; use _ContaoManager\Symfony\Component\Notifier\Channel\PushChannel; use _ContaoManager\Symfony\Component\Notifier\Channel\SmsChannel; use _ContaoManager\Symfony\Component\Notifier\Chatter; use _ContaoManager\Symfony\Component\Notifier\ChatterInterface; use _ContaoManager\Symfony\Component\Notifier\EventListener\NotificationLoggerListener; use _ContaoManager\Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; use _ContaoManager\Symfony\Component\Notifier\Message\ChatMessage; use _ContaoManager\Symfony\Component\Notifier\Message\PushMessage; use _ContaoManager\Symfony\Component\Notifier\Message\SmsMessage; use _ContaoManager\Symfony\Component\Notifier\Messenger\MessageHandler; use _ContaoManager\Symfony\Component\Notifier\Notifier; use _ContaoManager\Symfony\Component\Notifier\NotifierInterface; use _ContaoManager\Symfony\Component\Notifier\Texter; use _ContaoManager\Symfony\Component\Notifier\TexterInterface; use _ContaoManager\Symfony\Component\Notifier\Transport; use _ContaoManager\Symfony\Component\Notifier\Transport\Transports; return static function (ContainerConfigurator $container) { $container->services()->set('notifier', Notifier::class)->args([tagged_locator('notifier.channel', 'channel'), service('notifier.channel_policy')->ignoreOnInvalid()])->alias(NotifierInterface::class, 'notifier')->set('notifier.channel_policy', ChannelPolicy::class)->args([[]])->set('notifier.channel.browser', BrowserChannel::class)->args([service('request_stack')])->tag('notifier.channel', ['channel' => 'browser'])->set('notifier.channel.chat', ChatChannel::class)->args([service('chatter.transports'), service('messenger.default_bus')->ignoreOnInvalid()])->tag('notifier.channel', ['channel' => 'chat'])->set('notifier.channel.sms', SmsChannel::class)->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()])->tag('notifier.channel', ['channel' => 'sms'])->set('notifier.channel.email', EmailChannel::class)->args([service('mailer.transports'), service('messenger.default_bus')->ignoreOnInvalid()])->tag('notifier.channel', ['channel' => 'email'])->set('notifier.channel.push', PushChannel::class)->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()])->tag('notifier.channel', ['channel' => 'push'])->set('notifier.monolog_handler', NotifierHandler::class)->args([service('notifier')])->set('notifier.failed_message_listener', SendFailedMessageToNotifierListener::class)->args([service('notifier')])->set('chatter', Chatter::class)->args([service('chatter.transports'), service('messenger.default_bus')->ignoreOnInvalid(), service('event_dispatcher')->ignoreOnInvalid()])->alias(ChatterInterface::class, 'chatter')->set('chatter.transports', Transports::class)->factory([service('chatter.transport_factory'), 'fromStrings'])->args([[]])->set('chatter.transport_factory', Transport::class)->args([tagged_iterator('chatter.transport_factory')])->set('chatter.messenger.chat_handler', MessageHandler::class)->args([service('chatter.transports')])->tag('messenger.message_handler', ['handles' => ChatMessage::class])->set('texter', Texter::class)->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid(), service('event_dispatcher')->ignoreOnInvalid()])->alias(TexterInterface::class, 'texter')->set('texter.transports', Transports::class)->factory([service('texter.transport_factory'), 'fromStrings'])->args([[]])->set('texter.transport_factory', Transport::class)->args([tagged_iterator('texter.transport_factory')])->set('texter.messenger.sms_handler', MessageHandler::class)->args([service('texter.transports')])->tag('messenger.message_handler', ['handles' => SmsMessage::class])->set('texter.messenger.push_handler', MessageHandler::class)->args([service('texter.transports')])->tag('messenger.message_handler', ['handles' => PushMessage::class])->set('notifier.logger_notification_listener', NotificationLoggerListener::class)->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; return static function (ContainerConfigurator $container) { $container->services()->set('data_collector.http_client', HttpClientDataCollector::class)->tag('data_collector', ['template' => '@WebProfiler/Collector/http_client.html.twig', 'id' => 'http_client', 'priority' => 250]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener; return static function (ContainerConfigurator $container) { $container->services()->set('request.add_request_formats_listener', AddRequestFormatsListener::class)->args([abstract_arg('formats')])->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\KernelBrowser; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Test\TestContainer; use _ContaoManager\Symfony\Component\BrowserKit\CookieJar; use _ContaoManager\Symfony\Component\BrowserKit\History; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\SessionListener; return static function (ContainerConfigurator $container) { $container->parameters()->set('test.client.parameters', []); $container->services()->set('test.client', KernelBrowser::class)->args([service('kernel'), param('test.client.parameters'), service('test.client.history'), service('test.client.cookiejar')])->share(\false)->public()->set('test.client.history', History::class)->share(\false)->set('test.client.cookiejar', CookieJar::class)->share(\false)->set('test.session.listener', SessionListener::class)->args([service_locator(['session' => service('.session.do-not-use')->ignoreOnInvalid(), 'session_factory' => service('session.factory')->ignoreOnInvalid()]), param('kernel.debug'), param('session.storage.options')])->tag('kernel.event_subscriber')->set('test.service_container', TestContainer::class)->args([service('kernel'), 'test.private_services_locator'])->public()->set('test.private_services_locator', ServiceLocator::class)->args([abstract_arg('callable collection')])->public(); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Validator\DataCollector\ValidatorDataCollector; use _ContaoManager\Symfony\Component\Validator\Validator\TraceableValidator; return static function (ContainerConfigurator $container) { $container->services()->set('debug.validator', TraceableValidator::class)->decorate('validator', null, 255)->args([service('debug.validator.inner')])->tag('kernel.reset', ['method' => 'reset'])->set('data_collector.validator', ValidatorDataCollector::class)->args([service('debug.validator')])->tag('data_collector', ['template' => '@WebProfiler/Collector/validator.html.twig', 'id' => 'validator', 'priority' => 320]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; return static function (ContainerConfigurator $container) { $container->services()->set('error_handler.error_renderer.html', HtmlErrorRenderer::class)->args([inline_service()->factory([HtmlErrorRenderer::class, 'isDebug'])->args([service('request_stack'), param('kernel.debug')]), param('kernel.charset'), service('debug.file_link_formatter')->nullOnInvalid(), param('kernel.project_dir'), inline_service()->factory([HtmlErrorRenderer::class, 'getAndCleanOutputBuffer'])->args([service('request_stack')]), service('logger')->nullOnInvalid()])->alias('error_renderer.html', 'error_handler.error_renderer.html')->alias('error_renderer', 'error_renderer.html'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Mime\MimeTypeGuesserInterface; use _ContaoManager\Symfony\Component\Mime\MimeTypes; use _ContaoManager\Symfony\Component\Mime\MimeTypesInterface; return static function (ContainerConfigurator $container) { $container->services()->set('mime_types', MimeTypes::class)->call('setDefault', [service('mime_types')])->alias(MimeTypesInterface::class, 'mime_types')->alias(MimeTypeGuesserInterface::class, 'mime_types'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; use _ContaoManager\Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; use _ContaoManager\Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\DispatchPcntlSignalListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\ResetServicesListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener; use _ContaoManager\Symfony\Component\Messenger\EventListener\StopWorkerOnSigtermSignalListener; use _ContaoManager\Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\RouterContextMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\SendMessageMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\TraceableMiddleware; use _ContaoManager\Symfony\Component\Messenger\Middleware\ValidationMiddleware; use _ContaoManager\Symfony\Component\Messenger\Retry\MultiplierRetryStrategy; use _ContaoManager\Symfony\Component\Messenger\RoutableMessageBus; use _ContaoManager\Symfony\Component\Messenger\Transport\InMemoryTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Transport\Sender\SendersLocator; use _ContaoManager\Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; use _ContaoManager\Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; use _ContaoManager\Symfony\Component\Messenger\Transport\Serialization\Serializer; use _ContaoManager\Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use _ContaoManager\Symfony\Component\Messenger\Transport\Sync\SyncTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Transport\TransportFactory; return static function (ContainerConfigurator $container) { $container->services()->alias(SerializerInterface::class, 'messenger.default_serializer')->set('messenger.senders_locator', SendersLocator::class)->args([abstract_arg('per message senders map'), abstract_arg('senders service locator')])->set('messenger.middleware.send_message', SendMessageMiddleware::class)->args([service('messenger.senders_locator'), service('event_dispatcher')])->call('setLogger', [service('logger')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'messenger'])->set('messenger.transport.symfony_serializer', Serializer::class)->args([service('serializer'), abstract_arg('format'), abstract_arg('context')])->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class)->tag('serializer.normalizer', ['priority' => -880])->set('messenger.transport.native_php_serializer', PhpSerializer::class)->set('messenger.middleware.handle_message', HandleMessageMiddleware::class)->abstract()->args([abstract_arg('bus handler resolver')])->tag('monolog.logger', ['channel' => 'messenger'])->call('setLogger', [service('logger')->ignoreOnInvalid()])->set('messenger.middleware.add_bus_name_stamp_middleware', AddBusNameStampMiddleware::class)->abstract()->set('messenger.middleware.dispatch_after_current_bus', DispatchAfterCurrentBusMiddleware::class)->set('messenger.middleware.validation', ValidationMiddleware::class)->args([service('validator')])->set('messenger.middleware.reject_redelivered_message_middleware', RejectRedeliveredMessageMiddleware::class)->set('messenger.middleware.failed_message_processing_middleware', FailedMessageProcessingMiddleware::class)->set('messenger.middleware.traceable', TraceableMiddleware::class)->abstract()->args([service('debug.stopwatch')])->set('messenger.middleware.router_context', RouterContextMiddleware::class)->args([service('router')])->set('messenger.receiver_locator', ServiceLocator::class)->args([[]])->tag('container.service_locator')->set('messenger.transport_factory', TransportFactory::class)->args([tagged_iterator('messenger.transport_factory')])->set('messenger.transport.amqp.factory', AmqpTransportFactory::class)->set('messenger.transport.redis.factory', RedisTransportFactory::class)->set('messenger.transport.sync.factory', SyncTransportFactory::class)->args([service('messenger.routable_message_bus')])->tag('messenger.transport_factory')->set('messenger.transport.in_memory.factory', InMemoryTransportFactory::class)->tag('messenger.transport_factory')->tag('kernel.reset', ['method' => 'reset'])->set('messenger.transport.sqs.factory', AmazonSqsTransportFactory::class)->args([service('logger')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'messenger'])->set('messenger.transport.beanstalkd.factory', BeanstalkdTransportFactory::class)->set('messenger.retry_strategy_locator', ServiceLocator::class)->args([[]])->tag('container.service_locator')->set('messenger.retry.abstract_multiplier_retry_strategy', MultiplierRetryStrategy::class)->abstract()->args([abstract_arg('max retries'), abstract_arg('delay ms'), abstract_arg('multiplier'), abstract_arg('max delay ms')])->set('messenger.retry.send_failed_message_for_retry_listener', SendFailedMessageForRetryListener::class)->args([abstract_arg('senders service locator'), service('messenger.retry_strategy_locator'), service('logger')->ignoreOnInvalid(), service('event_dispatcher')])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'messenger'])->set('messenger.failure.add_error_details_stamp_listener', AddErrorDetailsStampListener::class)->tag('kernel.event_subscriber')->set('messenger.failure.send_failed_message_to_failure_transport_listener', SendFailedMessageToFailureTransportListener::class)->args([abstract_arg('failure transports'), service('logger')->ignoreOnInvalid()])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'messenger'])->set('messenger.listener.dispatch_pcntl_signal_listener', DispatchPcntlSignalListener::class)->tag('kernel.event_subscriber')->set('messenger.listener.stop_worker_on_restart_signal_listener', StopWorkerOnRestartSignalListener::class)->args([service('cache.messenger.restart_workers_signal'), service('logger')->ignoreOnInvalid()])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'messenger'])->set('messenger.listener.stop_worker_on_sigterm_signal_listener', StopWorkerOnSigtermSignalListener::class)->args([service('logger')->ignoreOnInvalid()])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'messenger'])->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class)->tag('kernel.event_subscriber')->set('messenger.listener.reset_services', ResetServicesListener::class)->args([service('services_resetter')])->set('messenger.routable_message_bus', RoutableMessageBus::class)->args([abstract_arg('message bus locator'), service('messenger.default_bus')]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\AboutCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand; use _ContaoManager\Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber; use _ContaoManager\Symfony\Component\Console\EventListener\ErrorListener; use _ContaoManager\Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand; use _ContaoManager\Symfony\Component\Messenger\Command\ConsumeMessagesCommand; use _ContaoManager\Symfony\Component\Messenger\Command\DebugCommand; use _ContaoManager\Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand; use _ContaoManager\Symfony\Component\Messenger\Command\FailedMessagesRetryCommand; use _ContaoManager\Symfony\Component\Messenger\Command\FailedMessagesShowCommand; use _ContaoManager\Symfony\Component\Messenger\Command\SetupTransportsCommand; use _ContaoManager\Symfony\Component\Messenger\Command\StopWorkersCommand; use _ContaoManager\Symfony\Component\Translation\Command\TranslationPullCommand; use _ContaoManager\Symfony\Component\Translation\Command\TranslationPushCommand; use _ContaoManager\Symfony\Component\Translation\Command\XliffLintCommand; use _ContaoManager\Symfony\Component\Validator\Command\DebugCommand as ValidatorDebugCommand; return static function (ContainerConfigurator $container) { $container->services()->set('console.error_listener', ErrorListener::class)->args([service('logger')->nullOnInvalid()])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'console'])->set('console.suggest_missing_package_subscriber', SuggestMissingPackageSubscriber::class)->tag('kernel.event_subscriber')->set('console.command.about', AboutCommand::class)->tag('console.command')->set('console.command.assets_install', AssetsInstallCommand::class)->args([service('filesystem'), param('kernel.project_dir')])->tag('console.command')->set('console.command.cache_clear', CacheClearCommand::class)->args([service('cache_clearer'), service('filesystem')])->tag('console.command')->set('console.command.cache_pool_clear', CachePoolClearCommand::class)->args([service('cache.global_clearer')])->tag('console.command')->set('console.command.cache_pool_prune', CachePoolPruneCommand::class)->args([[]])->tag('console.command')->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class)->args([service('cache.global_clearer')])->tag('console.command')->set('console.command.cache_pool_list', CachePoolListCommand::class)->args([null])->tag('console.command')->set('console.command.cache_warmup', CacheWarmupCommand::class)->args([service('cache_warmer')])->tag('console.command')->set('console.command.config_debug', ConfigDebugCommand::class)->tag('console.command')->set('console.command.config_dump_reference', ConfigDumpReferenceCommand::class)->tag('console.command')->set('console.command.container_debug', ContainerDebugCommand::class)->tag('console.command')->set('console.command.container_lint', ContainerLintCommand::class)->tag('console.command')->set('console.command.debug_autowiring', DebugAutowiringCommand::class)->args([null, service('debug.file_link_formatter')->nullOnInvalid()])->tag('console.command')->set('console.command.dotenv_debug', DotenvDebugCommand::class)->args([param('kernel.environment'), param('kernel.project_dir')])->tag('console.command')->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class)->args([tagged_locator('event_dispatcher.dispatcher', 'name')])->tag('console.command')->set('console.command.messenger_consume_messages', ConsumeMessagesCommand::class)->args([ abstract_arg('Routable message bus'), service('messenger.receiver_locator'), service('event_dispatcher'), service('logger')->nullOnInvalid(), [], // Receiver names service('messenger.listener.reset_services')->nullOnInvalid(), [], ])->tag('console.command')->tag('monolog.logger', ['channel' => 'messenger'])->set('console.command.messenger_setup_transports', SetupTransportsCommand::class)->args([service('messenger.receiver_locator'), []])->tag('console.command')->set('console.command.messenger_debug', DebugCommand::class)->args([[]])->tag('console.command')->set('console.command.messenger_stop_workers', StopWorkersCommand::class)->args([service('cache.messenger.restart_workers_signal')])->tag('console.command')->set('console.command.messenger_failed_messages_retry', FailedMessagesRetryCommand::class)->args([abstract_arg('Default failure receiver name'), abstract_arg('Receivers'), service('messenger.routable_message_bus'), service('event_dispatcher'), service('logger')->nullOnInvalid()])->tag('console.command')->tag('monolog.logger', ['channel' => 'messenger'])->set('console.command.messenger_failed_messages_show', FailedMessagesShowCommand::class)->args([abstract_arg('Default failure receiver name'), abstract_arg('Receivers')])->tag('console.command')->set('console.command.messenger_failed_messages_remove', FailedMessagesRemoveCommand::class)->args([abstract_arg('Default failure receiver name'), abstract_arg('Receivers')])->tag('console.command')->set('console.command.router_debug', RouterDebugCommand::class)->args([service('router'), service('debug.file_link_formatter')->nullOnInvalid()])->tag('console.command')->set('console.command.router_match', RouterMatchCommand::class)->args([service('router'), tagged_iterator('routing.expression_language_provider')])->tag('console.command')->set('console.command.translation_debug', TranslationDebugCommand::class)->args([ service('translator'), service('translation.reader'), service('translation.extractor'), param('translator.default_path'), null, // twig.default_path [], // Translator paths [], // Twig paths param('kernel.enabled_locales'), ])->tag('console.command')->set('console.command.translation_extract', TranslationUpdateCommand::class)->args([ service('translation.writer'), service('translation.reader'), service('translation.extractor'), param('kernel.default_locale'), param('translator.default_path'), null, // twig.default_path [], // Translator paths [], // Twig paths param('kernel.enabled_locales'), ])->tag('console.command')->set('console.command.validator_debug', ValidatorDebugCommand::class)->args([service('validator')])->tag('console.command')->set('console.command.translation_pull', TranslationPullCommand::class)->args([ service('translation.provider_collection'), service('translation.writer'), service('translation.reader'), param('kernel.default_locale'), [], // Translator paths [], ])->tag('console.command', ['command' => 'translation:pull'])->set('console.command.translation_push', TranslationPushCommand::class)->args([ service('translation.provider_collection'), service('translation.reader'), [], // Translator paths [], ])->tag('console.command', ['command' => 'translation:push'])->set('console.command.workflow_dump', WorkflowDumpCommand::class)->tag('console.command')->set('console.command.xliff_lint', XliffLintCommand::class)->tag('console.command')->set('console.command.yaml_lint', YamlLintCommand::class)->tag('console.command')->set('console.command.form_debug', \_ContaoManager\Symfony\Component\Form\Command\DebugCommand::class)->args([ service('form.registry'), [], // All form types namespaces are stored here by FormPass [], // All services form types are stored here by FormPass [], // All type extensions are stored here by FormPass [], // All type guessers are stored here by FormPass service('debug.file_link_formatter')->nullOnInvalid(), ])->tag('console.command')->set('console.command.secrets_set', SecretsSetCommand::class)->args([service('secrets.vault'), service('secrets.local_vault')->nullOnInvalid()])->tag('console.command')->set('console.command.secrets_remove', SecretsRemoveCommand::class)->args([service('secrets.vault'), service('secrets.local_vault')->nullOnInvalid()])->tag('console.command')->set('console.command.secrets_generate_key', SecretsGenerateKeysCommand::class)->args([service('secrets.vault'), service('secrets.local_vault')->ignoreOnInvalid()])->tag('console.command')->set('console.command.secrets_list', SecretsListCommand::class)->args([service('secrets.vault'), service('secrets.local_vault')->ignoreOnInvalid()])->tag('console.command')->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class)->args([service('secrets.vault'), service('secrets.local_vault')->ignoreOnInvalid()])->tag('console.command')->set('console.command.secrets_encrypt_from_local', SecretsEncryptFromLocalCommand::class)->args([service('secrets.vault'), service('secrets.local_vault')->ignoreOnInvalid()])->tag('console.command'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Uid\Factory\NameBasedUuidFactory; use _ContaoManager\Symfony\Component\Uid\Factory\RandomBasedUuidFactory; use _ContaoManager\Symfony\Component\Uid\Factory\TimeBasedUuidFactory; use _ContaoManager\Symfony\Component\Uid\Factory\UlidFactory; use _ContaoManager\Symfony\Component\Uid\Factory\UuidFactory; return static function (ContainerConfigurator $container) { $container->services()->set('ulid.factory', UlidFactory::class)->alias(UlidFactory::class, 'ulid.factory')->set('uuid.factory', UuidFactory::class)->alias(UuidFactory::class, 'uuid.factory')->set('name_based_uuid.factory', NameBasedUuidFactory::class)->factory([service('uuid.factory'), 'nameBased'])->args([abstract_arg('Please set the "framework.uid.name_based_uuid_namespace" configuration option to use the "name_based_uuid.factory" service')])->alias(NameBasedUuidFactory::class, 'name_based_uuid.factory')->set('random_based_uuid.factory', RandomBasedUuidFactory::class)->factory([service('uuid.factory'), 'randomBased'])->alias(RandomBasedUuidFactory::class, 'random_based_uuid.factory')->set('time_based_uuid.factory', TimeBasedUuidFactory::class)->factory([service('uuid.factory'), 'timeBased'])->alias(TimeBasedUuidFactory::class, 'time_based_uuid.factory'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInfoExtractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; return static function (ContainerConfigurator $container) { $container->services()->set('property_info', PropertyInfoExtractor::class)->args([[], [], [], [], []])->alias(PropertyAccessExtractorInterface::class, 'property_info')->alias(PropertyDescriptionExtractorInterface::class, 'property_info')->alias(PropertyInfoExtractorInterface::class, 'property_info')->alias(PropertyTypeExtractorInterface::class, 'property_info')->alias(PropertyListExtractorInterface::class, 'property_info')->alias(PropertyInitializableExtractorInterface::class, 'property_info')->set('property_info.cache', PropertyInfoCacheExtractor::class)->decorate('property_info')->args([service('property_info.cache.inner'), service('cache.property_info')])->set('property_info.reflection_extractor', ReflectionExtractor::class)->tag('property_info.list_extractor', ['priority' => -1000])->tag('property_info.type_extractor', ['priority' => -1002])->tag('property_info.access_extractor', ['priority' => -1000])->tag('property_info.initializable_extractor', ['priority' => -1000])->alias(PropertyReadInfoExtractorInterface::class, 'property_info.reflection_extractor')->alias(PropertyWriteInfoExtractorInterface::class, 'property_info.reflection_extractor'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\SurrogateListener; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\Ssi; return static function (ContainerConfigurator $container) { $container->services()->set('ssi', Ssi::class)->set('ssi_listener', SurrogateListener::class)->args([service('ssi')->ignoreOnInvalid()])->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ErrorController; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\ErrorListener; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\LocaleListener; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\ResponseListener; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\StreamedResponseListener; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; return static function (ContainerConfigurator $container) { $container->services()->set('controller_resolver', ControllerResolver::class)->args([service('service_container'), service('logger')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'request'])->set('argument_metadata_factory', ArgumentMetadataFactory::class)->set('argument_resolver', ArgumentResolver::class)->args([service('argument_metadata_factory'), abstract_arg('argument value resolvers')])->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class)->tag('controller.argument_value_resolver', ['priority' => 100])->set('argument_resolver.request', RequestValueResolver::class)->tag('controller.argument_value_resolver', ['priority' => 50])->set('argument_resolver.session', SessionValueResolver::class)->tag('controller.argument_value_resolver', ['priority' => 50])->set('argument_resolver.service', ServiceValueResolver::class)->args([abstract_arg('service locator, set in RegisterControllerArgumentLocatorsPass')])->tag('controller.argument_value_resolver', ['priority' => -50])->set('argument_resolver.default', DefaultValueResolver::class)->tag('controller.argument_value_resolver', ['priority' => -100])->set('argument_resolver.variadic', VariadicValueResolver::class)->tag('controller.argument_value_resolver', ['priority' => -150])->set('response_listener', ResponseListener::class)->args([param('kernel.charset'), abstract_arg('The "set_content_language_from_locale" config value')])->tag('kernel.event_subscriber')->set('streamed_response_listener', StreamedResponseListener::class)->tag('kernel.event_subscriber')->set('locale_listener', LocaleListener::class)->args([service('request_stack'), param('kernel.default_locale'), service('router')->ignoreOnInvalid(), abstract_arg('The "set_locale_from_accept_language" config value'), param('kernel.enabled_locales')])->tag('kernel.event_subscriber')->set('validate_request_listener', ValidateRequestListener::class)->tag('kernel.event_subscriber')->set('disallow_search_engine_index_response_listener', DisallowRobotsIndexingListener::class)->tag('kernel.event_subscriber')->set('error_controller', ErrorController::class)->public()->args([service('http_kernel'), param('kernel.error_controller'), service('error_renderer')])->set('exception_listener', ErrorListener::class)->args([param('kernel.error_controller'), service('logger')->nullOnInvalid(), param('kernel.debug'), abstract_arg('an exceptions to log & status code mapping')])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'request']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Messenger\DataCollector\MessengerDataCollector; return static function (ContainerConfigurator $container) { $container->services()->set('data_collector.messenger', MessengerDataCollector::class)->tag('data_collector', ['template' => '@WebProfiler/Collector/messenger.html.twig', 'id' => 'messenger', 'priority' => 100]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\EventDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; return static function (ContainerConfigurator $container) { $container->services()->set('data_collector.config', ConfigDataCollector::class)->call('setKernel', [service('kernel')->ignoreOnInvalid()])->tag('data_collector', ['template' => '@WebProfiler/Collector/config.html.twig', 'id' => 'config', 'priority' => -255])->set('data_collector.request', RequestDataCollector::class)->args([service('request_stack')->ignoreOnInvalid()])->tag('kernel.event_subscriber')->tag('data_collector', ['template' => '@WebProfiler/Collector/request.html.twig', 'id' => 'request', 'priority' => 335])->set('data_collector.request.session_collector', \Closure::class)->factory([\Closure::class, 'fromCallable'])->args([[service('data_collector.request'), 'collectSessionUsage']])->set('data_collector.ajax', AjaxDataCollector::class)->tag('data_collector', ['template' => '@WebProfiler/Collector/ajax.html.twig', 'id' => 'ajax', 'priority' => 315])->set('data_collector.exception', ExceptionDataCollector::class)->tag('data_collector', ['template' => '@WebProfiler/Collector/exception.html.twig', 'id' => 'exception', 'priority' => 305])->set('data_collector.events', EventDataCollector::class)->args([service('debug.event_dispatcher')->ignoreOnInvalid(), service('request_stack')->ignoreOnInvalid()])->tag('data_collector', ['template' => '@WebProfiler/Collector/events.html.twig', 'id' => 'events', 'priority' => 290])->set('data_collector.logger', LoggerDataCollector::class)->args([service('logger')->ignoreOnInvalid(), \sprintf('%s/%s', param('kernel.build_dir'), param('kernel.container_class')), service('request_stack')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'profiler'])->tag('data_collector', ['template' => '@WebProfiler/Collector/logger.html.twig', 'id' => 'logger', 'priority' => 300])->set('data_collector.time', TimeDataCollector::class)->args([service('kernel')->ignoreOnInvalid(), service('debug.stopwatch')->ignoreOnInvalid()])->tag('data_collector', ['template' => '@WebProfiler/Collector/time.html.twig', 'id' => 'time', 'priority' => 330])->set('data_collector.memory', MemoryDataCollector::class)->tag('data_collector', ['template' => '@WebProfiler/Collector/memory.html.twig', 'id' => 'memory', 'priority' => 325])->set('data_collector.router', RouterDataCollector::class)->tag('kernel.event_listener', ['event' => KernelEvents::CONTROLLER, 'method' => 'onKernelController'])->tag('data_collector', ['template' => '@WebProfiler/Collector/router.html.twig', 'id' => 'router', 'priority' => 285]); }; error_controller::preview html \d+ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; return static function (ContainerConfigurator $container) { $container->services()->set('secrets.vault', SodiumVault::class)->args([abstract_arg('Secret dir, set in FrameworkExtension'), service('secrets.decryption_key')->ignoreOnInvalid()])->tag('container.env_var_loader')->set('secrets.decryption_key')->parent('container.env')->args([abstract_arg('Decryption env var, set in FrameworkExtension')])->set('secrets.local_vault', DotenvVault::class)->args([abstract_arg('.env file path, set in FrameworkExtension')]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; use _ContaoManager\Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; use _ContaoManager\Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\ChoiceType; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\ColorType; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\FileType; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\FormType; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\SubmitType; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension; use _ContaoManager\Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension; use _ContaoManager\Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use _ContaoManager\Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension; use _ContaoManager\Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension; use _ContaoManager\Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension; use _ContaoManager\Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension; use _ContaoManager\Symfony\Component\Form\Extension\Validator\Type\UploadValidatorExtension; use _ContaoManager\Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser; use _ContaoManager\Symfony\Component\Form\FormFactory; use _ContaoManager\Symfony\Component\Form\FormFactoryInterface; use _ContaoManager\Symfony\Component\Form\FormRegistry; use _ContaoManager\Symfony\Component\Form\FormRegistryInterface; use _ContaoManager\Symfony\Component\Form\ResolvedFormTypeFactory; use _ContaoManager\Symfony\Component\Form\ResolvedFormTypeFactoryInterface; use _ContaoManager\Symfony\Component\Form\Util\ServerParams; return static function (ContainerConfigurator $container) { $container->services()->set('form.resolved_type_factory', ResolvedFormTypeFactory::class)->alias(ResolvedFormTypeFactoryInterface::class, 'form.resolved_type_factory')->set('form.registry', FormRegistry::class)->args([[ /* * We don't need to be able to add more extensions. * more types can be registered with the form.type tag * more type extensions can be registered with the form.type_extension tag * more type_guessers can be registered with the form.type_guesser tag */ service('form.extension'), ], service('form.resolved_type_factory')])->alias(FormRegistryInterface::class, 'form.registry')->set('form.factory', FormFactory::class)->public()->args([service('form.registry')])->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->alias(FormFactoryInterface::class, 'form.factory')->set('form.extension', DependencyInjectionExtension::class)->args([abstract_arg('All services with tag "form.type" are stored in a service locator by FormPass'), abstract_arg('All services with tag "form.type_extension" are stored here by FormPass'), abstract_arg('All services with tag "form.type_guesser" are stored here by FormPass')])->set('form.type_guesser.validator', ValidatorTypeGuesser::class)->args([service('validator.mapping.class_metadata_factory')])->tag('form.type_guesser')->alias('form.property_accessor', 'property_accessor')->set('form.choice_list_factory.default', DefaultChoiceListFactory::class)->set('form.choice_list_factory.property_access', PropertyAccessDecorator::class)->args([service('form.choice_list_factory.default'), service('form.property_accessor')])->set('form.choice_list_factory.cached', CachingFactoryDecorator::class)->args([service('form.choice_list_factory.property_access')])->tag('kernel.reset', ['method' => 'reset'])->alias('form.choice_list_factory', 'form.choice_list_factory.cached')->set('form.type.form', FormType::class)->args([service('form.property_accessor')])->tag('form.type')->set('form.type.choice', ChoiceType::class)->args([service('form.choice_list_factory'), service('translator')->ignoreOnInvalid()])->tag('form.type')->set('form.type.file', FileType::class)->public()->args([service('translator')->ignoreOnInvalid()])->tag('form.type')->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->set('form.type.color', ColorType::class)->args([service('translator')->ignoreOnInvalid()])->tag('form.type')->set('form.type_extension.form.transformation_failure_handling', TransformationFailureExtension::class)->args([service('translator')->ignoreOnInvalid()])->tag('form.type_extension', ['extended-type' => FormType::class])->set('form.type_extension.form.http_foundation', FormTypeHttpFoundationExtension::class)->args([service('form.type_extension.form.request_handler')])->tag('form.type_extension')->set('form.type_extension.form.request_handler', HttpFoundationRequestHandler::class)->args([service('form.server_params')])->set('form.server_params', ServerParams::class)->args([service('request_stack')])->set('form.type_extension.form.validator', FormTypeValidatorExtension::class)->args([service('validator'), \true, service('twig.form.renderer')->ignoreOnInvalid(), service('translator')->ignoreOnInvalid()])->tag('form.type_extension', ['extended-type' => FormType::class])->set('form.type_extension.repeated.validator', RepeatedTypeValidatorExtension::class)->tag('form.type_extension')->set('form.type_extension.submit.validator', SubmitTypeValidatorExtension::class)->tag('form.type_extension', ['extended-type' => SubmitType::class])->set('form.type_extension.upload.validator', UploadValidatorExtension::class)->args([service('translator'), param('validator.translation_domain')])->tag('form.type_extension'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension; return static function (ContainerConfigurator $container) { $container->services()->set('form.type_extension.csrf', FormTypeCsrfExtension::class)->args([service('security.csrf.token_manager'), param('form.type_extension.csrf.enabled'), param('form.type_extension.csrf.field_name'), service('translator')->nullOnInvalid(), param('validator.translation_domain'), service('form.server_params')])->tag('form.type_extension'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Workflow\EventListener\ExpressionLanguage; use _ContaoManager\Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; use _ContaoManager\Symfony\Component\Workflow\Registry; use _ContaoManager\Symfony\Component\Workflow\StateMachine; use _ContaoManager\Symfony\Component\Workflow\Workflow; return static function (ContainerConfigurator $container) { $container->services()->set('workflow.abstract', Workflow::class)->args([abstract_arg('workflow definition'), abstract_arg('marking store'), service('event_dispatcher')->ignoreOnInvalid(), abstract_arg('workflow name'), abstract_arg('events to dispatch')])->abstract()->public()->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3'])->set('state_machine.abstract', StateMachine::class)->args([abstract_arg('workflow definition'), abstract_arg('marking store'), service('event_dispatcher')->ignoreOnInvalid(), abstract_arg('workflow name'), abstract_arg('events to dispatch')])->abstract()->public()->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3'])->set('workflow.marking_store.method', MethodMarkingStore::class)->abstract()->set('workflow.registry', Registry::class)->alias(Registry::class, 'workflow.registry')->set('workflow.security.expression_language', ExpressionLanguage::class); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bridge\Twig\Extension\CsrfExtension; use _ContaoManager\Symfony\Bridge\Twig\Extension\CsrfRuntime; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManager; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use _ContaoManager\Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; use _ContaoManager\Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; return static function (ContainerConfigurator $container) { $container->services()->set('security.csrf.token_generator', UriSafeTokenGenerator::class)->alias(TokenGeneratorInterface::class, 'security.csrf.token_generator')->set('security.csrf.token_storage', SessionTokenStorage::class)->args([service('request_stack')])->alias(TokenStorageInterface::class, 'security.csrf.token_storage')->set('security.csrf.token_manager', CsrfTokenManager::class)->public()->args([service('security.csrf.token_generator'), service('security.csrf.token_storage'), service('request_stack')->ignoreOnInvalid()])->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])->alias(CsrfTokenManagerInterface::class, 'security.csrf.token_manager')->set('twig.runtime.security_csrf', CsrfRuntime::class)->args([service('security.csrf.token_manager')])->tag('twig.runtime')->set('twig.extension.security_csrf', CsrfExtension::class)->tag('twig.extension'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Transport\AbstractTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Transport\NullTransportFactory; return static function (ContainerConfigurator $container) { $container->services()->alias('notifier.transport_factory.allmysms', 'notifier.transport_factory.all-my-sms')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.all-my-sms" instead.')->alias('notifier.transport_factory.fakechat', 'notifier.transport_factory.fake-chat')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.fake-chat" instead.')->alias('notifier.transport_factory.fakesms', 'notifier.transport_factory.fake-sms')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.fake-sms" instead.')->alias('notifier.transport_factory.freemobile', 'notifier.transport_factory.free-mobile')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.free-mobile" instead.')->alias('notifier.transport_factory.gatewayapi', 'notifier.transport_factory.gateway-api')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.gateway-api" instead.')->alias('notifier.transport_factory.googlechat', 'notifier.transport_factory.google-chat')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.google-chat" instead.')->alias('notifier.transport_factory.lightsms', 'notifier.transport_factory.light-sms')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.light-sms" instead.')->alias('notifier.transport_factory.linkedin', 'notifier.transport_factory.linked-in')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.linked-in" instead.')->alias('notifier.transport_factory.microsoftteams', 'notifier.transport_factory.microsoft-teams')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.microsoft-teams" instead.')->alias('notifier.transport_factory.onesignal', 'notifier.transport_factory.one-signal')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.one-signal" instead.')->alias('notifier.transport_factory.ovhcloud', 'notifier.transport_factory.ovh-cloud')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.ovh-cloud" instead.')->alias('notifier.transport_factory.rocketchat', 'notifier.transport_factory.rocket-chat')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.rocket-chat" instead.')->alias('notifier.transport_factory.spothit', 'notifier.transport_factory.spot-hit')->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.spot-hit" instead.')->set('notifier.transport_factory.abstract', AbstractTransportFactory::class)->abstract()->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid()])->set('notifier.transport_factory.slack', SlackTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.linked-in', LinkedInTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.telegram', TelegramTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.mattermost', MattermostTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.nexmo', NexmoTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id% service is deprecated, use "notifier.transport_factory.vonage" instead.')->set('notifier.transport_factory.vonage', VonageTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.rocket-chat', RocketChatTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.google-chat', GoogleChatTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.twilio', TwilioTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.all-my-sms', AllMySmsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.firebase', FirebaseTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.free-mobile', FreeMobileTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.spot-hit', SpotHitTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.fake-chat', FakeChatTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.fake-sms', FakeSmsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.ovh-cloud', OvhCloudTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.sinch', SinchTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.zulip', ZulipTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.infobip', InfobipTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.mobyt', MobytTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.smsapi', SmsapiTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.esendex', EsendexTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.sendinblue', SendinblueTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.iqsms', IqsmsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.octopush', OctopushTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.discord', DiscordTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.microsoft-teams', MicrosoftTeamsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.gateway-api', GatewayApiTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.mercure', MercureTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.gitter', GitterTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->set('notifier.transport_factory.clickatell', ClickatellTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.amazon-sns', AmazonSnsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->tag('chatter.transport_factory')->set('notifier.transport_factory.null', NullTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('chatter.transport_factory')->tag('texter.transport_factory')->set('notifier.transport_factory.light-sms', LightSmsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.sms-biuras', SmsBiurasTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.smsc', SmscTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.message-bird', MessageBirdTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.message-media', MessageMediaTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.telnyx', TelnyxTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.mailjet', MailjetTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.yunpian', YunpianTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.turbo-sms', TurboSmsTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.sms77', Sms77TransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.one-signal', OneSignalTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory')->set('notifier.transport_factory.expo', ExpoTransportFactory::class)->parent('notifier.transport_factory.abstract')->tag('texter.transport_factory'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; return static function (ContainerConfigurator $container) { $container->parameters()->set('debug.error_handler.throw_at', -1); $container->services()->set('debug.debug_handlers_listener', DebugHandlersListener::class)->args([ null, // Exception handler service('logger')->nullOnInvalid(), null, // Log levels map for enabled error levels param('debug.error_handler.throw_at'), param('kernel.debug'), param('kernel.debug'), null, ])->tag('kernel.event_subscriber')->tag('monolog.logger', ['channel' => 'php'])->set('debug.file_link_formatter', FileLinkFormatter::class)->args([param('debug.file_link_format')])->alias(FileLinkFormatter::class, 'debug.file_link_formatter'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessor; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessorInterface; return static function (ContainerConfigurator $container) { $container->services()->set('property_accessor', PropertyAccessor::class)->args([abstract_arg('magic methods allowed, set by the extension'), abstract_arg('throw exceptions, set by the extension'), service('cache.property_access')->ignoreOnInvalid(), abstract_arg('propertyReadInfoExtractor, set by the extension'), abstract_arg('propertyWriteInfoExtractor, set by the extension')])->alias(PropertyAccessorInterface::class, 'property_accessor'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Asset\Context\RequestStackContext; use _ContaoManager\Symfony\Component\Asset\Package; use _ContaoManager\Symfony\Component\Asset\Packages; use _ContaoManager\Symfony\Component\Asset\PathPackage; use _ContaoManager\Symfony\Component\Asset\UrlPackage; use _ContaoManager\Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; use _ContaoManager\Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; use _ContaoManager\Symfony\Component\Asset\VersionStrategy\RemoteJsonManifestVersionStrategy; use _ContaoManager\Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; return static function (ContainerConfigurator $container) { $container->parameters()->set('asset.request_context.base_path', null)->set('asset.request_context.secure', null); $container->services()->set('assets.packages', Packages::class)->args([service('assets._default_package'), tagged_iterator('assets.package', 'package')])->alias(Packages::class, 'assets.packages')->set('assets.empty_package', Package::class)->args([service('assets.empty_version_strategy')])->alias('assets._default_package', 'assets.empty_package')->set('assets.context', RequestStackContext::class)->args([service('request_stack'), param('asset.request_context.base_path'), param('asset.request_context.secure')])->set('assets.path_package', PathPackage::class)->abstract()->args([abstract_arg('base path'), abstract_arg('version strategy'), service('assets.context')])->set('assets.url_package', UrlPackage::class)->abstract()->args([abstract_arg('base URLs'), abstract_arg('version strategy'), service('assets.context')])->set('assets.static_version_strategy', StaticVersionStrategy::class)->abstract()->args([abstract_arg('version'), abstract_arg('format')])->set('assets.empty_version_strategy', EmptyVersionStrategy::class)->set('assets.json_manifest_version_strategy', JsonManifestVersionStrategy::class)->abstract()->args([abstract_arg('manifest path'), service('http_client')->nullOnInvalid(), \false])->set('assets.remote_json_manifest_version_strategy', RemoteJsonManifestVersionStrategy::class)->abstract()->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "assets.json_manifest_version_strategy" instead.')->args([abstract_arg('manifest url'), service('http_client')]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer\CachePoolClearerCacheWarmer; use _ContaoManager\Symfony\Component\Cache\DataCollector\CacheDataCollector; return static function (ContainerConfigurator $container) { $container->services()->set('data_collector.cache', CacheDataCollector::class)->public()->tag('data_collector', ['template' => '@WebProfiler/Collector/cache.html.twig', 'id' => 'cache', 'priority' => 275])->set('cache_pool_clearer.cache_warmer', CachePoolClearerCacheWarmer::class)->args([service('cache.system_clearer'), ['cache.validator', 'cache.serializer']])->tag('kernel.cache_warmer', ['priority' => 64]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken; use _ContaoManager\Symfony\Component\BrowserKit\Cookie; use _ContaoManager\Symfony\Component\BrowserKit\CookieJar; use _ContaoManager\Symfony\Component\BrowserKit\History; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelBrowser; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Simulates a browser and makes requests to a Kernel object. * * @author Fabien Potencier */ class KernelBrowser extends HttpKernelBrowser { private $hasPerformedRequest = \false; private $profiler = \false; private $reboot = \true; /** * {@inheritdoc} */ public function __construct(KernelInterface $kernel, array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { parent::__construct($kernel, $server, $history, $cookieJar); } /** * Returns the container. * * @return ContainerInterface */ public function getContainer() { $container = $this->kernel->getContainer(); return $container->has('test.service_container') ? $container->get('test.service_container') : $container; } /** * Returns the kernel. * * @return KernelInterface */ public function getKernel() { return $this->kernel; } /** * Gets the profile associated with the current Response. * * @return HttpProfile|false|null */ public function getProfile() { if (null === $this->response || !$this->getContainer()->has('profiler')) { return \false; } return $this->getContainer()->get('profiler')->loadProfileFromResponse($this->response); } /** * Enables the profiler for the very next request. * * If the profiler is not enabled, the call to this method does nothing. */ public function enableProfiler() { if ($this->getContainer()->has('profiler')) { $this->profiler = \true; } } /** * Disables kernel reboot between requests. * * By default, the Client reboots the Kernel for each request. This method * allows to keep the same kernel across requests. */ public function disableReboot() { $this->reboot = \false; } /** * Enables kernel reboot between requests. */ public function enableReboot() { $this->reboot = \true; } /** * @param UserInterface $user * * @return $this */ public function loginUser(object $user, string $firewallContext = 'main') : self { if (!\interface_exists(UserInterface::class)) { throw new \LogicException(\sprintf('"%s" requires symfony/security-core to be installed.', __METHOD__)); } if (!$user instanceof UserInterface) { throw new \LogicException(\sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user))); } $token = new TestBrowserToken($user->getRoles(), $user, $firewallContext); // @deprecated since Symfony 5.4 if (\method_exists($token, 'setAuthenticated')) { $token->setAuthenticated(\true, \false); } $container = $this->getContainer(); $container->get('security.untracked_token_storage')->setToken($token); if ($container->has('session.factory')) { $session = $container->get('session.factory')->createSession(); } elseif ($container->has('session')) { $session = $container->get('session'); } else { return $this; } $session->set('_security_' . $firewallContext, \serialize($token)); $session->save(); $domains = \array_unique(\array_map(function (Cookie $cookie) use($session) { return $cookie->getName() === $session->getName() ? $cookie->getDomain() : ''; }, $this->getCookieJar()->all())) ?: ['']; foreach ($domains as $domain) { $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); $this->getCookieJar()->set($cookie); } return $this; } /** * {@inheritdoc} * * @param Request $request * * @return Response */ protected function doRequest(object $request) { // avoid shutting down the Kernel if no request has been performed yet // WebTestCase::createClient() boots the Kernel but do not handle a request if ($this->hasPerformedRequest && $this->reboot) { $this->kernel->boot(); $this->kernel->shutdown(); } else { $this->hasPerformedRequest = \true; } if ($this->profiler) { $this->profiler = \false; $this->kernel->boot(); $this->getContainer()->get('profiler')->enable(); } return parent::doRequest($request); } /** * {@inheritdoc} * * @param Request $request * * @return Response */ protected function doRequestInProcess(object $request) { $response = parent::doRequestInProcess($request); $this->profiler = \false; return $response; } /** * Returns the script to execute when the request must be insulated. * * It assumes that the autoloader is named 'autoload.php' and that it is * stored in the same directory as the kernel (this is the case for the * Symfony Standard Edition). If this is not your case, create your own * client and override this method. * * @param Request $request * * @return string */ protected function getScript(object $request) { $kernel = \var_export(\serialize($this->kernel), \true); $request = \var_export(\serialize($request), \true); $errorReporting = \error_reporting(); $requires = ''; foreach (\get_declared_classes() as $class) { if (\str_starts_with($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); $file = \dirname($r->getFileName(), 2) . '/autoload.php'; if (\is_file($file)) { $requires .= 'require_once ' . \var_export($file, \true) . ";\n"; } } } if (!$requires) { throw new \RuntimeException('Composer autoloader not found.'); } $requires .= 'require_once ' . \var_export((new \ReflectionObject($this->kernel))->getFileName(), \true) . ";\n"; $profilerCode = ''; if ($this->profiler) { $profilerCode = <<<'EOF' $container = $kernel->getContainer(); $container = $container->has('test.service_container') ? $container->get('test.service_container') : $container; $container->get('profiler')->enable(); EOF; } $code = <<boot(); {$profilerCode} \$request = unserialize({$request}); EOF; return $code . $this->getHandleScript(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Doctrine\Common\Annotations\AnnotationException; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; use _ContaoManager\Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; use _ContaoManager\Symfony\Component\Validator\Mapping\Loader\LoaderChain; use _ContaoManager\Symfony\Component\Validator\Mapping\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; use _ContaoManager\Symfony\Component\Validator\ValidatorBuilder; /** * Warms up XML and YAML validator metadata. * * @author Titouan Galopin */ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer { private $validatorBuilder; /** * @param string $phpArrayFile The PHP file where metadata are cached */ public function __construct(ValidatorBuilder $validatorBuilder, string $phpArrayFile) { parent::__construct($phpArrayFile); $this->validatorBuilder = $validatorBuilder; } /** * {@inheritdoc} */ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) { if (!\method_exists($this->validatorBuilder, 'getLoaders')) { return \false; } $loaders = $this->validatorBuilder->getLoaders(); $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), $arrayAdapter); foreach ($this->extractSupportedLoaders($loaders) as $loader) { foreach ($loader->getMappedClasses() as $mappedClass) { try { if ($metadataFactory->hasMetadataFor($mappedClass)) { $metadataFactory->getMetadataFor($mappedClass); } } catch (AnnotationException $e) { // ignore failing annotations } catch (\Exception $e) { $this->ignoreAutoloadException($mappedClass, $e); } } } return \true; } /** * @return string[] A list of classes to preload on PHP 7.4+ */ protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) { // make sure we don't cache null values $values = \array_filter($values, function ($val) { return null !== $val; }); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } /** * @param LoaderInterface[] $loaders * * @return XmlFileLoader[]|YamlFileLoader[] */ private function extractSupportedLoaders(array $loaders) : array { $supportedLoaders = []; foreach ($loaders as $loader) { if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) { $supportedLoaders[] = $loader; } elseif ($loader instanceof LoaderChain) { $supportedLoaders = \array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders())); } } return $supportedLoaders; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Config\Builder\ConfigBuilderGenerator; use _ContaoManager\Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * Generate all config builders. * * @author Tobias Nyholm */ class ConfigBuilderCacheWarmer implements CacheWarmerInterface { private $kernel; private $logger; public function __construct(KernelInterface $kernel, ?LoggerInterface $logger = null) { $this->kernel = $kernel; $this->logger = $logger; } /** * {@inheritdoc} * * @return string[] */ public function warmUp(string $cacheDir) { $generator = new ConfigBuilderGenerator($this->kernel->getBuildDir()); foreach ($this->kernel->getBundles() as $bundle) { $extension = $bundle->getContainerExtension(); if (null === $extension) { continue; } try { $this->dumpExtension($extension, $generator); } catch (\Exception $e) { if ($this->logger) { $this->logger->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]); } } } // No need to preload anything return []; } private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGeneratorInterface $generator) : void { $configuration = null; if ($extension instanceof ConfigurationInterface) { $configuration = $extension; } elseif ($extension instanceof ConfigurationExtensionInterface) { $configuration = $extension->getConfiguration([], new ContainerBuilder($this->kernel->getContainer()->getParameterBag())); } if (!$configuration) { return; } $generator->build($configuration); } /** * {@inheritdoc} */ public function isOptional() { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\NullAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; use _ContaoManager\Symfony\Component\Config\Resource\ClassExistenceResource; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface { private $phpArrayFile; /** * @param string $phpArrayFile The PHP file where metadata are cached */ public function __construct(string $phpArrayFile) { $this->phpArrayFile = $phpArrayFile; } /** * {@inheritdoc} */ public function isOptional() { return \true; } /** * {@inheritdoc} * * @return string[] A list of classes to preload on PHP 7.4+ */ public function warmUp(string $cacheDir) { $arrayAdapter = new ArrayAdapter(); \spl_autoload_register([ClassExistenceResource::class, 'throwOnRequiredClass']); try { if (!$this->doWarmUp($cacheDir, $arrayAdapter)) { return []; } } finally { \spl_autoload_unregister([ClassExistenceResource::class, 'throwOnRequiredClass']); } // the ArrayAdapter stores the values serialized // to avoid mutation of the data after it was written to the cache // so here we un-serialize the values first $values = \array_map(function ($val) { return null !== $val ? \unserialize($val) : null; }, $arrayAdapter->getValues()); return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values); } /** * @return string[] A list of classes to preload on PHP 7.4+ */ protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) { return (array) $phpArrayAdapter->warmUp($values); } /** * @internal */ protected final function ignoreAutoloadException(string $class, \Exception $exception) : void { try { ClassExistenceResource::throwOnRequiredClass($class, $exception); } catch (\ReflectionException $e) { } } /** * @return bool false if there is nothing to warm-up */ protected abstract function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use _ContaoManager\Symfony\Component\Routing\RouterInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; /** * Generates the router matcher and generator classes. * * @author Fabien Potencier * * @final */ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { private $container; public function __construct(ContainerInterface $container) { // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. $this->container = $container; } /** * {@inheritdoc} */ public function warmUp(string $cacheDir) : array { $router = $this->container->get('router'); if ($router instanceof WarmableInterface) { return (array) $router->warmUp($cacheDir); } throw new \LogicException(\sprintf('The router "%s" cannot be warmed up because it does not implement "%s".', \get_debug_type($router), WarmableInterface::class)); } /** * {@inheritdoc} */ public function isOptional() : bool { return \true; } /** * {@inheritdoc} */ public static function getSubscribedServices() : array { return ['router' => RouterInterface::class]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; /** * Generates the catalogues for translations. * * @author Xavier Leune */ class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { private $container; private $translator; public function __construct(ContainerInterface $container) { // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. $this->container = $container; } /** * {@inheritdoc} * * @return string[] */ public function warmUp(string $cacheDir) { if (null === $this->translator) { $this->translator = $this->container->get('translator'); } if ($this->translator instanceof WarmableInterface) { return (array) $this->translator->warmUp($cacheDir); } return []; } /** * {@inheritdoc} */ public function isOptional() { return \true; } /** * {@inheritdoc} */ public static function getSubscribedServices() { return ['translator' => TranslatorInterface::class]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Doctrine\Common\Annotations\AnnotationException; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use _ContaoManager\Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use _ContaoManager\Symfony\Component\Serializer\Mapping\Loader\LoaderChain; use _ContaoManager\Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; /** * Warms up XML and YAML serializer metadata. * * @author Titouan Galopin */ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer { private $loaders; /** * @param LoaderInterface[] $loaders The serializer metadata loaders * @param string $phpArrayFile The PHP file where metadata are cached */ public function __construct(array $loaders, string $phpArrayFile) { parent::__construct($phpArrayFile); $this->loaders = $loaders; } /** * {@inheritdoc} */ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) { if (!\class_exists(CacheClassMetadataFactory::class) || !\method_exists(XmlFileLoader::class, 'getMappedClasses') || !\method_exists(YamlFileLoader::class, 'getMappedClasses')) { return \false; } $metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayAdapter); foreach ($this->extractSupportedLoaders($this->loaders) as $loader) { foreach ($loader->getMappedClasses() as $mappedClass) { try { $metadataFactory->getMetadataFor($mappedClass); } catch (AnnotationException $e) { // ignore failing annotations } catch (\Exception $e) { $this->ignoreAutoloadException($mappedClass, $e); } } } return \true; } /** * @param LoaderInterface[] $loaders * * @return XmlFileLoader[]|YamlFileLoader[] */ private function extractSupportedLoaders(array $loaders) : array { $supportedLoaders = []; foreach ($loaders as $loader) { if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) { $supportedLoaders[] = $loader; } elseif ($loader instanceof LoaderChain) { $supportedLoaders = \array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders())); } } return $supportedLoaders; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; /** * Clears the cache pools when warming up the cache. * * Do not use in production! * * @author Teoh Han Hui * * @internal */ final class CachePoolClearerCacheWarmer implements CacheWarmerInterface { private $poolClearer; private $pools; /** * @param string[] $pools */ public function __construct(Psr6CacheClearer $poolClearer, array $pools = []) { $this->poolClearer = $poolClearer; $this->pools = $pools; } /** * {@inheritdoc} * * @return string[] */ public function warmUp(string $cacheDirectory) : array { foreach ($this->pools as $pool) { if ($this->poolClearer->hasPool($pool)) { $this->poolClearer->clearPool($pool); } } return []; } /** * {@inheritdoc} */ public function isOptional() : bool { // optional cache warmers are not run when handling the request return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\CacheWarmer; use _ContaoManager\Doctrine\Common\Annotations\AnnotationException; use _ContaoManager\Doctrine\Common\Annotations\PsrCachedReader; use _ContaoManager\Doctrine\Common\Annotations\Reader; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; /** * Warms up annotation caches for classes found in composer's autoload class map * and declared in DI bundle extensions using the addAnnotatedClassesToCache method. * * @author Titouan Galopin */ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer { private $annotationReader; private $excludeRegexp; private $debug; /** * @param string $phpArrayFile The PHP file where annotations are cached */ public function __construct(Reader $annotationReader, string $phpArrayFile, ?string $excludeRegexp = null, bool $debug = \false) { parent::__construct($phpArrayFile); $this->annotationReader = $annotationReader; $this->excludeRegexp = $excludeRegexp; $this->debug = $debug; } /** * {@inheritdoc} */ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) { $annotatedClassPatterns = $cacheDir . '/annotations.map'; if (!\is_file($annotatedClassPatterns)) { return \true; } $annotatedClasses = (include $annotatedClassPatterns); $reader = new PsrCachedReader($this->annotationReader, $arrayAdapter, $this->debug); foreach ($annotatedClasses as $class) { if (null !== $this->excludeRegexp && \preg_match($this->excludeRegexp, $class)) { continue; } try { $this->readAllComponents($reader, $class); } catch (\Exception $e) { $this->ignoreAutoloadException($class, $e); } } return \true; } /** * @return string[] A list of classes to preload on PHP 7.4+ */ protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) { // make sure we don't cache null values $values = \array_filter($values, function ($val) { return null !== $val; }); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } private function readAllComponents(Reader $reader, string $class) { $reflectionClass = new \ReflectionClass($class); try { $reader->getClassAnnotations($reflectionClass); } catch (AnnotationException $e) { /* * Ignore any AnnotationException to not break the cache warming process if an Annotation is badly * configured or could not be found / read / etc. * * In particular cases, an Annotation in your code can be used and defined only for a specific * environment but is always added to the annotations.map file by some Symfony default behaviors, * and you always end up with a not found Annotation. */ } foreach ($reflectionClass->getMethods() as $reflectionMethod) { try { $reader->getMethodAnnotations($reflectionMethod); } catch (AnnotationException $e) { } } foreach ($reflectionClass->getProperties() as $reflectionProperty) { try { $reader->getPropertyAnnotations($reflectionProperty); } catch (AnnotationException $e) { } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller; use _ContaoManager\Doctrine\Persistence\ManagerRegistry; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Psr\Link\LinkInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; use _ContaoManager\Symfony\Component\Form\Extension\Core\Type\FormType; use _ContaoManager\Symfony\Component\Form\FormBuilderInterface; use _ContaoManager\Symfony\Component\Form\FormFactoryInterface; use _ContaoManager\Symfony\Component\Form\FormInterface; use _ContaoManager\Symfony\Component\Form\FormView; use _ContaoManager\Symfony\Component\HttpFoundation\BinaryFileResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use _ContaoManager\Symfony\Component\HttpFoundation\JsonResponse; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpFoundation\ResponseHeaderBag; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\StreamedResponse; use _ContaoManager\Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\Messenger\Envelope; use _ContaoManager\Symfony\Component\Messenger\MessageBusInterface; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Routing\RouterInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccessDeniedException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfToken; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use _ContaoManager\Symfony\Component\Serializer\SerializerInterface; use _ContaoManager\Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; use _ContaoManager\Symfony\Component\WebLink\GenericLinkProvider; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; use _ContaoManager\Twig\Environment; /** * Provides shortcuts for HTTP-related features in controllers. * * @author Fabien Potencier */ abstract class AbstractController implements ServiceSubscriberInterface { /** * @var ContainerInterface */ protected $container; /** * @required */ public function setContainer(ContainerInterface $container) : ?ContainerInterface { $previous = $this->container; $this->container = $container; return $previous; } /** * Gets a container parameter by its name. * * @return array|bool|float|int|string|\UnitEnum|null */ protected function getParameter(string $name) { if (!$this->container->has('parameter_bag')) { throw new ServiceNotFoundException('parameter_bag.', null, null, [], \sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); } return $this->container->get('parameter_bag')->get($name); } public static function getSubscribedServices() { return [ 'router' => '?' . RouterInterface::class, 'request_stack' => '?' . RequestStack::class, 'http_kernel' => '?' . HttpKernelInterface::class, 'serializer' => '?' . SerializerInterface::class, 'session' => '?' . SessionInterface::class, 'security.authorization_checker' => '?' . AuthorizationCheckerInterface::class, 'twig' => '?' . Environment::class, 'doctrine' => '?' . ManagerRegistry::class, // to be removed in 6.0 'form.factory' => '?' . FormFactoryInterface::class, 'security.token_storage' => '?' . TokenStorageInterface::class, 'security.csrf.token_manager' => '?' . CsrfTokenManagerInterface::class, 'parameter_bag' => '?' . ContainerBagInterface::class, 'message_bus' => '?' . MessageBusInterface::class, // to be removed in 6.0 'messenger.default_bus' => '?' . MessageBusInterface::class, ]; } /** * Returns true if the service id is defined. * * @deprecated since Symfony 5.4, use method or constructor injection in your controller instead */ protected function has(string $id) : bool { \trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__); return $this->container->has($id); } /** * Gets a container service by its id. * * @return object The service * * @deprecated since Symfony 5.4, use method or constructor injection in your controller instead */ protected function get(string $id) : object { \trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__); return $this->container->get($id); } /** * Generates a URL from the given parameters. * * @see UrlGeneratorInterface */ protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH) : string { return $this->container->get('router')->generate($route, $parameters, $referenceType); } /** * Forwards the request to another controller. * * @param string $controller The controller name (a string like "App\Controller\PostController::index" or "App\Controller\PostController" if it is invokable) */ protected function forward(string $controller, array $path = [], array $query = []) : Response { $request = $this->container->get('request_stack')->getCurrentRequest(); $path['_controller'] = $controller; $subRequest = $request->duplicate($query, null, $path); return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); } /** * Returns a RedirectResponse to the given URL. */ protected function redirect(string $url, int $status = 302) : RedirectResponse { return new RedirectResponse($url, $status); } /** * Returns a RedirectResponse to the given route with the given parameters. */ protected function redirectToRoute(string $route, array $parameters = [], int $status = 302) : RedirectResponse { return $this->redirect($this->generateUrl($route, $parameters), $status); } /** * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. */ protected function json($data, int $status = 200, array $headers = [], array $context = []) : JsonResponse { if ($this->container->has('serializer')) { $json = $this->container->get('serializer')->serialize($data, 'json', \array_merge(['json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS], $context)); return new JsonResponse($json, $status, $headers, \true); } return new JsonResponse($data, $status, $headers); } /** * Returns a BinaryFileResponse object with original or customized file name and disposition header. * * @param \SplFileInfo|string $file File object or path to file to be sent as response */ protected function file($file, ?string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT) : BinaryFileResponse { $response = new BinaryFileResponse($file); $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName); return $response; } /** * Adds a flash message to the current session for type. * * @throws \LogicException */ protected function addFlash(string $type, $message) : void { try { $this->container->get('request_stack')->getSession()->getFlashBag()->add($type, $message); } catch (SessionNotFoundException $e) { throw new \LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e); } } /** * Checks if the attribute is granted against the current authentication token and optionally supplied subject. * * @throws \LogicException */ protected function isGranted($attribute, $subject = null) : bool { if (!$this->container->has('security.authorization_checker')) { throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); } return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject); } /** * Throws an exception unless the attribute is granted against the current authentication token and optionally * supplied subject. * * @throws AccessDeniedException */ protected function denyAccessUnlessGranted($attribute, $subject = null, string $message = 'Access Denied.') : void { if (!$this->isGranted($attribute, $subject)) { $exception = $this->createAccessDeniedException($message); $exception->setAttributes([$attribute]); $exception->setSubject($subject); throw $exception; } } /** * Returns a rendered view. */ protected function renderView(string $view, array $parameters = []) : string { if (!$this->container->has('twig')) { throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); } return $this->container->get('twig')->render($view, $parameters); } /** * Renders a view. */ protected function render(string $view, array $parameters = [], ?Response $response = null) : Response { $content = $this->renderView($view, $parameters); if (null === $response) { $response = new Response(); } $response->setContent($content); return $response; } /** * Renders a view and sets the appropriate status code when a form is listed in parameters. * * If an invalid form is found in the list of parameters, a 422 status code is returned. */ protected function renderForm(string $view, array $parameters = [], ?Response $response = null) : Response { if (null === $response) { $response = new Response(); } foreach ($parameters as $k => $v) { if ($v instanceof FormView) { throw new \LogicException(\sprintf('Passing a FormView to "%s::renderForm()" is not supported, pass directly the form instead for parameter "%s".', \get_debug_type($this), $k)); } if (!$v instanceof FormInterface) { continue; } $parameters[$k] = $v->createView(); if (200 === $response->getStatusCode() && $v->isSubmitted() && !$v->isValid()) { $response->setStatusCode(422); } } return $this->render($view, $parameters, $response); } /** * Streams a view. */ protected function stream(string $view, array $parameters = [], ?StreamedResponse $response = null) : StreamedResponse { if (!$this->container->has('twig')) { throw new \LogicException('You cannot use the "stream" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); } $twig = $this->container->get('twig'); $callback = function () use($twig, $view, $parameters) { $twig->display($view, $parameters); }; if (null === $response) { return new StreamedResponse($callback); } $response->setCallback($callback); return $response; } /** * Returns a NotFoundHttpException. * * This will result in a 404 response code. Usage example: * * throw $this->createNotFoundException('Page not found!'); */ protected function createNotFoundException(string $message = 'Not Found', ?\Throwable $previous = null) : NotFoundHttpException { return new NotFoundHttpException($message, $previous); } /** * Returns an AccessDeniedException. * * This will result in a 403 response code. Usage example: * * throw $this->createAccessDeniedException('Unable to access this page!'); * * @throws \LogicException If the Security component is not available */ protected function createAccessDeniedException(string $message = 'Access Denied.', ?\Throwable $previous = null) : AccessDeniedException { if (!\class_exists(AccessDeniedException::class)) { throw new \LogicException('You cannot use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".'); } return new AccessDeniedException($message, $previous); } /** * Creates and returns a Form instance from the type of the form. */ protected function createForm(string $type, $data = null, array $options = []) : FormInterface { return $this->container->get('form.factory')->create($type, $data, $options); } /** * Creates and returns a form builder instance. */ protected function createFormBuilder($data = null, array $options = []) : FormBuilderInterface { return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options); } /** * Shortcut to return the Doctrine Registry service. * * @throws \LogicException If DoctrineBundle is not available * * @deprecated since Symfony 5.4, inject an instance of ManagerRegistry in your controller instead */ protected function getDoctrine() : ManagerRegistry { \trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of ManagerRegistry in your controller instead.', __METHOD__); if (!$this->container->has('doctrine')) { throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".'); } return $this->container->get('doctrine'); } /** * Get a user from the Security Token Storage. * * @return UserInterface|null * * @throws \LogicException If SecurityBundle is not available * * @see TokenInterface::getUser() */ protected function getUser() { if (!$this->container->has('security.token_storage')) { throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); } if (null === ($token = $this->container->get('security.token_storage')->getToken())) { return null; } // @deprecated since 5.4, $user will always be a UserInterface instance if (!\is_object($user = $token->getUser())) { // e.g. anonymous authentication return null; } return $user; } /** * Checks the validity of a CSRF token. * * @param string $id The id used when generating the token * @param string|null $token The actual token sent with the request that should be validated */ protected function isCsrfTokenValid(string $id, ?string $token) : bool { if (!$this->container->has('security.csrf.token_manager')) { throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); } return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token)); } /** * Dispatches a message to the bus. * * @param object|Envelope $message The message or the message pre-wrapped in an envelope * * @deprecated since Symfony 5.4, inject an instance of MessageBusInterface in your controller instead */ protected function dispatchMessage(object $message, array $stamps = []) : Envelope { \trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of MessageBusInterface in your controller instead.', __METHOD__); if (!$this->container->has('messenger.default_bus')) { $message = \class_exists(Envelope::class) ? 'You need to define the "messenger.default_bus" configuration option.' : 'Try running "composer require symfony/messenger".'; throw new \LogicException('The message bus is not enabled in your application. ' . $message); } return $this->container->get('messenger.default_bus')->dispatch($message, $stamps); } /** * Adds a Link HTTP header to the current response. * * @see https://tools.ietf.org/html/rfc5988 */ protected function addLink(Request $request, LinkInterface $link) : void { if (!\class_exists(AddLinkHeaderListener::class)) { throw new \LogicException('You cannot use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); } if (null === ($linkProvider = $request->attributes->get('_links'))) { $request->attributes->set('_links', new GenericLinkProvider([$link])); return; } $request->attributes->set('_links', $linkProvider->withLink($link)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Twig\Environment; /** * TemplateController. * * @author Fabien Potencier * * @final */ class TemplateController { private $twig; public function __construct(?Environment $twig = null) { $this->twig = $twig; } /** * Renders a template. * * @param string $template The template name * @param int|null $maxAge Max age for client caching * @param int|null $sharedAge Max age for shared (proxy) caching * @param bool|null $private Whether or not caching should apply for client caches only * @param array $context The context (arguments) of the template * @param int $statusCode The HTTP status code to return with the response. Defaults to 200 */ public function templateAction(string $template, ?int $maxAge = null, ?int $sharedAge = null, ?bool $private = null, array $context = [], int $statusCode = 200) : Response { if (null === $this->twig) { throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available.'); } $response = new Response($this->twig->render($template, $context), $statusCode); if ($maxAge) { $response->setMaxAge($maxAge); } if (null !== $sharedAge) { $response->setSharedMaxAge($sharedAge); } if ($private) { $response->setPrivate(); } elseif (\false === $private || null === $private && (null !== $maxAge || null !== $sharedAge)) { $response->setPublic(); } return $response; } public function __invoke(string $template, ?int $maxAge = null, ?int $sharedAge = null, ?bool $private = null, array $context = [], int $statusCode = 200) : Response { return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerAwareInterface; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; /** * @author Fabien Potencier * * @final */ class ControllerResolver extends ContainerControllerResolver { /** * {@inheritdoc} */ protected function instantiateController(string $class) : object { $controller = parent::instantiateController($class); if ($controller instanceof ContainerAwareInterface) { $controller->setContainer($this->container); } if ($controller instanceof AbstractController) { if (null === ($previousContainer = $controller->setContainer($this->container))) { throw new \LogicException(\sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class)); } else { $controller->setContainer($previousContainer); } } return $controller; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\HeaderUtils; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** * Redirects a request to another URL. * * @author Fabien Potencier * * @final */ class RedirectController { private $router; private $httpPort; private $httpsPort; public function __construct(?UrlGeneratorInterface $router = null, ?int $httpPort = null, ?int $httpsPort = null) { $this->router = $router; $this->httpPort = $httpPort; $this->httpsPort = $httpsPort; } /** * Redirects to another route with the given name. * * The response status code is 302 if the permanent parameter is false (default), * and 301 if the redirection is permanent. * * In case the route name is empty, the status code will be 404 when permanent is false * and 410 otherwise. * * @param string $route The route name to redirect to * @param bool $permanent Whether the redirection is permanent * @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method * * @throws HttpException In case the route name is empty */ public function redirectAction(Request $request, string $route, bool $permanent = \false, $ignoreAttributes = \false, bool $keepRequestMethod = \false, bool $keepQueryParams = \false) : Response { if ('' == $route) { throw new HttpException($permanent ? 410 : 404); } $attributes = []; if (\false === $ignoreAttributes || \is_array($ignoreAttributes)) { $attributes = $request->attributes->get('_route_params'); if ($keepQueryParams) { if ($query = $request->server->get('QUERY_STRING')) { $query = HeaderUtils::parseQuery($query); } else { $query = $request->query->all(); } $attributes = \array_merge($query, $attributes); } unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes'], $attributes['keepRequestMethod'], $attributes['keepQueryParams']); if ($ignoreAttributes) { $attributes = \array_diff_key($attributes, \array_flip($ignoreAttributes)); } } if ($keepRequestMethod) { $statusCode = $permanent ? 308 : 307; } else { $statusCode = $permanent ? 301 : 302; } return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $statusCode); } /** * Redirects to a URL. * * The response status code is 302 if the permanent parameter is false (default), * and 301 if the redirection is permanent. * * In case the path is empty, the status code will be 404 when permanent is false * and 410 otherwise. * * @param string $path The absolute path or URL to redirect to * @param bool $permanent Whether the redirect is permanent or not * @param string|null $scheme The URL scheme (null to keep the current one) * @param int|null $httpPort The HTTP port (null to keep the current one for the same scheme or the default configured port) * @param int|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the default configured port) * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method * * @throws HttpException In case the path is empty */ public function urlRedirectAction(Request $request, string $path, bool $permanent = \false, ?string $scheme = null, ?int $httpPort = null, ?int $httpsPort = null, bool $keepRequestMethod = \false) : Response { if ('' == $path) { throw new HttpException($permanent ? 410 : 404); } if ($keepRequestMethod) { $statusCode = $permanent ? 308 : 307; } else { $statusCode = $permanent ? 301 : 302; } // redirect if the path is a full URL if (\parse_url($path, \PHP_URL_SCHEME)) { return new RedirectResponse($path, $statusCode); } if (null === $scheme) { $scheme = $request->getScheme(); } if ($qs = $request->server->get('QUERY_STRING') ?: $request->getQueryString()) { if (!\str_contains($path, '?')) { $qs = '?' . $qs; } else { $qs = '&' . $qs; } } $port = ''; if ('http' === $scheme) { if (null === $httpPort) { if ('http' === $request->getScheme()) { $httpPort = $request->getPort(); } else { $httpPort = $this->httpPort; } } if (null !== $httpPort && 80 != $httpPort) { $port = ":{$httpPort}"; } } elseif ('https' === $scheme) { if (null === $httpsPort) { if ('https' === $request->getScheme()) { $httpsPort = $request->getPort(); } else { $httpsPort = $this->httpsPort; } } if (null !== $httpsPort && 443 != $httpsPort) { $port = ":{$httpsPort}"; } } $url = $scheme . '://' . $request->getHost() . $port . $request->getBaseUrl() . $path . $qs; return new RedirectResponse($url, $statusCode); } public function __invoke(Request $request) : Response { $p = $request->attributes->get('_route_params', []); if (\array_key_exists('route', $p)) { if (\array_key_exists('path', $p)) { throw new \RuntimeException(\sprintf('Ambiguous redirection settings, use the "path" or "route" parameter, not both: "%s" and "%s" found respectively in "%s" routing configuration.', $p['path'], $p['route'], $request->attributes->get('_route'))); } return $this->redirectAction($request, $p['route'], $p['permanent'] ?? \false, $p['ignoreAttributes'] ?? \false, $p['keepRequestMethod'] ?? \false, $p['keepQueryParams'] ?? \false); } if (\array_key_exists('path', $p)) { return $this->urlRedirectAction($request, $p['path'], $p['permanent'] ?? \false, $p['scheme'] ?? null, $p['httpPort'] ?? null, $p['httpsPort'] ?? null, $p['keepRequestMethod'] ?? \false); } throw new \RuntimeException(\sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route'))); } } FrameworkBundle =============== FrameworkBundle provides a tight integration between Symfony components and the Symfony full-stack framework. Resources --------- * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\Esi; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\Store; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\StoreInterface; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * Manages HTTP cache objects in a Container. * * @author Fabien Potencier */ class HttpCache extends BaseHttpCache { protected $cacheDir; protected $kernel; private $store; private $surrogate; private $options; /** * @param string|StoreInterface $cache The cache directory (default used if null) or the storage instance */ public function __construct(KernelInterface $kernel, $cache = null, ?SurrogateInterface $surrogate = null, ?array $options = null) { $this->kernel = $kernel; $this->surrogate = $surrogate; $this->options = $options ?? []; if ($cache instanceof StoreInterface) { $this->store = $cache; } elseif (null !== $cache && !\is_string($cache)) { throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be a string or a SurrogateInterface, "%s" given.', __METHOD__, \get_debug_type($cache))); } else { $this->cacheDir = $cache; } if (null === $options && $kernel->isDebug()) { $this->options = ['debug' => \true]; } if ($this->options['debug'] ?? \false) { $this->options += ['stale_if_error' => 0]; } parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), \array_merge($this->options, $this->getOptions())); } /** * {@inheritdoc} */ protected function forward(Request $request, bool $catch = \false, ?Response $entry = null) { $this->getKernel()->boot(); $this->getKernel()->getContainer()->set('cache', $this); return parent::forward($request, $catch, $entry); } /** * Returns an array of options to customize the Cache configuration. * * @return array */ protected function getOptions() { return []; } /** * @return SurrogateInterface */ protected function createSurrogate() { return $this->surrogate ?? new Esi(); } /** * @return StoreInterface */ protected function createStore() { return $this->store ?? new Store($this->cacheDir ?: $this->kernel->getCacheDir() . '/http_cache'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AssetsContextPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ErrorLoggerCompilerPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SessionPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass; use _ContaoManager\Symfony\Component\Cache\Adapter\ApcuAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ChainAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\PhpFilesAdapter; use _ContaoManager\Symfony\Component\Cache\DependencyInjection\CacheCollectorPass; use _ContaoManager\Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass; use _ContaoManager\Symfony\Component\Cache\DependencyInjection\CachePoolPass; use _ContaoManager\Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass; use _ContaoManager\Symfony\Component\Config\Resource\ClassExistenceResource; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PassConfig; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\Dotenv\Dotenv; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorHandler; use _ContaoManager\Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use _ContaoManager\Symfony\Component\Form\DependencyInjection\FormPass; use _ContaoManager\Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\Bundle; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Messenger\DependencyInjection\MessengerPass; use _ContaoManager\Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass; use _ContaoManager\Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; use _ContaoManager\Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; use _ContaoManager\Symfony\Component\Serializer\DependencyInjection\SerializerPass; use _ContaoManager\Symfony\Component\Translation\DependencyInjection\TranslationDumperPass; use _ContaoManager\Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass; use _ContaoManager\Symfony\Component\Translation\DependencyInjection\TranslatorPass; use _ContaoManager\Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass; use _ContaoManager\Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass; use _ContaoManager\Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use _ContaoManager\Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass; use _ContaoManager\Symfony\Component\VarExporter\Internal\Hydrator; use _ContaoManager\Symfony\Component\VarExporter\Internal\Registry; // Help opcache.preload discover always-needed symbols \class_exists(ApcuAdapter::class); \class_exists(ArrayAdapter::class); \class_exists(ChainAdapter::class); \class_exists(PhpArrayAdapter::class); \class_exists(PhpFilesAdapter::class); \class_exists(Dotenv::class); \class_exists(ErrorHandler::class); \class_exists(Hydrator::class); \class_exists(Registry::class); /** * Bundle. * * @author Fabien Potencier */ class FrameworkBundle extends Bundle { public function boot() { ErrorHandler::register(null, \false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), \true); if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); } } public function build(ContainerBuilder $container) { parent::build($container); $registerListenersPass = new RegisterListenersPass(); $registerListenersPass->setHotPathEvents([KernelEvents::REQUEST, KernelEvents::CONTROLLER, KernelEvents::CONTROLLER_ARGUMENTS, KernelEvents::RESPONSE, KernelEvents::FINISH_REQUEST]); if (\class_exists(ConsoleEvents::class)) { $registerListenersPass->setNoPreloadEvents([ConsoleEvents::COMMAND, ConsoleEvents::TERMINATE, ConsoleEvents::ERROR]); } $container->addCompilerPass(new AssetsContextPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); $container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new RegisterControllerArgumentLocatorsPass()); $container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RoutingResolverPass()); $container->addCompilerPass(new DataCollectorTranslatorPass()); $container->addCompilerPass(new ProfilerPass()); // must be registered before removing private services as some might be listeners/subscribers // but as late as possible to get resolved parameters $container->addCompilerPass($registerListenersPass, PassConfig::TYPE_BEFORE_REMOVING); $this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class); $container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_AFTER_REMOVING, -255); $this->addCompilerPassIfExists($container, AddValidatorInitializersPass::class); $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING); // must be registered as late as possible to get access to all Twig paths registered in // twig.template_iterator definition $this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new LoggingTranslatorPass()); $container->addCompilerPass(new AddExpressionLanguageProvidersPass(\false)); $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); $this->addCompilerPassIfExists($container, TranslationDumperPass::class); $container->addCompilerPass(new FragmentRendererPass()); $this->addCompilerPassIfExists($container, SerializerPass::class); $this->addCompilerPassIfExists($container, PropertyInfoPass::class); $container->addCompilerPass(new ControllerArgumentValueResolverPass()); $container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32); $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, FormPass::class); $container->addCompilerPass(new WorkflowGuardListenerPass()); $container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new RegisterLocaleAwareServicesPass()); $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class); $this->addCompilerPassIfExists($container, MessengerPass::class); $this->addCompilerPassIfExists($container, HttpClientPass::class); $this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class); $container->addCompilerPass(new RegisterReverseContainerPass(\true)); $container->addCompilerPass(new RegisterReverseContainerPass(\false), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); $container->addCompilerPass(new SessionPass()); // must be registered after MonologBundle's LoggerChannelPass $container->addCompilerPass(new ErrorLoggerCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2); $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_BEFORE_REMOVING, -255); $container->addCompilerPass(new CacheCollectorPass(), PassConfig::TYPE_BEFORE_REMOVING); } } private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { $container->addResource(new ClassExistenceResource($class)); if (\class_exists($class)) { $container->addCompilerPass(new $class(), $type, $priority); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Helper\Dumper; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Tobias Schultze * @author Jérémy Derussé * @author Nicolas Grekas * * @internal */ final class SecretsListCommand extends Command { protected static $defaultName = 'secrets:list'; protected static $defaultDescription = 'List all secrets'; private $vault; private $localVault; public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; parent::__construct(); } protected function configure() { $this->setDescription(self::$defaultDescription)->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names')->setHelp(<<<'EOF' The %command.name% command list all stored secrets. %command.full_name% When the option --reveal is provided, the decrypted secrets are also displayed. %command.full_name% --reveal EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); $io->comment('Use "%env()%" to reference a secret in a config file.'); if (!($reveal = $input->getOption('reveal'))) { $io->comment(\sprintf('To reveal the secrets run php %s %s --reveal', $_SERVER['PHP_SELF'], $this->getName())); } $secrets = $this->vault->list($reveal); $localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null; $rows = []; $dump = new Dumper($output); $dump = static function (?string $v) use($dump) { return null === $v ? '******' : $dump($v); }; foreach ($secrets as $name => $value) { $rows[$name] = [$name, $dump($value)]; } if (null !== ($message = $this->vault->getLastMessage())) { $io->comment($message); } foreach ($localSecrets ?? [] as $name => $value) { if (isset($rows[$name])) { $rows[$name][] = $dump($value); } } if (null !== $this->localVault && null !== ($message = $this->localVault->getLastMessage())) { $io->comment($message); } (new SymfonyStyle($input, $output))->table(['Secret', 'Value'] + (null !== $localSecrets ? [2 => 'Local Value'] : []), $rows); $io->comment("Local values override secret values.\nUse secrets:set --local to define them."); return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Jérémy Derussé * @author Nicolas Grekas * * @internal */ final class SecretsRemoveCommand extends Command { protected static $defaultName = 'secrets:remove'; protected static $defaultDescription = 'Remove a secret from the vault'; private $vault; private $localVault; public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; parent::__construct(); } protected function configure() { $this->setDescription(self::$defaultDescription)->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')->setHelp(<<<'EOF' The %command.name% command removes a secret from the vault. %command.full_name% EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); $vault = $input->getOption('local') ? $this->localVault : $this->vault; if (null === $vault) { $io->success('The local vault is disabled.'); return 1; } if ($vault->remove($name = $input->getArgument('name'))) { $io->success($vault->getLastMessage() ?? 'Secret was removed from the vault.'); } else { $io->comment($vault->getLastMessage() ?? 'Secret was not found in the vault.'); } if ($this->vault === $vault && null !== $this->localVault->reveal($name)) { $io->comment('Note that this secret is overridden in the local vault.'); } return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if (!$input->mustSuggestArgumentValuesFor('name')) { return; } $vaultKeys = \array_keys($this->vault->list(\false)); if ($input->getOption('local')) { if (null === $this->localVault) { return; } $vaultKeys = \array_intersect($vaultKeys, \array_keys($this->localVault->list(\false))); } $suggestions->suggestValues($vaultKeys); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\Dumper\Preloader; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcher; use _ContaoManager\Symfony\Component\Filesystem\Exception\IOException; use _ContaoManager\Symfony\Component\Filesystem\Filesystem; use _ContaoManager\Symfony\Component\Finder\Finder; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use _ContaoManager\Symfony\Component\HttpKernel\RebootableInterface; /** * Clear and Warmup the cache. * * @author Francis Besset * @author Fabien Potencier * * @final */ class CacheClearCommand extends Command { protected static $defaultName = 'cache:clear'; protected static $defaultDescription = 'Clear the cache'; private $cacheClearer; private $filesystem; public function __construct(CacheClearerInterface $cacheClearer, ?Filesystem $filesystem = null) { parent::__construct(); $this->cacheClearer = $cacheClearer; $this->filesystem = $filesystem ?? new Filesystem(); } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'), new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command clears and warms up the application cache for a given environment and debug mode: php %command.full_name% --env=dev php %command.full_name% --env=prod --no-debug EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $fs = $this->filesystem; $io = new SymfonyStyle($input, $output); $kernel = $this->getApplication()->getKernel(); $realCacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); $realBuildDir = $kernel->getContainer()->hasParameter('kernel.build_dir') ? $kernel->getContainer()->getParameter('kernel.build_dir') : $realCacheDir; // the old cache dir name must not be longer than the real one to avoid exceeding // the maximum length of a directory or file path within it (esp. Windows MAX_PATH) $oldCacheDir = \substr($realCacheDir, 0, -1) . (\str_ends_with($realCacheDir, '~') ? '+' : '~'); $fs->remove($oldCacheDir); if (!\is_writable($realCacheDir)) { throw new RuntimeException(\sprintf('Unable to write in the "%s" directory.', $realCacheDir)); } $useBuildDir = $realBuildDir !== $realCacheDir; $oldBuildDir = \substr($realBuildDir, 0, -1) . ('~' === \substr($realBuildDir, -1) ? '+' : '~'); if ($useBuildDir) { $fs->remove($oldBuildDir); if (!\is_writable($realBuildDir)) { throw new RuntimeException(\sprintf('Unable to write in the "%s" directory.', $realBuildDir)); } if ($this->isNfs($realCacheDir)) { $fs->remove($realCacheDir); } else { $fs->rename($realCacheDir, $oldCacheDir); } $fs->mkdir($realCacheDir); } $io->comment(\sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), \var_export($kernel->isDebug(), \true))); if ($useBuildDir) { $this->cacheClearer->clear($realBuildDir); } $this->cacheClearer->clear($realCacheDir); // The current event dispatcher is stale, let's not use it anymore $this->getApplication()->setDispatcher(new EventDispatcher()); $containerFile = (new \ReflectionObject($kernel->getContainer()))->getFileName(); $containerDir = \basename(\dirname($containerFile)); // the warmup cache dir name must have the same length as the real one // to avoid the many problems in serialized resources files $warmupDir = \substr($realBuildDir, 0, -1) . ('_' === \substr($realBuildDir, -1) ? '-' : '_'); if ($output->isVerbose() && $fs->exists($warmupDir)) { $io->comment('Clearing outdated warmup directory...'); } $fs->remove($warmupDir); if ($_SERVER['REQUEST_TIME'] <= \filemtime($containerFile) && \filemtime($containerFile) <= \time()) { if ($output->isVerbose()) { $io->comment('Cache is fresh.'); } if (!$input->getOption('no-warmup') && !$input->getOption('no-optional-warmers')) { if ($output->isVerbose()) { $io->comment('Warming up optional cache...'); } $this->warmupOptionals($realCacheDir, $realBuildDir); } } else { $fs->mkdir($warmupDir); if (!$input->getOption('no-warmup')) { if ($output->isVerbose()) { $io->comment('Warming up cache...'); } $this->warmup($warmupDir, $realBuildDir); if (!$input->getOption('no-optional-warmers')) { if ($output->isVerbose()) { $io->comment('Warming up optional cache...'); } $this->warmupOptionals($useBuildDir ? $realCacheDir : $warmupDir, $warmupDir); } } if (!$fs->exists($warmupDir . '/' . $containerDir)) { $fs->rename($realBuildDir . '/' . $containerDir, $warmupDir . '/' . $containerDir); \touch($warmupDir . '/' . $containerDir . '.legacy'); } if ($this->isNfs($realBuildDir)) { $io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.'); $fs->remove($realBuildDir); } else { $fs->rename($realBuildDir, $oldBuildDir); } $fs->rename($warmupDir, $realBuildDir); if ($output->isVerbose()) { $io->comment('Removing old build and cache directory...'); } if ($useBuildDir) { try { $fs->remove($oldBuildDir); } catch (IOException $e) { if ($output->isVerbose()) { $io->warning($e->getMessage()); } } } try { $fs->remove($oldCacheDir); } catch (IOException $e) { if ($output->isVerbose()) { $io->warning($e->getMessage()); } } } if ($output->isVerbose()) { $io->comment('Finished'); } $io->success(\sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), \var_export($kernel->isDebug(), \true))); return 0; } private function isNfs(string $dir) : bool { static $mounts = null; if (null === $mounts) { $mounts = []; if ('/' === \DIRECTORY_SEPARATOR && ($files = @\file('/proc/mounts'))) { foreach ($files as $mount) { $mount = \array_slice(\explode(' ', $mount), 1, -3); if (!\in_array(\array_pop($mount), ['vboxsf', 'nfs'])) { continue; } $mounts[] = \implode(' ', $mount) . '/'; } } } foreach ($mounts as $mount) { if (0 === \strpos($dir, $mount)) { return \true; } } return \false; } private function warmup(string $warmupDir, string $realBuildDir) : void { // create a temporary kernel $kernel = $this->getApplication()->getKernel(); if (!$kernel instanceof RebootableInterface) { throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\\Component\\HttpKernel\\RebootableInterface" is not supported.'); } $kernel->reboot($warmupDir); // fix references to cached files with the real cache directory name $search = [$warmupDir, \str_replace('\\', '\\\\', $warmupDir)]; $replace = \str_replace('\\', '/', $realBuildDir); foreach (Finder::create()->files()->in($warmupDir) as $file) { $content = \str_replace($search, $replace, \file_get_contents($file), $count); if ($count) { \file_put_contents($file, $content); } } } private function warmupOptionals(string $cacheDir, string $warmupDir) : void { $kernel = $this->getApplication()->getKernel(); $warmer = $kernel->getContainer()->get('cache_warmer'); // non optional warmers already ran during container compilation $warmer->enableOnlyOptionalWarmers(); $preload = (array) $warmer->warmUp($cacheDir); if ($preload && \file_exists($preloadFile = $warmupDir . '/' . $kernel->getContainer()->getParameter('kernel.container_class') . '.preload.php')) { Preloader::append($preloadFile, $preload); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand; /** * Validates XLIFF files syntax and outputs encountered errors. * * @author Grégoire Pineau * @author Robin Chalas * @author Javier Eguiluz * * @final */ class XliffLintCommand extends BaseLintCommand { protected static $defaultName = 'lint:xliff'; protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors'; public function __construct() { $directoryIteratorProvider = function ($directory, $default) { if (!\is_dir($directory)) { $directory = $this->getApplication()->getKernel()->locateResource($directory); } return $default($directory); }; $isReadableProvider = function ($fileOrDirectory, $default) { return \str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); }; parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } /** * {@inheritdoc} */ protected function configure() { parent::configure(); $this->setHelp($this->getHelp() . <<<'EOF' Or find all files in a bundle: php %command.full_name% @AcmeDemoBundle EOF ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Nicolas Grekas * * @internal */ final class SecretsDecryptToLocalCommand extends Command { protected static $defaultName = 'secrets:decrypt-to-local'; protected static $defaultDescription = 'Decrypt all secrets and stores them in the local vault'; private $vault; private $localVault; public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; parent::__construct(); } protected function configure() { $this->setDescription(self::$defaultDescription)->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault')->setHelp(<<<'EOF' The %command.name% command decrypts all secrets and copies them in the local vault. %command.full_name% When the option --force is provided, secrets that already exist in the local vault are overriden. %command.full_name% --force EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); if (null === $this->localVault) { $io->error('The local vault is disabled.'); return 1; } $secrets = $this->vault->list(\true); $io->comment(\sprintf('%d secret%s found in the vault.', \count($secrets), 1 !== \count($secrets) ? 's' : '')); $skipped = 0; if (!$input->getOption('force')) { foreach ($this->localVault->list() as $k => $v) { if (isset($secrets[$k])) { ++$skipped; unset($secrets[$k]); } } } if ($skipped > 0) { $io->warning([\sprintf('%d secret%s already overridden in the local vault and will be skipped.', $skipped, 1 !== $skipped ? 's are' : ' is'), 'Use the --force flag to override these.']); } foreach ($secrets as $k => $v) { if (null === $v) { $io->error($this->vault->getLastMessage() ?? \sprintf('Secret "%s" has been skipped as there was an error reading it.', $k)); continue; } $this->localVault->seal($k, $v); $io->note($this->localVault->getLastMessage()); } return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\StyleInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; /** * A console command for dumping available configuration reference. * * @author Kevin Bond * @author Wouter J * @author Grégoire Pineau */ abstract class AbstractConfigCommand extends ContainerDebugCommand { /** * @param OutputInterface|StyleInterface $output */ protected function listBundles($output) { $title = 'Available registered bundles with their extension alias if available'; $headers = ['Bundle name', 'Extension alias']; $rows = []; $bundles = $this->getApplication()->getKernel()->getBundles(); \usort($bundles, function ($bundleA, $bundleB) { return \strcmp($bundleA->getName(), $bundleB->getName()); }); foreach ($bundles as $bundle) { $extension = $bundle->getContainerExtension(); $rows[] = [$bundle->getName(), $extension ? $extension->getAlias() : '']; } if ($output instanceof StyleInterface) { $output->title($title); $output->table($headers, $rows); } else { $output->writeln($title); $table = new Table($output); $table->setHeaders($headers)->setRows($rows)->render(); } } /** * @param OutputInterface|StyleInterface $output */ protected function listNonBundleExtensions($output) { $title = 'Available registered non-bundle extension aliases'; $headers = ['Extension alias']; $rows = []; $kernel = $this->getApplication()->getKernel(); $bundleExtensions = []; foreach ($kernel->getBundles() as $bundle) { if ($extension = $bundle->getContainerExtension()) { $bundleExtensions[\get_class($extension)] = \true; } } $extensions = $this->getContainerBuilder($kernel)->getExtensions(); foreach ($extensions as $alias => $extension) { if (isset($bundleExtensions[\get_class($extension)])) { continue; } $rows[] = [$alias]; } if (!$rows) { return; } if ($output instanceof StyleInterface) { $output->title($title); $output->table($headers, $rows); } else { $output->writeln($title); $table = new Table($output); $table->setHeaders($headers)->setRows($rows)->render(); } } /** * @return ExtensionInterface */ protected function findExtension(string $name) { $bundles = $this->initializeBundles(); $minScore = \INF; $kernel = $this->getApplication()->getKernel(); if ($kernel instanceof ExtensionInterface && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)) { if ($name === $kernel->getAlias()) { return $kernel; } if ($kernel->getAlias()) { $distance = \levenshtein($name, $kernel->getAlias()); if ($distance < $minScore) { $guess = $kernel->getAlias(); $minScore = $distance; } } } foreach ($bundles as $bundle) { if ($name === $bundle->getName()) { if (!$bundle->getContainerExtension()) { throw new \LogicException(\sprintf('Bundle "%s" does not have a container extension.', $name)); } return $bundle->getContainerExtension(); } $distance = \levenshtein($name, $bundle->getName()); if ($distance < $minScore) { $guess = $bundle->getName(); $minScore = $distance; } } $container = $this->getContainerBuilder($kernel); if ($container->hasExtension($name)) { return $container->getExtension($name); } foreach ($container->getExtensions() as $extension) { $distance = \levenshtein($name, $extension->getAlias()); if ($distance < $minScore) { $guess = $extension->getAlias(); $minScore = $distance; } } if (!\str_ends_with($name, 'Bundle')) { $message = \sprintf('No extensions with configuration available for "%s".', $name); } else { $message = \sprintf('No extension with alias "%s" is enabled.', $name); } if (isset($guess) && $minScore < 3) { $message .= \sprintf("\n\nDid you mean \"%s\"?", $guess); } throw new LogicException($message); } public function validateConfiguration(ExtensionInterface $extension, $configuration) { if (!$configuration) { throw new \LogicException(\sprintf('The extension with alias "%s" does not have its getConfiguration() method setup.', $extension->getAlias())); } if (!$configuration instanceof ConfigurationInterface) { throw new \LogicException(\sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', \get_debug_type($configuration))); } } private function initializeBundles() { // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method // as this method is not called when the container is loaded from the cache. $kernel = $this->getApplication()->getKernel(); $container = $this->getContainerBuilder($kernel); $bundles = $kernel->getBundles(); foreach ($bundles as $bundle) { if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); } } foreach ($bundles as $bundle) { $bundle->build($container); } return $bundles; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\Routing\RouteCollection; use _ContaoManager\Symfony\Component\Routing\RouterInterface; /** * A console command for retrieving information about routes. * * @author Fabien Potencier * @author Tobias Schultze * * @final */ class RouterDebugCommand extends Command { use BuildDebugContainerTrait; protected static $defaultName = 'debug:router'; protected static $defaultDescription = 'Display current routes for an application'; private $router; private $fileLinkFormatter; public function __construct(RouterInterface $router, ?FileLinkFormatter $fileLinkFormatter = null) { parent::__construct(); $this->router = $router; $this->fileLinkFormatter = $fileLinkFormatter; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('name', InputArgument::OPTIONAL, 'A route name'), new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% displays the configured routes: php %command.full_name% EOF ); } /** * {@inheritdoc} * * @throws InvalidArgumentException When route does not exist */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); $helper = new DescriptorHelper($this->fileLinkFormatter); $routes = $this->router->getRouteCollection(); $container = null; if ($this->fileLinkFormatter) { $container = function () { return $this->getContainerBuilder($this->getApplication()->getKernel()); }; } if ($name) { $route = $routes->get($name); $matchingRoutes = $this->findRouteNameContaining($name, $routes); if (!$input->isInteractive() && !$route && \count($matchingRoutes) > 1) { $helper->describe($io, $this->findRouteContaining($name, $routes), ['format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), 'output' => $io]); return 0; } if (!$route && $matchingRoutes) { $default = 1 === \count($matchingRoutes) ? $matchingRoutes[0] : null; $name = $io->choice('Select one of the matching routes', $matchingRoutes, $default); $route = $routes->get($name); } if (!$route) { throw new InvalidArgumentException(\sprintf('The route "%s" does not exist.', $name)); } $helper->describe($io, $route, ['format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'name' => $name, 'output' => $io, 'container' => $container]); } else { $helper->describe($io, $routes, ['format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), 'output' => $io, 'container' => $container]); } return 0; } private function findRouteNameContaining(string $name, RouteCollection $routes) : array { $foundRoutesNames = []; foreach ($routes as $routeName => $route) { if (\false !== \stripos($routeName, $name)) { $foundRoutesNames[] = $routeName; } } return $foundRoutesNames; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('name')) { $suggestions->suggestValues(\array_keys($this->router->getRouteCollection()->all())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } private function findRouteContaining(string $name, RouteCollection $routes) : RouteCollection { $foundRoutes = new RouteCollection(); foreach ($routes as $routeName => $route) { if (\false !== \stripos($routeName, $name)) { $foundRoutes->add($routeName, $route); } } return $foundRoutes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Component\Translation\Catalogue\MergeOperation; use _ContaoManager\Symfony\Component\Translation\Catalogue\TargetOperation; use _ContaoManager\Symfony\Component\Translation\Extractor\ExtractorInterface; use _ContaoManager\Symfony\Component\Translation\MessageCatalogue; use _ContaoManager\Symfony\Component\Translation\MessageCatalogueInterface; use _ContaoManager\Symfony\Component\Translation\Reader\TranslationReaderInterface; use _ContaoManager\Symfony\Component\Translation\Writer\TranslationWriterInterface; /** * A command that parses templates to extract translation messages and adds them * into the translation files. * * @author Michel Salib * * @final */ class TranslationUpdateCommand extends Command { private const ASC = 'asc'; private const DESC = 'desc'; private const SORT_ORDERS = [self::ASC, self::DESC]; private const FORMATS = ['xlf12' => ['xlf', '1.2'], 'xlf20' => ['xlf', '2.0']]; protected static $defaultName = 'translation:extract|translation:update'; protected static $defaultDescription = 'Extract missing translations keys from code to translation files.'; private $writer; private $reader; private $extractor; private $defaultLocale; private $defaultTransPath; private $defaultViewsPath; private $transPaths; private $codePaths; private $enabledLocales; public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, ?string $defaultTransPath = null, ?string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { parent::__construct(); $this->writer = $writer; $this->reader = $reader; $this->extractor = $extractor; $this->defaultLocale = $defaultLocale; $this->defaultTransPath = $defaultTransPath; $this->defaultViewsPath = $defaultViewsPath; $this->transPaths = $transPaths; $this->codePaths = $codePaths; $this->enabledLocales = $enabledLocales; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'), new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format (deprecated)'), new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'), new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'), new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'), new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'), new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'), new InputOption('xliff-version', null, InputOption::VALUE_OPTIONAL, 'Override the default xliff version (deprecated)'), new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically (only works with --dump-messages)', 'asc'), new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command extracts translation strings from templates of a given bundle or the default translations directory. It can display them or merge the new ones into the translation files. When new translation strings are found it can automatically add a prefix to the translation message. Example running against a Bundle (AcmeBundle) php %command.full_name% --dump-messages en AcmeBundle php %command.full_name% --force --prefix="new_" fr AcmeBundle Example running against default messages directory php %command.full_name% --dump-messages en php %command.full_name% --force --prefix="new_" fr You can sort the output with the --sort flag: php %command.full_name% --dump-messages --sort=asc en AcmeBundle php %command.full_name% --dump-messages --sort=desc fr You can dump a tree-like structure using the yaml format with --as-tree flag: php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; if ('translation:update' === $input->getFirstArgument()) { $errorIo->caution('Command "translation:update" is deprecated since version 5.4 and will be removed in Symfony 6.0. Use "translation:extract" instead.'); } $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); // check presence of force or dump-message if (\true !== $input->getOption('force') && \true !== $input->getOption('dump-messages')) { $errorIo->error('You must choose one of --force or --dump-messages'); return 1; } $format = $input->getOption('output-format') ?: $input->getOption('format'); $xliffVersion = $input->getOption('xliff-version') ?? '1.2'; if ($input->getOption('xliff-version')) { $errorIo->warning(\sprintf('The "--xliff-version" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion)); } if ($input->getOption('output-format')) { $errorIo->warning(\sprintf('The "--output-format" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion)); } if (\in_array($format, \array_keys(self::FORMATS), \true)) { [$format, $xliffVersion] = self::FORMATS[$format]; } // check format $supportedFormats = $this->writer->getFormats(); if (!\in_array($format, $supportedFormats, \true)) { $errorIo->error(['Wrong output format', 'Supported formats are: ' . \implode(', ', $supportedFormats) . ', xlf12 and xlf20.']); return 1; } /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); // Define Root Paths $transPaths = $this->getRootTransPaths(); $codePaths = $this->getRootCodePaths($kernel); $currentName = 'default directory'; // Override with provided Bundle info if (null !== $input->getArgument('bundle')) { try { $foundBundle = $kernel->getBundle($input->getArgument('bundle')); $bundleDir = $foundBundle->getPath(); $transPaths = [\is_dir($bundleDir . '/Resources/translations') ? $bundleDir . '/Resources/translations' : $bundleDir . '/translations']; $codePaths = [\is_dir($bundleDir . '/Resources/views') ? $bundleDir . '/Resources/views' : $bundleDir . '/templates']; if ($this->defaultTransPath) { $transPaths[] = $this->defaultTransPath; } if ($this->defaultViewsPath) { $codePaths[] = $this->defaultViewsPath; } $currentName = $foundBundle->getName(); } catch (\InvalidArgumentException $e) { // such a bundle does not exist, so treat the argument as path $path = $input->getArgument('bundle'); $transPaths = [$path . '/translations']; $codePaths = [$path . '/templates']; if (!\is_dir($transPaths[0])) { throw new InvalidArgumentException(\sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); } } } $io->title('Translation Messages Extractor and Dumper'); $io->comment(\sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); $io->comment('Parsing templates...'); $extractedCatalogue = $this->extractMessages($input->getArgument('locale'), $codePaths, $input->getOption('prefix')); $io->comment('Loading translation files...'); $currentCatalogue = $this->loadCurrentMessages($input->getArgument('locale'), $transPaths); if (null !== ($domain = $input->getOption('domain'))) { $currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain); $extractedCatalogue = $this->filterCatalogue($extractedCatalogue, $domain); } // process catalogues $operation = $input->getOption('clean') ? new TargetOperation($currentCatalogue, $extractedCatalogue) : new MergeOperation($currentCatalogue, $extractedCatalogue); // Exit if no messages found. if (!\count($operation->getDomains())) { $errorIo->warning('No translation messages were found.'); return 0; } $resultMessage = 'Translation files were successfully updated'; $operation->moveMessagesToIntlDomainsIfPossible('new'); // show compiled list of messages if (\true === $input->getOption('dump-messages')) { $extractedMessagesCount = 0; $io->newLine(); foreach ($operation->getDomains() as $domain) { $newKeys = \array_keys($operation->getNewMessages($domain)); $allKeys = \array_keys($operation->getMessages($domain)); $list = \array_merge(\array_diff($allKeys, $newKeys), \array_map(function ($id) { return \sprintf('%s', $id); }, $newKeys), \array_map(function ($id) { return \sprintf('%s', $id); }, \array_keys($operation->getObsoleteMessages($domain)))); $domainMessagesCount = \count($list); if ($sort = $input->getOption('sort')) { $sort = \strtolower($sort); if (!\in_array($sort, self::SORT_ORDERS, \true)) { $errorIo->error(['Wrong sort order', 'Supported formats are: ' . \implode(', ', self::SORT_ORDERS) . '.']); return 1; } if (self::DESC === $sort) { \rsort($list); } else { \sort($list); } } $io->section(\sprintf('Messages extracted for domain "%s" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : '')); $io->listing($list); $extractedMessagesCount += $domainMessagesCount; } if ('xlf' === $format) { $io->comment(\sprintf('Xliff output version is %s', $xliffVersion)); } $resultMessage = \sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); } // save the files if (\true === $input->getOption('force')) { $io->comment('Writing files...'); $bundleTransPath = \false; foreach ($transPaths as $path) { if (\is_dir($path)) { $bundleTransPath = $path; } } if (!$bundleTransPath) { $bundleTransPath = \end($transPaths); } $this->writer->write($operation->getResult(), $format, ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale, 'xliff_version' => $xliffVersion, 'as_tree' => $input->getOption('as-tree'), 'inline' => $input->getOption('as-tree') ?? 0]); if (\true === $input->getOption('dump-messages')) { $resultMessage .= ' and translation files were updated'; } } $io->success($resultMessage . '.'); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('locale')) { $suggestions->suggestValues($this->enabledLocales); return; } /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); if ($input->mustSuggestArgumentValuesFor('bundle')) { $bundles = []; foreach ($kernel->getBundles() as $bundle) { $bundles[] = $bundle->getName(); if ($bundle->getContainerExtension()) { $bundles[] = $bundle->getContainerExtension()->getAlias(); } } $suggestions->suggestValues($bundles); return; } if ($input->mustSuggestOptionValuesFor('format')) { $suggestions->suggestValues(\array_merge($this->writer->getFormats(), \array_keys(self::FORMATS))); return; } if ($input->mustSuggestOptionValuesFor('domain') && ($locale = $input->getArgument('locale'))) { $extractedCatalogue = $this->extractMessages($locale, $this->getRootCodePaths($kernel), $input->getOption('prefix')); $currentCatalogue = $this->loadCurrentMessages($locale, $this->getRootTransPaths()); // process catalogues $operation = $input->getOption('clean') ? new TargetOperation($currentCatalogue, $extractedCatalogue) : new MergeOperation($currentCatalogue, $extractedCatalogue); $suggestions->suggestValues($operation->getDomains()); return; } if ($input->mustSuggestOptionValuesFor('sort')) { $suggestions->suggestValues(self::SORT_ORDERS); } } private function filterCatalogue(MessageCatalogue $catalogue, string $domain) : MessageCatalogue { $filteredCatalogue = new MessageCatalogue($catalogue->getLocale()); // extract intl-icu messages only $intlDomain = $domain . MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; if ($intlMessages = $catalogue->all($intlDomain)) { $filteredCatalogue->add($intlMessages, $intlDomain); } // extract all messages and subtract intl-icu messages if ($messages = \array_diff($catalogue->all($domain), $intlMessages)) { $filteredCatalogue->add($messages, $domain); } foreach ($catalogue->getResources() as $resource) { $filteredCatalogue->addResource($resource); } if ($metadata = $catalogue->getMetadata('', $intlDomain)) { foreach ($metadata as $k => $v) { $filteredCatalogue->setMetadata($k, $v, $intlDomain); } } if ($metadata = $catalogue->getMetadata('', $domain)) { foreach ($metadata as $k => $v) { $filteredCatalogue->setMetadata($k, $v, $domain); } } return $filteredCatalogue; } private function extractMessages(string $locale, array $transPaths, string $prefix) : MessageCatalogue { $extractedCatalogue = new MessageCatalogue($locale); $this->extractor->setPrefix($prefix); $transPaths = $this->filterDuplicateTransPaths($transPaths); foreach ($transPaths as $path) { if (\is_dir($path) || \is_file($path)) { $this->extractor->extract($path, $extractedCatalogue); } } return $extractedCatalogue; } private function filterDuplicateTransPaths(array $transPaths) : array { $transPaths = \array_filter(\array_map('realpath', $transPaths)); \sort($transPaths); $filteredPaths = []; foreach ($transPaths as $path) { foreach ($filteredPaths as $filteredPath) { if (\str_starts_with($path, $filteredPath . \DIRECTORY_SEPARATOR)) { continue 2; } } $filteredPaths[] = $path; } return $filteredPaths; } private function loadCurrentMessages(string $locale, array $transPaths) : MessageCatalogue { $currentCatalogue = new MessageCatalogue($locale); foreach ($transPaths as $path) { if (\is_dir($path)) { $this->reader->read($path, $currentCatalogue); } } return $currentCatalogue; } private function getRootTransPaths() : array { $transPaths = $this->transPaths; if ($this->defaultTransPath) { $transPaths[] = $this->defaultTransPath; } return $transPaths; } private function getRootCodePaths(KernelInterface $kernel) : array { $codePaths = $this->codePaths; $codePaths[] = $kernel->getProjectDir() . '/src'; if ($this->defaultViewsPath) { $codePaths[] = $this->defaultViewsPath; } return $codePaths; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; use _ContaoManager\Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\Yaml\Yaml; /** * A console command for dumping available configuration reference. * * @author Kevin Bond * @author Wouter J * @author Grégoire Pineau * * @final */ class ConfigDumpReferenceCommand extends AbstractConfigCommand { protected static $defaultName = 'config:dump-reference'; protected static $defaultDescription = 'Dump the default configuration for an extension'; /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command dumps the default configuration for an extension/bundle. Either the extension alias or bundle name can be used: php %command.full_name% framework php %command.full_name% FrameworkBundle With the --format option specifies the format of the configuration, this is either yaml or xml. When the option is not provided, yaml is used. php %command.full_name% FrameworkBundle --format=xml For dumping a specific option, add its path as second argument (only available for the yaml format): php %command.full_name% framework http_client.default_options EOF ); } /** * {@inheritdoc} * * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); if (null === ($name = $input->getArgument('name'))) { $this->listBundles($errorIo); $this->listNonBundleExtensions($errorIo); $errorIo->comment(['Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. config:dump-reference FrameworkBundle)', 'For dumping a specific option, add its path as the second argument of this command. (e.g. config:dump-reference FrameworkBundle http_client.default_options to dump the framework.http_client.default_options configuration)']); return 0; } $extension = $this->findExtension($name); if ($extension instanceof ConfigurationInterface) { $configuration = $extension; } else { $configuration = $extension->getConfiguration([], $this->getContainerBuilder($this->getApplication()->getKernel())); } $this->validateConfiguration($extension, $configuration); $format = $input->getOption('format'); if ('yaml' === $format && !\class_exists(Yaml::class)) { $errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=xml" instead.'); return 1; } $path = $input->getArgument('path'); if (null !== $path && 'yaml' !== $format) { $errorIo->error('The "path" option is only available for the "yaml" format.'); return 1; } if ($name === $extension->getAlias()) { $message = \sprintf('Default configuration for extension with alias: "%s"', $name); } else { $message = \sprintf('Default configuration for "%s"', $name); } if (null !== $path) { $message .= \sprintf(' at path "%s"', $path); } switch ($format) { case 'yaml': $io->writeln(\sprintf('# %s', $message)); $dumper = new YamlReferenceDumper(); break; case 'xml': $io->writeln(\sprintf('', $message)); $dumper = new XmlReferenceDumper(); break; default: $io->writeln($message); throw new InvalidArgumentException('Only the yaml and xml formats are supported.'); } $io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path)); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('name')) { $suggestions->suggestValues($this->getAvailableExtensions()); $suggestions->suggestValues($this->getAvailableBundles()); } if ($input->mustSuggestOptionValuesFor('format')) { $suggestions->suggestValues($this->getAvailableFormatOptions()); } } private function getAvailableExtensions() : array { $kernel = $this->getApplication()->getKernel(); $extensions = []; foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { $extensions[] = $alias; } return $extensions; } private function getAvailableBundles() : array { $bundles = []; foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { $bundles[] = $bundle->getName(); } return $bundles; } private function getAvailableFormatOptions() : array { return ['yaml', 'xml']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\Filesystem\Exception\IOException; use _ContaoManager\Symfony\Component\Filesystem\Filesystem; use _ContaoManager\Symfony\Component\Finder\Finder; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\BundleInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * Command that places bundle web assets into a given directory. * * @author Fabien Potencier * @author Gábor Egyed * * @final */ class AssetsInstallCommand extends Command { public const METHOD_COPY = 'copy'; public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; protected static $defaultName = 'assets:install'; protected static $defaultDescription = 'Install bundle\'s web assets under a public directory'; private $filesystem; private $projectDir; public function __construct(Filesystem $filesystem, string $projectDir) { parent::__construct(); $this->filesystem = $filesystem; $this->projectDir = $projectDir; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', null)])->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them')->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist')->setDescription(self::$defaultDescription)->setHelp(<<<'EOT' The %command.name% command installs bundle assets into a given directory (e.g. the public directory). php %command.full_name% public A "bundles" directory will be created inside the target directory and the "Resources/public" directory of each bundle will be copied into it. To create a symlink to each bundle instead of copying its assets, use the --symlink option (will fall back to hard copies when symbolic links aren't possible: php %command.full_name% public --symlink To make symlink relative, add the --relative option: php %command.full_name% public --symlink --relative EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); $targetArg = \rtrim($input->getArgument('target') ?? '', '/'); if (!$targetArg) { $targetArg = $this->getPublicDirectory($kernel->getContainer()); } if (!\is_dir($targetArg)) { $targetArg = $kernel->getProjectDir() . '/' . $targetArg; if (!\is_dir($targetArg)) { throw new InvalidArgumentException(\sprintf('The target directory "%s" does not exist.', $targetArg)); } } $bundlesDir = $targetArg . '/bundles/'; $io = new SymfonyStyle($input, $output); $io->newLine(); if ($input->getOption('relative')) { $expectedMethod = self::METHOD_RELATIVE_SYMLINK; $io->text('Trying to install assets as relative symbolic links.'); } elseif ($input->getOption('symlink')) { $expectedMethod = self::METHOD_ABSOLUTE_SYMLINK; $io->text('Trying to install assets as absolute symbolic links.'); } else { $expectedMethod = self::METHOD_COPY; $io->text('Installing assets as hard copies.'); } $io->newLine(); $rows = []; $copyUsed = \false; $exitCode = 0; $validAssetDirs = []; /** @var BundleInterface $bundle */ foreach ($kernel->getBundles() as $bundle) { if (!\is_dir($originDir = $bundle->getPath() . '/Resources/public') && !\is_dir($originDir = $bundle->getPath() . '/public')) { continue; } $assetDir = \preg_replace('/bundle$/', '', \strtolower($bundle->getName())); $targetDir = $bundlesDir . $assetDir; $validAssetDirs[] = $assetDir; if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $message = \sprintf("%s\n-> %s", $bundle->getName(), $targetDir); } else { $message = $bundle->getName(); } try { $this->filesystem->remove($targetDir); if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) { $method = $this->relativeSymlinkWithFallback($originDir, $targetDir); } elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) { $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); } else { $method = $this->hardCopy($originDir, $targetDir); } if (self::METHOD_COPY === $method) { $copyUsed = \true; } if ($method === $expectedMethod) { $rows[] = [\sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "✔"), $message, $method]; } else { $rows[] = [\sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method]; } } catch (\Exception $e) { $exitCode = 1; $rows[] = [\sprintf('%s', '\\' === \DIRECTORY_SEPARATOR ? 'ERROR' : "✘"), $message, $e->getMessage()]; } } // remove the assets of the bundles that no longer exist if (!$input->getOption('no-cleanup') && \is_dir($bundlesDir)) { $dirsToRemove = Finder::create()->depth(0)->directories()->exclude($validAssetDirs)->in($bundlesDir); $this->filesystem->remove($dirsToRemove); } if ($rows) { $io->table(['', 'Bundle', 'Method / Error'], $rows); } if (0 !== $exitCode) { $io->error('Some errors occurred while installing assets.'); } else { if ($copyUsed) { $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.'); } $io->success($rows ? 'All assets were successfully installed.' : 'No assets were provided by any bundle.'); } return $exitCode; } /** * Try to create relative symlink. * * Falling back to absolute symlink and finally hard copy. */ private function relativeSymlinkWithFallback(string $originDir, string $targetDir) : string { try { $this->symlink($originDir, $targetDir, \true); $method = self::METHOD_RELATIVE_SYMLINK; } catch (IOException $e) { $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); } return $method; } /** * Try to create absolute symlink. * * Falling back to hard copy. */ private function absoluteSymlinkWithFallback(string $originDir, string $targetDir) : string { try { $this->symlink($originDir, $targetDir); $method = self::METHOD_ABSOLUTE_SYMLINK; } catch (IOException $e) { // fall back to copy $method = $this->hardCopy($originDir, $targetDir); } return $method; } /** * Creates symbolic link. * * @throws IOException if link cannot be created */ private function symlink(string $originDir, string $targetDir, bool $relative = \false) { if ($relative) { $this->filesystem->mkdir(\dirname($targetDir)); $originDir = $this->filesystem->makePathRelative($originDir, \realpath(\dirname($targetDir))); } $this->filesystem->symlink($originDir, $targetDir); if (!\file_exists($targetDir)) { throw new IOException(\sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir); } } /** * Copies origin to target. */ private function hardCopy(string $originDir, string $targetDir) : string { $this->filesystem->mkdir($targetDir, 0777); // We use a custom iterator to ignore VCS files $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(\false)->in($originDir)); return self::METHOD_COPY; } private function getPublicDirectory(ContainerInterface $container) : string { $defaultPublicDir = 'public'; if (null === $this->projectDir && !$container->hasParameter('kernel.project_dir')) { return $defaultPublicDir; } $composerFilePath = ($this->projectDir ?? $container->getParameter('kernel.project_dir')) . '/composer.json'; if (!\file_exists($composerFilePath)) { return $defaultPublicDir; } $composerConfig = \json_decode(\file_get_contents($composerFilePath), \true); return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * Cache pool pruner command. * * @author Rob Frawley 2nd */ final class CachePoolPruneCommand extends Command { protected static $defaultName = 'cache:pool:prune'; protected static $defaultDescription = 'Prune cache pools'; private $pools; /** * @param iterable $pools */ public function __construct(iterable $pools) { parent::__construct(); $this->pools = $pools; } /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command deletes all expired items from all pruneable pools. %command.full_name% EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); foreach ($this->pools as $name => $pool) { $io->comment(\sprintf('Pruning cache pool: %s', $name)); $pool->prune(); } $io->success('Successfully pruned cache pool(s).'); return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; /** * Clear cache pools. * * @author Nicolas Grekas */ final class CachePoolClearCommand extends Command { protected static $defaultName = 'cache:pool:clear'; protected static $defaultDescription = 'Clear cache pools'; private $poolClearer; private $poolNames; /** * @param string[]|null $poolNames */ public function __construct(Psr6CacheClearer $poolClearer, ?array $poolNames = null) { parent::__construct(); $this->poolClearer = $poolClearer; $this->poolNames = $poolNames; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command clears the given cache pools or cache pool clearers. %command.full_name% [...] EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $kernel = $this->getApplication()->getKernel(); $pools = []; $clearers = []; foreach ($input->getArgument('pools') as $id) { if ($this->poolClearer->hasPool($id)) { $pools[$id] = $id; } else { $pool = $kernel->getContainer()->get($id); if ($pool instanceof CacheItemPoolInterface) { $pools[$id] = $pool; } elseif ($pool instanceof Psr6CacheClearer) { $clearers[$id] = $pool; } else { throw new InvalidArgumentException(\sprintf('"%s" is not a cache pool nor a cache clearer.', $id)); } } } foreach ($clearers as $id => $clearer) { $io->comment(\sprintf('Calling cache clearer: %s', $id)); $clearer->clear($kernel->getContainer()->getParameter('kernel.cache_dir')); } $failure = \false; foreach ($pools as $id => $pool) { $io->comment(\sprintf('Clearing cache pool: %s', $id)); if ($pool instanceof CacheItemPoolInterface) { if (!$pool->clear()) { $io->warning(\sprintf('Cache pool "%s" could not be cleared.', $pool)); $failure = \true; } } else { if (\false === $this->poolClearer->clearPool($id)) { $io->warning(\sprintf('Cache pool "%s" could not be cleared.', $pool)); $failure = \true; } } } if ($failure) { return 1; } $io->success('Cache was successfully cleared.'); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pools')) { $suggestions->suggestValues($this->poolNames); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Config\ConfigCache; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PassConfig; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; final class ContainerLintCommand extends Command { protected static $defaultName = 'lint:container'; protected static $defaultDescription = 'Ensure that arguments injected into services match type declarations'; /** * @var ContainerBuilder */ private $containerBuilder; /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.'); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); try { $container = $this->getContainerBuilder(); } catch (RuntimeException $e) { $errorIo->error($e->getMessage()); return 2; } $container->setParameter('container.build_time', \time()); try { $container->compile(); } catch (InvalidArgumentException $e) { $errorIo->error($e->getMessage()); return 1; } $io->success('The container was linted successfully: all services are injected with values that are compatible with their type declarations.'); return 0; } private function getContainerBuilder() : ContainerBuilder { if ($this->containerBuilder) { return $this->containerBuilder; } $kernel = $this->getApplication()->getKernel(); $kernelContainer = $kernel->getContainer(); if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), \true))->isFresh()) { if (!$kernel instanceof Kernel) { throw new RuntimeException(\sprintf('This command does not support the application kernel: "%s" does not extend "%s".', \get_debug_type($kernel), Kernel::class)); } $buildContainer = \Closure::bind(function () : ContainerBuilder { $this->initializeBundles(); return $this->buildContainer(); }, $kernel, \get_class($kernel)); $container = $buildContainer(); $skippedIds = []; } else { if (!$kernelContainer instanceof Container) { throw new RuntimeException(\sprintf('This command does not support the application container: "%s" does not extend "%s".', \get_debug_type($kernelContainer), Container::class)); } (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump')); $refl = new \ReflectionProperty($parameterBag, 'resolved'); $refl->setAccessible(\true); $refl->setValue($parameterBag, \true); $skippedIds = []; foreach ($container->getServiceIds() as $serviceId) { if (\str_starts_with($serviceId, '.errored.')) { $skippedIds[$serviceId] = \true; } } $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } $container->setParameter('container.build_hash', 'lint_container'); $container->setParameter('container.build_id', 'lint_container'); $container->addCompilerPass(new CheckTypeDeclarationsPass(\true, $skippedIds), PassConfig::TYPE_AFTER_REMOVING, -100); return $this->containerBuilder = $container; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Nicolas Grekas * * @internal */ final class SecretsEncryptFromLocalCommand extends Command { protected static $defaultName = 'secrets:encrypt-from-local'; protected static $defaultDescription = 'Encrypt all local secrets to the vault'; private $vault; private $localVault; public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; parent::__construct(); } protected function configure() { $this->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command encrypts all locally overridden secrets to the vault. %command.full_name% EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); if (null === $this->localVault) { $io->error('The local vault is disabled.'); return 1; } foreach ($this->vault->list(\true) as $name => $value) { $localValue = $this->localVault->reveal($name); if (null !== $localValue && $value !== $localValue) { $this->vault->seal($name, $localValue); } elseif (null !== ($message = $this->localVault->getLastMessage())) { $io->error($message); return 1; } } return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterStyle; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; /** * A console command for autowiring information. * * @author Ryan Weaver * * @internal */ class DebugAutowiringCommand extends ContainerDebugCommand { protected static $defaultName = 'debug:autowiring'; protected static $defaultDescription = 'List classes/interfaces you can use for autowiring'; private $supportsHref; private $fileLinkFormatter; public function __construct(?string $name = null, ?FileLinkFormatter $fileLinkFormatter = null) { $this->supportsHref = \method_exists(OutputFormatterStyle::class, 'setHref'); $this->fileLinkFormatter = $fileLinkFormatter; parent::__construct($name); } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'), new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command displays the classes and interfaces that you can use as type-hints for autowiring: php %command.full_name% You can also pass a search term to filter the list: php %command.full_name% log EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); $serviceIds = $builder->getServiceIds(); $serviceIds = \array_filter($serviceIds, [$this, 'filterToServiceTypes']); if ($search = $input->getArgument('search')) { $searchNormalized = \preg_replace('/[^a-zA-Z0-9\\x7f-\\xff $]++/', '', $search); $serviceIds = \array_filter($serviceIds, function ($serviceId) use($searchNormalized) { return \false !== \stripos(\str_replace('\\', '', $serviceId), $searchNormalized) && !\str_starts_with($serviceId, '.'); }); if (empty($serviceIds)) { $errorIo->error(\sprintf('No autowirable classes or interfaces found matching "%s"', $search)); return 1; } } \uasort($serviceIds, 'strnatcmp'); $io->title('Autowirable Types'); $io->text('The following classes & interfaces can be used as type-hints when autowiring:'); if ($search) { $io->text(\sprintf('(only showing classes/interfaces matching %s)', $search)); } $hasAlias = []; $all = $input->getOption('all'); $previousId = '-'; $serviceIdsNb = 0; foreach ($serviceIds as $serviceId) { $text = []; $resolvedServiceId = $serviceId; if (!\str_starts_with($serviceId, $previousId)) { $text[] = ''; if ('' !== ($description = Descriptor::getClassDescription($serviceId, $resolvedServiceId))) { if (isset($hasAlias[$serviceId])) { continue; } $text[] = $description; } $previousId = $serviceId . ' $'; } $serviceLine = \sprintf('%s', $serviceId); if ($this->supportsHref && '' !== ($fileLink = $this->getFileLink($serviceId))) { $serviceLine = \sprintf('%s', $fileLink, $serviceId); } if ($builder->hasAlias($serviceId)) { $hasAlias[$serviceId] = \true; $serviceAlias = $builder->getAlias($serviceId); $serviceLine .= ' (' . $serviceAlias . ')'; if ($serviceAlias->isDeprecated()) { $serviceLine .= ' - deprecated'; } } elseif (!$all) { ++$serviceIdsNb; continue; } $text[] = $serviceLine; $io->text($text); } $io->newLine(); if (0 < $serviceIdsNb) { $io->text(\sprintf('%s more concrete service%s would be displayed when adding the "--all" option.', $serviceIdsNb, $serviceIdsNb > 1 ? 's' : '')); } if ($all) { $io->text('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.'); } $io->newLine(); return 0; } private function getFileLink(string $class) : string { if (null === $this->fileLinkFormatter || null === ($r = $this->getContainerBuilder($this->getApplication()->getKernel())->getReflectionClass($class, \false))) { return ''; } return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('search')) { $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); $suggestions->suggestValues(\array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes'])); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\Dumper\Preloader; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; /** * Warmup the cache. * * @author Fabien Potencier * * @final */ class CacheWarmupCommand extends Command { protected static $defaultName = 'cache:warmup'; protected static $defaultDescription = 'Warm up an empty cache'; private $cacheWarmer; public function __construct(CacheWarmerAggregate $cacheWarmer) { parent::__construct(); $this->cacheWarmer = $cacheWarmer; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command warms up the cache. Before running this command, the cache must be empty. EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $kernel = $this->getApplication()->getKernel(); $io->comment(\sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), \var_export($kernel->isDebug(), \true))); if (!$input->getOption('no-optional-warmers')) { $this->cacheWarmer->enableOptionalWarmers(); } $cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); if ($kernel instanceof WarmableInterface) { $kernel->warmUp($cacheDir); } $preload = $this->cacheWarmer->warmUp($cacheDir); if ($preload && \file_exists($preloadFile = $cacheDir . '/' . $kernel->getContainer()->getParameter('kernel.container_class') . '.preload.php')) { Preloader::append($preloadFile, $preload); } $io->success(\sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), \var_export($kernel->isDebug(), \true))); return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Workflow\Definition; use _ContaoManager\Symfony\Component\Workflow\Dumper\GraphvizDumper; use _ContaoManager\Symfony\Component\Workflow\Dumper\MermaidDumper; use _ContaoManager\Symfony\Component\Workflow\Dumper\PlantUmlDumper; use _ContaoManager\Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper; use _ContaoManager\Symfony\Component\Workflow\Marking; /** * @author Grégoire Pineau * * @final */ class WorkflowDumpCommand extends Command { protected static $defaultName = 'workflow:dump'; protected static $defaultDescription = 'Dump a workflow'; /** * string is the service id. * * @var array */ private $workflows = []; private const DUMP_FORMAT_OPTIONS = ['puml', 'mermaid', 'dot']; public function __construct(array $workflows) { parent::__construct(); $this->workflows = $workflows; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'), new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'), new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'), new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format [' . \implode('|', self::DUMP_FORMAT_OPTIONS) . ']', 'dot')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command dumps the graphical representation of a workflow in different formats DOT: %command.full_name% | dot -Tpng > workflow.png PUML: %command.full_name% --dump-format=puml | java -jar plantuml.jar -p > workflow.png MERMAID: %command.full_name% --dump-format=mermaid | mmdc -o workflow.svg EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $workflowName = $input->getArgument('name'); $workflow = null; if (isset($this->workflows['workflow.' . $workflowName])) { $workflow = $this->workflows['workflow.' . $workflowName]; $type = 'workflow'; } elseif (isset($this->workflows['state_machine.' . $workflowName])) { $workflow = $this->workflows['state_machine.' . $workflowName]; $type = 'state_machine'; } if (null === $workflow) { throw new InvalidArgumentException(\sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName)); } switch ($input->getOption('dump-format')) { case 'puml': $transitionType = 'workflow' === $type ? PlantUmlDumper::WORKFLOW_TRANSITION : PlantUmlDumper::STATEMACHINE_TRANSITION; $dumper = new PlantUmlDumper($transitionType); break; case 'mermaid': $transitionType = 'workflow' === $type ? MermaidDumper::TRANSITION_TYPE_WORKFLOW : MermaidDumper::TRANSITION_TYPE_STATEMACHINE; $dumper = new MermaidDumper($transitionType); break; case 'dot': default: $dumper = 'workflow' === $type ? new GraphvizDumper() : new StateMachineGraphvizDumper(); } $marking = new Marking(); foreach ($input->getArgument('marking') as $place) { $marking->mark($place); } $options = ['name' => $workflowName, 'nofooter' => \true, 'graph' => ['label' => $input->getOption('label')]]; $output->writeln($dumper->dump($workflow, $marking, $options)); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('name')) { $suggestions->suggestValues(\array_keys($this->workflows)); } if ($input->mustSuggestOptionValuesFor('dump-format')) { $suggestions->suggestValues(self::DUMP_FORMAT_OPTIONS); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceProviderInterface; /** * A console command for retrieving information about event dispatcher. * * @author Matthieu Auger * * @final */ class EventDispatcherDebugCommand extends Command { private const DEFAULT_DISPATCHER = 'event_dispatcher'; protected static $defaultName = 'debug:event-dispatcher'; protected static $defaultDescription = 'Display configured listeners for an application'; private $dispatchers; public function __construct(ContainerInterface $dispatchers) { parent::__construct(); $this->dispatchers = $dispatchers; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'), new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command displays all configured listeners: php %command.full_name% To get specific listeners for an event, specify its name: php %command.full_name% kernel.request EOF ); } /** * {@inheritdoc} * * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $options = []; $dispatcherServiceName = $input->getOption('dispatcher'); if (!$this->dispatchers->has($dispatcherServiceName)) { $io->getErrorStyle()->error(\sprintf('Event dispatcher "%s" is not available.', $dispatcherServiceName)); return 1; } $dispatcher = $this->dispatchers->get($dispatcherServiceName); if ($event = $input->getArgument('event')) { if ($dispatcher->hasListeners($event)) { $options = ['event' => $event]; } else { // if there is no direct match, try find partial matches $events = $this->searchForEvent($dispatcher, $event); if (0 === \count($events)) { $io->getErrorStyle()->warning(\sprintf('The event "%s" does not have any registered listeners.', $event)); return 0; } elseif (1 === \count($events)) { $options = ['event' => $events[\array_key_first($events)]]; } else { $options = ['events' => $events]; } } } $helper = new DescriptorHelper(); if (self::DEFAULT_DISPATCHER !== $dispatcherServiceName) { $options['dispatcher_service_name'] = $dispatcherServiceName; } $options['format'] = $input->getOption('format'); $options['raw_text'] = $input->getOption('raw'); $options['output'] = $io; $helper->describe($io, $dispatcher, $options); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('event')) { $dispatcherServiceName = $input->getOption('dispatcher'); if ($this->dispatchers->has($dispatcherServiceName)) { $dispatcher = $this->dispatchers->get($dispatcherServiceName); $suggestions->suggestValues(\array_keys($dispatcher->getListeners())); } return; } if ($input->mustSuggestOptionValuesFor('dispatcher')) { if ($this->dispatchers instanceof ServiceProviderInterface) { $suggestions->suggestValues(\array_keys($this->dispatchers->getProvidedServices())); } return; } if ($input->mustSuggestOptionValuesFor('format')) { $suggestions->suggestValues((new DescriptorHelper())->getFormats()); } } private function searchForEvent(EventDispatcherInterface $dispatcher, string $needle) : array { $output = []; $lcNeedle = \strtolower($needle); $allEvents = \array_keys($dispatcher->getListeners()); foreach ($allEvents as $event) { if (\str_contains(\strtolower($event), $lcNeedle)) { $output[] = $event; } } return $output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** * A console command for retrieving information about services. * * @author Ryan Weaver * * @internal */ class ContainerDebugCommand extends Command { use BuildDebugContainerTrait; protected static $defaultName = 'debug:container'; protected static $defaultDescription = 'Display current services for an application'; /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'), new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Show arguments in services'), new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Show hidden (internal) services'), new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'), new InputOption('tags', null, InputOption::VALUE_NONE, 'Display tagged services for an application'), new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Display a specific parameter for an application'), new InputOption('parameters', null, InputOption::VALUE_NONE, 'Display parameters for an application'), new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'), new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'), new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command displays all configured public services: php %command.full_name% To see deprecations generated during container compilation and cache warmup, use the --deprecations option: php %command.full_name% --deprecations To get specific information about a service, specify its name: php %command.full_name% validator To get specific information about a service including all its arguments, use the --show-arguments flag: php %command.full_name% validator --show-arguments To see available types that can be used for autowiring, use the --types flag: php %command.full_name% --types To see environment variables used by the container, use the --env-vars flag: php %command.full_name% --env-vars Display a specific environment variable by specifying its name with the --env-var option: php %command.full_name% --env-var=APP_ENV Use the --tags option to display tagged public services grouped by tag: php %command.full_name% --tags Find all services with a specific tag by specifying the tag name with the --tag option: php %command.full_name% --tag=form.type Use the --parameters option to display all parameters: php %command.full_name% --parameters Display a specific parameter by specifying its name with the --parameter option: php %command.full_name% --parameter=kernel.debug By default, internal services are hidden. You can display them using the --show-hidden flag: php %command.full_name% --show-hidden EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); $this->validateInput($input); $kernel = $this->getApplication()->getKernel(); $object = $this->getContainerBuilder($kernel); if ($input->getOption('env-vars')) { $options = ['env-vars' => \true]; } elseif ($envVar = $input->getOption('env-var')) { $options = ['env-vars' => \true, 'name' => $envVar]; } elseif ($input->getOption('types')) { $options = []; $options['filter'] = [$this, 'filterToServiceTypes']; } elseif ($input->getOption('parameters')) { $parameters = []; foreach ($object->getParameterBag()->all() as $k => $v) { $parameters[$k] = $object->resolveEnvPlaceholders($v); } $object = new ParameterBag($parameters); $options = []; } elseif ($parameter = $input->getOption('parameter')) { $options = ['parameter' => $parameter]; } elseif ($input->getOption('tags')) { $options = ['group_by' => 'tags']; } elseif ($tag = $input->getOption('tag')) { $options = ['tag' => $tag]; } elseif ($name = $input->getArgument('name')) { $name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden')); $options = ['id' => $name]; } elseif ($input->getOption('deprecations')) { $options = ['deprecations' => \true]; } else { $options = []; } $helper = new DescriptorHelper(); $options['format'] = $input->getOption('format'); $options['show_arguments'] = $input->getOption('show-arguments'); $options['show_hidden'] = $input->getOption('show-hidden'); $options['raw_text'] = $input->getOption('raw'); $options['output'] = $io; $options['is_debug'] = $kernel->isDebug(); try { $helper->describe($io, $object, $options); if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) { $errorIo->note(\sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id'])); } } catch (ServiceNotFoundException $e) { if ('' !== $e->getId() && '@' === $e->getId()[0]) { throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [\substr($e->getId(), 1)]); } throw $e; } if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && !$input->getOption('env-vars') && !$input->getOption('env-var') && $input->isInteractive()) { if ($input->getOption('tags')) { $errorIo->comment('To search for a specific tag, re-run this command with a search term. (e.g. debug:container --tag=form.type)'); } elseif ($input->getOption('parameters')) { $errorIo->comment('To search for a specific parameter, re-run this command with a search term. (e.g. debug:container --parameter=kernel.debug)'); } elseif (!$input->getOption('deprecations')) { $errorIo->comment('To search for a specific service, re-run this command with a search term. (e.g. debug:container log)'); } } return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); return; } $kernel = $this->getApplication()->getKernel(); $object = $this->getContainerBuilder($kernel); if ($input->mustSuggestArgumentValuesFor('name') && !$input->getOption('tag') && !$input->getOption('tags') && !$input->getOption('parameter') && !$input->getOption('parameters') && !$input->getOption('env-var') && !$input->getOption('env-vars') && !$input->getOption('types') && !$input->getOption('deprecations')) { $suggestions->suggestValues($this->findServiceIdsContaining($object, $input->getCompletionValue(), (bool) $input->getOption('show-hidden'))); return; } if ($input->mustSuggestOptionValuesFor('tag')) { $suggestions->suggestValues($object->findTags()); return; } if ($input->mustSuggestOptionValuesFor('parameter')) { $suggestions->suggestValues(\array_keys($object->getParameterBag()->all())); } } /** * Validates input arguments and options. * * @throws \InvalidArgumentException */ protected function validateInput(InputInterface $input) { $options = ['tags', 'tag', 'parameters', 'parameter']; $optionsCount = 0; foreach ($options as $option) { if ($input->getOption($option)) { ++$optionsCount; } } $name = $input->getArgument('name'); if (null !== $name && $optionsCount > 0) { throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined with the service name argument.'); } elseif (null === $name && $optionsCount > 1) { throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined together.'); } } private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden) : string { $name = \ltrim($name, '\\'); if ($builder->has($name) || !$input->isInteractive()) { return $name; } $matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden); if (empty($matchingServices)) { throw new InvalidArgumentException(\sprintf('No services found that match "%s".', $name)); } if (1 === \count($matchingServices)) { return $matchingServices[0]; } return $io->choice('Select one of the following services to display its information', $matchingServices); } private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden) : array { $serviceIds = $builder->getServiceIds(); $foundServiceIds = $foundServiceIdsIgnoringBackslashes = []; foreach ($serviceIds as $serviceId) { if (!$showHidden && \str_starts_with($serviceId, '.')) { continue; } if (\false !== \stripos(\str_replace('\\', '', $serviceId), $name)) { $foundServiceIdsIgnoringBackslashes[] = $serviceId; } if ('' === $name || \false !== \stripos($serviceId, $name)) { $foundServiceIds[] = $serviceId; } } return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes; } /** * @internal */ public function filterToServiceTypes(string $serviceId) : bool { // filter out things that could not be valid class names if (!\preg_match('/(?(DEFINE)(?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+))^(?&V)(?:\\\\(?&V))*+(?: \\$(?&V))?$/', $serviceId)) { return \false; } // if the id has a \, assume it is a class if (\str_contains($serviceId, '\\')) { return \true; } return \class_exists($serviceId) || \interface_exists($serviceId, \false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Component\Translation\Catalogue\MergeOperation; use _ContaoManager\Symfony\Component\Translation\DataCollectorTranslator; use _ContaoManager\Symfony\Component\Translation\Extractor\ExtractorInterface; use _ContaoManager\Symfony\Component\Translation\LoggingTranslator; use _ContaoManager\Symfony\Component\Translation\MessageCatalogue; use _ContaoManager\Symfony\Component\Translation\Reader\TranslationReaderInterface; use _ContaoManager\Symfony\Component\Translation\Translator; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; /** * Helps finding unused or missing translation messages in a given locale * and comparing them with the fallback ones. * * @author Florian Voutzinos * * @final */ class TranslationDebugCommand extends Command { public const EXIT_CODE_GENERAL_ERROR = 64; public const EXIT_CODE_MISSING = 65; public const EXIT_CODE_UNUSED = 66; public const EXIT_CODE_FALLBACK = 68; public const MESSAGE_MISSING = 0; public const MESSAGE_UNUSED = 1; public const MESSAGE_EQUALS_FALLBACK = 2; protected static $defaultName = 'debug:translation'; protected static $defaultDescription = 'Display translation messages information'; private $translator; private $reader; private $extractor; private $defaultTransPath; private $defaultViewsPath; private $transPaths; private $codePaths; private $enabledLocales; public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, ?string $defaultTransPath = null, ?string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { parent::__construct(); $this->translator = $translator; $this->reader = $reader; $this->extractor = $extractor; $this->defaultTransPath = $defaultTransPath; $this->defaultViewsPath = $defaultViewsPath; $this->transPaths = $transPaths; $this->codePaths = $codePaths; $this->enabledLocales = $enabledLocales; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'), new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Display only missing messages'), new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'), new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command helps finding unused or missing translation messages and comparing them with the fallback ones by inspecting the templates and translation files of a given bundle or the default translations directory. You can display information about bundle translations in a specific locale: php %command.full_name% en AcmeDemoBundle You can also specify a translation domain for the search: php %command.full_name% --domain=messages en AcmeDemoBundle You can only display missing messages: php %command.full_name% --only-missing en AcmeDemoBundle You can only display unused messages: php %command.full_name% --only-unused en AcmeDemoBundle You can display information about application translations in a specific locale: php %command.full_name% en You can display information about translations in all registered bundles in a specific locale: php %command.full_name% --all en EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); $exitCode = self::SUCCESS; /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); // Define Root Paths $transPaths = $this->getRootTransPaths(); $codePaths = $this->getRootCodePaths($kernel); // Override with provided Bundle info if (null !== $input->getArgument('bundle')) { try { $bundle = $kernel->getBundle($input->getArgument('bundle')); $bundleDir = $bundle->getPath(); $transPaths = [\is_dir($bundleDir . '/Resources/translations') ? $bundleDir . '/Resources/translations' : $bundleDir . '/translations']; $codePaths = [\is_dir($bundleDir . '/Resources/views') ? $bundleDir . '/Resources/views' : $bundleDir . '/templates']; if ($this->defaultTransPath) { $transPaths[] = $this->defaultTransPath; } if ($this->defaultViewsPath) { $codePaths[] = $this->defaultViewsPath; } } catch (\InvalidArgumentException $e) { // such a bundle does not exist, so treat the argument as path $path = $input->getArgument('bundle'); $transPaths = [$path . '/translations']; $codePaths = [$path . '/templates']; if (!\is_dir($transPaths[0])) { throw new InvalidArgumentException(\sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); } } } elseif ($input->getOption('all')) { foreach ($kernel->getBundles() as $bundle) { $bundleDir = $bundle->getPath(); $transPaths[] = \is_dir($bundleDir . '/Resources/translations') ? $bundleDir . '/Resources/translations' : $bundle->getPath() . '/translations'; $codePaths[] = \is_dir($bundleDir . '/Resources/views') ? $bundleDir . '/Resources/views' : $bundle->getPath() . '/templates'; } } // Extract used messages $extractedCatalogue = $this->extractMessages($locale, $codePaths); // Load defined messages $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths); // Merge defined and extracted messages to get all message ids $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue); $allMessages = $mergeOperation->getResult()->all($domain); if (null !== $domain) { $allMessages = [$domain => $allMessages]; } // No defined or extracted messages if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) { $outputMessage = \sprintf('No defined or extracted messages for locale "%s"', $locale); if (null !== $domain) { $outputMessage .= \sprintf(' and domain "%s"', $domain); } $io->getErrorStyle()->warning($outputMessage); return self::EXIT_CODE_GENERAL_ERROR; } // Load the fallback catalogues $fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths); // Display header line $headers = ['State', 'Domain', 'Id', \sprintf('Message Preview (%s)', $locale)]; foreach ($fallbackCatalogues as $fallbackCatalogue) { $headers[] = \sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale()); } $rows = []; // Iterate all message ids and determine their state foreach ($allMessages as $domain => $messages) { foreach (\array_keys($messages) as $messageId) { $value = $currentCatalogue->get($messageId, $domain); $states = []; if ($extractedCatalogue->defines($messageId, $domain)) { if (!$currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_MISSING; if (!$input->getOption('only-unused')) { $exitCode = $exitCode | self::EXIT_CODE_MISSING; } } } elseif ($currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_UNUSED; if (!$input->getOption('only-missing')) { $exitCode = $exitCode | self::EXIT_CODE_UNUSED; } } if (!\in_array(self::MESSAGE_UNUSED, $states) && $input->getOption('only-unused') || !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing')) { continue; } foreach ($fallbackCatalogues as $fallbackCatalogue) { if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) { $states[] = self::MESSAGE_EQUALS_FALLBACK; $exitCode = $exitCode | self::EXIT_CODE_FALLBACK; break; } } $row = [$this->formatStates($states), $domain, $this->formatId($messageId), $this->sanitizeString($value)]; foreach ($fallbackCatalogues as $fallbackCatalogue) { $row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain)); } $rows[] = $row; } } $io->table($headers, $rows); return $exitCode; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('locale')) { $suggestions->suggestValues($this->enabledLocales); return; } /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); if ($input->mustSuggestArgumentValuesFor('bundle')) { $availableBundles = []; foreach ($kernel->getBundles() as $bundle) { $availableBundles[] = $bundle->getName(); if ($extension = $bundle->getContainerExtension()) { $availableBundles[] = $extension->getAlias(); } } $suggestions->suggestValues($availableBundles); return; } if ($input->mustSuggestOptionValuesFor('domain')) { $locale = $input->getArgument('locale'); $mergeOperation = new MergeOperation($this->extractMessages($locale, $this->getRootCodePaths($kernel)), $this->loadCurrentMessages($locale, $this->getRootTransPaths())); $suggestions->suggestValues($mergeOperation->getDomains()); } } private function formatState(int $state) : string { if (self::MESSAGE_MISSING === $state) { return ' missing '; } if (self::MESSAGE_UNUSED === $state) { return ' unused '; } if (self::MESSAGE_EQUALS_FALLBACK === $state) { return ' fallback '; } return $state; } private function formatStates(array $states) : string { $result = []; foreach ($states as $state) { $result[] = $this->formatState($state); } return \implode(' ', $result); } private function formatId(string $id) : string { return \sprintf('%s', $id); } private function sanitizeString(string $string, int $length = 40) : string { $string = \trim(\preg_replace('/\\s+/', ' ', $string)); if (\false !== ($encoding = \mb_detect_encoding($string, null, \true))) { if (\mb_strlen($string, $encoding) > $length) { return \mb_substr($string, 0, $length - 3, $encoding) . '...'; } } elseif (\strlen($string) > $length) { return \substr($string, 0, $length - 3) . '...'; } return $string; } private function extractMessages(string $locale, array $transPaths) : MessageCatalogue { $extractedCatalogue = new MessageCatalogue($locale); foreach ($transPaths as $path) { if (\is_dir($path) || \is_file($path)) { $this->extractor->extract($path, $extractedCatalogue); } } return $extractedCatalogue; } private function loadCurrentMessages(string $locale, array $transPaths) : MessageCatalogue { $currentCatalogue = new MessageCatalogue($locale); foreach ($transPaths as $path) { if (\is_dir($path)) { $this->reader->read($path, $currentCatalogue); } } return $currentCatalogue; } /** * @return MessageCatalogue[] */ private function loadFallbackCatalogues(string $locale, array $transPaths) : array { $fallbackCatalogues = []; if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) { foreach ($this->translator->getFallbackLocales() as $fallbackLocale) { if ($fallbackLocale === $locale) { continue; } $fallbackCatalogue = new MessageCatalogue($fallbackLocale); foreach ($transPaths as $path) { if (\is_dir($path)) { $this->reader->read($path, $fallbackCatalogue); } } $fallbackCatalogues[] = $fallbackCatalogue; } } return $fallbackCatalogues; } private function getRootTransPaths() : array { $transPaths = $this->transPaths; if ($this->defaultTransPath) { $transPaths[] = $this->defaultTransPath; } return $transPaths; } private function getRootCodePaths(KernelInterface $kernel) : array { $codePaths = $this->codePaths; $codePaths[] = $kernel->getProjectDir() . '/src'; if ($this->defaultViewsPath) { $codePaths[] = $this->defaultViewsPath; } return $codePaths; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand; /** * Validates YAML files syntax and outputs encountered errors. * * @author Grégoire Pineau * @author Robin Chalas * * @final */ class YamlLintCommand extends BaseLintCommand { protected static $defaultName = 'lint:yaml'; protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors'; public function __construct() { $directoryIteratorProvider = function ($directory, $default) { if (!\is_dir($directory)) { $directory = $this->getApplication()->getKernel()->locateResource($directory); } return $default($directory); }; $isReadableProvider = function ($fileOrDirectory, $default) { return \str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); }; parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } /** * {@inheritdoc} */ protected function configure() { parent::configure(); $this->setHelp($this->getHelp() . <<<'EOF' Or find all files in a bundle: php %command.full_name% @AcmeDemoBundle EOF ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * List available cache pools. * * @author Tobias Nyholm */ final class CachePoolListCommand extends Command { protected static $defaultName = 'cache:pool:list'; protected static $defaultDescription = 'List available cache pools'; private $poolNames; /** * @param string[] $poolNames */ public function __construct(array $poolNames) { parent::__construct(); $this->poolNames = $poolNames; } /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command lists all available cache pools. EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $io->table(['Pool name'], \array_map(function ($pool) { return [$pool]; }, $this->poolNames)); return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Tobias Schultze * @author Jérémy Derussé * @author Nicolas Grekas * * @internal */ final class SecretsGenerateKeysCommand extends Command { protected static $defaultName = 'secrets:generate-keys'; protected static $defaultDescription = 'Generate new encryption keys'; private $vault; private $localVault; public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; parent::__construct(); } protected function configure() { $this->setDescription(self::$defaultDescription)->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.')->setHelp(<<<'EOF' The %command.name% command generates a new encryption key. %command.full_name% If encryption keys already exist, the command must be called with the --rotate option in order to override those keys and re-encrypt existing secrets. %command.full_name% --rotate EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); $vault = $input->getOption('local') ? $this->localVault : $this->vault; if (null === $vault) { $io->success('The local vault is disabled.'); return 1; } if (!$input->getOption('rotate')) { if ($vault->generateKeys()) { $io->success($vault->getLastMessage()); if ($this->vault === $vault) { $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠️'); } return 0; } $io->warning($vault->getLastMessage()); return 1; } $secrets = []; foreach ($vault->list(\true) as $name => $value) { if (null === $value) { $io->error($vault->getLastMessage()); return 1; } $secrets[$name] = $value; } if (!$vault->generateKeys(\true)) { $io->warning($vault->getLastMessage()); return 1; } $io->success($vault->getLastMessage()); if ($secrets) { foreach ($secrets as $name => $value) { $vault->seal($name, $value); } $io->comment('Existing secrets have been rotated to the new keys.'); } if ($this->vault === $vault) { $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠️'); } return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; /** * Delete an item from a cache pool. * * @author Pierre du Plessis */ final class CachePoolDeleteCommand extends Command { protected static $defaultName = 'cache:pool:delete'; protected static $defaultDescription = 'Delete an item from a cache pool'; private $poolClearer; private $poolNames; /** * @param string[]|null $poolNames */ public function __construct(Psr6CacheClearer $poolClearer, ?array $poolNames = null) { parent::__construct(); $this->poolClearer = $poolClearer; $this->poolNames = $poolNames; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'), new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% deletes an item from a given cache pool. %command.full_name% EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $pool = $input->getArgument('pool'); $key = $input->getArgument('key'); $cachePool = $this->poolClearer->getPool($pool); if (!$cachePool->hasItem($key)) { $io->note(\sprintf('Cache item "%s" does not exist in cache pool "%s".', $key, $pool)); return 0; } if (!$cachePool->deleteItem($key)) { throw new \Exception(\sprintf('Cache item "%s" could not be deleted.', $key)); } $io->success(\sprintf('Cache item "%s" was successfully deleted.', $key)); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pool')) { $suggestions->suggestValues($this->poolNames); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Config\ConfigCache; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * @internal * * @author Robin Chalas * @author Nicolas Grekas */ trait BuildDebugContainerTrait { protected $containerBuilder; /** * Loads the ContainerBuilder from the cache. * * @throws \LogicException */ protected function getContainerBuilder(KernelInterface $kernel) : ContainerBuilder { if ($this->containerBuilder) { return $this->containerBuilder; } if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), \true))->isFresh()) { $buildContainer = \Closure::bind(function () { $this->initializeBundles(); return $this->buildContainer(); }, $kernel, \get_class($kernel)); $container = $buildContainer(); $container->getCompilerPassConfig()->setRemovingPasses([]); $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); } else { $buildContainer = \Closure::bind(function () { $containerBuilder = $this->getContainerBuilder(); $this->prepareContainer($containerBuilder); return $containerBuilder; }, $kernel, \get_class($kernel)); $container = $buildContainer(); (new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); $locatorPass = new ServiceLocatorTagPass(); $locatorPass->process($container); $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } return $this->containerBuilder = $container; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Helper\Helper; use _ContaoManager\Symfony\Component\Console\Helper\TableSeparator; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * A console command to display information about the current installation. * * @author Roland Franssen * * @final */ class AboutCommand extends Command { protected static $defaultName = 'about'; protected static $defaultDescription = 'Display information about the current project'; /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->setHelp(<<<'EOT' The %command.name% command displays information about the current Symfony project. The PHP section displays important configuration that could affect your application. The values might be different between web and CLI. EOT ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); if (\method_exists($kernel, 'getBuildDir')) { $buildDir = $kernel->getBuildDir(); } else { $buildDir = $kernel->getCacheDir(); } $rows = [['Symfony'], new TableSeparator(), ['Version', Kernel::VERSION], ['Long-Term Support', 4 === Kernel::MINOR_VERSION ? 'Yes' : 'No'], ['End of maintenance', Kernel::END_OF_MAINTENANCE . (self::isExpired(Kernel::END_OF_MAINTENANCE) ? ' Expired' : ' (' . self::daysBeforeExpiration(Kernel::END_OF_MAINTENANCE) . ')')], ['End of life', Kernel::END_OF_LIFE . (self::isExpired(Kernel::END_OF_LIFE) ? ' Expired' : ' (' . self::daysBeforeExpiration(Kernel::END_OF_LIFE) . ')')], new TableSeparator(), ['Kernel'], new TableSeparator(), ['Type', \get_class($kernel)], ['Environment', $kernel->getEnvironment()], ['Debug', $kernel->isDebug() ? 'true' : 'false'], ['Charset', $kernel->getCharset()], ['Cache directory', self::formatPath($kernel->getCacheDir(), $kernel->getProjectDir()) . ' (' . self::formatFileSize($kernel->getCacheDir()) . ')'], ['Build directory', self::formatPath($buildDir, $kernel->getProjectDir()) . ' (' . self::formatFileSize($buildDir) . ')'], ['Log directory', self::formatPath($kernel->getLogDir(), $kernel->getProjectDir()) . ' (' . self::formatFileSize($kernel->getLogDir()) . ')'], new TableSeparator(), ['PHP'], new TableSeparator(), ['Version', \PHP_VERSION], ['Architecture', \PHP_INT_SIZE * 8 . ' bits'], ['Intl locale', \class_exists(\Locale::class, \false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'], ['Timezone', \date_default_timezone_get() . ' (' . (new \DateTime())->format(\DateTime::W3C) . ')'], ['OPcache', \extension_loaded('Zend OPcache') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], ['APCu', \extension_loaded('apcu') && \filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], ['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false']]; $io->table([], $rows); return 0; } private static function formatPath(string $path, string $baseDir) : string { return \preg_replace('~^' . \preg_quote($baseDir, '~') . '~', '.', $path); } private static function formatFileSize(string $path) : string { if (\is_file($path)) { $size = \filesize($path) ?: 0; } else { if (!\is_dir($path)) { return 'n/a'; } $size = 0; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) { if ($file->isReadable()) { $size += $file->getSize(); } } } return Helper::formatMemory($size); } private static function isExpired(string $date) : bool { $date = \DateTime::createFromFormat('d/m/Y', '01/' . $date); return \false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59'); } private static function daysBeforeExpiration(string $date) : string { $date = \DateTime::createFromFormat('d/m/Y', '01/' . $date); return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\TraceableUrlMatcher; use _ContaoManager\Symfony\Component\Routing\RouterInterface; /** * A console command to test route matching. * * @author Fabien Potencier * * @final */ class RouterMatchCommand extends Command { protected static $defaultName = 'router:match'; protected static $defaultDescription = 'Help debug routes by simulating a path info match'; private $router; private $expressionLanguageProviders; /** * @param iterable $expressionLanguageProviders */ public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = []) { parent::__construct(); $this->router = $router; $this->expressionLanguageProviders = $expressionLanguageProviders; } /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'), new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Set the HTTP method'), new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'), new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% shows which routes match a given request and which don't and for what reason: php %command.full_name% /foo or php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $context = $this->router->getContext(); if (null !== ($method = $input->getOption('method'))) { $context->setMethod($method); } if (null !== ($scheme = $input->getOption('scheme'))) { $context->setScheme($scheme); } if (null !== ($host = $input->getOption('host'))) { $context->setHost($host); } $matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context); foreach ($this->expressionLanguageProviders as $provider) { $matcher->addExpressionLanguageProvider($provider); } $traces = $matcher->getTraces($input->getArgument('path_info')); $io->newLine(); $matches = \false; foreach ($traces as $trace) { if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) { $io->text(\sprintf('Route "%s" almost matches but %s', $trace['name'], \lcfirst($trace['log']))); } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) { $io->success(\sprintf('Route "%s" matches', $trace['name'])); $routerDebugCommand = $this->getApplication()->find('debug:router'); $routerDebugCommand->run(new ArrayInput(['name' => $trace['name']]), $output); $matches = \true; } elseif ($input->getOption('verbose')) { $io->text(\sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); } } if (!$matches) { $io->error(\sprintf('None of the routes match the path "%s"', $input->getArgument('path_info'))); return 1; } return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * @author Tobias Schultze * @author Jérémy Derussé * @author Nicolas Grekas * * @internal */ final class SecretsSetCommand extends Command { protected static $defaultName = 'secrets:set'; protected static $defaultDescription = 'Set a secret in the vault'; private $vault; private $localVault; public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) { $this->vault = $vault; $this->localVault = $localVault; parent::__construct(); } protected function configure() { $this->setDescription(self::$defaultDescription)->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN')->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generate a random value.', \false)->setHelp(<<<'EOF' The %command.name% command stores a secret in the vault. %command.full_name% To reference secrets in services.yaml or any other config files, use "%env()%". By default, the secret value should be entered interactively. Alternatively, provide a file where to read the secret from: php %command.full_name% filename Use "-" as a file name to read from STDIN: cat filename | php %command.full_name% - Use --local to override secrets for local needs. EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; $io = new SymfonyStyle($input, $errOutput); $name = $input->getArgument('name'); $vault = $input->getOption('local') ? $this->localVault : $this->vault; if (null === $vault) { $io->error('The local vault is disabled.'); return 1; } if ($this->localVault === $vault && !\array_key_exists($name, $this->vault->list())) { $io->error(\sprintf('Secret "%s" does not exist in the vault, you cannot override it locally.', $name)); return 1; } if (0 < ($random = $input->getOption('random') ?? 16)) { $value = \strtr(\substr(\base64_encode(\random_bytes($random)), 0, $random), '+/', '-_'); } elseif (!($file = $input->getArgument('file'))) { $value = $io->askHidden('Please type the secret value'); if (null === $value) { $io->warning('No value provided: using empty string'); $value = ''; } } elseif ('-' === $file) { $value = \file_get_contents('php://stdin'); } elseif (\is_file($file) && \is_readable($file)) { $value = \file_get_contents($file); } elseif (!\is_file($file)) { throw new \InvalidArgumentException(\sprintf('File not found: "%s".', $file)); } elseif (!\is_readable($file)) { throw new \InvalidArgumentException(\sprintf('File is not readable: "%s".', $file)); } if ($vault->generateKeys()) { $io->success($vault->getLastMessage()); if ($this->vault === $vault) { $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠️'); } } $vault->seal($name, $value); $io->success($vault->getLastMessage() ?? 'Secret was successfully stored in the vault.'); if (0 < $random) { $errOutput->write(' // The generated random value is: '); $output->write($value); $errOutput->writeln(''); $io->newLine(); } if ($this->vault === $vault && null !== $this->localVault->reveal($name)) { $io->comment('Note that this secret is overridden in the local vault.'); } return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('name')) { $suggestions->suggestValues(\array_keys($this->vault->list(\false))); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Command; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Processor; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\Yaml\Yaml; /** * A console command for dumping available configuration reference. * * @author Grégoire Pineau * * @final */ class ConfigDebugCommand extends AbstractConfigCommand { protected static $defaultName = 'debug:config'; protected static $defaultDescription = 'Dump the current configuration for an extension'; /** * {@inheritdoc} */ protected function configure() { $this->setDefinition([new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path')])->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' The %command.name% command dumps the current configuration for an extension/bundle. Either the extension alias or bundle name can be used: php %command.full_name% framework php %command.full_name% FrameworkBundle For dumping a specific option, add its path as second argument: php %command.full_name% framework serializer.enabled EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); if (null === ($name = $input->getArgument('name'))) { $this->listBundles($errorIo); $this->listNonBundleExtensions($errorIo); $errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. debug:config FrameworkBundle)'); $errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. debug:config FrameworkBundle serializer to dump the framework.serializer configuration)'); return 0; } $extension = $this->findExtension($name); $extensionAlias = $extension->getAlias(); $container = $this->compileContainer(); $config = $this->getConfig($extension, $container); if (null === ($path = $input->getArgument('path'))) { $io->title(\sprintf('Current configuration for %s', $name === $extensionAlias ? \sprintf('extension with alias "%s"', $extensionAlias) : \sprintf('"%s"', $name))); $io->writeln(Yaml::dump([$extensionAlias => $config], 10)); return 0; } try { $config = $this->getConfigForPath($config, $path, $extensionAlias); } catch (LogicException $e) { $errorIo->error($e->getMessage()); return 1; } $io->title(\sprintf('Current configuration for "%s.%s"', $extensionAlias, $path)); $io->writeln(Yaml::dump($config, 10)); return 0; } private function compileContainer() : ContainerBuilder { $kernel = clone $this->getApplication()->getKernel(); $kernel->boot(); $method = new \ReflectionMethod($kernel, 'buildContainer'); $method->setAccessible(\true); $container = $method->invoke($kernel); $container->getCompiler()->compile($container); return $container; } /** * Iterate over configuration until the last step of the given path. * * @return mixed * * @throws LogicException If the configuration does not exist */ private function getConfigForPath(array $config, string $path, string $alias) { $steps = \explode('.', $path); foreach ($steps as $step) { if (!\array_key_exists($step, $config)) { throw new LogicException(\sprintf('Unable to find configuration for "%s.%s".', $alias, $path)); } $config = $config[$step]; } return $config; } private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container) : array { $extensionAlias = $extension->getAlias(); $extensionConfig = []; foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { if ($pass instanceof ValidateEnvPlaceholdersPass) { $extensionConfig = $pass->getExtensionConfig(); break; } } if (isset($extensionConfig[$extensionAlias])) { return $extensionConfig[$extensionAlias]; } // Fall back to default config if the extension has one if (!$extension instanceof ConfigurationExtensionInterface && !$extension instanceof ConfigurationInterface) { throw new \LogicException(\sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); } $configs = $container->getExtensionConfig($extensionAlias); $configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($configs, $container); $this->validateConfiguration($extension, $configuration); return (new Processor())->processConfiguration($configuration, $configs); } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('name')) { $suggestions->suggestValues($this->getAvailableExtensions()); $suggestions->suggestValues($this->getAvailableBundles()); return; } if ($input->mustSuggestArgumentValuesFor('path') && null !== ($name = $input->getArgument('name'))) { try { $config = $this->getConfig($this->findExtension($name), $this->compileContainer()); $paths = \array_keys(self::buildPathsCompletion($config)); $suggestions->suggestValues($paths); } catch (LogicException $e) { } } } private function getAvailableExtensions() : array { $kernel = $this->getApplication()->getKernel(); $extensions = []; foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { $extensions[] = $alias; } return $extensions; } private function getAvailableBundles() : array { $availableBundles = []; foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { $availableBundles[] = $bundle->getName(); } return $availableBundles; } private function getConfig(ExtensionInterface $extension, ContainerBuilder $container) { return $container->resolveEnvPlaceholders($container->getParameterBag()->resolveValue($this->getConfigForExtension($extension, $container))); } private static function buildPathsCompletion(array $paths, string $prefix = '') : array { $completionPaths = []; foreach ($paths as $key => $values) { if (\is_array($values)) { $completionPaths = $completionPaths + self::buildPathsCompletion($values, $prefix . $key . '.'); } else { $completionPaths[$prefix . $key] = null; } } return $completionPaths; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing; use _ContaoManager\Symfony\Component\Routing\Matcher\CompiledUrlMatcher; use _ContaoManager\Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; /** * @author Fabien Potencier * * @internal */ class RedirectableCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface { /** * {@inheritdoc} */ public function redirect(string $path, string $route, ?string $scheme = null) : array { return ['_controller' => 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction', 'path' => $path, 'permanent' => \true, 'scheme' => $scheme, 'httpPort' => $this->context->getHttpPort(), 'httpsPort' => $this->context->getHttpsPort(), '_route' => $route]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing; /** * Marker interface for service route loaders. */ interface RouteLoaderInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\Config\Resource\FileExistenceResource; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\DependencyInjection\Config\ContainerParametersResource; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Routing\RouteCollection; use _ContaoManager\Symfony\Component\Routing\Router as BaseRouter; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; /** * This Router creates the Loader only when the cache is empty. * * @author Fabien Potencier */ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface { private $container; private $collectedParameters = []; private $paramFetcher; /** * @param mixed $resource The main resource to load */ public function __construct(ContainerInterface $container, $resource, array $options = [], ?RequestContext $context = null, ?ContainerInterface $parameters = null, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->container = $container; $this->resource = $resource; $this->context = $context ?? new RequestContext(); $this->logger = $logger; $this->setOptions($options); if ($parameters) { $this->paramFetcher = \Closure::fromCallable([$parameters, 'get']); } elseif ($container instanceof SymfonyContainerInterface) { $this->paramFetcher = \Closure::fromCallable([$container, 'getParameter']); } else { throw new \LogicException(\sprintf('You should either pass a "%s" instance or provide the $parameters argument of the "%s" method.', SymfonyContainerInterface::class, __METHOD__)); } $this->defaultLocale = $defaultLocale; } /** * {@inheritdoc} */ public function getRouteCollection() { if (null === $this->collection) { $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']); $this->resolveParameters($this->collection); $this->collection->addResource(new ContainerParametersResource($this->collectedParameters)); try { $containerFile = ($this->paramFetcher)('kernel.cache_dir') . '/' . ($this->paramFetcher)('kernel.container_class') . '.php'; if (\file_exists($containerFile)) { $this->collection->addResource(new FileResource($containerFile)); } else { $this->collection->addResource(new FileExistenceResource($containerFile)); } } catch (ParameterNotFoundException $exception) { } } return $this->collection; } /** * {@inheritdoc} * * @return string[] A list of classes to preload on PHP 7.4+ */ public function warmUp(string $cacheDir) { $currentDir = $this->getOption('cache_dir'); // force cache generation $this->setOption('cache_dir', $cacheDir); $this->getMatcher(); $this->getGenerator(); $this->setOption('cache_dir', $currentDir); return [$this->getOption('generator_class'), $this->getOption('matcher_class')]; } /** * Replaces placeholders with service container parameter values in: * - the route defaults, * - the route requirements, * - the route path, * - the route host, * - the route schemes, * - the route methods. */ private function resolveParameters(RouteCollection $collection) { foreach ($collection as $route) { foreach ($route->getDefaults() as $name => $value) { $route->setDefault($name, $this->resolve($value)); } foreach ($route->getRequirements() as $name => $value) { $route->setRequirement($name, $this->resolve($value)); } $route->setPath($this->resolve($route->getPath())); $route->setHost($this->resolve($route->getHost())); $schemes = []; foreach ($route->getSchemes() as $scheme) { $schemes[] = \explode('|', $this->resolve($scheme)); } $route->setSchemes(\array_merge([], ...$schemes)); $methods = []; foreach ($route->getMethods() as $method) { $methods[] = \explode('|', $this->resolve($method)); } $route->setMethods(\array_merge([], ...$methods)); $route->setCondition($this->resolve($route->getCondition())); } } /** * Recursively replaces placeholders with the service container parameters. * * @param mixed $value The source which might contain "%placeholders%" * * @return mixed The source with the placeholders replaced by the container * parameters. Arrays are resolved recursively. * * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter * @throws RuntimeException When a container value is not a string or a numeric value */ private function resolve($value) { if (\is_array($value)) { foreach ($value as $key => $val) { $value[$key] = $this->resolve($val); } return $value; } if (!\is_string($value)) { return $value; } $escapedValue = \preg_replace_callback('/%%|%([^%\\s]++)%/', function ($match) use($value) { // skip %% if (!isset($match[1])) { return '%%'; } if (\preg_match('/^env\\((?:\\w++:)*+\\w++\\)$/', $match[1])) { throw new RuntimeException(\sprintf('Using "%%%s%%" is not allowed in routing configuration.', $match[1])); } $resolved = ($this->paramFetcher)($match[1]); if (\is_scalar($resolved)) { $this->collectedParameters[$match[1]] = $resolved; if (\is_string($resolved)) { $resolved = $this->resolve($resolved); } if (\is_scalar($resolved)) { return \false === $resolved ? '0' : (string) $resolved; } } throw new RuntimeException(\sprintf('The container parameter "%s", used in the route configuration value "%s", must be a string or numeric, but it is of type "%s".', $match[1], $value, \get_debug_type($resolved))); }, $value); return \str_replace('%%', '%', $escapedValue); } /** * {@inheritdoc} */ public static function getSubscribedServices() { return ['routing.loader' => LoaderInterface::class]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing; use _ContaoManager\Symfony\Component\Config\Exception\LoaderLoadException; use _ContaoManager\Symfony\Component\Config\Loader\DelegatingLoader as BaseDelegatingLoader; use _ContaoManager\Symfony\Component\Config\Loader\LoaderResolverInterface; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * DelegatingLoader delegates route loading to other loaders using a loader resolver. * * This implementation resolves the _controller attribute from the short notation * to the fully-qualified form (from a:b:c to class::method). * * @author Fabien Potencier * * @final */ class DelegatingLoader extends BaseDelegatingLoader { private $loading = \false; private $defaultOptions; private $defaultRequirements; public function __construct(LoaderResolverInterface $resolver, array $defaultOptions = [], array $defaultRequirements = []) { $this->defaultOptions = $defaultOptions; $this->defaultRequirements = $defaultRequirements; parent::__construct($resolver); } /** * {@inheritdoc} */ public function load($resource, ?string $type = null) : RouteCollection { if ($this->loading) { // This can happen if a fatal error occurs in parent::load(). // Here is the scenario: // - while routes are being loaded by parent::load() below, a fatal error // occurs (e.g. parse error in a controller while loading annotations); // - PHP abruptly empties the stack trace, bypassing all catch/finally blocks; // it then calls the registered shutdown functions; // - the ErrorHandler catches the fatal error and re-injects it for rendering // thanks to HttpKernel->terminateWithException() (that calls handleException()); // - at this stage, if we try to load the routes again, we must prevent // the fatal error from occurring a second time, // otherwise the PHP process would be killed immediately; // - while rendering the exception page, the router can be required // (by e.g. the web profiler that needs to generate an URL); // - this handles the case and prevents the second fatal error // by triggering an exception beforehand. throw new LoaderLoadException($resource, null, 0, null, $type); } $this->loading = \true; try { $collection = parent::load($resource, $type); } finally { $this->loading = \false; } foreach ($collection->all() as $route) { if ($this->defaultOptions) { $route->setOptions($route->getOptions() + $this->defaultOptions); } if ($this->defaultRequirements) { $route->setRequirements($route->getRequirements() + $this->defaultRequirements); } if (!\is_string($controller = $route->getDefault('_controller'))) { continue; } if (\str_contains($controller, '::')) { continue; } $route->setDefault('_controller', $controller); } return $collection; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing; use _ContaoManager\Symfony\Component\Routing\Loader\AnnotationClassLoader; use _ContaoManager\Symfony\Component\Routing\Route; /** * AnnotatedRouteControllerLoader is an implementation of AnnotationClassLoader * that sets the '_controller' default based on the class and method names. * * @author Fabien Potencier */ class AnnotatedRouteControllerLoader extends AnnotationClassLoader { /** * Configures the _controller default parameter of a given Route instance. */ protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) { if ('__invoke' === $method->getName()) { $route->setDefault('_controller', $class->getName()); } else { $route->setDefault('_controller', $class->getName() . '::' . $method->getName()); } } /** * Makes the default route name more sane by removing common keywords. * * @return string */ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) { $name = \preg_replace('/(bundle|controller)_/', '_', parent::getDefaultRouteName($class, $method)); if (\str_ends_with($method->name, 'Action') || \str_ends_with($method->name, '_action')) { $name = \preg_replace('/action(_\\d+)?$/', '\\1', $name); } return \str_replace('__', '_', $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection; use _ContaoManager\Doctrine\Common\Annotations\Annotation; use _ContaoManager\Doctrine\Common\Annotations\PsrCachedReader; use _ContaoManager\Doctrine\Common\Cache\Cache; use _ContaoManager\Doctrine\DBAL\Connection; use _ContaoManager\Psr\Log\LogLevel; use _ContaoManager\Symfony\Bundle\FullStack; use _ContaoManager\Symfony\Component\Asset\Package; use _ContaoManager\Symfony\Component\Cache\Adapter\DoctrineAdapter; use _ContaoManager\Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeBuilder; use _ContaoManager\Symfony\Component\Config\Definition\Builder\TreeBuilder; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\Form\Form; use _ContaoManager\Symfony\Component\HttpClient\HttpClient; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\Lock\Lock; use _ContaoManager\Symfony\Component\Lock\Store\SemaphoreStore; use _ContaoManager\Symfony\Component\Mailer\Mailer; use _ContaoManager\Symfony\Component\Messenger\MessageBusInterface; use _ContaoManager\Symfony\Component\Notifier\Notifier; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use _ContaoManager\Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; use _ContaoManager\Symfony\Component\Serializer\Serializer; use _ContaoManager\Symfony\Component\Translation\Translator; use _ContaoManager\Symfony\Component\Uid\Factory\UuidFactory; use _ContaoManager\Symfony\Component\Validator\Validation; use _ContaoManager\Symfony\Component\WebLink\HttpHeaderSerializer; use _ContaoManager\Symfony\Component\Workflow\WorkflowEvents; /** * FrameworkExtension configuration structure. */ class Configuration implements ConfigurationInterface { private $debug; /** * @param bool $debug Whether debugging is enabled or not */ public function __construct(bool $debug) { $this->debug = $debug; } /** * Generates the configuration tree builder. * * @return TreeBuilder */ public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('framework'); $rootNode = $treeBuilder->getRootNode(); $rootNode->beforeNormalization()->ifTrue(function ($v) { return !isset($v['assets']) && isset($v['templating']) && \class_exists(Package::class); })->then(function ($v) { $v['assets'] = []; return $v; })->end()->fixXmlConfig('enabled_locale')->children()->scalarNode('secret')->end()->scalarNode('http_method_override')->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead")->defaultTrue()->end()->scalarNode('ide')->defaultNull()->end()->booleanNode('test')->end()->scalarNode('default_locale')->defaultValue('en')->end()->booleanNode('set_locale_from_accept_language')->info('Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed).')->defaultFalse()->end()->booleanNode('set_content_language_from_locale')->info('Whether to set the Content-Language HTTP header on the Response using the Request locale.')->defaultFalse()->end()->arrayNode('enabled_locales')->info('Defines the possible locales for the application. This list is used for generating translations files, but also to restrict which locales are allowed when it is set from Accept-Language header (using "set_locale_from_accept_language").')->prototype('scalar')->end()->end()->arrayNode('trusted_hosts')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->prototype('scalar')->end()->end()->scalarNode('trusted_proxies')->end()->arrayNode('trusted_headers')->fixXmlConfig('trusted_header')->performNoDeepMerging()->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto'])->beforeNormalization()->ifString()->then(function ($v) { return $v ? \array_map('trim', \explode(',', $v)) : []; })->end()->enumPrototype()->values(['forwarded', 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix'])->end()->end()->scalarNode('error_controller')->defaultValue('error_controller')->end()->end(); $willBeAvailable = static function (string $package, string $class, ?string $parentPackage = null) { $parentPackages = (array) $parentPackage; $parentPackages[] = 'symfony/framework-bundle'; return ContainerBuilder::willBeAvailable($package, $class, $parentPackages, \true); }; $enableIfStandalone = static function (string $package, string $class) use($willBeAvailable) { return !\class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; }; $this->addCsrfSection($rootNode); $this->addFormSection($rootNode, $enableIfStandalone); $this->addHttpCacheSection($rootNode); $this->addEsiSection($rootNode); $this->addSsiSection($rootNode); $this->addFragmentsSection($rootNode); $this->addProfilerSection($rootNode); $this->addWorkflowSection($rootNode); $this->addRouterSection($rootNode); $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); $this->addAssetsSection($rootNode, $enableIfStandalone); $this->addTranslatorSection($rootNode, $enableIfStandalone); $this->addValidationSection($rootNode, $enableIfStandalone, $willBeAvailable); $this->addAnnotationsSection($rootNode, $willBeAvailable); $this->addSerializerSection($rootNode, $enableIfStandalone, $willBeAvailable); $this->addPropertyAccessSection($rootNode, $willBeAvailable); $this->addPropertyInfoSection($rootNode, $enableIfStandalone); $this->addCacheSection($rootNode, $willBeAvailable); $this->addPhpErrorsSection($rootNode); $this->addExceptionsSection($rootNode); $this->addWebLinkSection($rootNode, $enableIfStandalone); $this->addLockSection($rootNode, $enableIfStandalone); $this->addMessengerSection($rootNode, $enableIfStandalone); $this->addRobotsIndexSection($rootNode); $this->addHttpClientSection($rootNode, $enableIfStandalone); $this->addMailerSection($rootNode, $enableIfStandalone); $this->addSecretsSection($rootNode); $this->addNotifierSection($rootNode, $enableIfStandalone); $this->addRateLimiterSection($rootNode, $enableIfStandalone); $this->addUidSection($rootNode, $enableIfStandalone); return $treeBuilder; } private function addSecretsSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('secrets')->canBeDisabled()->children()->scalarNode('vault_directory')->defaultValue('%kernel.project_dir%/config/secrets/%kernel.runtime_environment%')->cannotBeEmpty()->end()->scalarNode('local_dotenv_file')->defaultValue('%kernel.project_dir%/.env.%kernel.environment%.local')->end()->scalarNode('decryption_env_var')->defaultValue('base64:default::SYMFONY_DECRYPTION_SECRET')->end()->end()->end()->end(); } private function addCsrfSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('csrf_protection')->treatFalseLike(['enabled' => \false])->treatTrueLike(['enabled' => \true])->treatNullLike(['enabled' => \true])->addDefaultsIfNotSet()->children()->booleanNode('enabled')->defaultNull()->end()->end()->end()->end(); } private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('form')->info('form configuration')->{$enableIfStandalone('symfony/form', Form::class)}()->children()->arrayNode('csrf_protection')->treatFalseLike(['enabled' => \false])->treatTrueLike(['enabled' => \true])->treatNullLike(['enabled' => \true])->addDefaultsIfNotSet()->children()->booleanNode('enabled')->defaultNull()->end()->scalarNode('field_name')->defaultValue('_token')->end()->end()->end()->booleanNode('legacy_error_messages')->defaultTrue()->validate()->ifTrue()->then(function ($v) { \trigger_deprecation('symfony/framework-bundle', '5.2', 'Setting the "framework.form.legacy_error_messages" option to "true" is deprecated. It will have no effect as of Symfony 6.0.'); return $v; })->end()->end()->end()->end()->end(); } private function addHttpCacheSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('http_cache')->info('HTTP cache configuration')->canBeEnabled()->fixXmlConfig('private_header')->children()->booleanNode('debug')->defaultValue('%kernel.debug%')->end()->enumNode('trace_level')->values(['none', 'short', 'full'])->end()->scalarNode('trace_header')->end()->integerNode('default_ttl')->end()->arrayNode('private_headers')->performNoDeepMerging()->scalarPrototype()->end()->end()->booleanNode('allow_reload')->end()->booleanNode('allow_revalidate')->end()->integerNode('stale_while_revalidate')->end()->integerNode('stale_if_error')->end()->end()->end()->end(); } private function addEsiSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('esi')->info('esi configuration')->canBeEnabled()->end()->end(); } private function addSsiSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('ssi')->info('ssi configuration')->canBeEnabled()->end()->end(); } private function addFragmentsSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('fragments')->info('fragments configuration')->canBeEnabled()->children()->scalarNode('hinclude_default_template')->defaultNull()->end()->scalarNode('path')->defaultValue('/_fragment')->end()->end()->end()->end(); } private function addProfilerSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('profiler')->info('profiler configuration')->canBeEnabled()->children()->booleanNode('collect')->defaultTrue()->end()->scalarNode('collect_parameter')->defaultNull()->info('The name of the parameter to use to enable or disable collection on a per request basis')->end()->booleanNode('only_exceptions')->defaultFalse()->end()->booleanNode('only_main_requests')->defaultFalse()->end()->booleanNode('only_master_requests')->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, use "only_main_requests" instead.')->defaultFalse()->end()->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end()->end()->end()->end(); } private function addWorkflowSection(ArrayNodeDefinition $rootNode) { $rootNode->fixXmlConfig('workflow')->children()->arrayNode('workflows')->canBeEnabled()->beforeNormalization()->always(function ($v) { if (\is_array($v) && \true === $v['enabled']) { $workflows = $v; unset($workflows['enabled']); if (1 === \count($workflows) && isset($workflows[0]['enabled']) && 1 === \count($workflows[0])) { $workflows = []; } if (1 === \count($workflows) && isset($workflows['workflows']) && !\array_is_list($workflows['workflows']) && !empty(\array_diff(\array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions']))) { $workflows = $workflows['workflows']; } foreach ($workflows as $key => $workflow) { if (isset($workflow['enabled']) && \false === $workflow['enabled']) { throw new LogicException(\sprintf('Cannot disable a single workflow. Remove the configuration for the workflow "%s" instead.', $key)); } unset($workflows[$key]['enabled']); } $v = ['enabled' => \true, 'workflows' => $workflows]; } return $v; })->end()->children()->arrayNode('workflows')->useAttributeAsKey('name')->prototype('array')->fixXmlConfig('support')->fixXmlConfig('place')->fixXmlConfig('transition')->fixXmlConfig('event_to_dispatch', 'events_to_dispatch')->children()->arrayNode('audit_trail')->canBeEnabled()->end()->enumNode('type')->values(['workflow', 'state_machine'])->defaultValue('state_machine')->end()->arrayNode('marking_store')->children()->enumNode('type')->values(['method'])->end()->scalarNode('property')->defaultValue('marking')->end()->scalarNode('service')->cannotBeEmpty()->end()->end()->end()->arrayNode('supports')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->prototype('scalar')->cannotBeEmpty()->validate()->ifTrue(function ($v) { return !\class_exists($v) && !\interface_exists($v, \false); })->thenInvalid('The supported class or interface "%s" does not exist.')->end()->end()->end()->scalarNode('support_strategy')->cannotBeEmpty()->end()->arrayNode('initial_marking')->beforeNormalization()->castToArray()->end()->defaultValue([])->prototype('scalar')->end()->end()->variableNode('events_to_dispatch')->defaultValue(null)->validate()->ifTrue(function ($v) { if (null === $v) { return \false; } if (!\is_array($v)) { return \true; } foreach ($v as $value) { if (!\is_string($value)) { return \true; } if (\class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES)) { return \true; } } return \false; })->thenInvalid('The value must be "null" or an array of workflow events (like ["workflow.enter"]).')->end()->info('Select which Transition events should be dispatched for this Workflow')->example(['workflow.enter', 'workflow.transition'])->end()->arrayNode('places')->beforeNormalization()->always()->then(function ($places) { if (!\is_array($places)) { throw new InvalidConfigurationException('The "places" option must be an array in workflow configuration.'); } // It's an indexed array of shape ['place1', 'place2'] if (isset($places[0]) && \is_string($places[0])) { return \array_map(function (string $place) { return ['name' => $place]; }, $places); } // It's an indexed array, we let the validation occur if (isset($places[0]) && \is_array($places[0])) { return $places; } foreach ($places as $name => $place) { if (\is_array($place) && \array_key_exists('name', $place)) { continue; } $place['name'] = $name; $places[$name] = $place; } return \array_values($places); })->end()->isRequired()->requiresAtLeastOneElement()->prototype('array')->children()->scalarNode('name')->isRequired()->cannotBeEmpty()->end()->arrayNode('metadata')->normalizeKeys(\false)->defaultValue([])->example(['color' => 'blue', 'description' => 'Workflow to manage article.'])->prototype('variable')->end()->end()->end()->end()->end()->arrayNode('transitions')->beforeNormalization()->always()->then(function ($transitions) { if (!\is_array($transitions)) { throw new InvalidConfigurationException('The "transitions" option must be an array in workflow configuration.'); } // It's an indexed array, we let the validation occur if (isset($transitions[0]) && \is_array($transitions[0])) { return $transitions; } foreach ($transitions as $name => $transition) { if (\is_array($transition) && \array_key_exists('name', $transition)) { continue; } $transition['name'] = $name; $transitions[$name] = $transition; } return $transitions; })->end()->isRequired()->requiresAtLeastOneElement()->prototype('array')->children()->scalarNode('name')->isRequired()->cannotBeEmpty()->end()->scalarNode('guard')->cannotBeEmpty()->info('An expression to block the transition')->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'')->end()->arrayNode('from')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->requiresAtLeastOneElement()->prototype('scalar')->cannotBeEmpty()->end()->end()->arrayNode('to')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->requiresAtLeastOneElement()->prototype('scalar')->cannotBeEmpty()->end()->end()->arrayNode('metadata')->normalizeKeys(\false)->defaultValue([])->example(['color' => 'blue', 'description' => 'Workflow to manage article.'])->prototype('variable')->end()->end()->end()->end()->end()->arrayNode('metadata')->normalizeKeys(\false)->defaultValue([])->example(['color' => 'blue', 'description' => 'Workflow to manage article.'])->prototype('variable')->end()->end()->end()->validate()->ifTrue(function ($v) { return $v['supports'] && isset($v['support_strategy']); })->thenInvalid('"supports" and "support_strategy" cannot be used together.')->end()->validate()->ifTrue(function ($v) { return !$v['supports'] && !isset($v['support_strategy']); })->thenInvalid('"supports" or "support_strategy" should be configured.')->end()->beforeNormalization()->always()->then(function ($values) { // Special case to deal with XML when the user wants an empty array if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) { $values['events_to_dispatch'] = []; unset($values['event_to_dispatch']); } return $values; })->end()->end()->end()->end()->end()->end(); } private function addRouterSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('router')->info('router configuration')->canBeEnabled()->children()->scalarNode('resource')->isRequired()->end()->scalarNode('type')->end()->scalarNode('default_uri')->info('The default URI used to generate URLs in a non-HTTP context')->defaultNull()->end()->scalarNode('http_port')->defaultValue(80)->end()->scalarNode('https_port')->defaultValue(443)->end()->scalarNode('strict_requirements')->info("set to true to throw an exception when a parameter does not match the requirements\n" . "set to false to disable exceptions when a parameter does not match the requirements (and return null instead)\n" . "set to null to disable parameter checks against requirements\n" . "'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production")->defaultTrue()->end()->booleanNode('utf8')->defaultNull()->end()->end()->end()->end(); } private function addSessionSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('session')->info('session configuration')->canBeEnabled()->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && isset($v['storage_id']) && isset($v['storage_factory_id']); })->thenInvalid('You cannot use both "storage_id" and "storage_factory_id" at the same time under "framework.session"')->end()->children()->scalarNode('storage_id')->defaultValue('session.storage.native')->end()->scalarNode('storage_factory_id')->defaultNull()->end()->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()->scalarNode('name')->validate()->ifTrue(function ($v) { \parse_str($v, $parsed); return \implode('&', \array_keys($parsed)) !== (string) $v; })->thenInvalid('Session name %s contains illegal character(s)')->end()->end()->scalarNode('cookie_lifetime')->end()->scalarNode('cookie_path')->end()->scalarNode('cookie_domain')->end()->enumNode('cookie_secure')->values([\true, \false, 'auto'])->end()->booleanNode('cookie_httponly')->defaultTrue()->end()->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultNull()->end()->booleanNode('use_cookies')->end()->scalarNode('gc_divisor')->end()->scalarNode('gc_probability')->defaultValue(1)->end()->scalarNode('gc_maxlifetime')->end()->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()->integerNode('metadata_update_threshold')->defaultValue(0)->info('seconds to wait between 2 session metadata updates')->end()->integerNode('sid_length')->min(22)->max(256)->end()->integerNode('sid_bits_per_character')->min(4)->max(6)->end()->end()->end()->end(); } private function addRequestSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('request')->info('request configuration')->canBeEnabled()->fixXmlConfig('format')->children()->arrayNode('formats')->useAttributeAsKey('name')->prototype('array')->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); })->then(function ($v) { return $v['mime_type']; })->end()->beforeNormalization()->castToArray()->end()->prototype('scalar')->end()->end()->end()->end()->end()->end(); } private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('assets')->info('assets configuration')->{$enableIfStandalone('symfony/asset', Package::class)}()->fixXmlConfig('base_url')->children()->booleanNode('strict_mode')->info('Throw an exception if an entry is missing from the manifest.json')->defaultFalse()->end()->scalarNode('version_strategy')->defaultNull()->end()->scalarNode('version')->defaultNull()->end()->scalarNode('version_format')->defaultValue('%%s?%%s')->end()->scalarNode('json_manifest_path')->defaultNull()->end()->scalarNode('base_path')->defaultValue('')->end()->arrayNode('base_urls')->requiresAtLeastOneElement()->beforeNormalization()->castToArray()->end()->prototype('scalar')->end()->end()->end()->validate()->ifTrue(function ($v) { return isset($v['version_strategy']) && isset($v['version']); })->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets".')->end()->validate()->ifTrue(function ($v) { return isset($v['version_strategy']) && isset($v['json_manifest_path']); })->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets".')->end()->validate()->ifTrue(function ($v) { return isset($v['version']) && isset($v['json_manifest_path']); })->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets".')->end()->fixXmlConfig('package')->children()->arrayNode('packages')->normalizeKeys(\false)->useAttributeAsKey('name')->prototype('array')->fixXmlConfig('base_url')->children()->booleanNode('strict_mode')->info('Throw an exception if an entry is missing from the manifest.json')->defaultFalse()->end()->scalarNode('version_strategy')->defaultNull()->end()->scalarNode('version')->beforeNormalization()->ifTrue(function ($v) { return '' === $v; })->then(function ($v) { return; })->end()->end()->scalarNode('version_format')->defaultNull()->end()->scalarNode('json_manifest_path')->defaultNull()->end()->scalarNode('base_path')->defaultValue('')->end()->arrayNode('base_urls')->requiresAtLeastOneElement()->beforeNormalization()->castToArray()->end()->prototype('scalar')->end()->end()->end()->validate()->ifTrue(function ($v) { return isset($v['version_strategy']) && isset($v['version']); })->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets" packages.')->end()->validate()->ifTrue(function ($v) { return isset($v['version_strategy']) && isset($v['json_manifest_path']); })->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets" packages.')->end()->validate()->ifTrue(function ($v) { return isset($v['version']) && isset($v['json_manifest_path']); })->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets" packages.')->end()->end()->end()->end()->end()->end(); } private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('translator')->info('translator configuration')->{$enableIfStandalone('symfony/translation', Translator::class)}()->fixXmlConfig('fallback')->fixXmlConfig('path')->fixXmlConfig('enabled_locale')->fixXmlConfig('provider')->children()->arrayNode('fallbacks')->info('Defaults to the value of "default_locale".')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->prototype('scalar')->end()->defaultValue([])->end()->booleanNode('logging')->defaultValue(\false)->end()->scalarNode('formatter')->defaultValue('translator.formatter.default')->end()->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%/translations')->end()->scalarNode('default_path')->info('The default path used to load translations')->defaultValue('%kernel.project_dir%/translations')->end()->arrayNode('paths')->prototype('scalar')->end()->end()->arrayNode('enabled_locales')->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, set the "framework.enabled_locales" option instead.')->prototype('scalar')->end()->defaultValue([])->end()->arrayNode('pseudo_localization')->canBeEnabled()->fixXmlConfig('localizable_html_attribute')->children()->booleanNode('accents')->defaultTrue()->end()->floatNode('expansion_factor')->min(1.0)->defaultValue(1.0)->end()->booleanNode('brackets')->defaultTrue()->end()->booleanNode('parse_html')->defaultFalse()->end()->arrayNode('localizable_html_attributes')->prototype('scalar')->end()->end()->end()->end()->arrayNode('providers')->info('Translation providers you can read/write your translations from')->useAttributeAsKey('name')->prototype('array')->fixXmlConfig('domain')->fixXmlConfig('locale')->children()->scalarNode('dsn')->end()->arrayNode('domains')->prototype('scalar')->end()->defaultValue([])->end()->arrayNode('locales')->prototype('scalar')->end()->defaultValue([])->info('If not set, all locales listed under framework.enabled_locales are used.')->end()->end()->end()->defaultValue([])->end()->end()->end()->end(); } private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) { $rootNode->children()->arrayNode('validation')->info('validation configuration')->{$enableIfStandalone('symfony/validator', Validation::class)}()->children()->scalarNode('cache')->end()->booleanNode('enable_annotations')->{!\class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/validator')) ? 'defaultTrue' : 'defaultFalse'}()->end()->arrayNode('static_method')->defaultValue(['loadValidatorMetadata'])->prototype('scalar')->end()->treatFalseLike([])->validate()->castToArray()->end()->end()->scalarNode('translation_domain')->defaultValue('validators')->end()->enumNode('email_validation_mode')->values(['html5', 'loose', 'strict'])->end()->arrayNode('mapping')->addDefaultsIfNotSet()->fixXmlConfig('path')->children()->arrayNode('paths')->prototype('scalar')->end()->end()->end()->end()->arrayNode('not_compromised_password')->canBeDisabled()->children()->booleanNode('enabled')->defaultTrue()->info('When disabled, compromised passwords will be accepted as valid.')->end()->scalarNode('endpoint')->defaultNull()->info('API endpoint for the NotCompromisedPassword Validator.')->end()->end()->end()->arrayNode('auto_mapping')->info('A collection of namespaces for which auto-mapping will be enabled by default, or null to opt-in with the EnableAutoMapping constraint.')->example(['App\\Entity\\' => [], 'App\\WithSpecificLoaders\\' => ['validator.property_info_loader']])->useAttributeAsKey('namespace')->normalizeKeys(\false)->beforeNormalization()->ifArray()->then(function (array $values) : array { foreach ($values as $k => $v) { if (isset($v['service'])) { continue; } if (isset($v['namespace'])) { $values[$k]['services'] = []; continue; } if (!\is_array($v)) { $values[$v]['services'] = []; unset($values[$k]); continue; } $tmp = $v; unset($values[$k]); $values[$k]['services'] = $tmp; } return $values; })->end()->arrayPrototype()->fixXmlConfig('service')->children()->arrayNode('services')->prototype('scalar')->end()->end()->end()->end()->end()->end()->end()->end(); } private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $doctrineCache = $willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotations'); $psr6Cache = $willBeAvailable('symfony/cache', PsrCachedReader::class, 'doctrine/annotations'); $rootNode->children()->arrayNode('annotations')->info('annotation configuration')->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}()->children()->scalarNode('cache')->defaultValue($doctrineCache || $psr6Cache ? 'php_array' : 'none')->end()->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end()->booleanNode('debug')->defaultValue($this->debug)->end()->end()->end()->end(); } private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) { $rootNode->children()->arrayNode('serializer')->info('serializer configuration')->{$enableIfStandalone('symfony/serializer', Serializer::class)}()->children()->booleanNode('enable_annotations')->{!\class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/serializer')) ? 'defaultTrue' : 'defaultFalse'}()->end()->scalarNode('name_converter')->end()->scalarNode('circular_reference_handler')->end()->scalarNode('max_depth_handler')->end()->arrayNode('mapping')->addDefaultsIfNotSet()->fixXmlConfig('path')->children()->arrayNode('paths')->prototype('scalar')->end()->end()->end()->end()->arrayNode('default_context')->normalizeKeys(\false)->useAttributeAsKey('name')->defaultValue([])->prototype('variable')->end()->end()->end()->end()->end(); } private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode->children()->arrayNode('property_access')->addDefaultsIfNotSet()->info('Property access configuration')->{$willBeAvailable('symfony/property-access', PropertyAccessor::class) ? 'canBeDisabled' : 'canBeEnabled'}()->children()->booleanNode('magic_call')->defaultFalse()->end()->booleanNode('magic_get')->defaultTrue()->end()->booleanNode('magic_set')->defaultTrue()->end()->booleanNode('throw_exception_on_invalid_index')->defaultFalse()->end()->booleanNode('throw_exception_on_invalid_property_path')->defaultTrue()->end()->end()->end()->end(); } private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('property_info')->info('Property info configuration')->{$enableIfStandalone('symfony/property-info', PropertyInfoExtractorInterface::class)}()->end()->end(); } private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode->children()->arrayNode('cache')->info('Cache configuration')->addDefaultsIfNotSet()->fixXmlConfig('pool')->children()->scalarNode('prefix_seed')->info('Used to namespace cache keys when using several apps with the same shared backend')->defaultValue('_%kernel.project_dir%.%kernel.container_class%')->example('my-application-name/%kernel.environment%')->end()->scalarNode('app')->info('App related cache pools configuration')->defaultValue('cache.adapter.filesystem')->end()->scalarNode('system')->info('System related cache pools configuration')->defaultValue('cache.adapter.system')->end()->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools/app')->end()->scalarNode('default_doctrine_provider')->end()->scalarNode('default_psr6_provider')->end()->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end()->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end()->scalarNode('default_doctrine_dbal_provider')->defaultValue('database_connection')->end()->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) && \class_exists(DoctrineAdapter::class) ? 'database_connection' : null)->end()->arrayNode('pools')->useAttributeAsKey('name')->prototype('array')->fixXmlConfig('adapter')->beforeNormalization()->ifTrue(function ($v) { return isset($v['provider']) && \is_array($v['adapters'] ?? $v['adapter'] ?? null) && 1 < \count($v['adapters'] ?? $v['adapter']); })->thenInvalid('Pool cannot have a "provider" while more than one adapter is defined')->end()->children()->arrayNode('adapters')->performNoDeepMerging()->info('One or more adapters to chain for creating the pool, defaults to "cache.app".')->beforeNormalization()->castToArray()->end()->beforeNormalization()->always()->then(function ($values) { if ([0] === \array_keys($values) && \is_array($values[0])) { return $values[0]; } $adapters = []; foreach ($values as $k => $v) { if (\is_int($k) && \is_string($v)) { $adapters[] = $v; } elseif (!\is_array($v)) { $adapters[$k] = $v; } elseif (isset($v['provider'])) { $adapters[$v['provider']] = $v['name'] ?? $v; } else { $adapters[] = $v['name'] ?? $v; } } return $adapters; })->end()->prototype('scalar')->end()->end()->scalarNode('tags')->defaultNull()->end()->booleanNode('public')->defaultFalse()->end()->scalarNode('default_lifetime')->info('Default lifetime of the pool')->example('"300" for 5 minutes expressed in seconds, "PT5M" for five minutes expressed as ISO 8601 time interval, or "5 minutes" as a date expression')->end()->scalarNode('provider')->info('Overwrite the setting from the default provider for this adapter.')->end()->scalarNode('early_expiration_message_bus')->example('"messenger.default_bus" to send early expiration events to the default Messenger bus.')->end()->scalarNode('clearer')->end()->end()->end()->validate()->ifTrue(function ($v) { return isset($v['cache.app']) || isset($v['cache.system']); })->thenInvalid('"cache.app" and "cache.system" are reserved names')->end()->end()->end()->end()->end(); } private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->arrayNode('php_errors')->info('PHP errors handling configuration')->addDefaultsIfNotSet()->children()->variableNode('log')->info('Use the application logger instead of the PHP logger for logging PHP errors.')->example('"true" to use the default configuration: log all errors. "false" to disable. An integer bit field of E_* constants, or an array mapping E_* constants to log levels.')->defaultValue($this->debug)->treatNullLike($this->debug)->beforeNormalization()->ifArray()->then(function (array $v) : array { if (!($v[0]['type'] ?? \false)) { return $v; } // Fix XML normalization $ret = []; foreach ($v as ['type' => $type, 'logLevel' => $logLevel]) { $ret[$type] = $logLevel; } return $ret; })->end()->validate()->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v) || \is_array($v)); })->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array')->end()->end()->booleanNode('throw')->info('Throw PHP errors as \\ErrorException instances.')->defaultValue($this->debug)->treatNullLike($this->debug)->end()->end()->end()->end(); } private function addExceptionsSection(ArrayNodeDefinition $rootNode) { $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); $rootNode->fixXmlConfig('exception')->children()->arrayNode('exceptions')->info('Exception handling configuration')->useAttributeAsKey('class')->beforeNormalization()->ifArray()->then(function (array $v) : array { if (!\array_key_exists('exception', $v)) { return $v; } $v = $v['exception']; unset($v['exception']); foreach ($v as &$exception) { $exception['class'] = $exception['name']; unset($exception['name']); } return $v; })->end()->prototype('array')->children()->scalarNode('log_level')->info('The level of log message. Null to let Symfony decide.')->validate()->ifTrue(function ($v) use($logLevels) { return null !== $v && !\in_array($v, $logLevels, \true); })->thenInvalid(\sprintf('The log level is not valid. Pick one among "%s".', \implode('", "', $logLevels)))->end()->defaultNull()->end()->scalarNode('status_code')->info('The status code of the response. Null or 0 to let Symfony decide.')->beforeNormalization()->ifTrue(function ($v) { return 0 === $v; })->then(function ($v) { return null; })->end()->validate()->ifTrue(function ($v) { return null !== $v && ($v < 100 || $v > 599); })->thenInvalid('The status code is not valid. Pick a value between 100 and 599.')->end()->defaultNull()->end()->end()->end()->end()->end(); } private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('lock')->info('Lock configuration')->{$enableIfStandalone('symfony/lock', Lock::class)}()->beforeNormalization()->ifString()->then(function ($v) { return ['enabled' => \true, 'resources' => $v]; })->end()->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); })->then(function ($v) { return $v + ['enabled' => \true]; })->end()->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); })->then(function ($v) { $e = $v['enabled']; unset($v['enabled']); return ['enabled' => $e, 'resources' => $v]; })->end()->addDefaultsIfNotSet()->validate()->ifTrue(static function (array $config) { return $config['enabled'] && !$config['resources']; })->thenInvalid('At least one resource must be defined.')->end()->fixXmlConfig('resource')->children()->arrayNode('resources')->normalizeKeys(\false)->useAttributeAsKey('name')->defaultValue(['default' => [\class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']])->beforeNormalization()->ifString()->then(function ($v) { return ['default' => $v]; })->end()->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && \array_is_list($v); })->then(function ($v) { $resources = []; foreach ($v as $resource) { $resources[] = \is_array($resource) && isset($resource['name']) ? [$resource['name'] => $resource['value']] : ['default' => $resource]; } return \array_merge_recursive([], ...$resources); })->end()->prototype('array')->performNoDeepMerging()->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->prototype('scalar')->end()->end()->end()->end()->end()->end(); } private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('web_link')->info('web links configuration')->{$enableIfStandalone('symfony/weblink', HttpHeaderSerializer::class)}()->end()->end(); } private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('messenger')->info('Messenger configuration')->{$enableIfStandalone('symfony/messenger', MessageBusInterface::class)}()->fixXmlConfig('transport')->fixXmlConfig('bus', 'buses')->validate()->ifTrue(function ($v) { return isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']; })->thenInvalid('You must specify the "default_bus" if you define more than one bus.')->end()->validate()->ifTrue(static function ($v) : bool { return isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']]); })->then(static function (array $v) : void { throw new InvalidConfigurationException(\sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], \implode('", "', \array_keys($v['buses'])))); })->end()->children()->arrayNode('routing')->normalizeKeys(\false)->useAttributeAsKey('message_class')->beforeNormalization()->always()->then(function ($config) { if (!\is_array($config)) { return []; } // If XML config with only one routing attribute if (2 === \count($config) && isset($config['message-class']) && isset($config['sender'])) { $config = [0 => $config]; } $newConfig = []; foreach ($config as $k => $v) { if (!\is_int($k)) { $newConfig[$k] = ['senders' => $v['senders'] ?? (\is_array($v) ? \array_values($v) : [$v])]; } else { $newConfig[$v['message-class']]['senders'] = \array_map(function ($a) { return \is_string($a) ? $a : $a['service']; }, \array_values($v['sender'])); } } return $newConfig; })->end()->prototype('array')->performNoDeepMerging()->children()->arrayNode('senders')->requiresAtLeastOneElement()->prototype('scalar')->end()->end()->end()->end()->end()->arrayNode('serializer')->addDefaultsIfNotSet()->children()->scalarNode('default_serializer')->defaultValue('messenger.transport.native_php_serializer')->info('Service id to use as the default serializer for the transports.')->end()->arrayNode('symfony_serializer')->addDefaultsIfNotSet()->children()->scalarNode('format')->defaultValue('json')->info('Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default).')->end()->arrayNode('context')->normalizeKeys(\false)->useAttributeAsKey('name')->defaultValue([])->info('Context array for the messenger.transport.symfony_serializer service (which is not the serializer used by default).')->prototype('variable')->end()->end()->end()->end()->end()->end()->arrayNode('transports')->normalizeKeys(\false)->useAttributeAsKey('name')->arrayPrototype()->beforeNormalization()->ifString()->then(function (string $dsn) { return ['dsn' => $dsn]; })->end()->fixXmlConfig('option')->children()->scalarNode('dsn')->end()->scalarNode('serializer')->defaultNull()->info('Service id of a custom serializer to use.')->end()->arrayNode('options')->normalizeKeys(\false)->defaultValue([])->prototype('variable')->end()->end()->scalarNode('failure_transport')->defaultNull()->info('Transport name to send failed messages to (after all retries have failed).')->end()->arrayNode('retry_strategy')->addDefaultsIfNotSet()->beforeNormalization()->always(function ($v) { if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) { throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.'); } return $v; })->end()->children()->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end()->integerNode('max_retries')->defaultValue(3)->min(0)->end()->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end()->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()->end()->end()->end()->end()->end()->scalarNode('failure_transport')->defaultNull()->info('Transport name to send failed messages to (after all retries have failed).')->end()->booleanNode('reset_on_message')->defaultNull()->info('Reset container services after each message.')->end()->scalarNode('default_bus')->defaultNull()->end()->arrayNode('buses')->defaultValue(['messenger.bus.default' => ['default_middleware' => \true, 'middleware' => []]])->normalizeKeys(\false)->useAttributeAsKey('name')->arrayPrototype()->addDefaultsIfNotSet()->children()->enumNode('default_middleware')->values([\true, \false, 'allow_no_handlers'])->defaultTrue()->end()->arrayNode('middleware')->performNoDeepMerging()->beforeNormalization()->ifTrue(function ($v) { return \is_string($v) || \is_array($v) && !\is_int(\key($v)); })->then(function ($v) { return [$v]; })->end()->defaultValue([])->arrayPrototype()->beforeNormalization()->always()->then(function ($middleware) : array { if (!\is_array($middleware)) { return ['id' => $middleware]; } if (isset($middleware['id'])) { return $middleware; } if (1 < \count($middleware)) { throw new \InvalidArgumentException('Invalid middleware at path "framework.messenger": a map with a single factory id as key and its arguments as value was expected, ' . \json_encode($middleware) . ' given.'); } return ['id' => \key($middleware), 'arguments' => \current($middleware)]; })->end()->fixXmlConfig('argument')->children()->scalarNode('id')->isRequired()->cannotBeEmpty()->end()->arrayNode('arguments')->normalizeKeys(\false)->defaultValue([])->prototype('variable')->end()->end()->end()->end()->end()->end()->end()->end()->end()->end(); } private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) { $rootNode->children()->booleanNode('disallow_search_engine_index')->info('Enabled by default when debug is enabled.')->defaultValue($this->debug)->treatNullLike($this->debug)->end()->end(); } private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('http_client')->info('HTTP Client configuration')->{$enableIfStandalone('symfony/http-client', HttpClient::class)}()->fixXmlConfig('scoped_client')->beforeNormalization()->always(function ($config) { if (empty($config['scoped_clients']) || !\is_array($config['default_options']['retry_failed'] ?? null)) { return $config; } foreach ($config['scoped_clients'] as &$scopedConfig) { if (!isset($scopedConfig['retry_failed']) || \true === $scopedConfig['retry_failed']) { $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; continue; } if (\is_array($scopedConfig['retry_failed'])) { $scopedConfig['retry_failed'] = $scopedConfig['retry_failed'] + $config['default_options']['retry_failed']; } } return $config; })->end()->children()->integerNode('max_host_connections')->info('The maximum number of connections to a single host.')->end()->arrayNode('default_options')->fixXmlConfig('header')->children()->arrayNode('headers')->info('Associative array: header => value(s).')->useAttributeAsKey('name')->normalizeKeys(\false)->variablePrototype()->end()->end()->integerNode('max_redirects')->info('The maximum number of redirects to follow.')->end()->scalarNode('http_version')->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.')->end()->arrayNode('resolve')->info('Associative array: domain => IP.')->useAttributeAsKey('host')->beforeNormalization()->always(function ($config) { if (!\is_array($config)) { return []; } if (!isset($config['host'], $config['value']) || \count($config) > 2) { return $config; } return [$config['host'] => $config['value']]; })->end()->normalizeKeys(\false)->scalarPrototype()->end()->end()->scalarNode('proxy')->info('The URL of the proxy to pass requests through or null for automatic detection.')->end()->scalarNode('no_proxy')->info('A comma separated list of hosts that do not require a proxy to be reached.')->end()->floatNode('timeout')->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.')->end()->floatNode('max_duration')->info('The maximum execution time for the request+response as a whole.')->end()->scalarNode('bindto')->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')->end()->booleanNode('verify_peer')->info('Indicates if the peer should be verified in an SSL/TLS context.')->end()->booleanNode('verify_host')->info('Indicates if the host should exist as a certificate common name.')->end()->scalarNode('cafile')->info('A certificate authority file.')->end()->scalarNode('capath')->info('A directory that contains multiple certificate authority files.')->end()->scalarNode('local_cert')->info('A PEM formatted certificate file.')->end()->scalarNode('local_pk')->info('A private key file.')->end()->scalarNode('passphrase')->info('The passphrase used to encrypt the "local_pk" file.')->end()->scalarNode('ciphers')->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')->end()->arrayNode('peer_fingerprint')->info('Associative array: hashing algorithm => hash(es).')->normalizeKeys(\false)->children()->variableNode('sha1')->end()->variableNode('pin-sha256')->end()->variableNode('md5')->end()->end()->end()->append($this->addHttpClientRetrySection())->end()->end()->scalarNode('mock_response_factory')->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable.')->end()->arrayNode('scoped_clients')->useAttributeAsKey('name')->normalizeKeys(\false)->arrayPrototype()->fixXmlConfig('header')->beforeNormalization()->always()->then(function ($config) { if (!\class_exists(HttpClient::class)) { throw new LogicException('HttpClient support cannot be enabled as the component is not installed. Try running "composer require symfony/http-client".'); } return \is_array($config) ? $config : ['base_uri' => $config]; })->end()->validate()->ifTrue(function ($v) { return !isset($v['scope']) && !isset($v['base_uri']); })->thenInvalid('Either "scope" or "base_uri" should be defined.')->end()->validate()->ifTrue(function ($v) { return !empty($v['query']) && !isset($v['base_uri']); })->thenInvalid('"query" applies to "base_uri" but no base URI is defined.')->end()->children()->scalarNode('scope')->info('The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.')->cannotBeEmpty()->end()->scalarNode('base_uri')->info('The URI to resolve relative URLs, following rules in RFC 3985, section 2.')->cannotBeEmpty()->end()->scalarNode('auth_basic')->info('An HTTP Basic authentication "username:password".')->end()->scalarNode('auth_bearer')->info('A token enabling HTTP Bearer authorization.')->end()->scalarNode('auth_ntlm')->info('A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).')->end()->arrayNode('query')->info('Associative array of query string values merged with the base URI.')->useAttributeAsKey('key')->beforeNormalization()->always(function ($config) { if (!\is_array($config)) { return []; } if (!isset($config['key'], $config['value']) || \count($config) > 2) { return $config; } return [$config['key'] => $config['value']]; })->end()->normalizeKeys(\false)->scalarPrototype()->end()->end()->arrayNode('headers')->info('Associative array: header => value(s).')->useAttributeAsKey('name')->normalizeKeys(\false)->variablePrototype()->end()->end()->integerNode('max_redirects')->info('The maximum number of redirects to follow.')->end()->scalarNode('http_version')->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.')->end()->arrayNode('resolve')->info('Associative array: domain => IP.')->useAttributeAsKey('host')->beforeNormalization()->always(function ($config) { if (!\is_array($config)) { return []; } if (!isset($config['host'], $config['value']) || \count($config) > 2) { return $config; } return [$config['host'] => $config['value']]; })->end()->normalizeKeys(\false)->scalarPrototype()->end()->end()->scalarNode('proxy')->info('The URL of the proxy to pass requests through or null for automatic detection.')->end()->scalarNode('no_proxy')->info('A comma separated list of hosts that do not require a proxy to be reached.')->end()->floatNode('timeout')->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.')->end()->floatNode('max_duration')->info('The maximum execution time for the request+response as a whole.')->end()->scalarNode('bindto')->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')->end()->booleanNode('verify_peer')->info('Indicates if the peer should be verified in an SSL/TLS context.')->end()->booleanNode('verify_host')->info('Indicates if the host should exist as a certificate common name.')->end()->scalarNode('cafile')->info('A certificate authority file.')->end()->scalarNode('capath')->info('A directory that contains multiple certificate authority files.')->end()->scalarNode('local_cert')->info('A PEM formatted certificate file.')->end()->scalarNode('local_pk')->info('A private key file.')->end()->scalarNode('passphrase')->info('The passphrase used to encrypt the "local_pk" file.')->end()->scalarNode('ciphers')->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')->end()->arrayNode('peer_fingerprint')->info('Associative array: hashing algorithm => hash(es).')->normalizeKeys(\false)->children()->variableNode('sha1')->end()->variableNode('pin-sha256')->end()->variableNode('md5')->end()->end()->end()->append($this->addHttpClientRetrySection())->end()->end()->end()->end()->end()->end(); } private function addHttpClientRetrySection() { $root = new NodeBuilder(); return $root->arrayNode('retry_failed')->fixXmlConfig('http_code')->canBeEnabled()->addDefaultsIfNotSet()->beforeNormalization()->always(function ($v) { if (isset($v['retry_strategy']) && (isset($v['http_codes']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']) || isset($v['jitter']))) { throw new \InvalidArgumentException('The "retry_strategy" option cannot be used along with the "http_codes", "delay", "multiplier", "max_delay" or "jitter" options.'); } return $v; })->end()->children()->scalarNode('retry_strategy')->defaultNull()->info('service id to override the retry strategy')->end()->arrayNode('http_codes')->performNoDeepMerging()->beforeNormalization()->ifArray()->then(static function ($v) { $list = []; foreach ($v as $key => $val) { if (\is_numeric($val)) { $list[] = ['code' => $val]; } elseif (\is_array($val)) { if (isset($val['code']) || isset($val['methods'])) { $list[] = $val; } else { $list[] = ['code' => $key, 'methods' => $val]; } } elseif (\true === $val || null === $val) { $list[] = ['code' => $key]; } } return $list; })->end()->useAttributeAsKey('code')->arrayPrototype()->fixXmlConfig('method')->children()->integerNode('code')->end()->arrayNode('methods')->beforeNormalization()->ifArray()->then(function ($v) { return \array_map('strtoupper', $v); })->end()->prototype('scalar')->end()->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried')->end()->end()->end()->info('A list of HTTP status code that triggers a retry')->end()->integerNode('max_retries')->defaultValue(3)->min(0)->end()->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries)')->end()->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness in percent (between 0 and 1) to apply to the delay')->end()->end(); } private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('mailer')->info('Mailer configuration')->{$enableIfStandalone('symfony/mailer', Mailer::class)}()->validate()->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); })->thenInvalid('"dsn" and "transports" cannot be used together.')->end()->fixXmlConfig('transport')->fixXmlConfig('header')->children()->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end()->scalarNode('dsn')->defaultNull()->end()->arrayNode('transports')->useAttributeAsKey('name')->prototype('scalar')->end()->end()->arrayNode('envelope')->info('Mailer Envelope configuration')->children()->scalarNode('sender')->end()->arrayNode('recipients')->performNoDeepMerging()->beforeNormalization()->ifArray()->then(function ($v) { return \array_filter(\array_values($v)); })->end()->prototype('scalar')->end()->end()->end()->end()->arrayNode('headers')->normalizeKeys(\false)->useAttributeAsKey('name')->prototype('array')->normalizeKeys(\false)->beforeNormalization()->ifTrue(function ($v) { return !\is_array($v) || \array_keys($v) !== ['value']; })->then(function ($v) { return ['value' => $v]; })->end()->children()->variableNode('value')->end()->end()->end()->end()->end()->end()->end(); } private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('notifier')->info('Notifier configuration')->{$enableIfStandalone('symfony/notifier', Notifier::class)}()->fixXmlConfig('chatter_transport')->children()->arrayNode('chatter_transports')->useAttributeAsKey('name')->prototype('scalar')->end()->end()->end()->fixXmlConfig('texter_transport')->children()->arrayNode('texter_transports')->useAttributeAsKey('name')->prototype('scalar')->end()->end()->end()->children()->booleanNode('notification_on_failed_messages')->defaultFalse()->end()->end()->children()->arrayNode('channel_policy')->useAttributeAsKey('name')->prototype('array')->beforeNormalization()->ifString()->then(function (string $v) { return [$v]; })->end()->prototype('scalar')->end()->end()->end()->end()->fixXmlConfig('admin_recipient')->children()->arrayNode('admin_recipients')->prototype('array')->children()->scalarNode('email')->cannotBeEmpty()->end()->scalarNode('phone')->defaultValue('')->end()->end()->end()->end()->end()->end()->end(); } private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('rate_limiter')->info('Rate limiter configuration')->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}()->fixXmlConfig('limiter')->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); })->then(function (array $v) { $newV = ['enabled' => $v['enabled'] ?? \true]; unset($v['enabled']); $newV['limiters'] = $v; return $newV; })->end()->children()->arrayNode('limiters')->useAttributeAsKey('name')->arrayPrototype()->children()->scalarNode('lock_factory')->info('The service ID of the lock factory used by this limiter (or null to disable locking)')->defaultValue('lock.factory')->end()->scalarNode('cache_pool')->info('The cache pool to use for storing the current limiter state')->defaultValue('cache.rate_limiter')->end()->scalarNode('storage_service')->info('The service ID of a custom storage implementation, this precedes any configured "cache_pool"')->defaultNull()->end()->enumNode('policy')->info('The algorithm to be used by this limiter')->isRequired()->values(['fixed_window', 'token_bucket', 'sliding_window', 'no_limit'])->end()->integerNode('limit')->info('The maximum allowed hits in a fixed interval or burst')->isRequired()->end()->scalarNode('interval')->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).')->end()->arrayNode('rate')->info('Configures the fill rate if "policy" is set to "token_bucket"')->children()->scalarNode('interval')->info('Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).')->end()->integerNode('amount')->info('Amount of tokens to add each interval')->defaultValue(1)->end()->end()->end()->end()->end()->end()->end()->end()->end(); } private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode->children()->arrayNode('uid')->info('Uid configuration')->{$enableIfStandalone('symfony/uid', UuidFactory::class)}()->addDefaultsIfNotSet()->children()->enumNode('default_uuid_version')->defaultValue(6)->values([6, 4, 1])->end()->enumNode('name_based_uuid_version')->defaultValue(5)->values([5, 3])->end()->scalarNode('name_based_uuid_namespace')->cannotBeEmpty()->end()->enumNode('time_based_uuid_version')->defaultValue(6)->values([6, 1])->end()->scalarNode('time_based_uuid_node')->cannotBeEmpty()->end()->end()->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection; use Composer\InstalledVersions; use _ContaoManager\Doctrine\Common\Annotations\AnnotationRegistry; use _ContaoManager\Doctrine\Common\Annotations\Reader; use _ContaoManager\Http\Client\HttpClient; use _ContaoManager\phpDocumentor\Reflection\DocBlockFactoryInterface; use _ContaoManager\phpDocumentor\Reflection\Types\ContextFactory; use _ContaoManager\PHPStan\PhpDocParser\Parser\PhpDocParser; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Psr\Container\ContainerInterface as PsrContainerInterface; use _ContaoManager\Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use _ContaoManager\Psr\Http\Client\ClientInterface; use _ContaoManager\Psr\Log\LoggerAwareInterface; use _ContaoManager\Symfony\Bridge\Monolog\Processor\DebugProcessor; use _ContaoManager\Symfony\Bridge\Twig\Extension\CsrfExtension; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use _ContaoManager\Symfony\Bundle\FullStack; use _ContaoManager\Symfony\Bundle\MercureBundle\MercureBundle; use _ContaoManager\Symfony\Component\Asset\PackageInterface; use _ContaoManager\Symfony\Component\BrowserKit\AbstractBrowser; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ChainAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\DoctrineAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\TagAwareAdapter; use _ContaoManager\Symfony\Component\Cache\DependencyInjection\CachePoolPass; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\Config\Resource\DirectoryResource; use _ContaoManager\Symfony\Component\Config\ResourceCheckerInterface; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\EnvVarLoaderInterface; use _ContaoManager\Symfony\Component\DependencyInjection\EnvVarProcessorInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; use _ContaoManager\Symfony\Component\Dotenv\Command\DebugCommand; use _ContaoManager\Symfony\Component\EventDispatcher\Attribute\AsEventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; use _ContaoManager\Symfony\Component\Finder\Finder; use _ContaoManager\Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; use _ContaoManager\Symfony\Component\Form\Form; use _ContaoManager\Symfony\Component\Form\FormTypeExtensionInterface; use _ContaoManager\Symfony\Component\Form\FormTypeGuesserInterface; use _ContaoManager\Symfony\Component\Form\FormTypeInterface; use _ContaoManager\Symfony\Component\HttpClient\MockHttpClient; use _ContaoManager\Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use _ContaoManager\Symfony\Component\HttpClient\RetryableHttpClient; use _ContaoManager\Symfony\Component\HttpClient\ScopingHttpClient; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use _ContaoManager\Symfony\Component\HttpKernel\Attribute\AsController; use _ContaoManager\Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\Lock\Lock; use _ContaoManager\Symfony\Component\Lock\LockFactory; use _ContaoManager\Symfony\Component\Lock\LockInterface; use _ContaoManager\Symfony\Component\Lock\PersistingStoreInterface; use _ContaoManager\Symfony\Component\Lock\Store\StoreFactory; use _ContaoManager\Symfony\Component\Lock\StoreInterface; use _ContaoManager\Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; use _ContaoManager\Symfony\Component\Mailer\Mailer; use _ContaoManager\Symfony\Component\Mercure\HubRegistry; use _ContaoManager\Symfony\Component\Messenger\Attribute\AsMessageHandler; use _ContaoManager\Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; use _ContaoManager\Symfony\Component\Messenger\Handler\BatchHandlerInterface; use _ContaoManager\Symfony\Component\Messenger\Handler\MessageHandlerInterface; use _ContaoManager\Symfony\Component\Messenger\MessageBus; use _ContaoManager\Symfony\Component\Messenger\MessageBusInterface; use _ContaoManager\Symfony\Component\Messenger\Middleware\RouterContextMiddleware; use _ContaoManager\Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use _ContaoManager\Symfony\Component\Messenger\Transport\TransportFactoryInterface; use _ContaoManager\Symfony\Component\Messenger\Transport\TransportInterface; use _ContaoManager\Symfony\Component\Mime\Header\Headers; use _ContaoManager\Symfony\Component\Mime\MimeTypeGuesserInterface; use _ContaoManager\Symfony\Component\Mime\MimeTypes; use _ContaoManager\Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; use _ContaoManager\Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use _ContaoManager\Symfony\Component\Notifier\ChatterInterface; use _ContaoManager\Symfony\Component\Notifier\Notifier; use _ContaoManager\Symfony\Component\Notifier\Recipient\Recipient; use _ContaoManager\Symfony\Component\Notifier\TexterInterface; use _ContaoManager\Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessor; use _ContaoManager\Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; use _ContaoManager\Symfony\Component\RateLimiter\LimiterInterface; use _ContaoManager\Symfony\Component\RateLimiter\RateLimiterFactory; use _ContaoManager\Symfony\Component\RateLimiter\Storage\CacheStorage; use _ContaoManager\Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; use _ContaoManager\Symfony\Component\Routing\Loader\AnnotationFileLoader; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use _ContaoManager\Symfony\Component\Serializer\Encoder\DecoderInterface; use _ContaoManager\Symfony\Component\Serializer\Encoder\EncoderInterface; use _ContaoManager\Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use _ContaoManager\Symfony\Component\Serializer\Normalizer\NormalizerInterface; use _ContaoManager\Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; use _ContaoManager\Symfony\Component\String\LazyString; use _ContaoManager\Symfony\Component\String\Slugger\SluggerInterface; use _ContaoManager\Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; use _ContaoManager\Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; use _ContaoManager\Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; use _ContaoManager\Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; use _ContaoManager\Symfony\Component\Translation\PseudoLocalizationTranslator; use _ContaoManager\Symfony\Component\Translation\Translator; use _ContaoManager\Symfony\Component\Uid\Factory\UuidFactory; use _ContaoManager\Symfony\Component\Uid\UuidV4; use _ContaoManager\Symfony\Component\Validator\ConstraintValidatorInterface; use _ContaoManager\Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use _ContaoManager\Symfony\Component\Validator\ObjectInitializerInterface; use _ContaoManager\Symfony\Component\Validator\Validation; use _ContaoManager\Symfony\Component\WebLink\HttpHeaderSerializer; use _ContaoManager\Symfony\Component\Workflow; use _ContaoManager\Symfony\Component\Workflow\WorkflowInterface; use _ContaoManager\Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; use _ContaoManager\Symfony\Component\Yaml\Yaml; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; use _ContaoManager\Symfony\Contracts\Cache\CallbackInterface; use _ContaoManager\Symfony\Contracts\Cache\TagAwareCacheInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Contracts\HttpClient\HttpClientInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; use _ContaoManager\Symfony\Contracts\Translation\LocaleAwareInterface; /** * Process the configuration and prepare the dependency injection container with * parameters and services. */ class FrameworkExtension extends Extension { private $formConfigEnabled = \false; private $translationConfigEnabled = \false; private $sessionConfigEnabled = \false; private $annotationsConfigEnabled = \false; private $validatorConfigEnabled = \false; private $messengerConfigEnabled = \false; private $mailerConfigEnabled = \false; private $httpClientConfigEnabled = \false; private $notifierConfigEnabled = \false; private $propertyAccessConfigEnabled = \false; private static $lockConfigEnabled = \false; /** * Responds to the app.config configuration parameter. * * @throws LogicException */ public function load(array $configs, ContainerBuilder $container) { if (!\class_exists(InstalledVersions::class)) { \trigger_deprecation('symfony/framework-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.'); } $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__) . '/Resources/config')); $loader->load('web.php'); $loader->load('services.php'); $loader->load('fragment_renderer.php'); $loader->load('error_renderer.php'); if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'], \true)) { $container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher'); } $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class); if ($this->hasConsole()) { $loader->load('console.php'); if (!\class_exists(BaseXliffLintCommand::class)) { $container->removeDefinition('console.command.xliff_lint'); } if (!\class_exists(BaseYamlLintCommand::class)) { $container->removeDefinition('console.command.yaml_lint'); } if (!\class_exists(DebugCommand::class)) { $container->removeDefinition('console.command.dotenv_debug'); } } // Load Cache configuration first as it is used by other components $loader->load('cache.php'); $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); $this->annotationsConfigEnabled = $this->isConfigEnabled($container, $config['annotations']); $this->translationConfigEnabled = $this->isConfigEnabled($container, $config['translator']); // A translator must always be registered (as support is included by // default in the Form and Validator component). If disabled, an identity // translator will be used and everything will still work as expected. if ($this->isConfigEnabled($container, $config['translator']) || $this->isConfigEnabled($container, $config['form']) || $this->isConfigEnabled($container, $config['validation'])) { if (!\class_exists(Translator::class) && $this->isConfigEnabled($container, $config['translator'])) { throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".'); } if (\class_exists(Translator::class)) { $loader->load('identity_translator.php'); } } $container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']); $container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']); // If the slugger is used but the String component is not available, we should throw an error if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'], \true)) { $container->register('slugger', 'stdClass')->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".'); } else { if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'], \true)) { $container->register('slugger', 'stdClass')->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".'); } if (!\extension_loaded('intl') && !\defined('_ContaoManager\\PHPUNIT_COMPOSER_INSTALL')) { \trigger_deprecation('', '', 'Please install the "intl" PHP extension for best performance.'); } } if (isset($config['secret'])) { $container->setParameter('kernel.secret', $config['secret']); } $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); $container->setParameter('kernel.error_controller', $config['error_controller']); if (($config['trusted_proxies'] ?? \false) && ($config['trusted_headers'] ?? \false)) { $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); $container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers'])); } if (!$container->hasParameter('debug.file_link_format')) { $container->setParameter('debug.file_link_format', $config['ide']); } if (!empty($config['test'])) { $loader->load('test.php'); if (!\class_exists(AbstractBrowser::class)) { $container->removeDefinition('test.client'); } } if ($this->isConfigEnabled($container, $config['request'])) { $this->registerRequestConfiguration($config['request'], $container, $loader); } if ($this->isConfigEnabled($container, $config['assets'])) { if (!\class_exists(\_ContaoManager\Symfony\Component\Asset\Package::class)) { throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); } $this->registerAssetsConfiguration($config['assets'], $container, $loader); } if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) { $this->registerHttpClientConfiguration($config['http_client'], $container, $loader, $config['profiler']); } if ($this->mailerConfigEnabled = $this->isConfigEnabled($container, $config['mailer'])) { $this->registerMailerConfiguration($config['mailer'], $container, $loader); } $propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']); $this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']); $this->registerEsiConfiguration($config['esi'], $container, $loader); $this->registerSsiConfiguration($config['ssi'], $container, $loader); $this->registerFragmentsConfiguration($config['fragments'], $container, $loader); $this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']); $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); $this->registerDebugConfiguration($config['php_errors'], $container, $loader); // @deprecated since Symfony 5.4, in 6.0 change to: // $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']); $this->registerRouterConfiguration($config['router'], $container, $loader, $config['translator']['enabled_locales'] ?: $config['enabled_locales']); $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader); $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); $this->registerSecretsConfiguration($config['secrets'], $container, $loader); $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); if ($this->isConfigEnabled($container, $config['serializer'])) { if (!\class_exists(\_ContaoManager\Symfony\Component\Serializer\Serializer::class)) { throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); } $this->registerSerializerConfiguration($config['serializer'], $container, $loader); } if ($propertyInfoEnabled) { $this->registerPropertyInfoConfiguration($container, $loader); } if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) { $this->registerLockConfiguration($config['lock'], $container, $loader); } if ($this->isConfigEnabled($container, $config['rate_limiter'])) { if (!\interface_exists(LimiterInterface::class)) { throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); } $this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader); } if ($this->isConfigEnabled($container, $config['web_link'])) { if (!\class_exists(HttpHeaderSerializer::class)) { throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); } $loader->load('web_link.php'); } if ($this->isConfigEnabled($container, $config['uid'])) { if (!\class_exists(UuidFactory::class)) { throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".'); } $this->registerUidConfiguration($config['uid'], $container, $loader); } // register cache before session so both can share the connection services $this->registerCacheConfiguration($config['cache'], $container); if ($this->isConfigEnabled($container, $config['session'])) { if (!\extension_loaded('session')) { throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.'); } $this->sessionConfigEnabled = \true; $this->registerSessionConfiguration($config['session'], $container, $loader); if (!empty($config['test'])) { // test listener will replace the existing session listener // as we are aliasing to avoid duplicated registered events $container->setAlias('session_listener', 'test.session.listener'); } } elseif (!empty($config['test'])) { $container->removeDefinition('test.session.listener'); } // csrf depends on session being registered if (null === $config['csrf_protection']['enabled']) { $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !\class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle'], \true); } $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); // form depends on csrf being registered if ($this->isConfigEnabled($container, $config['form'])) { if (!\class_exists(Form::class)) { throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".'); } $this->formConfigEnabled = \true; $this->registerFormConfiguration($config, $container, $loader); if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'], \true)) { $config['validation']['enabled'] = \true; } else { $container->setParameter('validator.translation_domain', 'validators'); $container->removeDefinition('form.type_extension.form.validator'); $container->removeDefinition('form.type_guesser.validator'); } } else { $container->removeDefinition('console.command.form_debug'); } // validation depends on form, annotations being registered $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled); // messenger depends on validation being registered if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) { $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']); } else { $container->removeDefinition('console.command.messenger_consume_messages'); $container->removeDefinition('console.command.messenger_debug'); $container->removeDefinition('console.command.messenger_stop_workers'); $container->removeDefinition('console.command.messenger_setup_transports'); $container->removeDefinition('console.command.messenger_failed_messages_retry'); $container->removeDefinition('console.command.messenger_failed_messages_show'); $container->removeDefinition('console.command.messenger_failed_messages_remove'); $container->removeDefinition('cache.messenger.restart_workers_signal'); if ($container->hasDefinition('messenger.transport.amqp.factory') && !\class_exists(AmqpTransportFactory::class)) { if (\class_exists(\_ContaoManager\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) { $container->getDefinition('messenger.transport.amqp.factory')->setClass(\_ContaoManager\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)->addTag('messenger.transport_factory'); } else { $container->removeDefinition('messenger.transport.amqp.factory'); } } if ($container->hasDefinition('messenger.transport.redis.factory') && !\class_exists(RedisTransportFactory::class)) { if (\class_exists(\_ContaoManager\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) { $container->getDefinition('messenger.transport.redis.factory')->setClass(\_ContaoManager\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)->addTag('messenger.transport_factory'); } else { $container->removeDefinition('messenger.transport.redis.factory'); } } } // notifier depends on messenger, mailer being registered if ($this->notifierConfigEnabled = $this->isConfigEnabled($container, $config['notifier'])) { $this->registerNotifierConfiguration($config['notifier'], $container, $loader); } // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier being registered $this->registerProfilerConfiguration($config['profiler'], $container, $loader); $this->addAnnotatedClassesToCompile([ '**\\Controller\\', '**\\Entity\\', // Added explicitly so that we don't rely on the class map being dumped to make it work '_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', ]); if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'], \true)) { $loader->load('mime_type.php'); } $container->registerForAutoconfiguration(PackageInterface::class)->addTag('assets.package'); $container->registerForAutoconfiguration(Command::class)->addTag('console.command'); $container->registerForAutoconfiguration(ResourceCheckerInterface::class)->addTag('config_cache.resource_checker'); $container->registerForAutoconfiguration(EnvVarLoaderInterface::class)->addTag('container.env_var_loader'); $container->registerForAutoconfiguration(EnvVarProcessorInterface::class)->addTag('container.env_var_processor'); $container->registerForAutoconfiguration(CallbackInterface::class)->addTag('container.reversible'); $container->registerForAutoconfiguration(ServiceLocator::class)->addTag('container.service_locator'); $container->registerForAutoconfiguration(ServiceSubscriberInterface::class)->addTag('container.service_subscriber'); $container->registerForAutoconfiguration(ArgumentValueResolverInterface::class)->addTag('controller.argument_value_resolver'); $container->registerForAutoconfiguration(AbstractController::class)->addTag('controller.service_arguments'); $container->registerForAutoconfiguration(DataCollectorInterface::class)->addTag('data_collector'); $container->registerForAutoconfiguration(FormTypeInterface::class)->addTag('form.type'); $container->registerForAutoconfiguration(FormTypeGuesserInterface::class)->addTag('form.type_guesser'); $container->registerForAutoconfiguration(FormTypeExtensionInterface::class)->addTag('form.type_extension'); $container->registerForAutoconfiguration(CacheClearerInterface::class)->addTag('kernel.cache_clearer'); $container->registerForAutoconfiguration(CacheWarmerInterface::class)->addTag('kernel.cache_warmer'); $container->registerForAutoconfiguration(EventDispatcherInterface::class)->addTag('event_dispatcher.dispatcher'); $container->registerForAutoconfiguration(EventSubscriberInterface::class)->addTag('kernel.event_subscriber'); $container->registerForAutoconfiguration(LocaleAwareInterface::class)->addTag('kernel.locale_aware'); $container->registerForAutoconfiguration(ResetInterface::class)->addTag('kernel.reset', ['method' => 'reset']); if (!\interface_exists(MarshallerInterface::class)) { $container->registerForAutoconfiguration(ResettableInterface::class)->addTag('kernel.reset', ['method' => 'reset']); } $container->registerForAutoconfiguration(PropertyListExtractorInterface::class)->addTag('property_info.list_extractor'); $container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class)->addTag('property_info.type_extractor'); $container->registerForAutoconfiguration(PropertyDescriptionExtractorInterface::class)->addTag('property_info.description_extractor'); $container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)->addTag('property_info.access_extractor'); $container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class)->addTag('property_info.initializable_extractor'); $container->registerForAutoconfiguration(EncoderInterface::class)->addTag('serializer.encoder'); $container->registerForAutoconfiguration(DecoderInterface::class)->addTag('serializer.encoder'); $container->registerForAutoconfiguration(NormalizerInterface::class)->addTag('serializer.normalizer'); $container->registerForAutoconfiguration(DenormalizerInterface::class)->addTag('serializer.normalizer'); $container->registerForAutoconfiguration(ConstraintValidatorInterface::class)->addTag('validator.constraint_validator'); $container->registerForAutoconfiguration(ObjectInitializerInterface::class)->addTag('validator.initializer'); $container->registerForAutoconfiguration(MessageHandlerInterface::class)->addTag('messenger.message_handler'); $container->registerForAutoconfiguration(BatchHandlerInterface::class)->addTag('messenger.message_handler'); $container->registerForAutoconfiguration(TransportFactoryInterface::class)->addTag('messenger.transport_factory'); $container->registerForAutoconfiguration(MimeTypeGuesserInterface::class)->addTag('mime.mime_type_guesser'); $container->registerForAutoconfiguration(LoggerAwareInterface::class)->addMethodCall('setLogger', [new Reference('logger')]); $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \Reflector $reflector) { $tagAttributes = \get_object_vars($attribute); if ($reflector instanceof \ReflectionMethod) { if (isset($tagAttributes['method'])) { throw new LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); } $tagAttributes['method'] = $reflector->getName(); } $definition->addTag('kernel.event_listener', $tagAttributes); }); $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute) : void { $definition->addTag('controller.service_arguments'); }); $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute) : void { $tagAttributes = \get_object_vars($attribute); $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; unset($tagAttributes['fromTransport']); $definition->addTag('messenger.message_handler', $tagAttributes); }); if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers $container->getDefinition('config_cache_factory')->setArguments([]); } if (!$config['disallow_search_engine_index'] ?? \false) { $container->removeDefinition('disallow_search_engine_index_response_listener'); } $container->registerForAutoconfiguration(RouteLoaderInterface::class)->addTag('routing.route_loader'); $container->setParameter('container.behavior_describing_tags', ['annotations.cached_reader', 'container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'kernel.event_subscriber', 'kernel.event_listener', 'kernel.locale_aware', 'kernel.reset']); } /** * {@inheritdoc} */ public function getConfiguration(array $config, ContainerBuilder $container) { return new Configuration($container->getParameter('kernel.debug')); } protected function hasConsole() : bool { return \class_exists(Application::class); } private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('form.php'); $container->getDefinition('form.type_extension.form.validator')->setArgument(1, $config['form']['legacy_error_messages']); if (null === $config['form']['csrf_protection']['enabled']) { $config['form']['csrf_protection']['enabled'] = $config['csrf_protection']['enabled']; } if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) { if (!$container->hasDefinition('security.csrf.token_generator')) { throw new \LogicException('To use form CSRF protection, "framework.csrf_protection" must be enabled.'); } $loader->load('form_csrf.php'); $container->setParameter('form.type_extension.csrf.enabled', \true); $container->setParameter('form.type_extension.csrf.field_name', $config['form']['csrf_protection']['field_name']); } else { $container->setParameter('form.type_extension.csrf.enabled', \false); } if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'], \true)) { $container->removeDefinition('form.type_extension.upload.validator'); } if (!\method_exists(CachingFactoryDecorator::class, 'reset')) { $container->getDefinition('form.choice_list_factory.cached')->clearTag('kernel.reset'); } } private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride) { $options = $config; unset($options['enabled']); if (!$options['private_headers']) { unset($options['private_headers']); } $container->getDefinition('http_cache')->setPublic($config['enabled'])->replaceArgument(3, $options); if ($httpMethodOverride) { $container->getDefinition('http_cache')->addArgument((new Definition('void'))->setFactory([Request::class, 'enableHttpMethodParameterOverride'])); } } private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('fragment.renderer.esi'); return; } $loader->load('esi.php'); } private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('fragment.renderer.ssi'); return; } $loader->load('ssi.php'); } private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('fragment.renderer.hinclude'); return; } $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']); $loader->load('fragment_listener.php'); $container->setParameter('fragment.path', $config['path']); } private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { // this is needed for the WebProfiler to work even if the profiler is disabled $container->setParameter('data_collector.templates', []); return; } $loader->load('profiling.php'); $loader->load('collectors.php'); $loader->load('cache_debug.php'); if ($this->formConfigEnabled) { $loader->load('form_debug.php'); } if ($this->validatorConfigEnabled) { $loader->load('validator_debug.php'); } if ($this->translationConfigEnabled) { $loader->load('translation_debug.php'); $container->getDefinition('translator.data_collector')->setDecoratedService('translator'); } if ($this->messengerConfigEnabled) { $loader->load('messenger_debug.php'); } if ($this->mailerConfigEnabled) { $loader->load('mailer_debug.php'); } if ($this->httpClientConfigEnabled) { $loader->load('http_client_debug.php'); } if ($this->notifierConfigEnabled) { $loader->load('notifier_debug.php'); } $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests'] || $config['only_master_requests']); // Choose storage class based on the DSN [$class] = \explode(':', $config['dsn'], 2); if ('file' !== $class) { throw new \LogicException(\sprintf('Driver "%s" is not supported for the profiler.', $class)); } $container->setParameter('profiler.storage.dsn', $config['dsn']); $container->getDefinition('profiler')->addArgument($config['collect'])->addTag('kernel.reset', ['method' => 'reset']); $container->getDefinition('profiler_listener')->addArgument($config['collect_parameter']); } private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$config['enabled']) { $container->removeDefinition('console.command.workflow_dump'); return; } if (!\class_exists(Workflow\Workflow::class)) { throw new LogicException('Workflow support cannot be enabled as the Workflow component is not installed. Try running "composer require symfony/workflow".'); } $loader->load('workflow.php'); $registryDefinition = $container->getDefinition('workflow.registry'); $workflows = []; foreach ($config['workflows'] as $name => $workflow) { $type = $workflow['type']; $workflowId = \sprintf('%s.%s', $type, $name); // Process Metadata (workflow + places (transition is done in the "create transition" block)) $metadataStoreDefinition = new Definition(Workflow\Metadata\InMemoryMetadataStore::class, [[], [], null]); if ($workflow['metadata']) { $metadataStoreDefinition->replaceArgument(0, $workflow['metadata']); } $placesMetadata = []; foreach ($workflow['places'] as $place) { if ($place['metadata']) { $placesMetadata[$place['name']] = $place['metadata']; } } if ($placesMetadata) { $metadataStoreDefinition->replaceArgument(1, $placesMetadata); } // Create transitions $transitions = []; $guardsConfiguration = []; $transitionsMetadataDefinition = new Definition(\SplObjectStorage::class); // Global transition counter per workflow $transitionCounter = 0; foreach ($workflow['transitions'] as $transition) { if ('workflow' === $type) { $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $transition['from'], $transition['to']]); $transitionDefinition->setPublic(\false); $transitionId = \sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); $container->setDefinition($transitionId, $transitionDefinition); $transitions[] = new Reference($transitionId); if (isset($transition['guard'])) { $configuration = new Definition(Workflow\EventListener\GuardExpression::class); $configuration->addArgument(new Reference($transitionId)); $configuration->addArgument($transition['guard']); $configuration->setPublic(\false); $eventName = \sprintf('workflow.%s.guard.%s', $name, $transition['name']); $guardsConfiguration[$eventName][] = $configuration; } if ($transition['metadata']) { $transitionsMetadataDefinition->addMethodCall('attach', [new Reference($transitionId), $transition['metadata']]); } } elseif ('state_machine' === $type) { foreach ($transition['from'] as $from) { foreach ($transition['to'] as $to) { $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $from, $to]); $transitionDefinition->setPublic(\false); $transitionId = \sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); $container->setDefinition($transitionId, $transitionDefinition); $transitions[] = new Reference($transitionId); if (isset($transition['guard'])) { $configuration = new Definition(Workflow\EventListener\GuardExpression::class); $configuration->addArgument(new Reference($transitionId)); $configuration->addArgument($transition['guard']); $configuration->setPublic(\false); $eventName = \sprintf('workflow.%s.guard.%s', $name, $transition['name']); $guardsConfiguration[$eventName][] = $configuration; } if ($transition['metadata']) { $transitionsMetadataDefinition->addMethodCall('attach', [new Reference($transitionId), $transition['metadata']]); } } } } } $metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition); $container->setDefinition(\sprintf('%s.metadata_store', $workflowId), $metadataStoreDefinition); // Create places $places = \array_column($workflow['places'], 'name'); $initialMarking = $workflow['initial_marking'] ?? []; // Create a Definition $definitionDefinition = new Definition(Workflow\Definition::class); $definitionDefinition->setPublic(\false); $definitionDefinition->addArgument($places); $definitionDefinition->addArgument($transitions); $definitionDefinition->addArgument($initialMarking); $definitionDefinition->addArgument(new Reference(\sprintf('%s.metadata_store', $workflowId))); $workflows[$workflowId] = $definitionDefinition; // Create MarkingStore $markingStoreDefinition = null; if (isset($workflow['marking_store']['type'])) { $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method'); $markingStoreDefinition->setArguments([ 'state_machine' === $type, // single state $workflow['marking_store']['property'], ]); } elseif (isset($workflow['marking_store']['service'])) { $markingStoreDefinition = new Reference($workflow['marking_store']['service']); } // Create Workflow $workflowDefinition = new ChildDefinition(\sprintf('%s.abstract', $type)); $workflowDefinition->replaceArgument(0, new Reference(\sprintf('%s.definition', $workflowId))); $workflowDefinition->replaceArgument(1, $markingStoreDefinition); $workflowDefinition->replaceArgument(3, $name); $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); $workflowDefinition->addTag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3']); // Store to container $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition(\sprintf('%s.definition', $workflowId), $definitionDefinition); $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name . '.' . $type); // Validate Workflow if ('state_machine' === $workflow['type']) { $validator = new Workflow\Validator\StateMachineValidator(); } else { $validator = new Workflow\Validator\WorkflowValidator(); } $trs = \array_map(function (Reference $ref) use($container) : Workflow\Transition { return $container->get((string) $ref); }, $transitions); $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); $validator->validate($realDefinition, $name); // Add workflow to Registry if ($workflow['supports']) { foreach ($workflow['supports'] as $supportedClassName) { $strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, [$supportedClassName]); $strategyDefinition->setPublic(\false); $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), $strategyDefinition]); } } elseif (isset($workflow['support_strategy'])) { $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), new Reference($workflow['support_strategy'])]); } // Enable the AuditTrail if ($workflow['audit_trail']['enabled']) { $listener = new Definition(Workflow\EventListener\AuditTrailListener::class); $listener->addTag('monolog.logger', ['channel' => 'workflow']); $listener->addTag('kernel.event_listener', ['event' => \sprintf('workflow.%s.leave', $name), 'method' => 'onLeave']); $listener->addTag('kernel.event_listener', ['event' => \sprintf('workflow.%s.transition', $name), 'method' => 'onTransition']); $listener->addTag('kernel.event_listener', ['event' => \sprintf('workflow.%s.enter', $name), 'method' => 'onEnter']); $listener->addArgument(new Reference('logger')); $container->setDefinition(\sprintf('.%s.listener.audit_trail', $workflowId), $listener); } // Add Guard Listener if ($guardsConfiguration) { if (!\class_exists(ExpressionLanguage::class)) { throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } if (!\class_exists(Security::class)) { throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); } $guard = new Definition(Workflow\EventListener\GuardListener::class); $guard->setArguments([$guardsConfiguration, new Reference('workflow.security.expression_language'), new Reference('security.token_storage'), new Reference('security.authorization_checker'), new Reference('security.authentication.trust_resolver'), new Reference('security.role_hierarchy'), new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE)]); foreach ($guardsConfiguration as $eventName => $config) { $guard->addTag('kernel.event_listener', ['event' => $eventName, 'method' => 'onTransition']); } $container->setDefinition(\sprintf('.%s.listener.guard', $workflowId), $guard); $container->setParameter('workflow.has_guard_listeners', \true); } } $commandDumpDefinition = $container->getDefinition('console.command.workflow_dump'); $commandDumpDefinition->setArgument(0, $workflows); } private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('debug_prod.php'); if (\class_exists(Stopwatch::class)) { $container->register('debug.stopwatch', Stopwatch::class)->addArgument(\true)->addTag('kernel.reset', ['method' => 'reset']); $container->setAlias(Stopwatch::class, new Alias('debug.stopwatch', \false)); } $debug = $container->getParameter('kernel.debug'); if ($debug) { $container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml'); } if ($debug && \class_exists(Stopwatch::class)) { $loader->load('debug.php'); } $definition = $container->findDefinition('debug.debug_handlers_listener'); if (\false === $config['log']) { $definition->replaceArgument(1, null); } elseif (\true !== $config['log']) { $definition->replaceArgument(2, $config['log']); } if (!$config['throw']) { $container->setParameter('debug.error_handler.throw_at', 0); } if ($debug && \class_exists(DebugProcessor::class)) { $definition = new Definition(DebugProcessor::class); $definition->setPublic(\false); $definition->addArgument(new Reference('request_stack')); $container->setDefinition('debug.log_processor', $definition); } } private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('console.command.router_debug'); $container->removeDefinition('console.command.router_match'); $container->removeDefinition('messenger.middleware.router_context'); return; } if (!\class_exists(RouterContextMiddleware::class)) { $container->removeDefinition('messenger.middleware.router_context'); } $loader->load('routing.php'); if (null === $config['utf8']) { \trigger_deprecation('symfony/framework-bundle', '5.1', 'Not setting the "framework.router.utf8" configuration option is deprecated, it will default to "true" in version 6.0.'); } if ($config['utf8']) { $container->getDefinition('routing.loader')->replaceArgument(1, ['utf8' => \true]); } if ($enabledLocales) { $enabledLocales = \implode('|', \array_map('preg_quote', $enabledLocales)); $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]); } if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'], \true)) { $container->removeDefinition('router.expression_language_provider'); } $container->setParameter('router.resource', $config['resource']); $router = $container->findDefinition('router.default'); $argument = $router->getArgument(2); $argument['strict_requirements'] = $config['strict_requirements']; if (isset($config['type'])) { $argument['resource_type'] = $config['type']; } $router->replaceArgument(2, $argument); $container->setParameter('request_listener.http_port', $config['http_port']); $container->setParameter('request_listener.https_port', $config['https_port']); if (null !== $config['default_uri']) { $container->getDefinition('router.request_context')->replaceArgument(0, $config['default_uri']); } if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) { return; } $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class)->setPublic(\false)->addTag('routing.loader', ['priority' => -10])->setArguments([new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), '%kernel.environment%']); $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class)->setPublic(\false)->addTag('routing.loader', ['priority' => -10])->setArguments([new Reference('file_locator'), new Reference('routing.loader.annotation')]); $container->register('routing.loader.annotation.file', AnnotationFileLoader::class)->setPublic(\false)->addTag('routing.loader', ['priority' => -10])->setArguments([new Reference('file_locator'), new Reference('routing.loader.annotation')]); } private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('session.php'); // session storage if (null === $config['storage_factory_id']) { \trigger_deprecation('symfony/framework-bundle', '5.3', 'Not setting the "framework.session.storage_factory_id" configuration option is deprecated, it will default to "session.storage.factory.native" and will replace the "framework.session.storage_id" configuration option in version 6.0.'); $container->setAlias('session.storage', $config['storage_id']); $container->setAlias('session.storage.factory', 'session.storage.factory.service'); } else { $container->setAlias('session.storage.factory', $config['storage_factory_id']); $container->removeAlias(SessionStorageInterface::class); $container->removeDefinition('session.storage.metadata_bag'); $container->removeDefinition('session.storage.native'); $container->removeDefinition('session.storage.php_bridge'); $container->removeDefinition('session.storage.mock_file'); $container->removeAlias('session.storage.filesystem'); } $options = ['cache_limiter' => '0']; foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'sid_length', 'sid_bits_per_character'] as $key) { if (isset($config[$key])) { $options[$key] = $config[$key]; } } if ('auto' === ($options['cookie_secure'] ?? null)) { if (null === $config['storage_factory_id']) { $locator = $container->getDefinition('session_listener')->getArgument(0); $locator->setValues($locator->getValues() + ['session_storage' => new Reference('session.storage', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), 'request_stack' => new Reference('request_stack')]); } else { $container->getDefinition('session.storage.factory.native')->replaceArgument(3, \true); $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, \true); } } $container->setParameter('session.storage.options', $options); // session handler (the internal callback registered with PHP session management) if (null === $config['handler_id']) { $config['save_path'] = null; $container->setAlias('session.handler', 'session.handler.native'); } else { $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs); if ($usedEnvs || \preg_match('#^[a-z]++://#', $config['handler_id'])) { $id = '.cache_connection.' . ContainerBuilder::hash($config['handler_id']); $container->getDefinition('session.abstract_handler')->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']); $container->setAlias('session.handler', 'session.abstract_handler'); } else { $container->setAlias('session.handler', $config['handler_id']); } } $container->setParameter('session.save_path', $config['save_path']); $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); } private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if ($config['formats']) { $loader->load('request.php'); $listener = $container->getDefinition('request.add_request_formats_listener'); $listener->replaceArgument(0, $config['formats']); } } private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('assets.php'); if ($config['version_strategy']) { $defaultVersion = new Reference($config['version_strategy']); } else { $defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default', $config['strict_mode']); } $defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion); $container->setDefinition('assets._default_package', $defaultPackage); foreach ($config['packages'] as $name => $package) { if (null !== $package['version_strategy']) { $version = new Reference($package['version_strategy']); } elseif (!\array_key_exists('version', $package) && null === $package['json_manifest_path']) { // if neither version nor json_manifest_path are specified, use the default $version = $defaultVersion; } else { // let format fallback to main version_format $format = $package['version_format'] ?: $config['version_format']; $version = $package['version'] ?? null; $version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name, $package['strict_mode']); } $packageDefinition = $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version)->addTag('assets.package', ['package' => $name]); $container->setDefinition('assets._package_' . $name, $packageDefinition); $container->registerAliasForArgument('assets._package_' . $name, PackageInterface::class, $name . '.package'); } } /** * Returns a definition for an asset package. */ private function createPackageDefinition(?string $basePath, array $baseUrls, Reference $version) : Definition { if ($basePath && $baseUrls) { throw new \LogicException('An asset package cannot have base URLs and base paths.'); } $package = new ChildDefinition($baseUrls ? 'assets.url_package' : 'assets.path_package'); $package->setPublic(\false)->replaceArgument(0, $baseUrls ?: $basePath)->replaceArgument(1, $version); return $package; } private function createVersion(ContainerBuilder $container, ?string $version, ?string $format, ?string $jsonManifestPath, string $name, bool $strictMode) : Reference { // Configuration prevents $version and $jsonManifestPath from being set if (null !== $version) { $def = new ChildDefinition('assets.static_version_strategy'); $def->replaceArgument(0, $version)->replaceArgument(1, $format); $container->setDefinition('assets._version_' . $name, $def); return new Reference('assets._version_' . $name); } if (null !== $jsonManifestPath) { $def = new ChildDefinition('assets.json_manifest_version_strategy'); $def->replaceArgument(0, $jsonManifestPath); $def->replaceArgument(2, $strictMode); $container->setDefinition('assets._version_' . $name, $def); return new Reference('assets._version_' . $name); } return new Reference('assets.empty_version_strategy'); } private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('console.command.translation_debug'); $container->removeDefinition('console.command.translation_extract'); $container->removeDefinition('console.command.translation_pull'); $container->removeDefinition('console.command.translation_push'); return; } $loader->load('translation.php'); $loader->load('translation_providers.php'); // Use the "real" translator instead of the identity default $container->setAlias('translator', 'translator.default')->setPublic(\true); $container->setAlias('translator.formatter', new Alias($config['formatter'], \false)); $translator = $container->findDefinition('translator.default'); $translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]); $defaultOptions = $translator->getArgument(4); $defaultOptions['cache_dir'] = $config['cache_dir']; $translator->setArgument(4, $defaultOptions); // @deprecated since Symfony 5.4, in 6.0 change to: // $translator->setArgument(5, $enabledLocales); $translator->setArgument(5, $config['enabled_locales'] ?: $enabledLocales); $container->setParameter('translator.logging', $config['logging']); $container->setParameter('translator.default_path', $config['default_path']); // Discover translation directories $dirs = []; $transPaths = []; $nonExistingDirs = []; if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'], \true)) { $r = new \ReflectionClass(Validation::class); $dirs[] = $transPaths[] = \dirname($r->getFileName()) . '/Resources/translations'; } if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'], \true)) { $r = new \ReflectionClass(Form::class); $dirs[] = $transPaths[] = \dirname($r->getFileName()) . '/Resources/translations'; } if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'], \true)) { $r = new \ReflectionClass(AuthenticationException::class); $dirs[] = $transPaths[] = \dirname($r->getFileName(), 2) . '/Resources/translations'; } $defaultDir = $container->getParameterBag()->resolveValue($config['default_path']); foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { if ($container->fileExists($dir = $bundle['path'] . '/Resources/translations') || $container->fileExists($dir = $bundle['path'] . '/translations')) { $dirs[] = $dir; } else { $nonExistingDirs[] = $dir; } } foreach ($config['paths'] as $dir) { if ($container->fileExists($dir)) { $dirs[] = $transPaths[] = $dir; } else { throw new \UnexpectedValueException(\sprintf('"%s" defined in translator.paths does not exist or is not a directory.', $dir)); } } if ($container->hasDefinition('console.command.translation_debug')) { $container->getDefinition('console.command.translation_debug')->replaceArgument(5, $transPaths); } if ($container->hasDefinition('console.command.translation_extract')) { $container->getDefinition('console.command.translation_extract')->replaceArgument(6, $transPaths); } if (null === $defaultDir) { // allow null } elseif ($container->fileExists($defaultDir)) { $dirs[] = $defaultDir; } else { $nonExistingDirs[] = $defaultDir; } // Register translation resources if ($dirs) { $files = []; foreach ($dirs as $dir) { $finder = Finder::create()->followLinks()->files()->filter(function (\SplFileInfo $file) { return 2 <= \substr_count($file->getBasename(), '.') && \preg_match('/\\.\\w+$/', $file->getBasename()); })->in($dir)->sortByName(); foreach ($finder as $file) { $fileNameParts = \explode('.', \basename($file)); $locale = $fileNameParts[\count($fileNameParts) - 2]; if (!isset($files[$locale])) { $files[$locale] = []; } $files[$locale][] = (string) $file; } } $projectDir = $container->getParameter('kernel.project_dir'); $options = \array_merge($translator->getArgument(4), ['resource_files' => $files, 'scanned_directories' => $scannedDirectories = \array_merge($dirs, $nonExistingDirs), 'cache_vary' => ['scanned_directories' => \array_map(static function (string $dir) use($projectDir) : string { return \str_starts_with($dir, $projectDir . '/') ? \substr($dir, 1 + \strlen($projectDir)) : $dir; }, $scannedDirectories)]]); $translator->replaceArgument(4, $options); } if ($config['pseudo_localization']['enabled']) { $options = $config['pseudo_localization']; unset($options['enabled']); $container->register('translator.pseudo', PseudoLocalizationTranslator::class)->setDecoratedService('translator', null, -1)->setArguments([new Reference('translator.pseudo.inner'), $options]); } $classToServices = [CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', LocoProviderFactory::class => 'translation.provider_factory.loco', LokaliseProviderFactory::class => 'translation.provider_factory.lokalise']; $parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client']; foreach ($classToServices as $class => $service) { $package = \substr($service, \strlen('translation.provider_factory.')); if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(\sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages, \true)) { $container->removeDefinition($service); } } if (!$config['providers']) { return; } // @deprecated since Symfony 5.4, in 6.0 change to: // $locales = $enabledLocales; $locales = $config['enabled_locales'] ?: $enabledLocales; foreach ($config['providers'] as $provider) { if ($provider['locales']) { $locales += $provider['locales']; } } $locales = \array_unique($locales); $container->getDefinition('console.command.translation_pull')->replaceArgument(4, \array_merge($transPaths, [$config['default_path']]))->replaceArgument(5, $locales); $container->getDefinition('console.command.translation_push')->replaceArgument(2, \array_merge($transPaths, [$config['default_path']]))->replaceArgument(3, $locales); $container->getDefinition('translation.provider_collection_factory')->replaceArgument(1, $locales); $container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']); } private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled) { if (!($this->validatorConfigEnabled = $this->isConfigEnabled($container, $config))) { $container->removeDefinition('console.command.validator_debug'); return; } if (!\class_exists(Validation::class)) { throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".'); } if (!isset($config['email_validation_mode'])) { $config['email_validation_mode'] = 'loose'; } $loader->load('validator.php'); $validatorBuilder = $container->getDefinition('validator.builder'); $container->setParameter('validator.translation_domain', $config['translation_domain']); $files = ['xml' => [], 'yml' => []]; $this->registerValidatorMapping($container, $config, $files); if (!empty($files['xml'])) { $validatorBuilder->addMethodCall('addXmlMappings', [$files['xml']]); } if (!empty($files['yml'])) { $validatorBuilder->addMethodCall('addYamlMappings', [$files['yml']]); } $definition = $container->findDefinition('validator.email'); $definition->replaceArgument(0, $config['email_validation_mode']); if (\array_key_exists('enable_annotations', $config) && $config['enable_annotations']) { if (!$this->annotationsConfigEnabled && \PHP_VERSION_ID < 80000) { throw new \LogicException('"enable_annotations" on the validator cannot be set as the PHP version is lower than 8 and Doctrine Annotations support is disabled. Consider upgrading PHP.'); } $validatorBuilder->addMethodCall('enableAnnotationMapping', [\true]); if ($this->annotationsConfigEnabled) { $validatorBuilder->addMethodCall('setDoctrineAnnotationReader', [new Reference('annotation_reader')]); } } if (\array_key_exists('static_method', $config) && $config['static_method']) { foreach ($config['static_method'] as $methodName) { $validatorBuilder->addMethodCall('addMethodMapping', [$methodName]); } } if (!$container->getParameter('kernel.debug')) { $validatorBuilder->addMethodCall('setMappingCache', [new Reference('validator.mapping.cache.adapter')]); } $container->setParameter('validator.auto_mapping', $config['auto_mapping']); if (!$propertyInfoEnabled || !\class_exists(PropertyInfoLoader::class)) { $container->removeDefinition('validator.property_info_loader'); } $container->getDefinition('validator.not_compromised_password')->setArgument(2, $config['not_compromised_password']['enabled'])->setArgument(3, $config['not_compromised_password']['endpoint']); if (!\class_exists(ExpressionLanguage::class)) { $container->removeDefinition('validator.expression_language'); } } private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files) { $fileRecorder = function ($extension, $path) use(&$files) { $files['yaml' === $extension ? 'yml' : $extension][] = $path; }; if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'], \true)) { $reflClass = new \ReflectionClass(Form::class); $fileRecorder('xml', \dirname($reflClass->getFileName()) . '/Resources/config/validation.xml'); } foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { $configDir = \is_dir($bundle['path'] . '/Resources/config') ? $bundle['path'] . '/Resources/config' : $bundle['path'] . '/config'; if ($container->fileExists($file = $configDir . '/validation.yaml', \false) || $container->fileExists($file = $configDir . '/validation.yml', \false)) { $fileRecorder('yml', $file); } if ($container->fileExists($file = $configDir . '/validation.xml', \false)) { $fileRecorder('xml', $file); } if ($container->fileExists($dir = $configDir . '/validation', '/^$/')) { $this->registerMappingFilesFromDir($dir, $fileRecorder); } } $projectDir = $container->getParameter('kernel.project_dir'); if ($container->fileExists($dir = $projectDir . '/config/validator', '/^$/')) { $this->registerMappingFilesFromDir($dir, $fileRecorder); } $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); } private function registerMappingFilesFromDir(string $dir, callable $fileRecorder) { foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\\.(xml|ya?ml)$/')->sortByName() as $file) { $fileRecorder($file->getExtension(), $file->getRealPath()); } } private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder) { foreach ($config['mapping']['paths'] as $path) { if (\is_dir($path)) { $this->registerMappingFilesFromDir($path, $fileRecorder); $container->addResource(new DirectoryResource($path, '/^$/')); } elseif ($container->fileExists($path, \false)) { if (!\preg_match('/\\.(xml|ya?ml)$/', $path, $matches)) { throw new \RuntimeException(\sprintf('Unsupported mapping type in "%s", supported types are XML & Yaml.', $path)); } $fileRecorder($matches[1], $path); } else { throw new \RuntimeException(\sprintf('Could not open file or directory "%s".', $path)); } } } private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) { if (!$this->annotationsConfigEnabled) { return; } if (!\class_exists(\_ContaoManager\Doctrine\Common\Annotations\Annotation::class)) { throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed. Try running "composer require doctrine/annotations".'); } $loader->load('annotations.php'); // registerUniqueLoader exists since doctrine/annotations v1.6 if (!\method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { // registerLoader exists only in doctrine/annotations v1 if (\method_exists(AnnotationRegistry::class, 'registerLoader')) { $container->getDefinition('annotations.dummy_registry')->setMethodCalls([['registerLoader', ['class_exists']]]); } else { // remove the dummy registry when doctrine/annotations v2 is used $container->removeDefinition('annotations.dummy_registry'); } } if ('none' === $config['cache']) { $container->removeDefinition('annotations.cached_reader'); return; } $cacheService = $config['cache']; if (\in_array($config['cache'], ['php_array', 'file'])) { if ('php_array' === $config['cache']) { $cacheService = 'annotations.cache_adapter'; // Enable warmer only if PHP array is used for cache $definition = $container->findDefinition('annotations.cache_warmer'); $definition->addTag('kernel.cache_warmer'); } elseif ('file' === $config['cache']) { $cacheService = 'annotations.filesystem_cache_adapter'; $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); if (!\is_dir($cacheDir) && \false === @\mkdir($cacheDir, 0777, \true) && !\is_dir($cacheDir)) { throw new \RuntimeException(\sprintf('Could not create cache directory "%s".', $cacheDir)); } $container->getDefinition('annotations.filesystem_cache_adapter')->replaceArgument(2, $cacheDir); } } else { \trigger_deprecation('symfony/framework-bundle', '5.3', 'Using a custom service for "framework.annotation.cache" is deprecated, only values "none", "php_array" and "file" are valid in version 6.0.'); } $container->getDefinition('annotations.cached_reader')->replaceArgument(2, $config['debug'])->addArgument(new ServiceClosureArgument(new Reference($cacheService))); $container->setAlias('annotation_reader', 'annotations.cached_reader'); $container->setAlias(Reader::class, new Alias('annotations.cached_reader', \false)); $container->removeDefinition('annotations.psr_cached_reader'); } private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!($this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config))) { return; } $loader->load('property_access.php'); $magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS; $magicMethods |= $config['magic_call'] ? PropertyAccessor::MAGIC_CALL : 0; $magicMethods |= $config['magic_get'] ? PropertyAccessor::MAGIC_GET : 0; $magicMethods |= $config['magic_set'] ? PropertyAccessor::MAGIC_SET : 0; $throw = PropertyAccessor::DO_NOT_THROW; $throw |= $config['throw_exception_on_invalid_index'] ? PropertyAccessor::THROW_ON_INVALID_INDEX : 0; $throw |= $config['throw_exception_on_invalid_property_path'] ? PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH : 0; $container->getDefinition('property_accessor')->replaceArgument(0, $magicMethods)->replaceArgument(1, $throw)->replaceArgument(3, new Reference(PropertyReadInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))->replaceArgument(4, new Reference(PropertyWriteInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)); } private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('console.command.secrets_set'); $container->removeDefinition('console.command.secrets_list'); $container->removeDefinition('console.command.secrets_remove'); $container->removeDefinition('console.command.secrets_generate_key'); $container->removeDefinition('console.command.secrets_decrypt_to_local'); $container->removeDefinition('console.command.secrets_encrypt_from_local'); return; } $loader->load('secrets.php'); $container->getDefinition('secrets.vault')->replaceArgument(0, $config['vault_directory']); if ($config['local_dotenv_file']) { $container->getDefinition('secrets.local_vault')->replaceArgument(0, $config['local_dotenv_file']); } else { $container->removeDefinition('secrets.local_vault'); } if ($config['decryption_env_var']) { if (!\preg_match('/^(?:[-.\\w]*+:)*+\\w++$/', $config['decryption_env_var'])) { throw new InvalidArgumentException(\sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); } if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'], \true)) { $container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']); } else { $container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%"); $container->removeDefinition('secrets.decryption_key'); } } else { $container->getDefinition('secrets.vault')->replaceArgument(1, null); $container->removeDefinition('secrets.decryption_key'); } } private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { return; } if (!\class_exists(\_ContaoManager\Symfony\Component\Security\Csrf\CsrfToken::class)) { throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".'); } if (!$this->sessionConfigEnabled) { throw new \LogicException('CSRF protection needs sessions to be enabled.'); } // Enable services for CSRF protection (even without forms) $loader->load('security_csrf.php'); if (!\class_exists(CsrfExtension::class)) { $container->removeDefinition('twig.extension.security_csrf'); } } private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('serializer.php'); $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); if (!$this->propertyAccessConfigEnabled) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } if (!\class_exists(Yaml::class)) { $container->removeDefinition('serializer.encoder.yaml'); } if (!\class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) { $container->removeDefinition('serializer.denormalizer.unwrapping'); } if (!\class_exists(Headers::class)) { $container->removeDefinition('serializer.normalizer.mime_message'); } if ($container->getParameter('kernel.debug')) { $container->removeDefinition('serializer.mapping.cache_class_metadata_factory'); } $serializerLoaders = []; if (isset($config['enable_annotations']) && $config['enable_annotations']) { if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) { throw new \LogicException('"enable_annotations" on the serializer cannot be set as the PHP version is lower than 8 and Annotations support is disabled. Consider upgrading PHP.'); } $annotationLoader = new Definition('_ContaoManager\\Symfony\\Component\\Serializer\\Mapping\\Loader\\AnnotationLoader', [new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)]); $annotationLoader->setPublic(\false); $serializerLoaders[] = $annotationLoader; } $fileRecorder = function ($extension, $path) use(&$serializerLoaders) { $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? 'Symfony\\Component\\Serializer\\Mapping\\Loader\\YamlFileLoader' : 'Symfony\\Component\\Serializer\\Mapping\\Loader\\XmlFileLoader', [$path]); $definition->setPublic(\false); $serializerLoaders[] = $definition; }; foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { $configDir = \is_dir($bundle['path'] . '/Resources/config') ? $bundle['path'] . '/Resources/config' : $bundle['path'] . '/config'; if ($container->fileExists($file = $configDir . '/serialization.xml', \false)) { $fileRecorder('xml', $file); } if ($container->fileExists($file = $configDir . '/serialization.yaml', \false) || $container->fileExists($file = $configDir . '/serialization.yml', \false)) { $fileRecorder('yml', $file); } if ($container->fileExists($dir = $configDir . '/serialization', '/^$/')) { $this->registerMappingFilesFromDir($dir, $fileRecorder); } } $projectDir = $container->getParameter('kernel.project_dir'); if ($container->fileExists($dir = $projectDir . '/config/serializer', '/^$/')) { $this->registerMappingFilesFromDir($dir, $fileRecorder); } $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); $chainLoader->replaceArgument(0, $serializerLoaders); $container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders); if (isset($config['name_converter']) && $config['name_converter']) { $container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter'])); } $defaultContext = $config['default_context'] ?? []; if ($defaultContext) { $container->setParameter('serializer.default_context', $defaultContext); } if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) { $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments(); $context = ($arguments[6] ?? $defaultContext) + ['circular_reference_handler' => new Reference($config['circular_reference_handler'])]; $container->getDefinition('serializer.normalizer.object')->setArgument(5, null); $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context); } if ($config['max_depth_handler'] ?? \false) { $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments(); $context = ($arguments[6] ?? $defaultContext) + ['max_depth_handler' => new Reference($config['max_depth_handler'])]; $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context); } } private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader) { if (!\interface_exists(PropertyInfoExtractorInterface::class)) { throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".'); } $loader->load('property_info.php'); if (ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info'], \true) && ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info'], \true)) { $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class); $definition->addTag('property_info.type_extractor', ['priority' => -1000]); } if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], \true)) { $definition = $container->register('property_info.php_doc_extractor', '_ContaoManager\\Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor'); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); } if ($container->getParameter('kernel.debug')) { $container->removeDefinition('property_info.cache'); } } private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('lock.php'); foreach ($config['resources'] as $resourceName => $resourceStores) { if (0 === \count($resourceStores)) { continue; } // Generate stores $storeDefinitions = []; foreach ($resourceStores as $resourceStore) { $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); $storeDefinition = new Definition(\interface_exists(StoreInterface::class) ? StoreInterface::class : PersistingStoreInterface::class); $storeDefinition->setFactory([StoreFactory::class, 'createStore']); $storeDefinition->setArguments([$resourceStore]); $container->setDefinition($storeDefinitionId = '.lock.' . $resourceName . '.store.' . $container->hash($storeDsn), $storeDefinition); $storeDefinition = new Reference($storeDefinitionId); $storeDefinitions[] = $storeDefinition; } // Wrap array of stores with CombinedStore if (\count($storeDefinitions) > 1) { $combinedDefinition = new ChildDefinition('lock.store.combined.abstract'); $combinedDefinition->replaceArgument(0, $storeDefinitions); $container->setDefinition('lock.' . $resourceName . '.store', $combinedDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.' . $resourceName . '.factory" instead.'); $container->setDefinition($storeDefinitionId = '.lock.' . $resourceName . '.store.' . $container->hash($resourceStores), $combinedDefinition); } else { $container->setAlias('lock.' . $resourceName . '.store', (new Alias($storeDefinitionId, \false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.' . $resourceName . '.factory" instead.')); } // Generate factories for each resource $factoryDefinition = new ChildDefinition('lock.factory.abstract'); $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); $container->setDefinition('lock.' . $resourceName . '.factory', $factoryDefinition); // Generate services for lock instances $lockDefinition = new Definition(Lock::class); $lockDefinition->setPublic(\false); $lockDefinition->setFactory([new Reference('lock.' . $resourceName . '.factory'), 'createLock']); $lockDefinition->setArguments([$resourceName]); $container->setDefinition('lock.' . $resourceName, $lockDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.' . $resourceName . '.factory" instead.'); // provide alias for default resource if ('default' === $resourceName) { $container->setAlias('lock.store', (new Alias($storeDefinitionId, \false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.')); $container->setAlias('lock.factory', new Alias('lock.' . $resourceName . '.factory', \false)); $container->setAlias('lock', (new Alias('lock.' . $resourceName, \false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.')); $container->setAlias(PersistingStoreInterface::class, (new Alias($storeDefinitionId, \false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "' . LockFactory::class . '" instead.')); $container->setAlias(LockFactory::class, new Alias('lock.factory', \false)); $container->setAlias(LockInterface::class, (new Alias('lock.' . $resourceName, \false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "' . LockFactory::class . '" instead.')); } else { $container->registerAliasForArgument($storeDefinitionId, PersistingStoreInterface::class, $resourceName . '.lock.store')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "' . LockFactory::class . ' ' . $resourceName . 'LockFactory" instead.'); $container->registerAliasForArgument('lock.' . $resourceName . '.factory', LockFactory::class, $resourceName . '.lock.factory'); $container->registerAliasForArgument('lock.' . $resourceName, LockInterface::class, $resourceName . '.lock')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "' . LockFactory::class . ' $' . $resourceName . 'LockFactory" instead.'); } } } private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig) { if (!\interface_exists(MessageBusInterface::class)) { throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); } $loader->load('messenger.php'); if (!\interface_exists(DenormalizerInterface::class)) { $container->removeDefinition('serializer.normalizer.flatten_exception'); } if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], \true)) { $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); } if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], \true)) { $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); } if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], \true)) { $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); } if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], \true)) { $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); } if (null === $config['default_bus'] && 1 === \count($config['buses'])) { $config['default_bus'] = \key($config['buses']); } $defaultMiddleware = ['before' => [['id' => 'add_bus_name_stamp_middleware'], ['id' => 'reject_redelivered_message_middleware'], ['id' => 'dispatch_after_current_bus'], ['id' => 'failed_message_processing_middleware']], 'after' => [['id' => 'send_message'], ['id' => 'handle_message']]]; foreach ($config['buses'] as $busId => $bus) { $middleware = $bus['middleware']; if ($bus['default_middleware']) { if ('allow_no_handlers' === $bus['default_middleware']) { $defaultMiddleware['after'][1]['arguments'] = [\true]; } else { unset($defaultMiddleware['after'][1]['arguments']); } // argument to add_bus_name_stamp_middleware $defaultMiddleware['before'][0]['arguments'] = [$busId]; $middleware = \array_merge($defaultMiddleware['before'], $middleware, $defaultMiddleware['after']); } foreach ($middleware as $middlewareItem) { if (!$validationConfig['enabled'] && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], \true)) { throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".'); } } if ($container->getParameter('kernel.debug') && \class_exists(Stopwatch::class)) { \array_unshift($middleware, ['id' => 'traceable', 'arguments' => [$busId]]); } $container->setParameter($busId . '.middleware', $middleware); $container->register($busId, MessageBus::class)->addArgument([])->addTag('messenger.bus'); if ($busId === $config['default_bus']) { $container->setAlias('messenger.default_bus', $busId)->setPublic(\true); $container->setAlias(MessageBusInterface::class, $busId); } else { $container->registerAliasForArgument($busId, MessageBusInterface::class); } } if (empty($config['transports'])) { $container->removeDefinition('messenger.transport.symfony_serializer'); $container->removeDefinition('messenger.transport.amqp.factory'); $container->removeDefinition('messenger.transport.redis.factory'); $container->removeDefinition('messenger.transport.sqs.factory'); $container->removeDefinition('messenger.transport.beanstalkd.factory'); $container->removeAlias(SerializerInterface::class); } else { $container->getDefinition('messenger.transport.symfony_serializer')->replaceArgument(1, $config['serializer']['symfony_serializer']['format'])->replaceArgument(2, $config['serializer']['symfony_serializer']['context']); $container->setAlias('messenger.default_serializer', $config['serializer']['default_serializer']); } $failureTransports = []; if ($config['failure_transport']) { if (!isset($config['transports'][$config['failure_transport']])) { throw new LogicException(\sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $config['failure_transport'])); } $container->setAlias('messenger.failure_transports.default', 'messenger.transport.' . $config['failure_transport']); $failureTransports[] = $config['failure_transport']; } $failureTransportsByName = []; foreach ($config['transports'] as $name => $transport) { if ($transport['failure_transport']) { $failureTransports[] = $transport['failure_transport']; $failureTransportsByName[$name] = $transport['failure_transport']; } elseif ($config['failure_transport']) { $failureTransportsByName[$name] = $config['failure_transport']; } } $senderAliases = []; $transportRetryReferences = []; foreach ($config['transports'] as $name => $transport) { $serializerId = $transport['serializer'] ?? 'messenger.default_serializer'; $transportDefinition = (new Definition(TransportInterface::class))->setFactory([new Reference('messenger.transport_factory'), 'createTransport'])->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)])->addTag('messenger.receiver', ['alias' => $name, 'is_failure_transport' => \in_array($name, $failureTransports)]); $container->setDefinition($transportId = 'messenger.transport.' . $name, $transportDefinition); $senderAliases[$name] = $transportId; if (null !== $transport['retry_strategy']['service']) { $transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']); } else { $retryServiceId = \sprintf('messenger.retry.multiplier_retry_strategy.%s', $name); $retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy'); $retryDefinition->replaceArgument(0, $transport['retry_strategy']['max_retries'])->replaceArgument(1, $transport['retry_strategy']['delay'])->replaceArgument(2, $transport['retry_strategy']['multiplier'])->replaceArgument(3, $transport['retry_strategy']['max_delay']); $container->setDefinition($retryServiceId, $retryDefinition); $transportRetryReferences[$name] = new Reference($retryServiceId); } } $senderReferences = []; // alias => service_id foreach ($senderAliases as $alias => $serviceId) { $senderReferences[$alias] = new Reference($serviceId); } // service_id => service_id foreach ($senderAliases as $serviceId) { $senderReferences[$serviceId] = new Reference($serviceId); } foreach ($config['transports'] as $name => $transport) { if ($transport['failure_transport']) { if (!isset($senderReferences[$transport['failure_transport']])) { throw new LogicException(\sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $transport['failure_transport'])); } } } $failureTransportReferencesByTransportName = \array_map(function ($failureTransportName) use($senderReferences) { return $senderReferences[$failureTransportName]; }, $failureTransportsByName); $messageToSendersMapping = []; foreach ($config['routing'] as $message => $messageConfiguration) { if ('*' !== $message && !\class_exists($message) && !\interface_exists($message, \false)) { throw new LogicException(\sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message)); } // make sure senderAliases contains all senders foreach ($messageConfiguration['senders'] as $sender) { if (!isset($senderReferences[$sender])) { throw new LogicException(\sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender)); } } $messageToSendersMapping[$message] = $messageConfiguration['senders']; } $sendersServiceLocator = ServiceLocatorTagPass::register($container, $senderReferences); $container->getDefinition('messenger.senders_locator')->replaceArgument(0, $messageToSendersMapping)->replaceArgument(1, $sendersServiceLocator); $container->getDefinition('messenger.retry.send_failed_message_for_retry_listener')->replaceArgument(0, $sendersServiceLocator); $container->getDefinition('messenger.retry_strategy_locator')->replaceArgument(0, $transportRetryReferences); if (\count($failureTransports) > 0) { if ($this->hasConsole()) { $container->getDefinition('console.command.messenger_failed_messages_retry')->replaceArgument(0, $config['failure_transport']); $container->getDefinition('console.command.messenger_failed_messages_show')->replaceArgument(0, $config['failure_transport']); $container->getDefinition('console.command.messenger_failed_messages_remove')->replaceArgument(0, $config['failure_transport']); } $failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName); $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener')->replaceArgument(0, $failureTransportsByTransportNameServiceLocator); } else { $container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener'); $container->removeDefinition('console.command.messenger_failed_messages_retry'); $container->removeDefinition('console.command.messenger_failed_messages_show'); $container->removeDefinition('console.command.messenger_failed_messages_remove'); } if (\false === $config['reset_on_message']) { throw new LogicException('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.'); } if (!$container->hasDefinition('console.command.messenger_consume_messages')) { $container->removeDefinition('messenger.listener.reset_services'); } elseif (null === $config['reset_on_message']) { \trigger_deprecation('symfony/framework-bundle', '5.4', 'Not setting the "framework.messenger.reset_on_message" configuration option is deprecated, it will default to "true" in version 6.0.'); $container->getDefinition('console.command.messenger_consume_messages')->replaceArgument(5, null); $container->removeDefinition('messenger.listener.reset_services'); } } private function registerCacheConfiguration(array $config, ContainerBuilder $container) { if (!\class_exists(DefaultMarshaller::class)) { $container->removeDefinition('cache.default_marshaller'); } if (!\class_exists(DoctrineAdapter::class)) { $container->removeDefinition('cache.adapter.doctrine'); } if (!\class_exists(DoctrineDbalAdapter::class)) { $container->removeDefinition('cache.adapter.doctrine_dbal'); } $version = new Parameter('container.build_id'); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); if (isset($config['prefix_seed'])) { $container->setParameter('cache.prefix.seed', $config['prefix_seed']); } if ($container->hasParameter('cache.prefix.seed')) { // Inline any env vars referenced in the parameter $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), \true)); } foreach (['doctrine', 'psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) { if (isset($config[$name = 'default_' . $name . '_provider'])) { $container->setAlias('cache.' . $name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), \false)); } } foreach (['app', 'system'] as $name) { $config['pools']['cache.' . $name] = ['adapters' => [$config[$name]], 'public' => \true, 'tags' => \false]; } foreach ($config['pools'] as $name => $pool) { $pool['adapters'] = $pool['adapters'] ?: ['cache.app']; $isRedisTagAware = ['cache.adapter.redis_tag_aware'] === $pool['adapters']; foreach ($pool['adapters'] as $provider => $adapter) { if (($config['pools'][$adapter]['adapters'] ?? null) === ['cache.adapter.redis_tag_aware']) { $isRedisTagAware = \true; } elseif ($config['pools'][$adapter]['tags'] ?? \false) { $pool['adapters'][$provider] = $adapter = '.' . $adapter . '.inner'; } } if (1 === \count($pool['adapters'])) { if (!isset($pool['provider']) && !\is_int($provider)) { $pool['provider'] = $provider; } $definition = new ChildDefinition($adapter); } else { $definition = new Definition(ChainAdapter::class, [$pool['adapters'], 0]); $pool['reset'] = 'reset'; } if ($isRedisTagAware && 'cache.app' === $name) { $container->setAlias('cache.app.taggable', $name); } elseif ($isRedisTagAware) { $tagAwareId = $name; $container->setAlias('.' . $name . '.inner', $name); } elseif ($pool['tags']) { if (\true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? \false)) { $pool['tags'] = '.' . $pool['tags'] . '.inner'; } $container->register($name, TagAwareAdapter::class)->addArgument(new Reference('.' . $name . '.inner'))->addArgument(\true !== $pool['tags'] ? new Reference($pool['tags']) : null)->setPublic($pool['public']); if (\method_exists(TagAwareAdapter::class, 'setLogger')) { $container->getDefinition($name)->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])->addTag('monolog.logger', ['channel' => 'cache']); } $pool['name'] = $tagAwareId = $name; $pool['public'] = \false; $name = '.' . $name . '.inner'; } elseif (!\in_array($name, ['cache.app', 'cache.system'], \true)) { $tagAwareId = '.' . $name . '.taggable'; $container->register($tagAwareId, TagAwareAdapter::class)->addArgument(new Reference($name)); } if (!\in_array($name, ['cache.app', 'cache.system'], \true)) { $container->registerAliasForArgument($tagAwareId, TagAwareCacheInterface::class, $pool['name'] ?? $name); $container->registerAliasForArgument($name, CacheInterface::class, $pool['name'] ?? $name); $container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name'] ?? $name); } $definition->setPublic($pool['public']); unset($pool['adapters'], $pool['public'], $pool['tags']); $definition->addTag('cache.pool', $pool); $container->setDefinition($name, $definition); } if (\method_exists(PropertyAccessor::class, 'createCache')) { $propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class); $propertyAccessDefinition->setPublic(\false); if (!$container->getParameter('kernel.debug')) { $propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']); $propertyAccessDefinition->setArguments(['', 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); $propertyAccessDefinition->addTag('cache.pool', ['clearer' => 'cache.system_clearer']); $propertyAccessDefinition->addTag('monolog.logger', ['channel' => 'cache']); } else { $propertyAccessDefinition->setClass(ArrayAdapter::class); $propertyAccessDefinition->setArguments([0, \false]); } } } private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $profilerConfig) { $loader->load('http_client.php'); $options = $config['default_options'] ?? []; $retryOptions = $options['retry_failed'] ?? ['enabled' => \false]; unset($options['retry_failed']); $container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]); if (!($hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'], \true))) { $container->removeDefinition('psr18.http_client'); $container->removeAlias(ClientInterface::class); } if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'], \true)) { $container->removeDefinition(HttpClient::class); } if ($this->isConfigEnabled($container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); } $httpClientId = $retryOptions['enabled'] ?? \false ? 'http_client.retryable.inner' : ($this->isConfigEnabled($container, $profilerConfig) ? '.debug.http_client.inner' : 'http_client'); foreach ($config['scoped_clients'] as $name => $scopeConfig) { if ('http_client' === $name) { throw new InvalidArgumentException(\sprintf('Invalid scope name: "%s" is reserved.', $name)); } $scope = $scopeConfig['scope'] ?? null; unset($scopeConfig['scope']); $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => \false]; unset($scopeConfig['retry_failed']); if (null === $scope) { $baseUri = $scopeConfig['base_uri']; unset($scopeConfig['base_uri']); $container->register($name, ScopingHttpClient::class)->setFactory([ScopingHttpClient::class, 'forBaseUri'])->setArguments([new Reference($httpClientId), $baseUri, $scopeConfig])->addTag('http_client.client'); } else { $container->register($name, ScopingHttpClient::class)->setArguments([new Reference($httpClientId), [$scope => $scopeConfig], $scope])->addTag('http_client.client'); } if ($this->isConfigEnabled($container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, $name, $container); } $container->registerAliasForArgument($name, HttpClientInterface::class); if ($hasPsr18) { $container->setDefinition('psr18.' . $name, new ChildDefinition('psr18.http_client'))->replaceArgument(0, new Reference($name)); $container->registerAliasForArgument('psr18.' . $name, ClientInterface::class, $name); } } if ($responseFactoryId = $config['mock_response_factory'] ?? null) { $container->register($httpClientId . '.mock_client', MockHttpClient::class)->setDecoratedService($httpClientId, null, -10)->setArguments([new Reference($responseFactoryId)]); } } private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container) { if (!\class_exists(RetryableHttpClient::class)) { throw new LogicException('Support for retrying failed requests requires symfony/http-client 5.2 or higher, try upgrading.'); } if (null !== $options['retry_strategy']) { $retryStrategy = new Reference($options['retry_strategy']); } else { $retryStrategy = new ChildDefinition('http_client.abstract_retry_strategy'); $codes = []; foreach ($options['http_codes'] as $code => $codeOptions) { if ($codeOptions['methods']) { $codes[$code] = $codeOptions['methods']; } else { $codes[] = $code; } } $retryStrategy->replaceArgument(0, $codes ?: GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES)->replaceArgument(1, $options['delay'])->replaceArgument(2, $options['multiplier'])->replaceArgument(3, $options['max_delay'])->replaceArgument(4, $options['jitter']); $container->setDefinition($name . '.retry_strategy', $retryStrategy); $retryStrategy = new Reference($name . '.retry_strategy'); } $container->register($name . '.retryable', RetryableHttpClient::class)->setDecoratedService($name, null, 10)->setArguments([new Reference($name . '.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')])->addTag('monolog.logger', ['channel' => 'http_client']); } private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!\class_exists(Mailer::class)) { throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".'); } $loader->load('mailer.php'); $loader->load('mailer_transports.php'); if (!\count($config['transports']) && null === $config['dsn']) { $config['dsn'] = 'smtp://null'; } $transports = $config['dsn'] ? ['main' => $config['dsn']] : $config['transports']; $container->getDefinition('mailer.transports')->setArgument(0, $transports); $container->getDefinition('mailer.default_transport')->setArgument(0, \current($transports)); $container->removeDefinition('mailer.logger_message_listener'); $container->setAlias('mailer.logger_message_listener', (new Alias('mailer.message_logger_listener'))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "mailer.message_logger_listener" instead.')); $mailer = $container->getDefinition('mailer.mailer'); if (\false === ($messageBus = $config['message_bus'])) { $mailer->replaceArgument(1, null); } else { $mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); } $classToServices = [GmailTransportFactory::class => 'mailer.transport_factory.gmail', MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp', PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue', SesTransportFactory::class => 'mailer.transport_factory.amazon']; foreach ($classToServices as $class => $service) { $package = \substr($service, \strlen('mailer.transport_factory.')); if (!ContainerBuilder::willBeAvailable(\sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'], \true)) { $container->removeDefinition($service); } } $envelopeListener = $container->getDefinition('mailer.envelope_listener'); $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); if ($config['headers']) { $headers = new Definition(Headers::class); foreach ($config['headers'] as $name => $data) { $value = $data['value']; if (\in_array(\strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) { $value = (array) $value; } $headers->addMethodCall('addHeader', [$name, $value]); } $messageListener = $container->getDefinition('mailer.message_listener'); $messageListener->setArgument(0, $headers); } else { $container->removeDefinition('mailer.message_listener'); } } private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!\class_exists(Notifier::class)) { throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".'); } $loader->load('notifier.php'); $loader->load('notifier_transports.php'); if ($config['chatter_transports']) { $container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']); } else { $container->removeDefinition('chatter'); $container->removeAlias(ChatterInterface::class); } if ($config['texter_transports']) { $container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']); } else { $container->removeDefinition('texter'); $container->removeAlias(TexterInterface::class); } if ($this->mailerConfigEnabled) { $sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0); $container->getDefinition('notifier.channel.email')->setArgument(2, $sender); } else { $container->removeDefinition('notifier.channel.email'); } if ($this->messengerConfigEnabled) { if ($config['notification_on_failed_messages']) { $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); } // as we have a bus, the channels don't need the transports $container->getDefinition('notifier.channel.chat')->setArgument(0, null); if ($container->hasDefinition('notifier.channel.email')) { $container->getDefinition('notifier.channel.email')->setArgument(0, null); } $container->getDefinition('notifier.channel.sms')->setArgument(0, null); $container->getDefinition('notifier.channel.push')->setArgument(0, null); } $container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']); $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class)->addTag('chatter.transport_factory'); $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class)->addTag('texter.transport_factory'); $classToServices = [AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', DiscordTransportFactory::class => 'notifier.transport_factory.discord', EsendexTransportFactory::class => 'notifier.transport_factory.esendex', ExpoTransportFactory::class => 'notifier.transport_factory.expo', FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', GitterTransportFactory::class => 'notifier.transport_factory.gitter', GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', InfobipTransportFactory::class => 'notifier.transport_factory.infobip', IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', MercureTransportFactory::class => 'notifier.transport_factory.mercure', MessageBirdTransportFactory::class => 'notifier.transport_factory.message-bird', MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', MobytTransportFactory::class => 'notifier.transport_factory.mobyt', NexmoTransportFactory::class => 'notifier.transport_factory.nexmo', OctopushTransportFactory::class => 'notifier.transport_factory.octopush', OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', SinchTransportFactory::class => 'notifier.transport_factory.sinch', SlackTransportFactory::class => 'notifier.transport_factory.slack', Sms77TransportFactory::class => 'notifier.transport_factory.sms77', SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', SmscTransportFactory::class => 'notifier.transport_factory.smsc', SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', TelegramTransportFactory::class => 'notifier.transport_factory.telegram', TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', TurboSmsTransportFactory::class => 'notifier.transport_factory.turbo-sms', TwilioTransportFactory::class => 'notifier.transport_factory.twilio', VonageTransportFactory::class => 'notifier.transport_factory.vonage', YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', ZulipTransportFactory::class => 'notifier.transport_factory.zulip']; $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; foreach ($classToServices as $class => $service) { $package = \substr($service, \strlen('notifier.transport_factory.')); if (!ContainerBuilder::willBeAvailable(\sprintf('symfony/%s-notifier', $package), $class, $parentPackages, \true)) { $container->removeDefinition($service); $container->removeAlias(\str_replace('-', '', $service)); // @deprecated to be removed in 6.0 } } if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, \true) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages, \true) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), \true)) { $container->getDefinition($classToServices[MercureTransportFactory::class])->replaceArgument(0, new Reference(HubRegistry::class))->replaceArgument(1, new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, \true)) { $container->removeDefinition($classToServices[MercureTransportFactory::class]); } if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], \true)) { $container->getDefinition($classToServices[FakeChatTransportFactory::class])->replaceArgument(0, new Reference('mailer'))->replaceArgument(1, new Reference('logger'))->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); } if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], \true)) { $container->getDefinition($classToServices[FakeSmsTransportFactory::class])->replaceArgument(0, new Reference('mailer'))->replaceArgument(1, new Reference('logger'))->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); } if (isset($config['admin_recipients'])) { $notifier = $container->getDefinition('notifier'); foreach ($config['admin_recipients'] as $i => $recipient) { $id = 'notifier.admin_recipient.' . $i; $container->setDefinition($id, new Definition(Recipient::class, [$recipient['email'], $recipient['phone']])); $notifier->addMethodCall('addAdminRecipient', [new Reference($id)]); } } } private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('rate_limiter.php'); foreach ($config['limiters'] as $name => $limiterConfig) { self::registerRateLimiter($container, $name, $limiterConfig); } } public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) { // default configuration (when used by other DI extensions) $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; $limiter = $container->setDefinition($limiterId = 'limiter.' . $name, new ChildDefinition('limiter')); if (null !== $limiterConfig['lock_factory']) { if (!self::$lockConfigEnabled) { throw new LogicException(\sprintf('Rate limiter "%s" requires the Lock component to be installed and configured.', $name)); } $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); } unset($limiterConfig['lock_factory']); $storageId = $limiterConfig['storage_service'] ?? null; if (null === $storageId) { $container->register($storageId = 'limiter.storage.' . $name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); } $limiter->replaceArgument(1, new Reference($storageId)); unset($limiterConfig['storage_service']); unset($limiterConfig['cache_pool']); $limiterConfig['id'] = $name; $limiter->replaceArgument(0, $limiterConfig); $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name . '.limiter'); } private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('uid.php'); $container->getDefinition('uuid.factory')->setArguments([$config['default_uuid_version'], $config['time_based_uuid_version'], $config['name_based_uuid_version'], UuidV4::class, $config['time_based_uuid_node'] ?? null, $config['name_based_uuid_namespace'] ?? null]); if (isset($config['name_based_uuid_namespace'])) { $container->getDefinition('name_based_uuid.factory')->setArguments([$config['name_based_uuid_namespace']]); } } private function resolveTrustedHeaders(array $headers) : int { $trustedHeaders = 0; foreach ($headers as $h) { switch ($h) { case 'forwarded': $trustedHeaders |= Request::HEADER_FORWARDED; break; case 'x-forwarded-for': $trustedHeaders |= Request::HEADER_X_FORWARDED_FOR; break; case 'x-forwarded-host': $trustedHeaders |= Request::HEADER_X_FORWARDED_HOST; break; case 'x-forwarded-proto': $trustedHeaders |= Request::HEADER_X_FORWARDED_PROTO; break; case 'x-forwarded-port': $trustedHeaders |= Request::HEADER_X_FORWARDED_PORT; break; case 'x-forwarded-prefix': $trustedHeaders |= Request::HEADER_X_FORWARDED_PREFIX; break; } } return $trustedHeaders; } /** * {@inheritdoc} */ public function getXsdValidationBasePath() { return \dirname(__DIR__) . '/Resources/config/schema'; } public function getNamespace() { return 'http://symfony.com/schema/dic/symfony'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\Config\ConfigCache; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Dumper\XmlDumper; /** * Dumps the ContainerBuilder to a cache file so that it can be used by * debugging tools such as the debug:container console command. * * @author Ryan Weaver * @author Fabien Potencier */ class ContainerBuilderDebugDumpPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $cache = new ConfigCache($container->getParameter('debug.container.dump'), \true); if (!$cache->isFresh()) { $cache->write((new XmlDumper($container))->dump(), $container->getResources()); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Nicolas Grekas */ class TestServiceContainerRealRefPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('test.private_services_locator')) { return; } $privateContainer = $container->getDefinition('test.private_services_locator'); $definitions = $container->getDefinitions(); $privateServices = $privateContainer->getArgument(0); foreach ($privateServices as $id => $argument) { if (isset($definitions[$target = (string) $argument->getValues()[0]])) { $argument->setValues([new Reference($target)]); } else { unset($privateServices[$id]); } } foreach ($container->getAliases() as $id => $target) { while ($container->hasAlias($target = (string) $target)) { $target = $container->getAlias($target); } if ($definitions[$target]->hasTag('container.private')) { $privateServices[$id] = new ServiceClosureArgument(new Reference($target)); } } $privateContainer->replaceArgument(0, $privateServices); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Registers the expression language providers. * * @author Fabien Potencier */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { // routing if ($container->has('router.default')) { $definition = $container->findDefinition('router.default'); foreach ($container->findTaggedServiceIds('routing.expression_language_provider', \true) as $id => $attributes) { $definition->addMethodCall('addExpressionLanguageProvider', [new Reference($id)]); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Find all service tags which are defined, but not used and yield a warning log message. * * @author Florian Pfitzer */ class UnusedTagsPass implements CompilerPassInterface { private const KNOWN_TAGS = ['annotations.cached_reader', 'assets.package', 'auto_alias', 'cache.pool', 'cache.pool.clearer', 'chatter.transport_factory', 'config_cache.resource_checker', 'console.command', 'container.do_not_inline', 'container.env_var_loader', 'container.env_var_processor', 'container.hot_path', 'container.no_preload', 'container.preload', 'container.private', 'container.reversible', 'container.service_locator', 'container.service_locator_context', 'container.service_subscriber', 'container.stack', 'controller.argument_value_resolver', 'controller.service_arguments', 'data_collector', 'event_dispatcher.dispatcher', 'form.type', 'form.type_extension', 'form.type_guesser', 'http_client.client', 'kernel.cache_clearer', 'kernel.cache_warmer', 'kernel.event_listener', 'kernel.event_subscriber', 'kernel.fragment_renderer', 'kernel.locale_aware', 'kernel.reset', 'ldap', 'mailer.transport_factory', 'messenger.bus', 'messenger.message_handler', 'messenger.receiver', 'messenger.transport_factory', 'mime.mime_type_guesser', 'monolog.logger', 'notifier.channel', 'property_info.access_extractor', 'property_info.initializable_extractor', 'property_info.list_extractor', 'property_info.type_extractor', 'proxy', 'routing.expression_language_function', 'routing.expression_language_provider', 'routing.loader', 'routing.route_loader', 'security.authenticator.login_linker', 'security.expression_language_provider', 'security.remember_me_aware', 'security.remember_me_handler', 'security.voter', 'serializer.encoder', 'serializer.normalizer', 'texter.transport_factory', 'translation.dumper', 'translation.extractor', 'translation.loader', 'translation.provider_factory', 'twig.extension', 'twig.loader', 'twig.runtime', 'validator.auto_mapper', 'validator.constraint_validator', 'validator.initializer']; public function process(ContainerBuilder $container) { $tags = \array_unique(\array_merge($container->findTags(), self::KNOWN_TAGS)); foreach ($container->findUnusedTags() as $tag) { // skip known tags if (\in_array($tag, self::KNOWN_TAGS)) { continue; } // check for typos $candidates = []; foreach ($tags as $definedTag) { if ($definedTag === $tag) { continue; } if (\str_contains($definedTag, $tag) || \levenshtein($tag, $definedTag) <= \strlen($tag) / 3) { $candidates[] = $definedTag; } } $services = \array_keys($container->findTaggedServiceIds($tag)); $message = \sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, \implode('", "', $services)); if (!empty($candidates)) { $message .= \sprintf(' Did you mean "%s"?', \implode('", "', $candidates)); } $container->log($this, $message); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; class AssetsContextPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('assets.context')) { return; } if (!$container->hasDefinition('router.request_context')) { $container->setParameter('asset.request_context.base_path', $container->getParameter('asset.request_context.base_path') ?? ''); $container->setParameter('asset.request_context.secure', $container->getParameter('asset.request_context.secure') ?? \false); return; } $context = $container->getDefinition('assets.context'); if (null === $container->getParameter('asset.request_context.base_path')) { $context->replaceArgument(1, (new Definition('string'))->setFactory([new Reference('router.request_context'), 'getBaseUrl'])); } if (null === $container->getParameter('asset.request_context.secure')) { $context->replaceArgument(2, (new Definition('bool'))->setFactory([new Reference('router.request_context'), 'isSecure'])); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @internal */ class ErrorLoggerCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) : void { if (!$container->hasDefinition('debug.debug_handlers_listener')) { return; } $definition = $container->getDefinition('debug.debug_handlers_listener'); if ($container->hasDefinition('monolog.logger.php')) { $definition->replaceArgument(1, new Reference('monolog.logger.php')); } if ($container->hasDefinition('monolog.logger.deprecation')) { $definition->replaceArgument(6, new Reference('monolog.logger.deprecation')); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; /** * @author Christian Flothmann * @author Grégoire Pineau */ class WorkflowGuardListenerPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('workflow.has_guard_listeners')) { return; } $container->getParameterBag()->remove('workflow.has_guard_listeners'); $servicesNeeded = ['security.token_storage', 'security.authorization_checker', 'security.authentication.trust_resolver', 'security.role_hierarchy']; foreach ($servicesNeeded as $service) { if (!$container->has($service)) { throw new LogicException(\sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Nicolas Grekas */ class TestServiceContainerWeakRefPass implements CompilerPassInterface { private $privateTagName; public function __construct(string $privateTagName = 'container.private') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/framework-bundle', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->privateTagName = $privateTagName; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition('test.private_services_locator')) { return; } $privateServices = []; $definitions = $container->getDefinitions(); $hasErrors = \method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors'; foreach ($definitions as $id => $definition) { if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) && !$definition->{$hasErrors}() && !$definition->isAbstract()) { $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } $aliases = $container->getAliases(); foreach ($aliases as $id => $alias) { if ($id && '.' !== $id[0] && (!$alias->isPublic() || $alias->isPrivate())) { while (isset($aliases[$target = (string) $alias])) { $alias = $aliases[$target]; } if (isset($definitions[$target]) && !$definitions[$target]->{$hasErrors}() && !$definitions[$target]->isAbstract()) { $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } } if ($privateServices) { $id = (string) ServiceLocatorTagPass::register($container, $privateServices); $container->setDefinition('test.private_services_locator', $container->getDefinition($id))->setPublic(\true); $container->removeDefinition($id); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Translation\TranslatorBagInterface; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; /** * @author Abdellatif Ait boudad */ class LoggingTranslatorPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) { return; } if ($container->hasParameter('translator.logging') && $container->getParameter('translator.logging')) { $translatorAlias = $container->getAlias('translator'); $definition = $container->getDefinition((string) $translatorAlias); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias)); } if ($r->isSubclassOf(TranslatorInterface::class) && $r->isSubclassOf(TranslatorBagInterface::class)) { $container->getDefinition('translator.logging')->setDecoratedService('translator'); $warmer = $container->getDefinition('translation.warmer'); $subscriberAttributes = $warmer->getTag('container.service_subscriber'); $warmer->clearTag('container.service_subscriber'); foreach ($subscriberAttributes as $k => $v) { if ((!isset($v['id']) || 'translator' !== $v['id']) && (!isset($v['key']) || 'translator' !== $v['key'])) { $warmer->addTag('container.service_subscriber', $v); } } $warmer->addTag('container.service_subscriber', ['key' => 'translator', 'id' => 'translator.logging.inner']); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @internal to be removed in 6.0 */ class SessionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->has('session.factory')) { return; } // BC layer: Make "session" an alias of ".session.do-not-use" when not overridden by the user if (!$container->has('session')) { $alias = $container->setAlias('session', '.session.do-not-use'); $alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); // restore previous behavior $alias->setPublic(\true); return; } if ($container->hasDefinition('session')) { $definition = $container->getDefinition('session'); $definition->setDeprecated('symfony/framework-bundle', '5.3', 'The "%service_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); } else { $alias = $container->getAlias('session'); $alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.'); $definition = $container->findDefinition('session'); } // Convert internal service `.session.do-not-use` into alias of `session`. $container->setAlias('.session.do-not-use', 'session'); $bags = ['session.flash_bag' => $container->hasDefinition('session.flash_bag') ? $container->getDefinition('session.flash_bag') : null, 'session.attribute_bag' => $container->hasDefinition('session.attribute_bag') ? $container->getDefinition('session.attribute_bag') : null]; foreach ($definition->getArguments() as $v) { if (!$v instanceof Reference || !isset($bags[$bag = (string) $v]) || !\is_array($factory = $bags[$bag]->getFactory())) { continue; } if ([0, 1] !== \array_keys($factory) || !$factory[0] instanceof Reference || !\in_array((string) $factory[0], ['session', '.session.do-not-use'], \true)) { continue; } if ('get' . \ucfirst(\substr($bag, 8, -4)) . 'Bag' !== $factory[1]) { continue; } $bags[$bag]->setFactory(null); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * @author Christian Flothmann */ class DataCollectorTranslatorPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->has('translator')) { return; } $translatorClass = $container->getParameterBag()->resolveValue($container->findDefinition('translator')->getClass()); if (!\is_subclass_of($translatorClass, '_ContaoManager\\Symfony\\Component\\Translation\\TranslatorBagInterface')) { $container->removeDefinition('translator.data_collector'); $container->removeDefinition('data_collector.translation'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * @internal */ class AddAnnotationsCachedReaderPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { // "annotations.cached_reader" is wired late so that any passes using // "annotation_reader" at build time don't get any cache foreach ($container->findTaggedServiceIds('annotations.cached_reader') as $id => $tags) { $reader = $container->getDefinition($id); $properties = $reader->getProperties(); if (isset($properties['cacheProviderBackup'])) { $provider = $properties['cacheProviderBackup']->getValues()[0]; unset($properties['cacheProviderBackup']); $reader->setProperties($properties); $reader->replaceArgument(1, $provider); } elseif (4 <= \count($arguments = $reader->getArguments()) && $arguments[3] instanceof ServiceClosureArgument) { $arguments[1] = $arguments[3]->getValues()[0]; unset($arguments[3]); $reader->setArguments($arguments); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DataCollector\TemplateAwareDataCollectorInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Adds tagged data_collector services to profiler service. * * @author Fabien Potencier */ class ProfilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (\false === $container->hasDefinition('profiler')) { return; } $definition = $container->getDefinition('profiler'); $collectors = new \SplPriorityQueue(); $order = \PHP_INT_MAX; foreach ($container->findTaggedServiceIds('data_collector', \true) as $id => $attributes) { $priority = $attributes[0]['priority'] ?? 0; $template = null; $collectorClass = $container->findDefinition($id)->getClass(); $isTemplateAware = \is_subclass_of($collectorClass, TemplateAwareDataCollectorInterface::class); if (isset($attributes[0]['template']) || $isTemplateAware) { $idForTemplate = $attributes[0]['id'] ?? $collectorClass; if (!$idForTemplate) { throw new InvalidArgumentException(\sprintf('Data collector service "%s" must have an id attribute in order to specify a template.', $id)); } $template = [$idForTemplate, $attributes[0]['template'] ?? $collectorClass::getTemplate()]; } $collectors->insert([$id, $template], [$priority, --$order]); } $templates = []; foreach ($collectors as $collector) { $definition->addMethodCall('add', [new Reference($collector[0])]); $templates[$collector[0]] = $collector[1]; } $container->setParameter('data_collector.templates', $templates); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * @author Ahmed TAILOULOUTE */ class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('session.marshalling_handler')) { return; } $isMarshallerDecorated = \false; foreach ($container->getDefinitions() as $definition) { $decorated = $definition->getDecoratedService(); if (null !== $decorated && 'session.marshaller' === $decorated[0]) { $isMarshallerDecorated = \true; break; } } if (!$isMarshallerDecorated) { $container->removeDefinition('session.marshalling_handler'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; class AddDebugLogProcessorPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('profiler')) { return; } if (!$container->hasDefinition('monolog.logger_prototype')) { return; } if (!$container->hasDefinition('debug.log_processor')) { return; } $definition = $container->getDefinition('monolog.logger_prototype'); $definition->setConfigurator([__CLASS__, 'configureLogger']); $definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]); } public static function configureLogger($logger) { if (\is_object($logger) && \method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true)) { $logger->removeDebugLogger(); } } } { "name": "symfony\/framework-bundle", "type": "symfony-bundle", "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "ext-xml": "*", "symfony\/cache": "^5.2|^6.0", "symfony\/config": "^5.3|^6.0", "symfony\/dependency-injection": "^5.4.5|^6.0.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/event-dispatcher": "^5.1|^6.0", "symfony\/error-handler": "^4.4.1|^5.0.1|^6.0", "symfony\/http-foundation": "^5.4.24|^6.2.11", "symfony\/http-kernel": "^5.4|^6.0", "symfony\/polyfill-mbstring": "~1.0", "symfony\/polyfill-php80": "^1.16", "symfony\/polyfill-php81": "^1.22", "symfony\/filesystem": "^4.4|^5.0|^6.0", "symfony\/finder": "^4.4|^5.0|^6.0", "symfony\/routing": "^5.3|^6.0" }, "require-dev": { "doctrine\/annotations": "^1.13.1|^2", "doctrine\/cache": "^1.11|^2.0", "doctrine\/persistence": "^1.3|^2|^3", "symfony\/asset": "^5.3|^6.0", "symfony\/browser-kit": "^5.4|^6.0", "symfony\/console": "^5.4.9|^6.0.9", "symfony\/css-selector": "^4.4|^5.0|^6.0", "symfony\/dom-crawler": "^4.4.30|^5.3.7|^6.0", "symfony\/dotenv": "^5.1|^6.0", "symfony\/polyfill-intl-icu": "~1.0", "symfony\/form": "^5.2|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/http-client": "^4.4|^5.0|^6.0", "symfony\/lock": "^4.4|^5.0|^6.0", "symfony\/mailer": "^5.2|^6.0", "symfony\/messenger": "^5.4|^6.0", "symfony\/mime": "^4.4|^5.0|^6.0", "symfony\/notifier": "^5.4|^6.0", "symfony\/process": "^4.4|^5.0|^6.0", "symfony\/rate-limiter": "^5.2|^6.0", "symfony\/security-bundle": "^5.4|^6.0", "symfony\/serializer": "^5.4|^6.0", "symfony\/stopwatch": "^4.4|^5.0|^6.0", "symfony\/string": "^5.0|^6.0", "symfony\/translation": "^5.3|^6.0", "symfony\/twig-bundle": "^4.4|^5.0|^6.0", "symfony\/validator": "^5.3.11|^6.0", "symfony\/workflow": "^5.2|^6.0", "symfony\/yaml": "^4.4|^5.0|^6.0", "symfony\/property-info": "^4.4|^5.0|^6.0", "symfony\/web-link": "^4.4|^5.0|^6.0", "phpdocumentor\/reflection-docblock": "^3.0|^4.0|^5.0", "twig\/twig": "^2.10|^3.0" }, "conflict": { "doctrine\/annotations": "<1.13.1", "doctrine\/cache": "<1.11", "doctrine\/persistence": "<1.3", "phpdocumentor\/reflection-docblock": "<3.2.2", "phpdocumentor\/type-resolver": "<1.4.0", "symfony\/asset": "<5.3", "symfony\/console": "<5.2.5|>=7.0", "symfony\/dotenv": "<5.1", "symfony\/dom-crawler": "<4.4", "symfony\/http-client": "<4.4", "symfony\/form": "<5.2", "symfony\/lock": "<4.4", "symfony\/mailer": "<5.2", "symfony\/messenger": "<5.4", "symfony\/mime": "<4.4", "symfony\/property-info": "<4.4", "symfony\/property-access": "<5.3", "symfony\/serializer": "<5.2", "symfony\/service-contracts": ">=3.0", "symfony\/security-csrf": "<5.3", "symfony\/stopwatch": "<4.4", "symfony\/translation": "<5.3", "symfony\/twig-bridge": "<4.4", "symfony\/twig-bundle": "<4.4", "symfony\/validator": "<5.3.11", "symfony\/web-profiler-bundle": "<4.4", "symfony\/workflow": "<5.2" }, "suggest": { "ext-apcu": "For best performance of the system caches", "symfony\/console": "For using the console commands", "symfony\/form": "For using forms", "symfony\/serializer": "For using the serializer service", "symfony\/validator": "For using validation", "symfony\/yaml": "For using the debug:config and lint:yaml commands", "symfony\/property-info": "For using the property_info service", "symfony\/web-link": "For using web links, features such as preloading, prefetching or prerendering" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Bundle\\FrameworkBundle\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Kernel; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader as ContainerPhpFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use _ContaoManager\Symfony\Component\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader; use _ContaoManager\Symfony\Component\Routing\RouteCollection; use _ContaoManager\Symfony\Component\Routing\RouteCollectionBuilder; /** * A Kernel that provides configuration hooks. * * @author Ryan Weaver * @author Fabien Potencier */ trait MicroKernelTrait { /** * Configures the container. * * You can register extensions: * * $container->extension('framework', [ * 'secret' => '%secret%' * ]); * * Or services: * * $container->services()->set('halloween', 'FooBundle\HalloweenProvider'); * * Or parameters: * * $container->parameters()->set('halloween', 'lot of fun'); */ private function configureContainer(ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder) : void { $configDir = $this->getConfigDir(); $container->import($configDir . '/{packages}/*.yaml'); $container->import($configDir . '/{packages}/' . $this->environment . '/*.yaml'); if (\is_file($configDir . '/services.yaml')) { $container->import($configDir . '/services.yaml'); $container->import($configDir . '/{services}_' . $this->environment . '.yaml'); } else { $container->import($configDir . '/{services}.php'); } } /** * Adds or imports routes into your application. * * $routes->import($this->getConfigDir().'/*.{yaml,php}'); * $routes * ->add('admin_dashboard', '/admin') * ->controller('App\Controller\AdminController::dashboard') * ; */ private function configureRoutes(RoutingConfigurator $routes) : void { $configDir = $this->getConfigDir(); $routes->import($configDir . '/{routes}/' . $this->environment . '/*.yaml'); $routes->import($configDir . '/{routes}/*.yaml'); if (\is_file($configDir . '/routes.yaml')) { $routes->import($configDir . '/routes.yaml'); } else { $routes->import($configDir . '/{routes}.php'); } } /** * Gets the path to the configuration directory. */ private function getConfigDir() : string { return $this->getProjectDir() . '/config'; } /** * Gets the path to the bundles configuration file. */ private function getBundlesPath() : string { return $this->getConfigDir() . '/bundles.php'; } /** * {@inheritdoc} */ public function getCacheDir() : string { if (isset($_SERVER['APP_CACHE_DIR'])) { return $_SERVER['APP_CACHE_DIR'] . '/' . $this->environment; } return parent::getCacheDir(); } /** * {@inheritdoc} */ public function getLogDir() : string { return $_SERVER['APP_LOG_DIR'] ?? parent::getLogDir(); } /** * {@inheritdoc} */ public function registerBundles() : iterable { $contents = (require $this->getBundlesPath()); foreach ($contents as $class => $envs) { if ($envs[$this->environment] ?? $envs['all'] ?? \false) { (yield new $class()); } } } /** * {@inheritdoc} */ public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function (ContainerBuilder $container) use($loader) { $container->loadFromExtension('framework', ['router' => ['resource' => 'kernel::loadRoutes', 'type' => 'service']]); $kernelClass = \false !== \strpos(static::class, "@anonymous\x00") ? parent::class : static::class; if (!$container->hasDefinition('kernel')) { $container->register('kernel', $kernelClass)->addTag('controller.service_arguments')->setAutoconfigured(\true)->setSynthetic(\true)->setPublic(\true); } $kernelDefinition = $container->getDefinition('kernel'); $kernelDefinition->addTag('routing.route_loader'); $container->addObjectResource($this); $container->fileExists($this->getBundlesPath()); $configureContainer = new \ReflectionMethod($this, 'configureContainer'); $configuratorClass = $configureContainer->getNumberOfParameters() > 0 && ($type = $configureContainer->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null; if ($configuratorClass && !\is_a(ContainerConfigurator::class, $configuratorClass, \true)) { $configureContainer->getClosure($this)($container, $loader); return; } $file = (new \ReflectionObject($this))->getFileName(); /* @var ContainerPhpFileLoader $kernelLoader */ $kernelLoader = $loader->getResolver()->resolve($file); $kernelLoader->setCurrentDir(\dirname($file)); $instanceof =& \Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)(); $valuePreProcessor = AbstractConfigurator::$valuePreProcessor; AbstractConfigurator::$valuePreProcessor = function ($value) { return $this === $value ? new Reference('kernel') : $value; }; try { $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); } finally { $instanceof = []; $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; } $container->setAlias($kernelClass, 'kernel')->setPublic(\true); }); } /** * @internal */ public function loadRoutes(LoaderInterface $loader) : RouteCollection { $file = (new \ReflectionObject($this))->getFileName(); /* @var RoutingPhpFileLoader $kernelLoader */ $kernelLoader = $loader->getResolver()->resolve($file, 'php'); $kernelLoader->setCurrentDir(\dirname($file)); $collection = new RouteCollection(); $configureRoutes = new \ReflectionMethod($this, 'configureRoutes'); $configuratorClass = $configureRoutes->getNumberOfParameters() > 0 && ($type = $configureRoutes->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null; if ($configuratorClass && !\is_a(RoutingConfigurator::class, $configuratorClass, \true)) { \trigger_deprecation('symfony/framework-bundle', '5.1', 'Using type "%s" for argument 1 of method "%s:configureRoutes()" is deprecated, use "%s" instead.', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class); if (!\class_exists(RouteCollectionBuilder::class)) { throw new \InvalidArgumentException(\sprintf('Using type "%s" for argument 1 of method "%s:configureRoutes()" is not compatible with the installed "symfony/routing" version. Use "%s" instead, or run "composer require symfony/routing:^5.4".', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class)); } $routes = new RouteCollectionBuilder($loader); $this->configureRoutes($routes); return $routes->build(); } $configureRoutes->getClosure($this)(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment())); foreach ($collection as $route) { $controller = $route->getDefault('_controller'); if (\is_array($controller) && [0, 1] === \array_keys($controller) && $this === $controller[0]) { $route->setDefault('_controller', ['kernel', $controller[1]]); } } return $collection; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console; use _ContaoManager\Symfony\Component\Console\Application as BaseApplication; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Command\ListCommand; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerAwareInterface; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\Bundle; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * @author Fabien Potencier */ class Application extends BaseApplication { private $kernel; private $commandsRegistered = \false; private $registrationErrors = []; public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; parent::__construct('Symfony', Kernel::VERSION); $inputDefinition = $this->getDefinition(); $inputDefinition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $kernel->getEnvironment())); $inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switch off debug mode.')); } /** * Gets the Kernel associated with this Console. * * @return KernelInterface */ public function getKernel() { return $this->kernel; } /** * {@inheritdoc} */ public function reset() { if ($this->kernel->getContainer()->has('services_resetter')) { $this->kernel->getContainer()->get('services_resetter')->reset(); } } /** * Runs the current application. * * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { $this->registerCommands(); if ($this->registrationErrors) { $this->renderRegistrationErrors($input, $output); } $this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher')); return parent::doRun($input, $output); } /** * {@inheritdoc} */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { if (!$command instanceof ListCommand) { if ($this->registrationErrors) { $this->renderRegistrationErrors($input, $output); $this->registrationErrors = []; } return parent::doRunCommand($command, $input, $output); } $returnCode = parent::doRunCommand($command, $input, $output); if ($this->registrationErrors) { $this->renderRegistrationErrors($input, $output); $this->registrationErrors = []; } return $returnCode; } /** * {@inheritdoc} */ public function find(string $name) { $this->registerCommands(); return parent::find($name); } /** * {@inheritdoc} */ public function get(string $name) { $this->registerCommands(); $command = parent::get($name); if ($command instanceof ContainerAwareInterface) { $command->setContainer($this->kernel->getContainer()); } return $command; } /** * {@inheritdoc} */ public function all(?string $namespace = null) { $this->registerCommands(); return parent::all($namespace); } /** * {@inheritdoc} */ public function getLongVersion() { return parent::getLongVersion() . \sprintf(' (env: %s, debug: %s)', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } public function add(Command $command) { $this->registerCommands(); return parent::add($command); } protected function registerCommands() { if ($this->commandsRegistered) { return; } $this->commandsRegistered = \true; $this->kernel->boot(); $container = $this->kernel->getContainer(); foreach ($this->kernel->getBundles() as $bundle) { if ($bundle instanceof Bundle) { try { $bundle->registerCommands($this); } catch (\Throwable $e) { $this->registrationErrors[] = $e; } } } if ($container->has('console.command_loader')) { $this->setCommandLoader($container->get('console.command_loader')); } if ($container->hasParameter('console.command.ids')) { $lazyCommandIds = $container->hasParameter('console.lazy_command.ids') ? $container->getParameter('console.lazy_command.ids') : []; foreach ($container->getParameter('console.command.ids') as $id) { if (!isset($lazyCommandIds[$id])) { try { $this->add($container->get($id)); } catch (\Throwable $e) { $this->registrationErrors[] = $e; } } } } } private function renderRegistrationErrors(InputInterface $input, OutputInterface $output) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } (new SymfonyStyle($input, $output))->warning('Some commands could not be registered:'); foreach ($this->registrationErrors as $error) { $this->doRenderThrowable($error, $output); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Helper\Dumper; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Jean-François Simon * * @internal */ class TextDescriptor extends Descriptor { private $fileLinkFormatter; public function __construct(?FileLinkFormatter $fileLinkFormatter = null) { $this->fileLinkFormatter = $fileLinkFormatter; } protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; $tableHeaders = ['Name', 'Method', 'Scheme', 'Host', 'Path']; if ($showControllers) { $tableHeaders[] = 'Controller'; } $tableRows = []; foreach ($routes->all() as $name => $route) { $controller = $route->getDefault('_controller'); $row = [$name, $route->getMethods() ? \implode('|', $route->getMethods()) : 'ANY', $route->getSchemes() ? \implode('|', $route->getSchemes()) : 'ANY', '' !== $route->getHost() ? $route->getHost() : 'ANY', $this->formatControllerLink($controller, $route->getPath(), $options['container'] ?? null)]; if ($showControllers) { $row[] = $controller ? $this->formatControllerLink($controller, $this->formatCallable($controller), $options['container'] ?? null) : ''; } $tableRows[] = $row; } if (isset($options['output'])) { $options['output']->table($tableHeaders, $tableRows); } else { $table = new Table($this->getOutput()); $table->setHeaders($tableHeaders)->setRows($tableRows); $table->render(); } } protected function describeRoute(Route $route, array $options = []) { $defaults = $route->getDefaults(); if (isset($defaults['_controller'])) { $defaults['_controller'] = $this->formatControllerLink($defaults['_controller'], $this->formatCallable($defaults['_controller']), $options['container'] ?? null); } $tableHeaders = ['Property', 'Value']; $tableRows = [['Route Name', $options['name'] ?? ''], ['Path', $route->getPath()], ['Path Regex', $route->compile()->getRegex()], ['Host', '' !== $route->getHost() ? $route->getHost() : 'ANY'], ['Host Regex', '' !== $route->getHost() ? $route->compile()->getHostRegex() : ''], ['Scheme', $route->getSchemes() ? \implode('|', $route->getSchemes()) : 'ANY'], ['Method', $route->getMethods() ? \implode('|', $route->getMethods()) : 'ANY'], ['Requirements', $route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM'], ['Class', \get_class($route)], ['Defaults', $this->formatRouterConfig($defaults)], ['Options', $this->formatRouterConfig($route->getOptions())]]; if ('' !== $route->getCondition()) { $tableRows[] = ['Condition', $route->getCondition()]; } $table = new Table($this->getOutput()); $table->setHeaders($tableHeaders)->setRows($tableRows); $table->render(); } protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $tableHeaders = ['Parameter', 'Value']; $tableRows = []; foreach ($this->sortParameters($parameters) as $parameter => $value) { $tableRows[] = [$parameter, $this->formatParameter($value)]; } $options['output']->title('Symfony Container Parameters'); $options['output']->table($tableHeaders, $tableRows); } protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; if ($showHidden) { $options['output']->title('Symfony Container Hidden Tags'); } else { $options['output']->title('Symfony Container Tags'); } foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $options['output']->section(\sprintf('"%s" tag', $tag)); $options['output']->listing(\array_keys($definitions)); } } protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } if ($service instanceof Alias) { $this->describeContainerAlias($service, $options, $builder); } elseif ($service instanceof Definition) { $this->describeContainerDefinition($service, $options); } else { $options['output']->title(\sprintf('Information for Service "%s"', $options['id'])); $options['output']->table(['Service ID', 'Class'], [[$options['id'] ?? '-', \get_class($service)]]); } } protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $showTag = $options['tag'] ?? null; if ($showHidden) { $title = 'Symfony Container Hidden Services'; } else { $title = 'Symfony Container Services'; } if ($showTag) { $title .= \sprintf(' Tagged with "%s" Tag', $options['tag']); } $options['output']->title($title); $serviceIds = isset($options['tag']) && $options['tag'] ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) : $this->sortServiceIds($builder->getServiceIds()); $maxTags = []; if (isset($options['filter'])) { $serviceIds = \array_filter($serviceIds, $options['filter']); } foreach ($serviceIds as $key => $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); // filter out hidden services unless shown explicitly if ($showHidden xor '.' === ($serviceId[0] ?? null)) { unset($serviceIds[$key]); continue; } if ($definition instanceof Definition) { if ($showTag) { $tags = $definition->getTag($showTag); foreach ($tags as $tag) { foreach ($tag as $key => $value) { if (!isset($maxTags[$key])) { $maxTags[$key] = \strlen($key); } if (\strlen($value) > $maxTags[$key]) { $maxTags[$key] = \strlen($value); } } } } } } $tagsCount = \count($maxTags); $tagsNames = \array_keys($maxTags); $tableHeaders = \array_merge(['Service ID'], $tagsNames, ['Class name']); $tableRows = []; $rawOutput = isset($options['raw_text']) && $options['raw_text']; foreach ($serviceIds as $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); $styledServiceId = $rawOutput ? $serviceId : \sprintf('%s', OutputFormatter::escape($serviceId)); if ($definition instanceof Definition) { if ($showTag) { foreach ($this->sortByPriority($definition->getTag($showTag)) as $key => $tag) { $tagValues = []; foreach ($tagsNames as $tagName) { $tagValues[] = $tag[$tagName] ?? ''; } if (0 === $key) { $tableRows[] = \array_merge([$serviceId], $tagValues, [$definition->getClass()]); } else { $tableRows[] = \array_merge([' (same service as previous, another tag)'], $tagValues, ['']); } } } else { $tableRows[] = [$styledServiceId, $definition->getClass()]; } } elseif ($definition instanceof Alias) { $alias = $definition; $tableRows[] = \array_merge([$styledServiceId, \sprintf('alias for "%s"', $alias)], $tagsCount ? \array_fill(0, $tagsCount, '') : []); } else { $tableRows[] = \array_merge([$styledServiceId, \get_class($definition)], $tagsCount ? \array_fill(0, $tagsCount, '') : []); } } $options['output']->table($tableHeaders, $tableRows); } protected function describeContainerDefinition(Definition $definition, array $options = []) { if (isset($options['id'])) { $options['output']->title(\sprintf('Information for Service "%s"', $options['id'])); } if ('' !== ($classDescription = $this->getClassDescription((string) $definition->getClass()))) { $options['output']->text($classDescription . "\n"); } $tableHeaders = ['Option', 'Value']; $tableRows[] = ['Service ID', $options['id'] ?? '-']; $tableRows[] = ['Class', $definition->getClass() ?: '-']; $omitTags = isset($options['omit_tags']) && $options['omit_tags']; if (!$omitTags && ($tags = $definition->getTags())) { $tagInformation = []; foreach ($tags as $tagName => $tagData) { foreach ($tagData as $tagParameters) { $parameters = \array_map(function ($key, $value) { return \sprintf('%s: %s', $key, $value); }, \array_keys($tagParameters), \array_values($tagParameters)); $parameters = \implode(', ', $parameters); if ('' === $parameters) { $tagInformation[] = \sprintf('%s', $tagName); } else { $tagInformation[] = \sprintf('%s (%s)', $tagName, $parameters); } } } $tagInformation = \implode("\n", $tagInformation); } else { $tagInformation = '-'; } $tableRows[] = ['Tags', $tagInformation]; $calls = $definition->getMethodCalls(); if (\count($calls) > 0) { $callInformation = []; foreach ($calls as $call) { $callInformation[] = $call[0]; } $tableRows[] = ['Calls', \implode(', ', $callInformation)]; } $tableRows[] = ['Public', $definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no']; $tableRows[] = ['Synthetic', $definition->isSynthetic() ? 'yes' : 'no']; $tableRows[] = ['Lazy', $definition->isLazy() ? 'yes' : 'no']; $tableRows[] = ['Shared', $definition->isShared() ? 'yes' : 'no']; $tableRows[] = ['Abstract', $definition->isAbstract() ? 'yes' : 'no']; $tableRows[] = ['Autowired', $definition->isAutowired() ? 'yes' : 'no']; $tableRows[] = ['Autoconfigured', $definition->isAutoconfigured() ? 'yes' : 'no']; if ($definition->getFile()) { $tableRows[] = ['Required File', $definition->getFile() ?: '-']; } if ($factory = $definition->getFactory()) { if (\is_array($factory)) { if ($factory[0] instanceof Reference) { $tableRows[] = ['Factory Service', $factory[0]]; } elseif ($factory[0] instanceof Definition) { $tableRows[] = ['Factory Service', \sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'class not configured')]; } else { $tableRows[] = ['Factory Class', $factory[0]]; } $tableRows[] = ['Factory Method', $factory[1]]; } else { $tableRows[] = ['Factory Function', $factory]; } } $showArguments = isset($options['show_arguments']) && $options['show_arguments']; $argumentsInformation = []; if ($showArguments && ($arguments = $definition->getArguments())) { foreach ($arguments as $argument) { if ($argument instanceof ServiceClosureArgument) { $argument = $argument->getValues()[0]; } if ($argument instanceof Reference) { $argumentsInformation[] = \sprintf('Service(%s)', (string) $argument); } elseif ($argument instanceof IteratorArgument) { if ($argument instanceof TaggedIteratorArgument) { $argumentsInformation[] = \sprintf('Tagged Iterator for "%s"%s', $argument->getTag(), $options['is_debug'] ? '' : \sprintf(' (%d element(s))', \count($argument->getValues()))); } else { $argumentsInformation[] = \sprintf('Iterator (%d element(s))', \count($argument->getValues())); } foreach ($argument->getValues() as $ref) { $argumentsInformation[] = \sprintf('- Service(%s)', $ref); } } elseif ($argument instanceof ServiceLocatorArgument) { $argumentsInformation[] = \sprintf('Service locator (%d element(s))', \count($argument->getValues())); } elseif ($argument instanceof Definition) { $argumentsInformation[] = 'Inlined Service'; } elseif ($argument instanceof \UnitEnum) { $argumentsInformation[] = \ltrim(\var_export($argument, \true), '\\'); } elseif ($argument instanceof AbstractArgument) { $argumentsInformation[] = \sprintf('Abstract argument (%s)', $argument->getText()); } else { $argumentsInformation[] = \is_array($argument) ? \sprintf('Array (%d element(s))', \count($argument)) : $argument; } } $tableRows[] = ['Arguments', \implode("\n", $argumentsInformation)]; } $options['output']->table($tableHeaders, $tableRows); } protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []) : void { $containerDeprecationFilePath = \sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); if (!\file_exists($containerDeprecationFilePath)) { $options['output']->warning('The deprecation file does not exist, please try warming the cache first.'); return; } $logs = \unserialize(\file_get_contents($containerDeprecationFilePath)); if (0 === \count($logs)) { $options['output']->success('There are no deprecations in the logs!'); return; } $formattedLogs = []; $remainingCount = 0; foreach ($logs as $log) { $formattedLogs[] = \sprintf("%sx: %s\n in %s:%s", $log['count'], $log['message'], $log['file'], $log['line']); $remainingCount += $log['count']; } $options['output']->title(\sprintf('Remaining deprecations (%s)', $remainingCount)); $options['output']->listing($formattedLogs); } protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $builder = null) { if ($alias->isPublic() && !$alias->isPrivate()) { $options['output']->comment(\sprintf('This service is a public alias for the service %s', (string) $alias)); } else { $options['output']->comment(\sprintf('This service is a private alias for the service %s', (string) $alias)); } if (!$builder) { return; } $this->describeContainerDefinition($builder->getDefinition((string) $alias), \array_merge($options, ['id' => (string) $alias])); } protected function describeContainerParameter($parameter, array $options = []) { $options['output']->table(['Parameter', 'Value'], [[$options['parameter'], $this->formatParameter($parameter)]]); } protected function describeContainerEnvVars(array $envs, array $options = []) { $dump = new Dumper($this->output); $options['output']->title('Symfony Container Environment Variables'); if (null !== ($name = $options['name'] ?? null)) { $options['output']->comment('Displaying detailed environment variable usage matching ' . $name); $matches = \false; foreach ($envs as $env) { if ($name === $env['name'] || \false !== \stripos($env['name'], $name)) { $matches = \true; $options['output']->section('%env(' . $env['processor'] . ':' . $env['name'] . ')%'); $options['output']->table([], [['Default value', $env['default_available'] ? $dump($env['default_value']) : 'n/a'], ['Real value', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a'], ['Processed value', $env['default_available'] || $env['runtime_available'] ? $dump($env['processed_value']) : 'n/a']]); } } if (!$matches) { $options['output']->block('None of the environment variables match this name.'); } else { $options['output']->comment('Note real values might be different between web and CLI.'); } return; } if (!$envs) { $options['output']->block('No environment variables are being used.'); return; } $rows = []; $missing = []; foreach ($envs as $env) { if (isset($rows[$env['name']])) { continue; } $rows[$env['name']] = [$env['name'], $env['default_available'] ? $dump($env['default_value']) : 'n/a', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a']; if (!$env['default_available'] && !$env['runtime_available']) { $missing[$env['name']] = \true; } } $options['output']->table(['Name', 'Default value', 'Real value'], $rows); $options['output']->comment('Note real values might be different between web and CLI.'); if ($missing) { $options['output']->warning('The following variables are missing:'); $options['output']->listing(\array_keys($missing)); } } protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $event = $options['event'] ?? null; $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; $title = 'Registered Listeners'; if (null !== $dispatcherServiceName) { $title .= \sprintf(' of Event Dispatcher "%s"', $dispatcherServiceName); } if (null !== $event) { $title .= \sprintf(' for "%s" Event', $event); $registeredListeners = $eventDispatcher->getListeners($event); } else { $title .= ' Grouped by Event'; // Try to see if "events" exists $registeredListeners = \array_key_exists('events', $options) ? \array_combine($options['events'], \array_map(function ($event) use($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); } $options['output']->title($title); if (null !== $event) { $this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']); } else { \ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { $options['output']->section(\sprintf('"%s" event', $eventListened)); $this->renderEventListenerTable($eventDispatcher, $eventListened, $eventListeners, $options['output']); } } } protected function describeCallable($callable, array $options = []) { $this->writeText($this->formatCallable($callable), $options); } private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io) { $tableHeaders = ['Order', 'Callable', 'Priority']; $tableRows = []; foreach ($eventListeners as $order => $listener) { $tableRows[] = [\sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener)]; } $io->table($tableHeaders, $tableRows); } private function formatRouterConfig(array $config) : string { if (empty($config)) { return 'NONE'; } \ksort($config); $configAsString = ''; foreach ($config as $key => $value) { $configAsString .= \sprintf("\n%s: %s", $key, $this->formatValue($value)); } return \trim($configAsString); } private function formatControllerLink($controller, string $anchorText, ?callable $getContainer = null) : string { if (null === $this->fileLinkFormatter) { return $anchorText; } try { if (null === $controller) { return $anchorText; } elseif (\is_array($controller)) { $r = new \ReflectionMethod($controller[0], $controller[1]); } elseif ($controller instanceof \Closure) { $r = new \ReflectionFunction($controller); } elseif (\method_exists($controller, '__invoke')) { $r = new \ReflectionMethod($controller, '__invoke'); } elseif (!\is_string($controller)) { return $anchorText; } elseif (\str_contains($controller, '::')) { $r = new \ReflectionMethod($controller); } else { $r = new \ReflectionFunction($controller); } } catch (\ReflectionException $e) { if (\is_array($controller)) { $controller = \implode('::', $controller); } $id = $controller; $method = '__invoke'; if ($pos = \strpos($controller, '::')) { $id = \substr($controller, 0, $pos); $method = \substr($controller, $pos + 2); } if (!$getContainer || !($container = $getContainer()) || !$container->has($id)) { return $anchorText; } try { $r = new \ReflectionMethod($container->findDefinition($id)->getClass(), $method); } catch (\ReflectionException $e) { return $anchorText; } } $fileLink = $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); if ($fileLink) { return \sprintf('%s', $fileLink, $anchorText); } return $anchorText; } private function formatCallable($callable) : string { if (\is_array($callable)) { if (\is_object($callable[0])) { return \sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); } return \sprintf('%s::%s()', $callable[0], $callable[1]); } if (\is_string($callable)) { return \sprintf('%s()', $callable); } if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); if (\str_contains($r->name, '{closure}')) { return 'Closure()'; } if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return \sprintf('%s::%s()', $class->name, $r->name); } return $r->name . '()'; } if (\method_exists($callable, '__invoke')) { return \sprintf('%s::__invoke()', \get_class($callable)); } throw new \InvalidArgumentException('Callable is not describable.'); } private function writeText(string $content, array $options = []) { $this->write(isset($options['raw_text']) && $options['raw_text'] ? \strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : \true); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Jean-François Simon * * @internal */ class XmlDescriptor extends Descriptor { protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $this->writeDocument($this->getRouteCollectionDocument($routes)); } protected function describeRoute(Route $route, array $options = []) { $this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null)); } protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $this->writeDocument($this->getContainerParametersDocument($parameters)); } protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden'])); } protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments'])); } protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null)); } protected function describeContainerDefinition(Definition $definition, array $options = []) { $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])); } protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $builder = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), \true)); if (!$builder) { $this->writeDocument($dom); return; } $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $alias), (string) $alias)->childNodes->item(0), \true)); $this->writeDocument($dom); } protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options)); } protected function describeCallable($callable, array $options = []) { $this->writeDocument($this->getCallableDocument($callable)); } protected function describeContainerParameter($parameter, array $options = []) { $this->writeDocument($this->getContainerParameterDocument($parameter, $options)); } protected function describeContainerEnvVars(array $envs, array $options = []) { throw new LogicException('Using the XML format to debug environment variables is not supported.'); } protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []) : void { $containerDeprecationFilePath = \sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); if (!\file_exists($containerDeprecationFilePath)) { throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); } $logs = \unserialize(\file_get_contents($containerDeprecationFilePath)); $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($deprecationsXML = $dom->createElement('deprecations')); $formattedLogs = []; $remainingCount = 0; foreach ($logs as $log) { $deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation')); $deprecationXML->setAttribute('count', $log['count']); $deprecationXML->appendChild($dom->createElement('message', $log['message'])); $deprecationXML->appendChild($dom->createElement('file', $log['file'])); $deprecationXML->appendChild($dom->createElement('line', $log['line'])); $remainingCount += $log['count']; } $deprecationsXML->setAttribute('remainingCount', $remainingCount); $this->writeDocument($dom); } private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = \true; $this->write($dom->saveXML()); } private function getRouteCollectionDocument(RouteCollection $routes) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($routesXML = $dom->createElement('routes')); foreach ($routes->all() as $name => $route) { $routeXML = $this->getRouteDocument($route, $name); $routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), \true)); } return $dom; } private function getRouteDocument(Route $route, ?string $name = null) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($routeXML = $dom->createElement('route')); if ($name) { $routeXML->setAttribute('name', $name); } $routeXML->setAttribute('class', \get_class($route)); $routeXML->appendChild($pathXML = $dom->createElement('path')); $pathXML->setAttribute('regex', $route->compile()->getRegex()); $pathXML->appendChild(new \DOMText($route->getPath())); if ('' !== $route->getHost()) { $routeXML->appendChild($hostXML = $dom->createElement('host')); $hostXML->setAttribute('regex', $route->compile()->getHostRegex()); $hostXML->appendChild(new \DOMText($route->getHost())); } foreach ($route->getSchemes() as $scheme) { $routeXML->appendChild($schemeXML = $dom->createElement('scheme')); $schemeXML->appendChild(new \DOMText($scheme)); } foreach ($route->getMethods() as $method) { $routeXML->appendChild($methodXML = $dom->createElement('method')); $methodXML->appendChild(new \DOMText($method)); } if ($route->getDefaults()) { $routeXML->appendChild($defaultsXML = $dom->createElement('defaults')); foreach ($route->getDefaults() as $attribute => $value) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->setAttribute('key', $attribute); $defaultXML->appendChild(new \DOMText($this->formatValue($value))); } } $originRequirements = $requirements = $route->getRequirements(); unset($requirements['_scheme'], $requirements['_method']); if ($requirements) { $routeXML->appendChild($requirementsXML = $dom->createElement('requirements')); foreach ($originRequirements as $attribute => $pattern) { $requirementsXML->appendChild($requirementXML = $dom->createElement('requirement')); $requirementXML->setAttribute('key', $attribute); $requirementXML->appendChild(new \DOMText($pattern)); } } if ($route->getOptions()) { $routeXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($route->getOptions() as $name => $value) { $optionsXML->appendChild($optionXML = $dom->createElement('option')); $optionXML->setAttribute('key', $name); $optionXML->appendChild(new \DOMText($this->formatValue($value))); } } if ('' !== $route->getCondition()) { $routeXML->appendChild($conditionXML = $dom->createElement('condition')); $conditionXML->appendChild(new \DOMText($route->getCondition())); } return $dom; } private function getContainerParametersDocument(ParameterBag $parameters) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($parametersXML = $dom->createElement('parameters')); foreach ($this->sortParameters($parameters) as $key => $value) { $parametersXML->appendChild($parameterXML = $dom->createElement('parameter')); $parameterXML->setAttribute('key', $key); $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); } return $dom; } private function getContainerTagsDocument(ContainerBuilder $builder, bool $showHidden = \false) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $containerXML->appendChild($tagXML = $dom->createElement('tag')); $tagXML->setAttribute('name', $tag); foreach ($definitions as $serviceId => $definition) { $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, \true); $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), \true)); } } return $dom; } private function getContainerServiceDocument(object $service, string $id, ?ContainerBuilder $builder = null, bool $showArguments = \false) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); if ($service instanceof Alias) { $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), \true)); if ($builder) { $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $service), (string) $service, \false, $showArguments)->childNodes->item(0), \true)); } } elseif ($service instanceof Definition) { $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, \false, $showArguments)->childNodes->item(0), \true)); } else { $dom->appendChild($serviceXML = $dom->createElement('service')); $serviceXML->setAttribute('id', $id); $serviceXML->setAttribute('class', \get_class($service)); } return $dom; } private function getContainerServicesDocument(ContainerBuilder $builder, ?string $tag = null, bool $showHidden = \false, bool $showArguments = \false, ?callable $filter = null) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); $serviceIds = $tag ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag)) : $this->sortServiceIds($builder->getServiceIds()); if ($filter) { $serviceIds = \array_filter($serviceIds, $filter); } foreach ($serviceIds as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } $serviceXML = $this->getContainerServiceDocument($service, $serviceId, null, $showArguments); $containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), \true)); } return $dom; } private function getContainerDefinitionDocument(Definition $definition, ?string $id = null, bool $omitTags = \false, bool $showArguments = \false) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($serviceXML = $dom->createElement('definition')); if ($id) { $serviceXML->setAttribute('id', $id); } if ('' !== ($classDescription = $this->getClassDescription((string) $definition->getClass()))) { $serviceXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createCDATASection($classDescription)); } $serviceXML->setAttribute('class', $definition->getClass() ?? ''); if ($factory = $definition->getFactory()) { $serviceXML->appendChild($factoryXML = $dom->createElement('factory')); if (\is_array($factory)) { if ($factory[0] instanceof Reference) { $factoryXML->setAttribute('service', (string) $factory[0]); } elseif ($factory[0] instanceof Definition) { $factoryXML->setAttribute('service', \sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'not configured')); } else { $factoryXML->setAttribute('class', $factory[0]); } $factoryXML->setAttribute('method', $factory[1]); } else { $factoryXML->setAttribute('function', $factory); } } $serviceXML->setAttribute('public', $definition->isPublic() && !$definition->isPrivate() ? 'true' : 'false'); $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false'); $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false'); $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false'); $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false'); $serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false'); $serviceXML->setAttribute('file', $definition->getFile() ?? ''); $calls = $definition->getMethodCalls(); if (\count($calls) > 0) { $serviceXML->appendChild($callsXML = $dom->createElement('calls')); foreach ($calls as $callData) { $callsXML->appendChild($callXML = $dom->createElement('call')); $callXML->setAttribute('method', $callData[0]); if ($callData[2] ?? \false) { $callXML->setAttribute('returns-clone', 'true'); } } } if ($showArguments) { foreach ($this->getArgumentNodes($definition->getArguments(), $dom) as $node) { $serviceXML->appendChild($node); } } if (!$omitTags) { if ($tags = $this->sortTagsByPriority($definition->getTags())) { $serviceXML->appendChild($tagsXML = $dom->createElement('tags')); foreach ($tags as $tagName => $tagData) { foreach ($tagData as $parameters) { $tagsXML->appendChild($tagXML = $dom->createElement('tag')); $tagXML->setAttribute('name', $tagName); foreach ($parameters as $name => $value) { $tagXML->appendChild($parameterXML = $dom->createElement('parameter')); $parameterXML->setAttribute('name', $name); $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); } } } } } return $dom; } /** * @return \DOMNode[] */ private function getArgumentNodes(array $arguments, \DOMDocument $dom) : array { $nodes = []; foreach ($arguments as $argumentKey => $argument) { $argumentXML = $dom->createElement('argument'); if (\is_string($argumentKey)) { $argumentXML->setAttribute('key', $argumentKey); } if ($argument instanceof ServiceClosureArgument) { $argument = $argument->getValues()[0]; } if ($argument instanceof Reference) { $argumentXML->setAttribute('type', 'service'); $argumentXML->setAttribute('id', (string) $argument); } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) { $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator'); foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof Definition) { $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, \false, \true)->childNodes->item(0), \true)); } elseif ($argument instanceof AbstractArgument) { $argumentXML->setAttribute('type', 'abstract'); $argumentXML->appendChild(new \DOMText($argument->getText())); } elseif (\is_array($argument)) { $argumentXML->setAttribute('type', 'collection'); foreach ($this->getArgumentNodes($argument, $dom) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof \UnitEnum) { $argumentXML->setAttribute('type', 'constant'); $argumentXML->appendChild(new \DOMText(\ltrim(\var_export($argument, \true), '\\'))); } else { $argumentXML->appendChild(new \DOMText($argument)); } $nodes[] = $argumentXML; } return $nodes; } private function getContainerAliasDocument(Alias $alias, ?string $id = null) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($aliasXML = $dom->createElement('alias')); if ($id) { $aliasXML->setAttribute('id', $id); } $aliasXML->setAttribute('service', (string) $alias); $aliasXML->setAttribute('public', $alias->isPublic() && !$alias->isPrivate() ? 'true' : 'false'); return $dom; } private function getContainerParameterDocument($parameter, array $options = []) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($parameterXML = $dom->createElement('parameter')); if (isset($options['parameter'])) { $parameterXML->setAttribute('key', $options['parameter']); } $parameterXML->appendChild(new \DOMText($this->formatParameter($parameter))); return $dom; } private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, array $options) : \DOMDocument { $event = \array_key_exists('event', $options) ? $options['event'] : null; $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher')); if (null !== $event) { $registeredListeners = $eventDispatcher->getListeners($event); $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); } else { // Try to see if "events" exists $registeredListeners = \array_key_exists('events', $options) ? \array_combine($options['events'], \array_map(function ($event) use($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); \ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { $eventDispatcherXML->appendChild($eventXML = $dom->createElement('event')); $eventXML->setAttribute('name', $eventListened); $this->appendEventListenerDocument($eventDispatcher, $eventListened, $eventXML, $eventListeners); } } return $dom; } private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners) { foreach ($eventListeners as $listener) { $callableXML = $this->getCallableDocument($listener); $callableXML->childNodes->item(0)->setAttribute('priority', $eventDispatcher->getListenerPriority($event, $listener)); $element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), \true)); } } private function getCallableDocument($callable) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($callableXML = $dom->createElement('callable')); if (\is_array($callable)) { $callableXML->setAttribute('type', 'function'); if (\is_object($callable[0])) { $callableXML->setAttribute('name', $callable[1]); $callableXML->setAttribute('class', \get_class($callable[0])); } else { if (!\str_starts_with($callable[1], 'parent::')) { $callableXML->setAttribute('name', $callable[1]); $callableXML->setAttribute('class', $callable[0]); $callableXML->setAttribute('static', 'true'); } else { $callableXML->setAttribute('name', \substr($callable[1], 8)); $callableXML->setAttribute('class', $callable[0]); $callableXML->setAttribute('static', 'true'); $callableXML->setAttribute('parent', 'true'); } } return $dom; } if (\is_string($callable)) { $callableXML->setAttribute('type', 'function'); if (!\str_contains($callable, '::')) { $callableXML->setAttribute('name', $callable); } else { $callableParts = \explode('::', $callable); $callableXML->setAttribute('name', $callableParts[1]); $callableXML->setAttribute('class', $callableParts[0]); $callableXML->setAttribute('static', 'true'); } return $dom; } if ($callable instanceof \Closure) { $callableXML->setAttribute('type', 'closure'); $r = new \ReflectionFunction($callable); if (\str_contains($r->name, '{closure}')) { return $dom; } $callableXML->setAttribute('name', $r->name); if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $callableXML->setAttribute('class', $class->name); if (!$r->getClosureThis()) { $callableXML->setAttribute('static', 'true'); } } return $dom; } if (\method_exists($callable, '__invoke')) { $callableXML->setAttribute('type', 'object'); $callableXML->setAttribute('name', \get_class($callable)); return $dom; } throw new \InvalidArgumentException('Callable is not describable.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor; use _ContaoManager\Symfony\Component\Config\Resource\ClassExistenceResource; use _ContaoManager\Symfony\Component\Console\Descriptor\DescriptorInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Jean-François Simon * * @internal */ abstract class Descriptor implements DescriptorInterface { /** * @var OutputInterface */ protected $output; /** * {@inheritdoc} */ public function describe(OutputInterface $output, $object, array $options = []) { $this->output = $output; switch (\true) { case $object instanceof RouteCollection: $this->describeRouteCollection($object, $options); break; case $object instanceof Route: $this->describeRoute($object, $options); break; case $object instanceof ParameterBag: $this->describeContainerParameters($object, $options); break; case $object instanceof ContainerBuilder && !empty($options['env-vars']): $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options); break; case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']: $this->describeContainerTags($object, $options); break; case $object instanceof ContainerBuilder && isset($options['id']): $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object); break; case $object instanceof ContainerBuilder && isset($options['parameter']): $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $options); break; case $object instanceof ContainerBuilder && isset($options['deprecations']): $this->describeContainerDeprecations($object, $options); break; case $object instanceof ContainerBuilder: $this->describeContainerServices($object, $options); break; case $object instanceof Definition: $this->describeContainerDefinition($object, $options); break; case $object instanceof Alias: $this->describeContainerAlias($object, $options); break; case $object instanceof EventDispatcherInterface: $this->describeEventDispatcherListeners($object, $options); break; case \is_callable($object): $this->describeCallable($object, $options); break; default: throw new \InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', \get_debug_type($object))); } } protected function getOutput() : OutputInterface { return $this->output; } protected function write(string $content, bool $decorated = \false) { $this->output->write($content, \false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } protected abstract function describeRouteCollection(RouteCollection $routes, array $options = []); protected abstract function describeRoute(Route $route, array $options = []); protected abstract function describeContainerParameters(ParameterBag $parameters, array $options = []); protected abstract function describeContainerTags(ContainerBuilder $builder, array $options = []); /** * Describes a container service by its name. * * Common options are: * * name: name of described service * * @param Definition|Alias|object $service */ protected abstract function describeContainerService(object $service, array $options = [], ?ContainerBuilder $builder = null); /** * Describes container services. * * Common options are: * * tag: filters described services by given tag */ protected abstract function describeContainerServices(ContainerBuilder $builder, array $options = []); protected abstract function describeContainerDeprecations(ContainerBuilder $builder, array $options = []) : void; protected abstract function describeContainerDefinition(Definition $definition, array $options = []); protected abstract function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $builder = null); protected abstract function describeContainerParameter($parameter, array $options = []); protected abstract function describeContainerEnvVars(array $envs, array $options = []); /** * Describes event dispatcher listeners. * * Common options are: * * name: name of listened event */ protected abstract function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []); /** * Describes a callable. * * @param mixed $callable */ protected abstract function describeCallable($callable, array $options = []); /** * Formats a value as string. * * @param mixed $value */ protected function formatValue($value) : string { if ($value instanceof \UnitEnum) { return \ltrim(\var_export($value, \true), '\\'); } if (\is_object($value)) { return \sprintf('object(%s)', \get_class($value)); } if (\is_string($value)) { return $value; } return \preg_replace("/\n\\s*/s", '', \var_export($value, \true)); } /** * Formats a parameter. * * @param mixed $value */ protected function formatParameter($value) : string { if ($value instanceof \UnitEnum) { return \ltrim(\var_export($value, \true), '\\'); } // Recursively search for enum values, so we can replace it // before json_encode (which will not display anything for \UnitEnum otherwise) if (\is_array($value)) { \array_walk_recursive($value, static function (&$value) { if ($value instanceof \UnitEnum) { $value = \ltrim(\var_export($value, \true), '\\'); } }); } if (\is_bool($value) || \is_array($value) || null === $value) { $jsonString = \json_encode($value); if (\preg_match('/^(.{60})./us', $jsonString, $matches)) { return $matches[1] . '...'; } return $jsonString; } return (string) $value; } /** * @return mixed */ protected function resolveServiceDefinition(ContainerBuilder $builder, string $serviceId) { if ($builder->hasDefinition($serviceId)) { return $builder->getDefinition($serviceId); } // Some service IDs don't have a Definition, they're aliases if ($builder->hasAlias($serviceId)) { return $builder->getAlias($serviceId); } if ('service_container' === $serviceId) { return (new Definition(ContainerInterface::class))->setPublic(\true)->setSynthetic(\true); } // the service has been injected in some special way, just return the service return $builder->get($serviceId); } protected function findDefinitionsByTag(ContainerBuilder $builder, bool $showHidden) : array { $definitions = []; $tags = $builder->findTags(); \asort($tags); foreach ($tags as $tag) { foreach ($builder->findTaggedServiceIds($tag) as $serviceId => $attributes) { $definition = $this->resolveServiceDefinition($builder, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } if (!isset($definitions[$tag])) { $definitions[$tag] = []; } $definitions[$tag][$serviceId] = $definition; } } return $definitions; } protected function sortParameters(ParameterBag $parameters) { $parameters = $parameters->all(); \ksort($parameters); return $parameters; } protected function sortServiceIds(array $serviceIds) { \asort($serviceIds); return $serviceIds; } protected function sortTaggedServicesByPriority(array $services) : array { $maxPriority = []; foreach ($services as $service => $tags) { $maxPriority[$service] = \PHP_INT_MIN; foreach ($tags as $tag) { $currentPriority = $tag['priority'] ?? 0; if ($maxPriority[$service] < $currentPriority) { $maxPriority[$service] = $currentPriority; } } } \uasort($maxPriority, function ($a, $b) { return $b <=> $a; }); return \array_keys($maxPriority); } protected function sortTagsByPriority(array $tags) : array { $sortedTags = []; foreach ($tags as $tagName => $tag) { $sortedTags[$tagName] = $this->sortByPriority($tag); } return $sortedTags; } protected function sortByPriority(array $tag) : array { \usort($tag, function ($a, $b) { return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0); }); return $tag; } public static function getClassDescription(string $class, ?string &$resolvedClass = null) : string { $resolvedClass = $class; try { $resource = new ClassExistenceResource($class, \false); // isFresh() will explode ONLY if a parent class/trait does not exist $resource->isFresh(0); $r = new \ReflectionClass($class); $resolvedClass = $r->name; if ($docComment = $r->getDocComment()) { $docComment = \preg_split('#\\n\\s*\\*\\s*[\\n@]#', \substr($docComment, 3, -2), 2)[0]; return \trim(\preg_replace('#\\s*\\n\\s*\\*\\s*#', ' ', $docComment)); } } catch (\ReflectionException $e) { } return ''; } private function getContainerEnvVars(ContainerBuilder $container) : array { if (!$container->hasParameter('debug.container.dump')) { return []; } if (!\is_file($container->getParameter('debug.container.dump'))) { return []; } $file = \file_get_contents($container->getParameter('debug.container.dump')); \preg_match_all('{%env\\(((?:\\w++:)*+\\w++)\\)%}', $file, $envVars); $envVars = \array_unique($envVars[1]); $bag = $container->getParameterBag(); $getDefaultParameter = function (string $name) { return parent::get($name); }; $getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag)); $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); $getEnvReflection->setAccessible(\true); $envs = []; foreach ($envVars as $env) { $processor = 'string'; if (\false !== ($i = \strrpos($name = $env, ':'))) { $name = \substr($env, $i + 1); $processor = \substr($env, 0, $i); } $defaultValue = ($hasDefault = $container->hasParameter("env({$name})")) ? $getDefaultParameter("env({$name})") : null; if (\false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? \getenv($name))) { $runtimeValue = null; } $processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null; $envs["{$name}{$processor}"] = ['name' => $name, 'processor' => $processor, 'default_available' => $hasDefault, 'default_value' => $defaultValue, 'runtime_available' => $hasRuntime, 'runtime_value' => $runtimeValue, 'processed_value' => $processedValue]; } \ksort($envs); return \array_values($envs); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Jean-François Simon * * @internal */ class JsonDescriptor extends Descriptor { protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $data = []; foreach ($routes->all() as $name => $route) { $data[$name] = $this->getRouteData($route); } $this->writeData($data, $options); } protected function describeRoute(Route $route, array $options = []) { $this->writeData($this->getRouteData($route), $options); } protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $this->writeData($this->sortParameters($parameters), $options); } protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $data = []; foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $data[$tag] = []; foreach ($definitions as $definition) { $data[$tag][] = $this->getContainerDefinitionData($definition, \true); } } $this->writeData($data, $options); } protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } if ($service instanceof Alias) { $this->describeContainerAlias($service, $options, $builder); } elseif ($service instanceof Definition) { $this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); } else { $this->writeData(\get_class($service), $options); } } protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $serviceIds = isset($options['tag']) && $options['tag'] ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) : $this->sortServiceIds($builder->getServiceIds()); $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $omitTags = isset($options['omit_tags']) && $options['omit_tags']; $showArguments = isset($options['show_arguments']) && $options['show_arguments']; $data = ['definitions' => [], 'aliases' => [], 'services' => []]; if (isset($options['filter'])) { $serviceIds = \array_filter($serviceIds, $options['filter']); } foreach ($serviceIds as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } if ($service instanceof Alias) { $data['aliases'][$serviceId] = $this->getContainerAliasData($service); } elseif ($service instanceof Definition) { $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments); } else { $data['services'][$serviceId] = \get_class($service); } } $this->writeData($data, $options); } protected function describeContainerDefinition(Definition $definition, array $options = []) { $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); } protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $builder = null) { if (!$builder) { $this->writeData($this->getContainerAliasData($alias), $options); return; } $this->writeData([$this->getContainerAliasData($alias), $this->getContainerDefinitionData($builder->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])], \array_merge($options, ['id' => (string) $alias])); } protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options); } protected function describeCallable($callable, array $options = []) { $this->writeData($this->getCallableData($callable), $options); } protected function describeContainerParameter($parameter, array $options = []) { $key = $options['parameter'] ?? ''; $this->writeData([$key => $parameter], $options); } protected function describeContainerEnvVars(array $envs, array $options = []) { throw new LogicException('Using the JSON format to debug environment variables is not supported.'); } protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []) : void { $containerDeprecationFilePath = \sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); if (!\file_exists($containerDeprecationFilePath)) { throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); } $logs = \unserialize(\file_get_contents($containerDeprecationFilePath)); $formattedLogs = []; $remainingCount = 0; foreach ($logs as $log) { $formattedLogs[] = ['message' => $log['message'], 'file' => $log['file'], 'line' => $log['line'], 'count' => $log['count']]; $remainingCount += $log['count']; } $this->writeData(['remainingCount' => $remainingCount, 'deprecations' => $formattedLogs], $options); } private function writeData(array $data, array $options) { $flags = $options['json_encoding'] ?? 0; // Recursively search for enum values, so we can replace it // before json_encode (which will not display anything for \UnitEnum otherwise) \array_walk_recursive($data, static function (&$value) { if ($value instanceof \UnitEnum) { $value = \ltrim(\var_export($value, \true), '\\'); } }); $this->write(\json_encode($data, $flags | \JSON_PRETTY_PRINT) . "\n"); } protected function getRouteData(Route $route) : array { $data = ['path' => $route->getPath(), 'pathRegex' => $route->compile()->getRegex(), 'host' => '' !== $route->getHost() ? $route->getHost() : 'ANY', 'hostRegex' => '' !== $route->getHost() ? $route->compile()->getHostRegex() : '', 'scheme' => $route->getSchemes() ? \implode('|', $route->getSchemes()) : 'ANY', 'method' => $route->getMethods() ? \implode('|', $route->getMethods()) : 'ANY', 'class' => \get_class($route), 'defaults' => $route->getDefaults(), 'requirements' => $route->getRequirements() ?: 'NO CUSTOM', 'options' => $route->getOptions()]; if ('' !== $route->getCondition()) { $data['condition'] = $route->getCondition(); } return $data; } private function getContainerDefinitionData(Definition $definition, bool $omitTags = \false, bool $showArguments = \false) : array { $data = ['class' => (string) $definition->getClass(), 'public' => $definition->isPublic() && !$definition->isPrivate(), 'synthetic' => $definition->isSynthetic(), 'lazy' => $definition->isLazy(), 'shared' => $definition->isShared(), 'abstract' => $definition->isAbstract(), 'autowire' => $definition->isAutowired(), 'autoconfigure' => $definition->isAutoconfigured()]; if ('' !== ($classDescription = $this->getClassDescription((string) $definition->getClass()))) { $data['description'] = $classDescription; } if ($showArguments) { $data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments); } $data['file'] = $definition->getFile(); if ($factory = $definition->getFactory()) { if (\is_array($factory)) { if ($factory[0] instanceof Reference) { $data['factory_service'] = (string) $factory[0]; } elseif ($factory[0] instanceof Definition) { $data['factory_service'] = \sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'class not configured'); } else { $data['factory_class'] = $factory[0]; } $data['factory_method'] = $factory[1]; } else { $data['factory_function'] = $factory; } } $calls = $definition->getMethodCalls(); if (\count($calls) > 0) { $data['calls'] = []; foreach ($calls as $callData) { $data['calls'][] = $callData[0]; } } if (!$omitTags) { $data['tags'] = []; foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) { foreach ($tagData as $parameters) { $data['tags'][] = ['name' => $tagName, 'parameters' => $parameters]; } } } return $data; } private function getContainerAliasData(Alias $alias) : array { return ['service' => (string) $alias, 'public' => $alias->isPublic() && !$alias->isPrivate()]; } private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options) : array { $data = []; $event = \array_key_exists('event', $options) ? $options['event'] : null; if (null !== $event) { foreach ($eventDispatcher->getListeners($event) as $listener) { $l = $this->getCallableData($listener); $l['priority'] = $eventDispatcher->getListenerPriority($event, $listener); $data[] = $l; } } else { $registeredListeners = \array_key_exists('events', $options) ? \array_combine($options['events'], \array_map(function ($event) use($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); \ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { foreach ($eventListeners as $eventListener) { $l = $this->getCallableData($eventListener); $l['priority'] = $eventDispatcher->getListenerPriority($eventListened, $eventListener); $data[$eventListened][] = $l; } } } return $data; } private function getCallableData($callable) : array { $data = []; if (\is_array($callable)) { $data['type'] = 'function'; if (\is_object($callable[0])) { $data['name'] = $callable[1]; $data['class'] = \get_class($callable[0]); } else { if (!\str_starts_with($callable[1], 'parent::')) { $data['name'] = $callable[1]; $data['class'] = $callable[0]; $data['static'] = \true; } else { $data['name'] = \substr($callable[1], 8); $data['class'] = $callable[0]; $data['static'] = \true; $data['parent'] = \true; } } return $data; } if (\is_string($callable)) { $data['type'] = 'function'; if (!\str_contains($callable, '::')) { $data['name'] = $callable; } else { $callableParts = \explode('::', $callable); $data['name'] = $callableParts[1]; $data['class'] = $callableParts[0]; $data['static'] = \true; } return $data; } if ($callable instanceof \Closure) { $data['type'] = 'closure'; $r = new \ReflectionFunction($callable); if (\str_contains($r->name, '{closure}')) { return $data; } $data['name'] = $r->name; if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $data['class'] = $class->name; if (!$r->getClosureThis()) { $data['static'] = \true; } } return $data; } if (\method_exists($callable, '__invoke')) { $data['type'] = 'object'; $data['name'] = \get_class($callable); return $data; } throw new \InvalidArgumentException('Callable is not describable.'); } private function describeValue($value, bool $omitTags, bool $showArguments) { if (\is_array($value)) { $data = []; foreach ($value as $k => $v) { $data[$k] = $this->describeValue($v, $omitTags, $showArguments); } return $data; } if ($value instanceof ServiceClosureArgument) { $value = $value->getValues()[0]; } if ($value instanceof Reference) { return ['type' => 'service', 'id' => (string) $value]; } if ($value instanceof AbstractArgument) { return ['type' => 'abstract', 'text' => $value->getText()]; } if ($value instanceof ArgumentInterface) { return $this->describeValue($value->getValues(), $omitTags, $showArguments); } if ($value instanceof Definition) { return $this->getContainerDefinitionData($value, $omitTags, $showArguments); } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Jean-François Simon * * @internal */ class MarkdownDescriptor extends Descriptor { protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $first = \true; foreach ($routes->all() as $name => $route) { if ($first) { $first = \false; } else { $this->write("\n\n"); } $this->describeRoute($route, ['name' => $name]); } $this->write("\n"); } protected function describeRoute(Route $route, array $options = []) { $output = '- Path: ' . $route->getPath() . "\n" . '- Path Regex: ' . $route->compile()->getRegex() . "\n" . '- Host: ' . ('' !== $route->getHost() ? $route->getHost() : 'ANY') . "\n" . '- Host Regex: ' . ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '') . "\n" . '- Scheme: ' . ($route->getSchemes() ? \implode('|', $route->getSchemes()) : 'ANY') . "\n" . '- Method: ' . ($route->getMethods() ? \implode('|', $route->getMethods()) : 'ANY') . "\n" . '- Class: ' . \get_class($route) . "\n" . '- Defaults: ' . $this->formatRouterConfig($route->getDefaults()) . "\n" . '- Requirements: ' . ($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM') . "\n" . '- Options: ' . $this->formatRouterConfig($route->getOptions()); if ('' !== $route->getCondition()) { $output .= "\n" . '- Condition: ' . $route->getCondition(); } $this->write(isset($options['name']) ? $options['name'] . "\n" . \str_repeat('-', \strlen($options['name'])) . "\n\n" . $output : $output); $this->write("\n"); } protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $this->write("Container parameters\n====================\n"); foreach ($this->sortParameters($parameters) as $key => $value) { $this->write(\sprintf("\n- `%s`: `%s`", $key, $this->formatParameter($value))); } } protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $this->write("Container tags\n=============="); foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $this->write("\n\n" . $tag . "\n" . \str_repeat('-', \strlen($tag))); foreach ($definitions as $serviceId => $definition) { $this->write("\n\n"); $this->describeContainerDefinition($definition, ['omit_tags' => \true, 'id' => $serviceId]); } } } protected function describeContainerService(object $service, array $options = [], ?ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); } $childOptions = \array_merge($options, ['id' => $options['id'], 'as_array' => \true]); if ($service instanceof Alias) { $this->describeContainerAlias($service, $childOptions, $builder); } elseif ($service instanceof Definition) { $this->describeContainerDefinition($service, $childOptions); } else { $this->write(\sprintf('**`%s`:** `%s`', $options['id'], \get_class($service))); } } protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []) : void { $containerDeprecationFilePath = \sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); if (!\file_exists($containerDeprecationFilePath)) { throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); } $logs = \unserialize(\file_get_contents($containerDeprecationFilePath)); if (0 === \count($logs)) { $this->write("## There are no deprecations in the logs!\n"); return; } $formattedLogs = []; $remainingCount = 0; foreach ($logs as $log) { $formattedLogs[] = \sprintf("- %sx: \"%s\" in %s:%s\n", $log['count'], $log['message'], $log['file'], $log['line']); $remainingCount += $log['count']; } $this->write(\sprintf("## Remaining deprecations (%s)\n\n", $remainingCount)); foreach ($formattedLogs as $formattedLog) { $this->write($formattedLog); } } protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $title = $showHidden ? 'Hidden services' : 'Services'; if (isset($options['tag'])) { $title .= ' with tag `' . $options['tag'] . '`'; } $this->write($title . "\n" . \str_repeat('=', \strlen($title))); $serviceIds = isset($options['tag']) && $options['tag'] ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) : $this->sortServiceIds($builder->getServiceIds()); $showArguments = isset($options['show_arguments']) && $options['show_arguments']; $services = ['definitions' => [], 'aliases' => [], 'services' => []]; if (isset($options['filter'])) { $serviceIds = \array_filter($serviceIds, $options['filter']); } foreach ($serviceIds as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } if ($service instanceof Alias) { $services['aliases'][$serviceId] = $service; } elseif ($service instanceof Definition) { $services['definitions'][$serviceId] = $service; } else { $services['services'][$serviceId] = $service; } } if (!empty($services['definitions'])) { $this->write("\n\nDefinitions\n-----------\n"); foreach ($services['definitions'] as $id => $service) { $this->write("\n"); $this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments]); } } if (!empty($services['aliases'])) { $this->write("\n\nAliases\n-------\n"); foreach ($services['aliases'] as $id => $service) { $this->write("\n"); $this->describeContainerAlias($service, ['id' => $id]); } } if (!empty($services['services'])) { $this->write("\n\nServices\n--------\n"); foreach ($services['services'] as $id => $service) { $this->write("\n"); $this->write(\sprintf('- `%s`: `%s`', $id, \get_class($service))); } } } protected function describeContainerDefinition(Definition $definition, array $options = []) { $output = ''; if ('' !== ($classDescription = $this->getClassDescription((string) $definition->getClass()))) { $output .= '- Description: `' . $classDescription . '`' . "\n"; } $output .= '- Class: `' . $definition->getClass() . '`' . "\n" . '- Public: ' . ($definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no') . "\n" . '- Synthetic: ' . ($definition->isSynthetic() ? 'yes' : 'no') . "\n" . '- Lazy: ' . ($definition->isLazy() ? 'yes' : 'no') . "\n" . '- Shared: ' . ($definition->isShared() ? 'yes' : 'no') . "\n" . '- Abstract: ' . ($definition->isAbstract() ? 'yes' : 'no') . "\n" . '- Autowired: ' . ($definition->isAutowired() ? 'yes' : 'no') . "\n" . '- Autoconfigured: ' . ($definition->isAutoconfigured() ? 'yes' : 'no'); if (isset($options['show_arguments']) && $options['show_arguments']) { $output .= "\n" . '- Arguments: ' . ($definition->getArguments() ? 'yes' : 'no'); } if ($definition->getFile()) { $output .= "\n" . '- File: `' . $definition->getFile() . '`'; } if ($factory = $definition->getFactory()) { if (\is_array($factory)) { if ($factory[0] instanceof Reference) { $output .= "\n" . '- Factory Service: `' . $factory[0] . '`'; } elseif ($factory[0] instanceof Definition) { $output .= "\n" . \sprintf('- Factory Service: inline factory service (%s)', $factory[0]->getClass() ? \sprintf('`%s`', $factory[0]->getClass()) : 'not configured'); } else { $output .= "\n" . '- Factory Class: `' . $factory[0] . '`'; } $output .= "\n" . '- Factory Method: `' . $factory[1] . '`'; } else { $output .= "\n" . '- Factory Function: `' . $factory . '`'; } } $calls = $definition->getMethodCalls(); foreach ($calls as $callData) { $output .= "\n" . '- Call: `' . $callData[0] . '`'; } if (!(isset($options['omit_tags']) && $options['omit_tags'])) { foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) { foreach ($tagData as $parameters) { $output .= "\n" . '- Tag: `' . $tagName . '`'; foreach ($parameters as $name => $value) { $output .= "\n" . ' - ' . \ucfirst($name) . ': ' . $value; } } } } $this->write(isset($options['id']) ? \sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); } protected function describeContainerAlias(Alias $alias, array $options = [], ?ContainerBuilder $builder = null) { $output = '- Service: `' . $alias . '`' . "\n" . '- Public: ' . ($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no'); if (!isset($options['id'])) { $this->write($output); return; } $this->write(\sprintf("### %s\n\n%s\n", $options['id'], $output)); if (!$builder) { return; } $this->write("\n"); $this->describeContainerDefinition($builder->getDefinition((string) $alias), \array_merge($options, ['id' => (string) $alias])); } protected function describeContainerParameter($parameter, array $options = []) { $this->write(isset($options['parameter']) ? \sprintf("%s\n%s\n\n%s", $options['parameter'], \str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter); } protected function describeContainerEnvVars(array $envs, array $options = []) { throw new LogicException('Using the markdown format to debug environment variables is not supported.'); } protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $event = $options['event'] ?? null; $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; $title = 'Registered listeners'; if (null !== $dispatcherServiceName) { $title .= \sprintf(' of event dispatcher "%s"', $dispatcherServiceName); } if (null !== $event) { $title .= \sprintf(' for event `%s` ordered by descending priority', $event); $registeredListeners = $eventDispatcher->getListeners($event); } else { // Try to see if "events" exists $registeredListeners = \array_key_exists('events', $options) ? \array_combine($options['events'], \array_map(function ($event) use($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); } $this->write(\sprintf('# %s', $title) . "\n"); if (null !== $event) { foreach ($registeredListeners as $order => $listener) { $this->write("\n" . \sprintf('## Listener %d', $order + 1) . "\n"); $this->describeCallable($listener); $this->write(\sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($event, $listener)) . "\n"); } } else { \ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { $this->write("\n" . \sprintf('## %s', $eventListened) . "\n"); foreach ($eventListeners as $order => $eventListener) { $this->write("\n" . \sprintf('### Listener %d', $order + 1) . "\n"); $this->describeCallable($eventListener); $this->write(\sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($eventListened, $eventListener)) . "\n"); } } } } protected function describeCallable($callable, array $options = []) { $string = ''; if (\is_array($callable)) { $string .= "\n- Type: `function`"; if (\is_object($callable[0])) { $string .= "\n" . \sprintf('- Name: `%s`', $callable[1]); $string .= "\n" . \sprintf('- Class: `%s`', \get_class($callable[0])); } else { if (!\str_starts_with($callable[1], 'parent::')) { $string .= "\n" . \sprintf('- Name: `%s`', $callable[1]); $string .= "\n" . \sprintf('- Class: `%s`', $callable[0]); $string .= "\n- Static: yes"; } else { $string .= "\n" . \sprintf('- Name: `%s`', \substr($callable[1], 8)); $string .= "\n" . \sprintf('- Class: `%s`', $callable[0]); $string .= "\n- Static: yes"; $string .= "\n- Parent: yes"; } } return $this->write($string . "\n"); } if (\is_string($callable)) { $string .= "\n- Type: `function`"; if (!\str_contains($callable, '::')) { $string .= "\n" . \sprintf('- Name: `%s`', $callable); } else { $callableParts = \explode('::', $callable); $string .= "\n" . \sprintf('- Name: `%s`', $callableParts[1]); $string .= "\n" . \sprintf('- Class: `%s`', $callableParts[0]); $string .= "\n- Static: yes"; } return $this->write($string . "\n"); } if ($callable instanceof \Closure) { $string .= "\n- Type: `closure`"; $r = new \ReflectionFunction($callable); if (\str_contains($r->name, '{closure}')) { return $this->write($string . "\n"); } $string .= "\n" . \sprintf('- Name: `%s`', $r->name); if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $string .= "\n" . \sprintf('- Class: `%s`', $class->name); if (!$r->getClosureThis()) { $string .= "\n- Static: yes"; } } return $this->write($string . "\n"); } if (\method_exists($callable, '__invoke')) { $string .= "\n- Type: `object`"; $string .= "\n" . \sprintf('- Name: `%s`', \get_class($callable)); return $this->write($string . "\n"); } throw new \InvalidArgumentException('Callable is not describable.'); } private function formatRouterConfig(array $array) : string { if (!$array) { return 'NONE'; } $string = ''; \ksort($array); foreach ($array as $name => $value) { $string .= "\n" . ' - `' . $name . '`: ' . $this->formatValue($value); } return $string; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Helper; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor\JsonDescriptor; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor; use _ContaoManager\Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; /** * @author Jean-François Simon * * @internal */ class DescriptorHelper extends BaseDescriptorHelper { public function __construct(?FileLinkFormatter $fileLinkFormatter = null) { $this->register('txt', new TextDescriptor($fileLinkFormatter))->register('xml', new XmlDescriptor())->register('json', new JsonDescriptor())->register('md', new MarkdownDescriptor()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\EventListener; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\Console\Event\ConsoleErrorEvent; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Suggests a package, that should be installed (via composer), * if the package is missing, and the input command namespace can be mapped to a Symfony bundle. * * @author Przemysław Bogusz * * @internal */ final class SuggestMissingPackageSubscriber implements EventSubscriberInterface { private const PACKAGES = ['doctrine' => ['fixtures' => ['DoctrineFixturesBundle', 'doctrine/doctrine-fixtures-bundle --dev'], 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], '_default' => ['Doctrine ORM', 'symfony/orm-pack']], 'generate' => ['_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle']], 'make' => ['_default' => ['MakerBundle', 'symfony/maker-bundle --dev']], 'server' => ['_default' => ['Debug Bundle', 'symfony/debug-bundle --dev']]]; public function onConsoleError(ConsoleErrorEvent $event) : void { if (!$event->getError() instanceof CommandNotFoundException) { return; } [$namespace, $command] = \explode(':', $event->getInput()->getFirstArgument()) + [1 => '']; if (!isset(self::PACKAGES[$namespace])) { return; } if (isset(self::PACKAGES[$namespace][$command])) { $suggestion = self::PACKAGES[$namespace][$command]; $exact = \true; } else { $suggestion = self::PACKAGES[$namespace]['_default']; $exact = \false; } $error = $event->getError(); if ($error->getAlternatives() && !$exact) { return; } $message = \sprintf("%s\n\nYou may be looking for a command provided by the \"%s\" which is currently not installed. Try running \"composer require %s\".", $error->getMessage(), $suggestion[0], $suggestion[1]); $event->setError(new CommandNotFoundException($message)); } public static function getSubscribedEvents() : array { return [ConsoleEvents::ERROR => ['onConsoleError', 0]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; /** * @author Jérémy Derussé * * @internal to be removed in Symfony 6 */ final class ServiceSessionFactory implements SessionStorageFactoryInterface { private $storage; public function __construct(SessionStorageInterface $storage) { $this->storage = $storage; } public function createStorage(?Request $request) : SessionStorageInterface { if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { $this->storage->setOptions(['cookie_secure' => \true]); } return $this->storage; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\FrameworkBundle\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Provides session and trigger deprecation. * * Used by service that should trigger deprecation when accessed by the user. * * @author Jérémy Derussé * * @internal to be removed in 6.0 */ class DeprecatedSessionFactory { private $requestStack; public function __construct(RequestStack $requestStack) { $this->requestStack = $requestStack; } public function getSession() : ?SessionInterface { \trigger_deprecation('symfony/framework-bundle', '5.3', 'The "session" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); try { return $this->requestStack->getSession(); } catch (SessionNotFoundException $e) { return null; } } } Copyright (c) 2004-2019 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Unreleased ## 3.10.0 (2023-11-06) * Add configuration support for SamplingHandler ## 3.9.0 (2023-11-06) * Add support for the `WithMonologChannel` attribute of Monolog 3.5.0 to autoconfigure the `monolog.logger` tag * Add support for Symfony 7 * Remove support for Symfony 4 * Mark classes as internal when relevant * Add support for env placeholders in the `level` option of handlers ## 3.8.0 (2022-05-10) * Deprecated ambiguous `elasticsearch` type, use `elastica` instead * Added support for Monolog 3.0 (requires symfony/monolog-bridge 6.1) * Added support for `AsMonologProcessor` to autoconfigure processors * Added support for `FallbackGroupHandler` * Added support for `ElasticsearchHandler` as `elastic_search` type * Added support for `ElasticaHandler` as `elastica` type * Added support for `TelegramBotHandler` as `telegram` * Added `fill_extra_context` flag for `sentry` handlers * Added support for configuring PsrLogMessageProcessor (`date_format` and `remove_used_context_fields`) * Fixed issue on Windows + PHP 8, workaround for https://github.com/php/php-src/issues/8315 * Fixed MongoDBHandler support when no client id is provided ## 3.7.1 (2021-11-05) * Indicate compatibility with Symfony 6 ## 3.7.0 (2021-03-31) * Use `ActivationStrategy` instead of `actionLevel` when available * Register resettable processors (`ResettableInterface`) for autoconfiguration (tag: `kernel.reset`) * Drop support for Symfony 3.4 * Drop support for PHP < 7.1 * Fix call to undefined method pushProcessor on handler that does not implement ProcessableHandlerInterface * Use "use_locking" option with rotating file handler * Add ability to specify custom Sentry hub service ## 3.6.0 (2020-10-06) * Added support for Symfony Mailer * Added support for setting log levels from parameters or environment variables ## 3.5.0 (2019-11-13) * Added support for Monolog 2.0 * Added `sentry` type to use sentry 2.0 client * Added `insightops` handler * Added possibility for auto-wire monolog channel according to the type-hinted aliases, introduced in the Symfony 4.2 ## 3.4.0 (2019-06-20) * Deprecate "excluded_404s" option * Flush loggers on `kernel.reset` * Register processors (`ProcessorInterface`) for autoconfiguration (tag: `monolog.processor`) * Expose configuration for the `ConsoleHandler` * Fixed psr-3 processing being applied to all handlers, only leaf ones are now processing * Fixed regression when `app` channel is defined explicitly * Fixed handlers marked as nested not being ignored properly from the stack * Added support for Redis configuration * Drop support for Symfony <3 ## 3.3.1 (2018-11-04) * Fixed compatiblity with Symfony 4.2 ## 3.3.0 (2018-06-04) * Fixed the autowiring of the channel logger in autoconfigured services * Added timeouts to the pushover, hipchat, slack handlers * Dropped support for PHP 5.3, 5.4, and HHVM * Added configuration for HttpCodeActivationStrategy * Deprecated "excluded_404s" option for Symfony >= 3.4 ## 3.2.0 (2018-03-05) * Removed randomness from the container build * Fixed support for the `monolog.logger` tag specifying a channel in combination with Symfony 3.4+ autowiring * Fixed visibility of channels configured explicitly in the bundle config (they are now public in Symfony 4 too) * Fixed invalid service definitions ## 3.1.2 (2017-11-06) * fix invalid usage of count() ## 3.1.1 (2017-09-26) * added support for Symfony 4 ## 3.1.0 (2017-03-26) * Added support for server_log handler * Allow configuring VERBOSITY_QUIET in console handlers * Fixed autowiring * Fixed slackbot handler not escaping channel names properly * Fixed slackbot handler requiring `slack_team` instead of `team` to be configured ## 3.0.3 (2017-01-10) * Fixed deprecation notices when using Symfony 3.3+ and PHP7+ ## 3.0.2 (2017-01-03) * Revert disabling DebugHandler in CLI environments * Update configuration for slack handlers for Monolog 1.22 new options * Revert the removal of the DebugHandlerPass (needed for Symfony <3.2) ## 3.0.1 (2016-11-15) * Removed obsolete code (DebugHandlerPass) ## 3.0.0 (2016-11-06) * Removed class parameters for the container configuration * Bumped minimum version of supported Symfony version to 2.7 * Removed `NotFoundActivationStrategy` (the bundle now uses the class from MonologBridge) app %monolog.use_microseconds% app * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\SwiftMailer; /** * Helps create Swift_Message objects, lazily * * @author Ryan Weaver */ class MessageFactory { private $mailer; private $fromEmail; private $toEmail; private $subject; private $contentType; public function __construct(\_ContaoManager\Swift_Mailer $mailer, $fromEmail, $toEmail, $subject, $contentType = null) { $this->mailer = $mailer; $this->fromEmail = $fromEmail; $this->toEmail = $toEmail; $this->subject = $subject; $this->contentType = $contentType; } /** * Creates a Swift_Message template that will be used to send the log message * * @param string $content formatted email body to be sent * @param array $records Log records that formed the content * @return \Swift_Message */ public function createMessage($content, array $records) { /** @var \Swift_Message $message */ $message = $this->mailer->createMessage(); $message->setTo($this->toEmail); $message->setFrom($this->fromEmail); $message->setSubject($this->subject); if ($this->contentType) { $message->setContentType($this->contentType); } return $message; } } MonologBundle ============= The `MonologBundle` provides integration of the [Monolog](https://github.com/Seldaek/monolog) library into the Symfony framework. More information in the official [documentation](http://symfony.com/doc/current/cookbook/logging/index.html). License ======= This bundle is released under the [MIT license](LICENSE) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle; use _ContaoManager\Monolog\Formatter\JsonFormatter; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Handler\HandlerInterface; use _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddSwiftMailerTransportPass; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\Bundle; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass; use _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\DebugHandlerPass; use _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\AddProcessorsPass; use _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\FixEmptyLoggerPass; /** * @author Jordi Boggiano * * @finalsince 3.9.0 */ class MonologBundle extends Bundle { /** * @return void */ public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass($channelPass = new LoggerChannelPass()); $container->addCompilerPass(new FixEmptyLoggerPass($channelPass)); $container->addCompilerPass(new AddProcessorsPass()); $container->addCompilerPass(new AddSwiftMailerTransportPass()); } /** * @internal * @return void */ public static function includeStacktraces(HandlerInterface $handler) { $formatter = $handler->getFormatter(); if ($formatter instanceof LineFormatter || $formatter instanceof JsonFormatter) { $formatter->includeStacktraces(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection; use _ContaoManager\Monolog\Attribute\AsMonologProcessor; use _ContaoManager\Monolog\Attribute\WithMonologChannel; use _ContaoManager\Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; use _ContaoManager\Monolog\Handler\HandlerInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Monolog\Processor\ProcessorInterface; use _ContaoManager\Monolog\Processor\PsrLogMessageProcessor; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy; use _ContaoManager\Symfony\Bridge\Monolog\Processor\SwitchUserTokenProcessor; use _ContaoManager\Symfony\Bridge\Monolog\Processor\TokenProcessor; use _ContaoManager\Symfony\Bridge\Monolog\Processor\WebProcessor; use _ContaoManager\Symfony\Bridge\Monolog\Logger as LegacyLogger; use _ContaoManager\Symfony\Bundle\FullStack; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; use _ContaoManager\Symfony\Contracts\HttpClient\HttpClientInterface; /** * MonologExtension is an extension for the Monolog library. * * @author Jordi Boggiano * @author Christophe Coevoet * * @finalsince 3.9.0 */ class MonologExtension extends Extension { private $nestedHandlers = []; private $swiftMailerHandlers = []; /** * Loads the Monolog configuration. * * @param array $configs An array of configuration settings * @param ContainerBuilder $container A ContainerBuilder instance */ public function load(array $configs, ContainerBuilder $container) { $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); if (isset($config['handlers'])) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('monolog.xml'); if (!\class_exists(DebugLoggerConfigurator::class)) { $container->getDefinition('monolog.logger_prototype')->setClass(LegacyLogger::class); } $container->setParameter('monolog.use_microseconds', $config['use_microseconds']); $handlers = []; foreach ($config['handlers'] as $name => $handler) { $handlers[$handler['priority']][] = ['id' => $this->buildHandler($container, $name, $handler), 'channels' => empty($handler['channels']) ? null : $handler['channels']]; } $container->setParameter('monolog.swift_mailer.handlers', $this->swiftMailerHandlers); \ksort($handlers); $sortedHandlers = []; foreach ($handlers as $priorityHandlers) { foreach (\array_reverse($priorityHandlers) as $handler) { $sortedHandlers[] = $handler; } } $handlersToChannels = []; foreach ($sortedHandlers as $handler) { if (!\in_array($handler['id'], $this->nestedHandlers)) { $handlersToChannels[$handler['id']] = $handler['channels']; } } $container->setParameter('monolog.handlers_to_channels', $handlersToChannels); } $container->setParameter('monolog.additional_channels', isset($config['channels']) ? $config['channels'] : []); if (\interface_exists(ProcessorInterface::class)) { $container->registerForAutoconfiguration(ProcessorInterface::class)->addTag('monolog.processor'); } else { $container->registerForAutoconfiguration(WebProcessor::class)->addTag('monolog.processor'); } if (\interface_exists(ResettableInterface::class)) { $container->registerForAutoconfiguration(ResettableInterface::class)->addTag('kernel.reset', ['method' => 'reset']); } $container->registerForAutoconfiguration(TokenProcessor::class)->addTag('monolog.processor'); if (\interface_exists(HttpClientInterface::class)) { $handlerAutoconfiguration = $container->registerForAutoconfiguration(HandlerInterface::class); $handlerAutoconfiguration->setBindings($handlerAutoconfiguration->getBindings() + [HttpClientInterface::class => new BoundArgument(new Reference('monolog.http_client'), \false)]); } if (80000 <= \PHP_VERSION_ID) { $container->registerAttributeForAutoconfiguration(AsMonologProcessor::class, static function (ChildDefinition $definition, AsMonologProcessor $attribute, \Reflector $reflector) : void { $tagAttributes = \get_object_vars($attribute); if ($reflector instanceof \ReflectionMethod) { if (isset($tagAttributes['method'])) { throw new \LogicException(\sprintf('AsMonologProcessor attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); } $tagAttributes['method'] = $reflector->getName(); } $definition->addTag('monolog.processor', $tagAttributes); }); $container->registerAttributeForAutoconfiguration(WithMonologChannel::class, static function (ChildDefinition $definition, WithMonologChannel $attribute) : void { $definition->addTag('monolog.logger', ['channel' => $attribute->channel]); }); } } /** * Returns the base path for the XSD files. * * @return string The XSD base path */ public function getXsdValidationBasePath() { return __DIR__ . '/../Resources/config/schema'; } public function getNamespace() { return 'http://symfony.com/schema/dic/monolog'; } private function buildHandler(ContainerBuilder $container, $name, array $handler) { $handlerId = $this->getHandlerId($name); if ('service' === $handler['type']) { $container->setAlias($handlerId, $handler['id']); if (!empty($handler['nested']) && \true === $handler['nested']) { $this->markNestedHandler($handlerId); } return $handlerId; } $handlerClass = $this->getHandlerClassByType($handler['type']); $definition = new Definition($handlerClass); if ($handler['include_stacktraces']) { $definition->setConfigurator(['_ContaoManager\\Symfony\\Bundle\\MonologBundle\\MonologBundle', 'includeStacktraces']); } if (null === $handler['process_psr_3_messages']['enabled']) { $handler['process_psr_3_messages']['enabled'] = !isset($handler['handler']) && !$handler['members']; } if ($handler['process_psr_3_messages']['enabled'] && \method_exists($handlerClass, 'pushProcessor')) { $processorId = $this->buildPsrLogMessageProcessor($container, $handler['process_psr_3_messages']); $definition->addMethodCall('pushProcessor', [new Reference($processorId)]); } switch ($handler['type']) { case 'stream': $definition->setArguments([$handler['path'], $handler['level'], $handler['bubble'], $handler['file_permission'], $handler['use_locking']]); break; case 'console': $definition->setArguments([null, $handler['bubble'], isset($handler['verbosity_levels']) ? $handler['verbosity_levels'] : [], $handler['console_formatter_options']]); $definition->addTag('kernel.event_subscriber'); break; case 'chromephp': case 'firephp': $definition->setArguments([$handler['level'], $handler['bubble']]); $definition->addTag('kernel.event_listener', ['event' => 'kernel.response', 'method' => 'onKernelResponse']); break; case 'gelf': if (isset($handler['publisher']['id'])) { $publisher = new Reference($handler['publisher']['id']); } elseif (\class_exists('_ContaoManager\\Gelf\\Transport\\UdpTransport')) { $transport = new Definition("_ContaoManager\\Gelf\\Transport\\UdpTransport", [$handler['publisher']['hostname'], $handler['publisher']['port'], $handler['publisher']['chunk_size']]); $transport->setPublic(\false); $publisher = new Definition('_ContaoManager\\Gelf\\Publisher', []); $publisher->addMethodCall('addTransport', [$transport]); $publisher->setPublic(\false); } elseif (\class_exists('_ContaoManager\\Gelf\\MessagePublisher')) { $publisher = new Definition('_ContaoManager\\Gelf\\MessagePublisher', [$handler['publisher']['hostname'], $handler['publisher']['port'], $handler['publisher']['chunk_size']]); $publisher->setPublic(\false); } else { throw new \RuntimeException('The gelf handler requires the graylog2/gelf-php package to be installed'); } $definition->setArguments([$publisher, $handler['level'], $handler['bubble']]); break; case 'mongo': if (isset($handler['mongo']['id'])) { $client = new Reference($handler['mongo']['id']); } else { $server = 'mongodb://'; if (isset($handler['mongo']['user'])) { $server .= $handler['mongo']['user'] . ':' . $handler['mongo']['pass'] . '@'; } $server .= $handler['mongo']['host'] . ':' . $handler['mongo']['port']; $client = new Definition('_ContaoManager\\MongoDB\\Client', [$server]); $client->setPublic(\false); } $definition->setArguments([$client, $handler['mongo']['database'], $handler['mongo']['collection'], $handler['level'], $handler['bubble']]); break; case 'elasticsearch': @\trigger_error('The "elasticsearch" handler type is deprecated in MonologBundle since version 3.8.0, use the "elastica" type instead, or switch to the official Elastic client using the "elastic_search" type.', \E_USER_DEPRECATED); // no break case 'elastica': case 'elastic_search': if (isset($handler['elasticsearch']['id'])) { $client = new Reference($handler['elasticsearch']['id']); } else { if ($handler['type'] === 'elastic_search') { // v8 has a new Elastic\ prefix $client = new Definition(\class_exists('_ContaoManager\\Elastic\\Elasticsearch\\Client') ? 'Elastic\\Elasticsearch\\Client' : 'Elasticsearch\\Client'); $factory = \class_exists('_ContaoManager\\Elastic\\Elasticsearch\\ClientBuilder') ? 'Elastic\\Elasticsearch\\ClientBuilder' : 'Elasticsearch\\ClientBuilder'; $client->setFactory([$factory, 'fromConfig']); $clientArguments = ['host' => $handler['elasticsearch']['host']]; if (isset($handler['elasticsearch']['user'], $handler['elasticsearch']['password'])) { $clientArguments['basicAuthentication'] = [$handler['elasticsearch']['user'], $handler['elasticsearch']['password']]; } } else { $client = new Definition('_ContaoManager\\Elastica\\Client'); $clientArguments = ['host' => $handler['elasticsearch']['host'], 'port' => $handler['elasticsearch']['port'], 'transport' => $handler['elasticsearch']['transport']]; if (isset($handler['elasticsearch']['user'], $handler['elasticsearch']['password'])) { $clientArguments['headers'] = ['Authorization' => 'Basic ' . \base64_encode($handler['elasticsearch']['user'] . ':' . $handler['elasticsearch']['password'])]; } } $client->setArguments([$clientArguments]); $client->setPublic(\false); } // elastica handler definition $definition->setArguments([$client, ['index' => $handler['index'], 'type' => $handler['document_type'], 'ignore_error' => $handler['ignore_error']], $handler['level'], $handler['bubble']]); break; case 'telegram': if (!\class_exists('_ContaoManager\\Monolog\\Handler\\TelegramBotHandler')) { throw new \RuntimeException('The TelegramBotHandler is not available. Please update "monolog/monolog" to 2.2.0'); } $definition->setArguments([$handler['token'], $handler['channel'], $handler['level'], $handler['bubble'], $handler['parse_mode'], $handler['disable_webpage_preview'], $handler['disable_notification'], $handler['split_long_messages'], $handler['delay_between_messages']]); break; case 'redis': case 'predis': if (isset($handler['redis']['id'])) { $clientId = $handler['redis']['id']; } elseif ('redis' === $handler['type']) { if (!\class_exists(\Redis::class)) { throw new \RuntimeException('The \\Redis class is not available.'); } $client = new Definition(\Redis::class); $client->addMethodCall('connect', [$handler['redis']['host'], $handler['redis']['port']]); $client->addMethodCall('auth', [$handler['redis']['password']]); $client->addMethodCall('select', [$handler['redis']['database']]); $client->setPublic(\false); $clientId = \uniqid('monolog.redis.client.', \true); $container->setDefinition($clientId, $client); } else { if (!\class_exists(\_ContaoManager\Predis\Client::class)) { throw new \RuntimeException('The \\Predis\\Client class is not available.'); } $client = new Definition(\_ContaoManager\Predis\Client::class); $client->setArguments([$handler['redis']['host']]); $client->setPublic(\false); $clientId = \uniqid('monolog.predis.client.', \true); $container->setDefinition($clientId, $client); } $definition->setArguments([new Reference($clientId), $handler['redis']['key_name'], $handler['level'], $handler['bubble']]); break; case 'rotating_file': $definition->setArguments([$handler['path'], $handler['max_files'], $handler['level'], $handler['bubble'], $handler['file_permission'], $handler['use_locking']]); $definition->addMethodCall('setFilenameFormat', [$handler['filename_format'], $handler['date_format']]); break; case 'fingers_crossed': $nestedHandlerId = $this->getHandlerId($handler['handler']); $this->markNestedHandler($nestedHandlerId); $activation = $handler['action_level']; if (\class_exists(SwitchUserTokenProcessor::class)) { $activation = new Definition(ErrorLevelActivationStrategy::class, [$activation]); } if (isset($handler['activation_strategy'])) { $activation = new Reference($handler['activation_strategy']); } elseif (!empty($handler['excluded_404s'])) { if (\class_exists(HttpCodeActivationStrategy::class)) { @\trigger_error('The "excluded_404s" option is deprecated in MonologBundle since version 3.4.0, you should rely on the "excluded_http_codes" option instead.', \E_USER_DEPRECATED); } $activationDef = new Definition('_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FingersCrossed\\NotFoundActivationStrategy', [new Reference('request_stack'), $handler['excluded_404s'], $activation]); $container->setDefinition($handlerId . '.not_found_strategy', $activationDef); $activation = new Reference($handlerId . '.not_found_strategy'); } elseif (!empty($handler['excluded_http_codes'])) { $activationDef = new Definition('_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FingersCrossed\\HttpCodeActivationStrategy', [new Reference('request_stack'), $handler['excluded_http_codes'], $activation]); $container->setDefinition($handlerId . '.http_code_strategy', $activationDef); $activation = new Reference($handlerId . '.http_code_strategy'); } $definition->setArguments([new Reference($nestedHandlerId), $activation, $handler['buffer_size'], $handler['bubble'], $handler['stop_buffering'], $handler['passthru_level']]); break; case 'filter': $nestedHandlerId = $this->getHandlerId($handler['handler']); $this->markNestedHandler($nestedHandlerId); $minLevelOrList = !empty($handler['accepted_levels']) ? $handler['accepted_levels'] : $handler['min_level']; $definition->setArguments([new Reference($nestedHandlerId), $minLevelOrList, $handler['max_level'], $handler['bubble']]); break; case 'buffer': $nestedHandlerId = $this->getHandlerId($handler['handler']); $this->markNestedHandler($nestedHandlerId); $definition->setArguments([new Reference($nestedHandlerId), $handler['buffer_size'], $handler['level'], $handler['bubble'], $handler['flush_on_overflow']]); break; case 'deduplication': $nestedHandlerId = $this->getHandlerId($handler['handler']); $this->markNestedHandler($nestedHandlerId); $defaultStore = '%kernel.cache_dir%/monolog_dedup_' . \sha1($handlerId); $definition->setArguments([new Reference($nestedHandlerId), isset($handler['store']) ? $handler['store'] : $defaultStore, $handler['deduplication_level'], $handler['time'], $handler['bubble']]); break; case 'group': case 'whatfailuregroup': case 'fallbackgroup': $references = []; foreach ($handler['members'] as $nestedHandler) { $nestedHandlerId = $this->getHandlerId($nestedHandler); $this->markNestedHandler($nestedHandlerId); $references[] = new Reference($nestedHandlerId); } $definition->setArguments([$references, $handler['bubble']]); break; case 'syslog': $definition->setArguments([$handler['ident'], $handler['facility'], $handler['level'], $handler['bubble'], $handler['logopts']]); break; case 'syslogudp': $definition->setArguments([$handler['host'], $handler['port'], $handler['facility'], $handler['level'], $handler['bubble']]); if ($handler['ident']) { $definition->addArgument($handler['ident']); } break; case 'swift_mailer': $mailer = $handler['mailer'] ?: 'mailer'; if (isset($handler['email_prototype'])) { if (!empty($handler['email_prototype']['method'])) { $prototype = [new Reference($handler['email_prototype']['id']), $handler['email_prototype']['method']]; } else { $prototype = new Reference($handler['email_prototype']['id']); } } else { $messageFactory = new Definition('_ContaoManager\\Symfony\\Bundle\\MonologBundle\\SwiftMailer\\MessageFactory'); $messageFactory->setLazy(\true); $messageFactory->setPublic(\false); $messageFactory->setArguments([new Reference($mailer), $handler['from_email'], $handler['to_email'], $handler['subject'], $handler['content_type']]); $messageFactoryId = \sprintf('%s.mail_message_factory', $handlerId); $container->setDefinition($messageFactoryId, $messageFactory); // set the prototype as a callable $prototype = [new Reference($messageFactoryId), 'createMessage']; } $definition->setArguments([new Reference($mailer), $prototype, $handler['level'], $handler['bubble']]); $this->swiftMailerHandlers[] = $handlerId; $definition->addTag('kernel.event_listener', ['event' => 'kernel.terminate', 'method' => 'onKernelTerminate']); $definition->addTag('kernel.event_listener', ['event' => 'console.terminate', 'method' => 'onCliTerminate']); break; case 'native_mailer': $definition->setArguments([$handler['to_email'], $handler['subject'], $handler['from_email'], $handler['level'], $handler['bubble']]); if (!empty($handler['headers'])) { $definition->addMethodCall('addHeader', [$handler['headers']]); } break; case 'symfony_mailer': $mailer = $handler['mailer'] ?: 'mailer.mailer'; if (isset($handler['email_prototype'])) { if (!empty($handler['email_prototype']['method'])) { $prototype = [new Reference($handler['email_prototype']['id']), $handler['email_prototype']['method']]; } else { $prototype = new Reference($handler['email_prototype']['id']); } } else { $prototype = (new Definition('_ContaoManager\\Symfony\\Component\\Mime\\Email'))->setPublic(\false)->addMethodCall('from', [$handler['from_email']])->addMethodCall('to', $handler['to_email'])->addMethodCall('subject', [$handler['subject']]); } $definition->setArguments([new Reference($mailer), $prototype, $handler['level'], $handler['bubble']]); break; case 'socket': $definition->setArguments([$handler['connection_string'], $handler['level'], $handler['bubble']]); if (isset($handler['timeout'])) { $definition->addMethodCall('setTimeout', [$handler['timeout']]); } if (isset($handler['connection_timeout'])) { $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); } if (isset($handler['persistent'])) { $definition->addMethodCall('setPersistent', [$handler['persistent']]); } break; case 'pushover': $definition->setArguments([$handler['token'], $handler['user'], $handler['title'], $handler['level'], $handler['bubble']]); if (isset($handler['timeout'])) { $definition->addMethodCall('setTimeout', [$handler['timeout']]); } if (isset($handler['connection_timeout'])) { $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); } break; case 'hipchat': $definition->setArguments([$handler['token'], $handler['room'], $handler['nickname'], $handler['notify'], $handler['level'], $handler['bubble'], $handler['use_ssl'], $handler['message_format'], !empty($handler['host']) ? $handler['host'] : 'api.hipchat.com', !empty($handler['api_version']) ? $handler['api_version'] : 'v1']); if (isset($handler['timeout'])) { $definition->addMethodCall('setTimeout', [$handler['timeout']]); } if (isset($handler['connection_timeout'])) { $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); } break; case 'slack': $definition->setArguments([$handler['token'], $handler['channel'], $handler['bot_name'], $handler['use_attachment'], $handler['icon_emoji'], $handler['level'], $handler['bubble'], $handler['use_short_attachment'], $handler['include_extra']]); if (isset($handler['timeout'])) { $definition->addMethodCall('setTimeout', [$handler['timeout']]); } if (isset($handler['connection_timeout'])) { $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); } break; case 'slackwebhook': $definition->setArguments([$handler['webhook_url'], $handler['channel'], $handler['bot_name'], $handler['use_attachment'], $handler['icon_emoji'], $handler['use_short_attachment'], $handler['include_extra'], $handler['level'], $handler['bubble']]); break; case 'slackbot': $definition->setArguments([$handler['team'], $handler['token'], \urlencode($handler['channel']), $handler['level'], $handler['bubble']]); break; case 'cube': $definition->setArguments([$handler['url'], $handler['level'], $handler['bubble']]); break; case 'amqp': $definition->setArguments([new Reference($handler['exchange']), $handler['exchange_name'], $handler['level'], $handler['bubble']]); break; case 'error_log': $definition->setArguments([$handler['message_type'], $handler['level'], $handler['bubble']]); break; case 'sentry': if (null !== $handler['hub_id']) { $hub = new Reference($handler['hub_id']); } else { if (null !== $handler['client_id']) { $clientId = $handler['client_id']; } else { $options = new Definition('_ContaoManager\\Sentry\\Options', [['dsn' => $handler['dsn']]]); if (!empty($handler['environment'])) { $options->addMethodCall('setEnvironment', [$handler['environment']]); } if (!empty($handler['release'])) { $options->addMethodCall('setRelease', [$handler['release']]); } $builder = new Definition('_ContaoManager\\Sentry\\ClientBuilder', [$options]); $client = new Definition('_ContaoManager\\Sentry\\Client'); $client->setFactory([$builder, 'getClient']); $clientId = 'monolog.sentry.client.' . \sha1($handler['dsn']); $container->setDefinition($clientId, $client); if (!$container->hasAlias('_ContaoManager\\Sentry\\ClientInterface')) { $container->setAlias('_ContaoManager\\Sentry\\ClientInterface', $clientId); } } $hub = new Definition('_ContaoManager\\Sentry\\State\\Hub', [new Reference($clientId)]); $container->setDefinition(\sprintf('monolog.handler.%s.hub', $name), $hub); // can't set the hub to the current hub, getting into a recursion otherwise... //$hub->addMethodCall('setCurrent', array($hub)); } $definition->setArguments([$hub, $handler['level'], $handler['bubble'], $handler['fill_extra_context']]); break; case 'raven': if (null !== $handler['client_id']) { $clientId = $handler['client_id']; } else { $client = new Definition('Raven_Client', [$handler['dsn'], ['auto_log_stacks' => $handler['auto_log_stacks'], 'environment' => $handler['environment']]]); $client->setPublic(\false); $clientId = 'monolog.raven.client.' . \sha1($handler['dsn']); $container->setDefinition($clientId, $client); } $definition->setArguments([new Reference($clientId), $handler['level'], $handler['bubble']]); if (!empty($handler['release'])) { $definition->addMethodCall('setRelease', [$handler['release']]); } break; case 'loggly': $definition->setArguments([$handler['token'], $handler['level'], $handler['bubble']]); if (!empty($handler['tags'])) { $definition->addMethodCall('setTag', [\implode(',', $handler['tags'])]); } break; case 'logentries': $definition->setArguments([$handler['token'], $handler['use_ssl'], $handler['level'], $handler['bubble']]); if (isset($handler['timeout'])) { $definition->addMethodCall('setTimeout', [$handler['timeout']]); } if (isset($handler['connection_timeout'])) { $definition->addMethodCall('setConnectionTimeout', [$handler['connection_timeout']]); } break; case 'insightops': $definition->setArguments([$handler['token'], $handler['region'] ? $handler['region'] : 'us', $handler['use_ssl'], $handler['level'], $handler['bubble']]); break; case 'flowdock': $definition->setArguments([$handler['token'], $handler['level'], $handler['bubble']]); if (empty($handler['formatter'])) { $formatter = new Definition("_ContaoManager\\Monolog\\Formatter\\FlowdockFormatter", [$handler['source'], $handler['from_email']]); $formatterId = 'monolog.flowdock.formatter.' . \sha1($handler['source'] . '|' . $handler['from_email']); $formatter->setPublic(\false); $container->setDefinition($formatterId, $formatter); $definition->addMethodCall('setFormatter', [new Reference($formatterId)]); } break; case 'rollbar': if (!empty($handler['id'])) { $rollbarId = $handler['id']; } else { $config = $handler['config'] ?: []; $config['access_token'] = $handler['token']; $rollbar = new Definition('RollbarNotifier', [$config]); $rollbarId = 'monolog.rollbar.notifier.' . \sha1(\json_encode($config)); $rollbar->setPublic(\false); $container->setDefinition($rollbarId, $rollbar); } $definition->setArguments([new Reference($rollbarId), $handler['level'], $handler['bubble']]); break; case 'newrelic': $definition->setArguments([$handler['level'], $handler['bubble'], $handler['app_name']]); break; case 'server_log': $definition->setArguments([$handler['host'], $handler['level'], $handler['bubble']]); break; case 'sampling': $nestedHandlerId = $this->getHandlerId($handler['handler']); $this->markNestedHandler($nestedHandlerId); $definition->setArguments([new Reference($nestedHandlerId), $handler['factor']]); break; // Handlers using the constructor of AbstractHandler without adding their own arguments case 'browser_console': case 'test': case 'null': case 'noop': case 'debug': $definition->setArguments([$handler['level'], $handler['bubble']]); break; default: $nullWarning = ''; if ($handler['type'] == '') { $nullWarning = ', if you meant to define a null handler in a yaml config, make sure you quote "null" so it does not get converted to a php null'; } throw new \InvalidArgumentException(\sprintf('Invalid handler type "%s" given for handler "%s"' . $nullWarning, $handler['type'], $name)); } if (!empty($handler['nested']) && \true === $handler['nested']) { $this->markNestedHandler($handlerId); } if (!empty($handler['formatter'])) { $definition->addMethodCall('setFormatter', [new Reference($handler['formatter'])]); } if (!\in_array($handlerId, $this->nestedHandlers) && \is_subclass_of($handlerClass, ResettableInterface::class)) { $definition->addTag('kernel.reset', ['method' => 'reset']); } $container->setDefinition($handlerId, $definition); return $handlerId; } private function markNestedHandler($nestedHandlerId) { if (\in_array($nestedHandlerId, $this->nestedHandlers)) { return; } $this->nestedHandlers[] = $nestedHandlerId; } private function getHandlerId($name) { return \sprintf('monolog.handler.%s', $name); } private function getHandlerClassByType($handlerType) { $typeToClassMapping = ['stream' => '_ContaoManager\\Monolog\\Handler\\StreamHandler', 'console' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ConsoleHandler', 'group' => '_ContaoManager\\Monolog\\Handler\\GroupHandler', 'buffer' => '_ContaoManager\\Monolog\\Handler\\BufferHandler', 'deduplication' => '_ContaoManager\\Monolog\\Handler\\DeduplicationHandler', 'rotating_file' => '_ContaoManager\\Monolog\\Handler\\RotatingFileHandler', 'syslog' => '_ContaoManager\\Monolog\\Handler\\SyslogHandler', 'syslogudp' => '_ContaoManager\\Monolog\\Handler\\SyslogUdpHandler', 'null' => '_ContaoManager\\Monolog\\Handler\\NullHandler', 'test' => '_ContaoManager\\Monolog\\Handler\\TestHandler', 'gelf' => '_ContaoManager\\Monolog\\Handler\\GelfHandler', 'rollbar' => '_ContaoManager\\Monolog\\Handler\\RollbarHandler', 'flowdock' => '_ContaoManager\\Monolog\\Handler\\FlowdockHandler', 'browser_console' => '_ContaoManager\\Monolog\\Handler\\BrowserConsoleHandler', 'firephp' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\FirePHPHandler', 'chromephp' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ChromePhpHandler', 'debug' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\DebugHandler', 'swift_mailer' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\SwiftMailerHandler', 'native_mailer' => '_ContaoManager\\Monolog\\Handler\\NativeMailerHandler', 'symfony_mailer' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\MailerHandler', 'socket' => '_ContaoManager\\Monolog\\Handler\\SocketHandler', 'pushover' => '_ContaoManager\\Monolog\\Handler\\PushoverHandler', 'raven' => '_ContaoManager\\Monolog\\Handler\\RavenHandler', 'sentry' => '_ContaoManager\\Sentry\\Monolog\\Handler', 'newrelic' => '_ContaoManager\\Monolog\\Handler\\NewRelicHandler', 'hipchat' => '_ContaoManager\\Monolog\\Handler\\HipChatHandler', 'slack' => '_ContaoManager\\Monolog\\Handler\\SlackHandler', 'slackwebhook' => '_ContaoManager\\Monolog\\Handler\\SlackWebhookHandler', 'slackbot' => '_ContaoManager\\Monolog\\Handler\\SlackbotHandler', 'cube' => '_ContaoManager\\Monolog\\Handler\\CubeHandler', 'amqp' => '_ContaoManager\\Monolog\\Handler\\AmqpHandler', 'error_log' => '_ContaoManager\\Monolog\\Handler\\ErrorLogHandler', 'loggly' => '_ContaoManager\\Monolog\\Handler\\LogglyHandler', 'logentries' => '_ContaoManager\\Monolog\\Handler\\LogEntriesHandler', 'whatfailuregroup' => '_ContaoManager\\Monolog\\Handler\\WhatFailureGroupHandler', 'fingers_crossed' => '_ContaoManager\\Monolog\\Handler\\FingersCrossedHandler', 'filter' => '_ContaoManager\\Monolog\\Handler\\FilterHandler', 'mongo' => '_ContaoManager\\Monolog\\Handler\\MongoDBHandler', 'elasticsearch' => '_ContaoManager\\Monolog\\Handler\\ElasticSearchHandler', 'telegram' => '_ContaoManager\\Monolog\\Handler\\TelegramBotHandler', 'server_log' => '_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\ServerLogHandler', 'redis' => '_ContaoManager\\Monolog\\Handler\\RedisHandler', 'predis' => '_ContaoManager\\Monolog\\Handler\\RedisHandler', 'insightops' => '_ContaoManager\\Monolog\\Handler\\InsightOpsHandler', 'sampling' => '_ContaoManager\\Monolog\\Handler\\SamplingHandler']; $v2HandlerTypesAdded = ['elastica' => '_ContaoManager\\Monolog\\Handler\\ElasticaHandler', 'elasticsearch' => '_ContaoManager\\Monolog\\Handler\\ElasticaHandler', 'elastic_search' => '_ContaoManager\\Monolog\\Handler\\ElasticsearchHandler', 'fallbackgroup' => '_ContaoManager\\Monolog\\Handler\\FallbackGroupHandler', 'noop' => '_ContaoManager\\Monolog\\Handler\\NoopHandler']; $v2HandlerTypesRemoved = ['hipchat', 'raven', 'slackbot']; $v3HandlerTypesRemoved = ['swift_mailer']; if (Logger::API >= 2) { $typeToClassMapping = \array_merge($typeToClassMapping, $v2HandlerTypesAdded); foreach ($v2HandlerTypesRemoved as $v2HandlerTypeRemoved) { unset($typeToClassMapping[$v2HandlerTypeRemoved]); } } if (Logger::API >= 3) { foreach ($v3HandlerTypesRemoved as $v3HandlerTypeRemoved) { unset($typeToClassMapping[$v3HandlerTypeRemoved]); } } if (!isset($typeToClassMapping[$handlerType])) { if (Logger::API === 1 && \array_key_exists($handlerType, $v2HandlerTypesAdded)) { throw new \InvalidArgumentException(\sprintf('"%s" was added in Monolog v2, please upgrade if you wish to use it.', $handlerType)); } if (Logger::API >= 2 && \array_key_exists($handlerType, $v2HandlerTypesRemoved)) { throw new \InvalidArgumentException(\sprintf('"%s" was removed in Monolog v2.', $handlerType)); } if (Logger::API >= 3 && \array_key_exists($handlerType, $v3HandlerTypesRemoved)) { throw new \InvalidArgumentException(\sprintf('"%s" was removed in Monolog v3.', $handlerType)); } throw new \InvalidArgumentException(\sprintf('There is no handler class defined for handler "%s".', $handlerType)); } return $typeToClassMapping[$handlerType]; } private function buildPsrLogMessageProcessor(ContainerBuilder $container, array $processorOptions) : string { static $hasConstructorArguments; if (!isset($hasConstructorArguments)) { $reflectionConstructor = (new \ReflectionClass(PsrLogMessageProcessor::class))->getConstructor(); $hasConstructorArguments = null !== $reflectionConstructor && $reflectionConstructor->getNumberOfParameters() > 0; unset($reflectionConstructor); } $processorId = 'monolog.processor.psr_log_message'; $processorArguments = []; unset($processorOptions['enabled']); if (!empty($processorOptions)) { if (!$hasConstructorArguments) { throw new \RuntimeException('Monolog 1.26 or higher is required for the "date_format" and "remove_used_context_fields" options to be used.'); } $processorArguments = [$processorOptions['date_format'] ?? null, $processorOptions['remove_used_context_fields'] ?? \false]; $processorId .= '.' . ContainerBuilder::hash($processorArguments); } if (!$container->hasDefinition($processorId)) { $processor = new Definition(PsrLogMessageProcessor::class); $processor->setPublic(\false); $processor->setArguments($processorArguments); $container->setDefinition($processorId, $processor); } return $processorId; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Component\Config\Definition\BaseNode; use _ContaoManager\Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Builder\TreeBuilder; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; /** * This class contains the configuration information for the bundle * * This information is solely responsible for how the different configuration * sections are normalized, and merged. * * Possible handler types and related configurations (brackets indicate optional params): * * - service: * - id * * - stream: * - path: string * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [file_permission]: int|null, defaults to null (0644) * - [use_locking]: bool, defaults to false * * - console: * - [verbosity_levels]: level => verbosity configuration * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [console_formatter_options]: array * * - firephp: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - browser_console: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - gelf: * - publisher: {id: ...} or {hostname: ..., port: ..., chunk_size: ...} * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - chromephp: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - rotating_file: * - path: string * - [max_files]: files to keep, defaults to zero (infinite) * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [file_permission]: string|null, defaults to null * - [use_locking]: bool, defaults to false * - [filename_format]: string, defaults to '{filename}-{date}' * - [date_format]: string, defaults to 'Y-m-d' * * - mongo: * - mongo: * - id: optional if host is given * - host: database host name, optional if id is given * - [port]: defaults to 27017 * - [user]: database user name * - pass: mandatory only if user is present * - [database]: defaults to monolog * - [collection]: defaults to logs * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - elastic_search: * - elasticsearch: * - id: optional if host is given * - host: elastic search host name, with scheme (e.g. "https://127.0.0.1:9200") * - [user]: elastic search user name * - [password]: elastic search user password * - [index]: index name, defaults to monolog * - [document_type]: document_type, defaults to logs * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - elastica: * - elasticsearch: * - id: optional if host is given * - host: elastic search host name. Do not prepend with http(s):// * - [port]: defaults to 9200 * - [transport]: transport protocol (http by default) * - [user]: elastic search user name * - [password]: elastic search user password * - [index]: index name, defaults to monolog * - [document_type]: document_type, defaults to logs * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - redis: * - redis: * - id: optional if host is given * - host: 127.0.0.1 * - password: null * - port: 6379 * - database: 0 * - key_name: monolog_redis * * - predis: * - redis: * - id: optional if host is given * - host: tcp://10.0.0.1:6379 * - key_name: monolog_redis * * - fingers_crossed: * - handler: the wrapped handler's name * - [action_level|activation_strategy]: minimum level or service id to activate the handler, defaults to WARNING * - [excluded_404s]: if set, the strategy will be changed to one that excludes 404s coming from URLs matching any of those patterns * - [excluded_http_codes]: if set, the strategy will be changed to one that excludes specific HTTP codes (requires Symfony Monolog bridge 4.1+) * - [buffer_size]: defaults to 0 (unlimited) * - [stop_buffering]: bool to disable buffering once the handler has been activated, defaults to true * - [passthru_level]: level name or int value for messages to always flush, disabled by default * - [bubble]: bool, defaults to true * * - filter: * - handler: the wrapped handler's name * - [accepted_levels]: list of levels to accept * - [min_level]: minimum level to accept (only used if accepted_levels not specified) * - [max_level]: maximum level to accept (only used if accepted_levels not specified) * - [bubble]: bool, defaults to true * * - buffer: * - handler: the wrapped handler's name * - [buffer_size]: defaults to 0 (unlimited) * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [flush_on_overflow]: bool, defaults to false * * - deduplication: * - handler: the wrapped handler's name * - [store]: The file/path where the deduplication log should be kept, defaults to %kernel.cache_dir%/monolog_dedup_* * - [deduplication_level]: The minimum logging level for log records to be looked at for deduplication purposes, defaults to ERROR * - [time]: The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through, defaults to 60 * - [bubble]: bool, defaults to true * * - group: * - members: the wrapped handlers by name * - [bubble]: bool, defaults to true * * - whatfailuregroup: * - members: the wrapped handlers by name * - [bubble]: bool, defaults to true * * - fallbackgroup * - members: the wrapped handlers by name * - [bubble]: bool, defaults to true * * - syslog: * - ident: string * - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER * - [logopts]: defaults to LOG_PID * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - syslogudp: * - host: syslogd host name * - [port]: defaults to 514 * - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER * - [logopts]: defaults to LOG_PID * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [ident]: string, defaults to * * - swift_mailer: * - from_email: optional if email_prototype is given * - to_email: optional if email_prototype is given * - subject: optional if email_prototype is given * - [email_prototype]: service id of a message, defaults to a default message with the three fields above * - [content_type]: optional if email_prototype is given, defaults to text/plain * - [mailer]: mailer service, defaults to mailer * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [lazy]: use service lazy loading, bool, defaults to true * * - native_mailer: * - from_email: string * - to_email: string * - subject: string * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [headers]: optional array containing additional headers: ['Foo: Bar', '...'] * * - symfony_mailer: * - from_email: optional if email_prototype is given * - to_email: optional if email_prototype is given * - subject: optional if email_prototype is given * - [email_prototype]: service id of a message, defaults to a default message with the three fields above * - [mailer]: mailer service id, defaults to mailer.mailer * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - socket: * - connection_string: string * - [timeout]: float * - [connection_timeout]: float * - [persistent]: bool * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - pushover: * - token: pushover api token * - user: user id or array of ids * - [title]: optional title for messages, defaults to the server hostname * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [timeout]: float * - [connection_timeout]: float * * - raven / sentry: * - dsn: connection string * - client_id: Raven client custom service id (optional) * - [release]: release number of the application that will be attached to logs, defaults to null * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [auto_log_stacks]: bool, defaults to false * - [environment]: string, default to null (no env specified) * * - sentry: * - hub_id: Sentry hub custom service id (optional) * - [fill_extra_context]: bool, defaults to false * * - newrelic: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [app_name]: new relic app name, default null * * - hipchat: * - token: hipchat api token * - room: room id or name * - [notify]: defaults to false * - [nickname]: defaults to Monolog * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [use_ssl]: bool, defaults to true * - [message_format]: text or html, defaults to text * - [host]: defaults to "api.hipchat.com" * - [api_version]: defaults to "v1" * - [timeout]: float * - [connection_timeout]: float * * - slack: * - token: slack api token * - channel: channel name (with starting #) * - [bot_name]: defaults to Monolog * - [icon_emoji]: defaults to null * - [use_attachment]: bool, defaults to true * - [use_short_attachment]: bool, defaults to false * - [include_extra]: bool, defaults to false * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [timeout]: float * - [connection_timeout]: float * * - slackwebhook: * - webhook_url: slack webhook URL * - channel: channel name (with starting #) * - [bot_name]: defaults to Monolog * - [icon_emoji]: defaults to null * - [use_attachment]: bool, defaults to true * - [use_short_attachment]: bool, defaults to false * - [include_extra]: bool, defaults to false * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - slackbot: * - team: slack team slug * - token: slackbot token * - channel: channel name (with starting #) * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - cube: * - url: http/udp url to the cube server * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - amqp: * - exchange: service id of an AMQPExchange * - [exchange_name]: string, defaults to log * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - error_log: * - [message_type]: int 0 or 4, defaults to 0 * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - null: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - test: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - debug: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - loggly: * - token: loggly api token * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [tags]: tag names * * - logentries: * - token: logentries api token * - [use_ssl]: whether or not SSL encryption should be used, defaults to true * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [timeout]: float * - [connection_timeout]: float * * - insightops: * - token: Log token supplied by InsightOps * - region: Region where InsightOps account is hosted. Could be 'us' or 'eu'. Defaults to 'us' * - [use_ssl]: whether or not SSL encryption should be used, defaults to true * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - flowdock: * - token: flowdock api token * - source: human readable identifier of the application * - from_email: email address of the message sender * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - rollbar: * - id: RollbarNotifier service (mandatory if token is not provided) * - token: rollbar api token (skip if you provide a RollbarNotifier service id) * - [config]: config values from https://github.com/rollbar/rollbar-php#configuration-reference * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - server_log: * - host: server log host. ex: 127.0.0.1:9911 * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * * - telegram: * - token: Telegram bot access token provided by BotFather * - channel: Telegram channel name * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * - [parse_mode]: optional the kind of formatting that is used for the message * - [disable_webpage_preview]: bool, defaults to false, disables link previews for links in the message * - [disable_notification]: bool, defaults to false, sends the message silently. Users will receive a notification with no sound * - [split_long_messages]: bool, defaults to false, split messages longer than 4096 bytes into multiple messages * - [delay_between_messages]: bool, defaults to false, adds a 1sec delay/sleep between sending split messages * * - sampling: * - handler: the wrapped handler's name * - factor: the sampling factor (e.g. 10 means every ~10th record is sampled) * * All handlers can also be marked with `nested: true` to make sure they are never added explicitly to the stack * * @author Jordi Boggiano * @author Christophe Coevoet * * @finalsince 3.9.0 */ class Configuration implements ConfigurationInterface { /** * Generates the configuration tree builder. */ public function getConfigTreeBuilder() : TreeBuilder { $treeBuilder = new TreeBuilder('monolog'); $rootNode = $treeBuilder->getRootNode(); $handlers = $rootNode->fixXmlConfig('channel')->fixXmlConfig('handler')->children()->scalarNode('use_microseconds')->defaultTrue()->end()->arrayNode('channels')->canBeUnset()->prototype('scalar')->end()->end()->arrayNode('handlers'); $handlers->canBeUnset()->useAttributeAsKey('name')->validate()->ifTrue(function ($v) { return isset($v['debug']); })->thenInvalid('The "debug" name cannot be used as it is reserved for the handler of the profiler')->end()->example(['syslog' => ['type' => 'stream', 'path' => '/var/log/symfony.log', 'level' => 'ERROR', 'bubble' => 'false', 'formatter' => 'my_formatter'], 'main' => ['type' => 'fingers_crossed', 'action_level' => 'WARNING', 'buffer_size' => 30, 'handler' => 'custom'], 'custom' => ['type' => 'service', 'id' => 'my_handler']]); $handlerNode = $handlers->prototype('array')->fixXmlConfig('member')->fixXmlConfig('excluded_404')->fixXmlConfig('excluded_http_code')->fixXmlConfig('tag')->fixXmlConfig('accepted_level')->fixXmlConfig('header')->canBeUnset(); $handlerNode->children()->scalarNode('type')->isRequired()->treatNullLike('null')->beforeNormalization()->always()->then(function ($v) { return \strtolower($v); })->end()->end()->scalarNode('id')->end()->scalarNode('priority')->defaultValue(0)->end()->scalarNode('level')->defaultValue('DEBUG')->end()->booleanNode('bubble')->defaultTrue()->end()->scalarNode('app_name')->defaultNull()->end()->booleanNode('fill_extra_context')->defaultFalse()->end()->booleanNode('include_stacktraces')->defaultFalse()->end()->arrayNode('process_psr_3_messages')->addDefaultsIfNotSet()->beforeNormalization()->ifTrue(static function ($v) { return !\is_array($v); })->then(static function ($v) { return ['enabled' => $v]; })->end()->children()->booleanNode('enabled')->defaultNull()->end()->scalarNode('date_format')->end()->booleanNode('remove_used_context_fields')->end()->end()->end()->scalarNode('path')->defaultValue('%kernel.logs_dir%/%kernel.environment%.log')->end()->scalarNode('file_permission')->defaultNull()->beforeNormalization()->ifString()->then(function ($v) { if (\substr($v, 0, 1) === '0') { return \octdec($v); } return (int) $v; })->end()->end()->booleanNode('use_locking')->defaultFalse()->end()->scalarNode('filename_format')->defaultValue('{filename}-{date}')->end()->scalarNode('date_format')->defaultValue('Y-m-d')->end()->scalarNode('ident')->defaultFalse()->end()->scalarNode('logopts')->defaultValue(\LOG_PID)->end()->scalarNode('facility')->defaultValue('user')->end()->scalarNode('max_files')->defaultValue(0)->end()->scalarNode('action_level')->defaultValue('WARNING')->end()->scalarNode('activation_strategy')->defaultNull()->end()->booleanNode('stop_buffering')->defaultTrue()->end()->scalarNode('passthru_level')->defaultNull()->end()->arrayNode('excluded_404s')->canBeUnset()->prototype('scalar')->end()->end()->arrayNode('excluded_http_codes')->canBeUnset()->beforeNormalization()->always(function ($values) { return \array_map(function ($value) { /* * Allows YAML: * excluded_http_codes: [403, 404, { 400: ['^/foo', '^/bar'] }] * * and XML: * * ^/foo * ^/bar * * */ if (\is_array($value)) { return isset($value['code']) ? $value : ['code' => \key($value), 'urls' => \current($value)]; } return ['code' => $value, 'urls' => []]; }, $values); })->end()->prototype('array')->children()->scalarNode('code')->end()->arrayNode('urls')->prototype('scalar')->end()->end()->end()->end()->end()->arrayNode('accepted_levels')->canBeUnset()->prototype('scalar')->end()->end()->scalarNode('min_level')->defaultValue('DEBUG')->end()->scalarNode('max_level')->defaultValue('EMERGENCY')->end()->scalarNode('buffer_size')->defaultValue(0)->end()->booleanNode('flush_on_overflow')->defaultFalse()->end()->scalarNode('handler')->end()->scalarNode('url')->end()->scalarNode('exchange')->end()->scalarNode('exchange_name')->defaultValue('log')->end()->scalarNode('room')->end()->scalarNode('message_format')->defaultValue('text')->end()->scalarNode('api_version')->defaultNull()->end()->scalarNode('channel')->defaultNull()->end()->scalarNode('bot_name')->defaultValue('Monolog')->end()->scalarNode('use_attachment')->defaultTrue()->end()->scalarNode('use_short_attachment')->defaultFalse()->end()->scalarNode('include_extra')->defaultFalse()->end()->scalarNode('icon_emoji')->defaultNull()->end()->scalarNode('webhook_url')->end()->scalarNode('team')->end()->scalarNode('notify')->defaultFalse()->end()->scalarNode('nickname')->defaultValue('Monolog')->end()->scalarNode('token')->end()->scalarNode('region')->end()->scalarNode('source')->end()->booleanNode('use_ssl')->defaultTrue()->end()->variableNode('user')->validate()->ifTrue(function ($v) { return !\is_string($v) && !\is_array($v); })->thenInvalid('User must be a string or an array.')->end()->end()->scalarNode('title')->defaultNull()->end()->scalarNode('host')->defaultNull()->end()->scalarNode('port')->defaultValue(514)->end()->arrayNode('config')->canBeUnset()->prototype('scalar')->end()->end()->arrayNode('members')->canBeUnset()->performNoDeepMerging()->prototype('scalar')->end()->end()->scalarNode('connection_string')->end()->scalarNode('timeout')->end()->scalarNode('time')->defaultValue(60)->end()->scalarNode('deduplication_level')->defaultValue(Logger::ERROR)->end()->scalarNode('store')->defaultNull()->end()->scalarNode('connection_timeout')->end()->booleanNode('persistent')->end()->scalarNode('dsn')->end()->scalarNode('hub_id')->defaultNull()->end()->scalarNode('client_id')->defaultNull()->end()->scalarNode('auto_log_stacks')->defaultFalse()->end()->scalarNode('release')->defaultNull()->end()->scalarNode('environment')->defaultNull()->end()->scalarNode('message_type')->defaultValue(0)->end()->scalarNode('parse_mode')->defaultNull()->end()->booleanNode('disable_webpage_preview')->defaultNull()->end()->booleanNode('disable_notification')->defaultNull()->end()->booleanNode('split_long_messages')->defaultFalse()->end()->booleanNode('delay_between_messages')->defaultFalse()->end()->integerNode('factor')->defaultValue(1)->min(1)->end()->arrayNode('tags')->beforeNormalization()->ifString()->then(function ($v) { return \explode(',', $v); })->end()->beforeNormalization()->ifArray()->then(function ($v) { return \array_filter(\array_map('trim', $v)); })->end()->prototype('scalar')->end()->end()->variableNode('console_formater_options')->setDeprecated('symfony/monolog-bundle', 3.7, '"%path%.%node%" is deprecated, use "%path%.console_formatter_options" instead.')->validate()->ifTrue(function ($v) { return !\is_array($v); })->thenInvalid('The console_formater_options must be an array.')->end()->end()->variableNode('console_formatter_options')->defaultValue([])->validate()->ifTrue(static function ($v) { return !\is_array($v); })->thenInvalid('The console_formatter_options must be an array.')->end()->end()->scalarNode('formatter')->end()->booleanNode('nested')->defaultFalse()->end()->end(); $this->addGelfSection($handlerNode); $this->addMongoSection($handlerNode); $this->addElasticsearchSection($handlerNode); $this->addRedisSection($handlerNode); $this->addPredisSection($handlerNode); $this->addMailerSection($handlerNode); $this->addVerbosityLevelSection($handlerNode); $this->addChannelsSection($handlerNode); $handlerNode->beforeNormalization()->always(static function ($v) { if (empty($v['console_formatter_options']) && !empty($v['console_formater_options'])) { $v['console_formatter_options'] = $v['console_formater_options']; } return $v; })->end()->validate()->always(static function ($v) { unset($v['console_formater_options']); return $v; })->end()->validate()->ifTrue(function ($v) { return 'service' === $v['type'] && !empty($v['formatter']); })->thenInvalid('Service handlers can not have a formatter configured in the bundle, you must reconfigure the service itself instead')->end()->validate()->ifTrue(function ($v) { return ('fingers_crossed' === $v['type'] || 'buffer' === $v['type'] || 'filter' === $v['type'] || 'sampling' === $v['type']) && empty($v['handler']); })->thenInvalid('The handler has to be specified to use a FingersCrossedHandler or BufferHandler or FilterHandler or SamplingHandler')->end()->validate()->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_404s']) && !empty($v['activation_strategy']); })->thenInvalid('You can not use excluded_404s together with a custom activation_strategy in a FingersCrossedHandler')->end()->validate()->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['activation_strategy']); })->thenInvalid('You can not use excluded_http_codes together with a custom activation_strategy in a FingersCrossedHandler')->end()->validate()->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_http_codes']) && !empty($v['excluded_404s']); })->thenInvalid('You can not use excluded_http_codes together with excluded_404s in a FingersCrossedHandler')->end()->validate()->ifTrue(function ($v) { return 'fingers_crossed' !== $v['type'] && (!empty($v['excluded_http_codes']) || !empty($v['excluded_404s'])); })->thenInvalid('You can only use excluded_http_codes/excluded_404s with a FingersCrossedHandler definition')->end()->validate()->ifTrue(function ($v) { return 'filter' === $v['type'] && "DEBUG" !== $v['min_level'] && !empty($v['accepted_levels']); })->thenInvalid('You can not use min_level together with accepted_levels in a FilterHandler')->end()->validate()->ifTrue(function ($v) { return 'filter' === $v['type'] && "EMERGENCY" !== $v['max_level'] && !empty($v['accepted_levels']); })->thenInvalid('You can not use max_level together with accepted_levels in a FilterHandler')->end()->validate()->ifTrue(function ($v) { return 'rollbar' === $v['type'] && !empty($v['id']) && !empty($v['token']); })->thenInvalid('You can not use both an id and a token in a RollbarHandler')->end()->validate()->ifTrue(function ($v) { return 'rollbar' === $v['type'] && empty($v['id']) && empty($v['token']); })->thenInvalid('The id or the token has to be specified to use a RollbarHandler')->end()->validate()->ifTrue(function ($v) { return 'telegram' === $v['type'] && (empty($v['token']) || empty($v['channel'])); })->thenInvalid('The token and channel have to be specified to use a TelegramBotHandler')->end()->validate()->ifTrue(function ($v) { return 'service' === $v['type'] && !isset($v['id']); })->thenInvalid('The id has to be specified to use a service as handler')->end()->validate()->ifTrue(function ($v) { return 'syslogudp' === $v['type'] && !isset($v['host']); })->thenInvalid('The host has to be specified to use a syslogudp as handler')->end()->validate()->ifTrue(function ($v) { return 'socket' === $v['type'] && !isset($v['connection_string']); })->thenInvalid('The connection_string has to be specified to use a SocketHandler')->end()->validate()->ifTrue(function ($v) { return 'pushover' === $v['type'] && (empty($v['token']) || empty($v['user'])); })->thenInvalid('The token and user have to be specified to use a PushoverHandler')->end()->validate()->ifTrue(function ($v) { return 'raven' === $v['type'] && !\array_key_exists('dsn', $v) && null === $v['client_id']; })->thenInvalid('The DSN has to be specified to use a RavenHandler')->end()->validate()->ifTrue(function ($v) { return 'sentry' === $v['type'] && !\array_key_exists('dsn', $v) && null === $v['hub_id'] && null === $v['client_id']; })->thenInvalid('The DSN has to be specified to use Sentry\'s handler')->end()->validate()->ifTrue(function ($v) { return 'sentry' === $v['type'] && null !== $v['hub_id'] && null !== $v['client_id']; })->thenInvalid('You can not use both a hub_id and a client_id in a Sentry handler')->end()->validate()->ifTrue(function ($v) { return 'hipchat' === $v['type'] && (empty($v['token']) || empty($v['room'])); })->thenInvalid('The token and room have to be specified to use a HipChatHandler')->end()->validate()->ifTrue(function ($v) { return 'hipchat' === $v['type'] && !\in_array($v['message_format'], ['text', 'html']); })->thenInvalid('The message_format has to be "text" or "html" in a HipChatHandler')->end()->validate()->ifTrue(function ($v) { return 'hipchat' === $v['type'] && null !== $v['api_version'] && !\in_array($v['api_version'], ['v1', 'v2'], \true); })->thenInvalid('The api_version has to be "v1" or "v2" in a HipChatHandler')->end()->validate()->ifTrue(function ($v) { return 'slack' === $v['type'] && (empty($v['token']) || empty($v['channel'])); })->thenInvalid('The token and channel have to be specified to use a SlackHandler')->end()->validate()->ifTrue(function ($v) { return 'slackwebhook' === $v['type'] && empty($v['webhook_url']); })->thenInvalid('The webhook_url have to be specified to use a SlackWebhookHandler')->end()->validate()->ifTrue(function ($v) { return 'slackbot' === $v['type'] && (empty($v['team']) || empty($v['token']) || empty($v['channel'])); })->thenInvalid('The team, token and channel have to be specified to use a SlackbotHandler')->end()->validate()->ifTrue(function ($v) { return 'cube' === $v['type'] && empty($v['url']); })->thenInvalid('The url has to be specified to use a CubeHandler')->end()->validate()->ifTrue(function ($v) { return 'amqp' === $v['type'] && empty($v['exchange']); })->thenInvalid('The exchange has to be specified to use a AmqpHandler')->end()->validate()->ifTrue(function ($v) { return 'loggly' === $v['type'] && empty($v['token']); })->thenInvalid('The token has to be specified to use a LogglyHandler')->end()->validate()->ifTrue(function ($v) { return 'loggly' === $v['type'] && !empty($v['tags']); })->then(function ($v) { $invalidTags = \preg_grep('/^[a-z0-9][a-z0-9\\.\\-_]*$/i', $v['tags'], \PREG_GREP_INVERT); if (!empty($invalidTags)) { throw new InvalidConfigurationException(\sprintf('The following Loggly tags are invalid: %s.', \implode(', ', $invalidTags))); } return $v; })->end()->validate()->ifTrue(function ($v) { return 'logentries' === $v['type'] && empty($v['token']); })->thenInvalid('The token has to be specified to use a LogEntriesHandler')->end()->validate()->ifTrue(function ($v) { return 'insightops' === $v['type'] && empty($v['token']); })->thenInvalid('The token has to be specified to use a InsightOpsHandler')->end()->validate()->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['token']); })->thenInvalid('The token has to be specified to use a FlowdockHandler')->end()->validate()->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['from_email']); })->thenInvalid('The from_email has to be specified to use a FlowdockHandler')->end()->validate()->ifTrue(function ($v) { return 'flowdock' === $v['type'] && empty($v['source']); })->thenInvalid('The source has to be specified to use a FlowdockHandler')->end()->validate()->ifTrue(function ($v) { return 'server_log' === $v['type'] && empty($v['host']); })->thenInvalid('The host has to be specified to use a ServerLogHandler')->end(); return $treeBuilder; } private function addGelfSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('publisher')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['id' => $v]; })->end()->children()->scalarNode('id')->end()->scalarNode('hostname')->end()->scalarNode('port')->defaultValue(12201)->end()->scalarNode('chunk_size')->defaultValue(1420)->end()->end()->validate()->ifTrue(function ($v) { return !isset($v['id']) && !isset($v['hostname']); })->thenInvalid('What must be set is either the hostname or the id.')->end()->end()->end()->validate()->ifTrue(function ($v) { return 'gelf' === $v['type'] && !isset($v['publisher']); })->thenInvalid('The publisher has to be specified to use a GelfHandler')->end(); } private function addMongoSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('mongo')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['id' => $v]; })->end()->children()->scalarNode('id')->end()->scalarNode('host')->end()->scalarNode('port')->defaultValue(27017)->end()->scalarNode('user')->end()->scalarNode('pass')->end()->scalarNode('database')->defaultValue('monolog')->end()->scalarNode('collection')->defaultValue('logs')->end()->end()->validate()->ifTrue(function ($v) { return !isset($v['id']) && !isset($v['host']); })->thenInvalid('What must be set is either the host or the id.')->end()->validate()->ifTrue(function ($v) { return isset($v['user']) && !isset($v['pass']); })->thenInvalid('If you set user, you must provide a password.')->end()->end()->end()->validate()->ifTrue(function ($v) { return 'mongo' === $v['type'] && !isset($v['mongo']); })->thenInvalid('The mongo configuration has to be specified to use a MongoHandler')->end(); } private function addElasticsearchSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('elasticsearch')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['id' => $v]; })->end()->children()->scalarNode('id')->end()->scalarNode('host')->end()->scalarNode('port')->defaultValue(9200)->end()->scalarNode('transport')->defaultValue('Http')->end()->scalarNode('user')->defaultNull()->end()->scalarNode('password')->defaultNull()->end()->end()->validate()->ifTrue(function ($v) { return !isset($v['id']) && !isset($v['host']); })->thenInvalid('What must be set is either the host or the id.')->end()->end()->scalarNode('index')->defaultValue('monolog')->end()->scalarNode('document_type')->defaultValue('logs')->end()->scalarNode('ignore_error')->defaultValue(\false)->end()->end(); } private function addRedisSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('redis')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['id' => $v]; })->end()->children()->scalarNode('id')->end()->scalarNode('host')->end()->scalarNode('password')->defaultNull()->end()->scalarNode('port')->defaultValue(6379)->end()->scalarNode('database')->defaultValue(0)->end()->scalarNode('key_name')->defaultValue('monolog_redis')->end()->end()->validate()->ifTrue(function ($v) { return !isset($v['id']) && !isset($v['host']); })->thenInvalid('What must be set is either the host or the service id of the Redis client.')->end()->end()->end()->validate()->ifTrue(function ($v) { return 'redis' === $v['type'] && empty($v['redis']); })->thenInvalid('The host has to be specified to use a RedisLogHandler')->end(); } private function addPredisSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('predis')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['id' => $v]; })->end()->children()->scalarNode('id')->end()->scalarNode('host')->end()->end()->validate()->ifTrue(function ($v) { return !isset($v['id']) && !isset($v['host']); })->thenInvalid('What must be set is either the host or the service id of the Predis client.')->end()->end()->end()->validate()->ifTrue(function ($v) { return 'predis' === $v['type'] && empty($v['redis']); })->thenInvalid('The host has to be specified to use a RedisLogHandler')->end(); } private function addMailerSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->scalarNode('from_email')->end()->arrayNode('to_email')->prototype('scalar')->end()->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->end()->scalarNode('subject')->end()->scalarNode('content_type')->defaultNull()->end()->arrayNode('headers')->canBeUnset()->scalarPrototype()->end()->end()->scalarNode('mailer')->defaultNull()->end()->arrayNode('email_prototype')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['id' => $v]; })->end()->children()->scalarNode('id')->isRequired()->end()->scalarNode('method')->defaultNull()->end()->end()->end()->booleanNode('lazy')->defaultValue(\true)->end()->end()->validate()->ifTrue(function ($v) { return 'swift_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use a SwiftMailerHandler')->end()->validate()->ifTrue(function ($v) { return 'native_mailer' === $v['type'] && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })->thenInvalid('The sender, recipient and subject have to be specified to use a NativeMailerHandler')->end()->validate()->ifTrue(function ($v) { return 'symfony_mailer' === $v['type'] && empty($v['email_prototype']) && (empty($v['from_email']) || empty($v['to_email']) || empty($v['subject'])); })->thenInvalid('The sender, recipient and subject or an email prototype have to be specified to use the Symfony MailerHandler')->end(); } private function addVerbosityLevelSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('verbosity_levels')->beforeNormalization()->ifArray()->then(function ($v) { $map = []; $verbosities = ['VERBOSITY_QUIET', 'VERBOSITY_NORMAL', 'VERBOSITY_VERBOSE', 'VERBOSITY_VERY_VERBOSE', 'VERBOSITY_DEBUG']; // allow numeric indexed array with ascendning verbosity and lowercase names of the constants foreach ($v as $verbosity => $level) { if (\is_int($verbosity) && isset($verbosities[$verbosity])) { $map[$verbosities[$verbosity]] = \strtoupper($level); } else { $map[\strtoupper($verbosity)] = \strtoupper($level); } } return $map; })->end()->children()->scalarNode('VERBOSITY_QUIET')->defaultValue('ERROR')->end()->scalarNode('VERBOSITY_NORMAL')->defaultValue('WARNING')->end()->scalarNode('VERBOSITY_VERBOSE')->defaultValue('NOTICE')->end()->scalarNode('VERBOSITY_VERY_VERBOSE')->defaultValue('INFO')->end()->scalarNode('VERBOSITY_DEBUG')->defaultValue('DEBUG')->end()->end()->validate()->always(function ($v) { $map = []; foreach ($v as $verbosity => $level) { $verbosityConstant = 'Symfony\\Component\\Console\\Output\\OutputInterface::' . $verbosity; if (!\defined($verbosityConstant)) { throw new InvalidConfigurationException(\sprintf('The configured verbosity "%s" is invalid as it is not defined in Symfony\\Component\\Console\\Output\\OutputInterface.', $verbosity)); } try { if (Logger::API === 3) { $level = Logger::toMonologLevel($level)->value; } else { $level = Logger::toMonologLevel(\is_numeric($level) ? (int) $level : $level); } } catch (\_ContaoManager\Psr\Log\InvalidArgumentException $e) { throw new InvalidConfigurationException(\sprintf('The configured minimum log level "%s" for verbosity "%s" is invalid as it is not defined in Monolog\\Logger.', $level, $verbosity)); } $map[\constant($verbosityConstant)] = $level; } return $map; })->end()->end()->end(); } private function addChannelsSection(ArrayNodeDefinition $handerNode) { $handerNode->children()->arrayNode('channels')->fixXmlConfig('channel', 'elements')->canBeUnset()->beforeNormalization()->ifString()->then(function ($v) { return ['elements' => [$v]]; })->end()->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && \is_numeric(\key($v)); })->then(function ($v) { return ['elements' => $v]; })->end()->validate()->ifTrue(function ($v) { return empty($v); })->thenUnset()->end()->validate()->always(function ($v) { $isExclusive = null; if (isset($v['type'])) { $isExclusive = 'exclusive' === $v['type']; } $elements = []; foreach ($v['elements'] as $element) { if (0 === \strpos($element, '!')) { if (\false === $isExclusive) { throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list.'); } $elements[] = \substr($element, 1); $isExclusive = \true; } else { if (\true === $isExclusive) { throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list'); } $elements[] = $element; $isExclusive = \false; } } if (!\count($elements)) { return null; } // de-duplicating $elements here in case the handlers are redefined, see https://github.com/symfony/monolog-bundle/issues/433 return ['type' => $isExclusive ? 'exclusive' : 'inclusive', 'elements' => \array_unique($elements)]; })->end()->children()->scalarNode('type')->validate()->ifNotInArray(['inclusive', 'exclusive'])->thenInvalid('The type of channels has to be inclusive or exclusive')->end()->end()->arrayNode('elements')->prototype('scalar')->end()->end()->end()->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Registers processors in Monolog loggers or handlers. * * @author Christophe Coevoet * * @internalsince 3.9.0 */ class AddProcessorsPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('monolog.logger')) { return; } foreach ($container->findTaggedServiceIds('monolog.processor') as $id => $tags) { foreach ($tags as $tag) { if (!empty($tag['channel']) && !empty($tag['handler'])) { throw new \InvalidArgumentException(\sprintf('you cannot specify both the "handler" and "channel" attributes for the "monolog.processor" tag on service "%s"', $id)); } if (!empty($tag['handler'])) { $definition = $container->findDefinition(\sprintf('monolog.handler.%s', $tag['handler'])); $parentDef = $definition; while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { $parentDef = $container->findDefinition($parentDef->getParent()); } $class = $container->getParameterBag()->resolveValue($parentDef->getClass()); if (!\method_exists($class, 'pushProcessor')) { throw new \InvalidArgumentException(\sprintf('The "%s" handler does not accept processors', $tag['handler'])); } } elseif (!empty($tag['channel'])) { if ('app' === $tag['channel']) { $definition = $container->getDefinition('monolog.logger'); } else { $definition = $container->getDefinition(\sprintf('monolog.logger.%s', $tag['channel'])); } } else { $definition = $container->getDefinition('monolog.logger_prototype'); } if (!empty($tag['method'])) { $processor = [new Reference($id), $tag['method']]; } else { // If no method is defined, fallback to use __invoke $processor = new Reference($id); } $definition->addMethodCall('pushProcessor', [$processor]); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Replaces the default logger by another one with its own channel for tagged services. * * @author Christophe Coevoet * * @internalsince 3.9.0 */ class LoggerChannelPass implements CompilerPassInterface { protected $channels = ['app']; /** * {@inheritDoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('monolog.logger')) { return; } // create channels necessary for the handlers foreach ($container->findTaggedServiceIds('monolog.logger') as $id => $tags) { foreach ($tags as $tag) { if (empty($tag['channel']) || 'app' === $tag['channel']) { continue; } $resolvedChannel = $container->getParameterBag()->resolveValue($tag['channel']); $definition = $container->getDefinition($id); $loggerId = \sprintf('monolog.logger.%s', $resolvedChannel); $this->createLogger($resolvedChannel, $loggerId, $container); foreach ($definition->getArguments() as $index => $argument) { if ($argument instanceof Reference && 'logger' === (string) $argument) { $definition->replaceArgument($index, $this->changeReference($argument, $loggerId)); } } $calls = $definition->getMethodCalls(); foreach ($calls as $i => $call) { foreach ($call[1] as $index => $argument) { if ($argument instanceof Reference && 'logger' === (string) $argument) { $calls[$i][1][$index] = $this->changeReference($argument, $loggerId); } } } $definition->setMethodCalls($calls); $binding = new BoundArgument(new Reference($loggerId)); // Mark the binding as used already, to avoid reporting it as unused if the service does not use a // logger injected through the LoggerInterface alias. $values = $binding->getValues(); $values[2] = \true; $binding->setValues($values); $bindings = $definition->getBindings(); $bindings['Psr\\Log\\LoggerInterface'] = $binding; $definition->setBindings($bindings); } } // create additional channels foreach ($container->getParameter('monolog.additional_channels') as $chan) { if ($chan === 'app') { continue; } $loggerId = \sprintf('monolog.logger.%s', $chan); $this->createLogger($chan, $loggerId, $container); $container->getDefinition($loggerId)->setPublic(\true); } $container->getParameterBag()->remove('monolog.additional_channels'); // wire handlers to channels $handlersToChannels = $container->getParameter('monolog.handlers_to_channels'); foreach ($handlersToChannels as $handler => $channels) { foreach ($this->processChannels($channels) as $channel) { try { $logger = $container->getDefinition($channel === 'app' ? 'monolog.logger' : 'monolog.logger.' . $channel); } catch (InvalidArgumentException $e) { $msg = 'Monolog configuration error: The logging channel "' . $channel . '" assigned to the "' . \substr($handler, 16) . '" handler does not exist.'; throw new \InvalidArgumentException($msg, 0, $e); } $logger->addMethodCall('pushHandler', [new Reference($handler)]); } } } /** * @return array */ public function getChannels() { return $this->channels; } /** * @return array */ protected function processChannels(?array $configuration) { if (null === $configuration) { return $this->channels; } if ('inclusive' === $configuration['type']) { return $configuration['elements'] ?: $this->channels; } return \array_diff($this->channels, $configuration['elements']); } /** * Create new logger from the monolog.logger_prototype * * @return void */ protected function createLogger(string $channel, string $loggerId, ContainerBuilder $container) { if (!\in_array($channel, $this->channels)) { $logger = new ChildDefinition('monolog.logger_prototype'); $logger->replaceArgument(0, $channel); $container->setDefinition($loggerId, $logger); $this->channels[] = $channel; } $parameterName = $channel . 'Logger'; $container->registerAliasForArgument($loggerId, LoggerInterface::class, $parameterName); } /** * Creates a copy of a reference and alters the service ID. */ private function changeReference(Reference $reference, string $serviceId) : Reference { return new Reference($serviceId, $reference->getInvalidBehavior()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Monolog\Logger; /** * Adds the DebugHandler when the profiler is enabled and kernel.debug is true. * * @author Christophe Coevoet * @author Jordi Boggiano * * @deprecated since version 2.12, to be removed in 4.0. Use AddDebugLogProcessorPass in FrameworkBundle instead. */ class DebugHandlerPass implements CompilerPassInterface { private $channelPass; public function __construct(LoggerChannelPass $channelPass) { @\trigger_error('The ' . __CLASS__ . ' class is deprecated since version 2.12 and will be removed in 4.0. Use AddDebugLogProcessorPass in FrameworkBundle instead.', \E_USER_DEPRECATED); $this->channelPass = $channelPass; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition('profiler')) { return; } if (!$container->getParameter('kernel.debug')) { return; } $debugHandler = new Definition('_ContaoManager\\Symfony\\Bridge\\Monolog\\Handler\\DebugHandler', [Logger::DEBUG, \true]); $container->setDefinition('monolog.handler.debug', $debugHandler); foreach ($this->channelPass->getChannels() as $channel) { $container->getDefinition($channel === 'app' ? 'monolog.logger' : 'monolog.logger.' . $channel)->addMethodCall('pushHandler', [new Reference('monolog.handler.debug')]); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Fixes loggers with no handlers (by registering a "null" one). * * Monolog 1.x adds a default handler logging on STDERR when a logger has * no registered handlers. This is NOT what what we want in Symfony, so in such * cases, we add a "null" handler to avoid the issue. * * Note that Monolog 2.x does not register a default handler anymore, so this pass can * be removed when MonologBundle minimum version of Monolog is bumped to 2.0. * * @author Fabien Potencier * * @see https://github.com/Seldaek/monolog/commit/ad37b7b2d11f300cbace9f5e84f855d329519e28 * * @internalsince 3.9.0 */ class FixEmptyLoggerPass implements CompilerPassInterface { private $channelPass; public function __construct(LoggerChannelPass $channelPass) { $this->channelPass = $channelPass; } public function process(ContainerBuilder $container) { $container->register('monolog.handler.null_internal', '_ContaoManager\\Monolog\\Handler\\NullHandler'); foreach ($this->channelPass->getChannels() as $channel) { $def = $container->getDefinition($channel === 'app' ? 'monolog.logger' : 'monolog.logger.' . $channel); foreach ($def->getMethodCalls() as $method) { if ('pushHandler' === $method[0]) { continue 2; } } $def->addMethodCall('pushHandler', [new Reference('monolog.handler.null_internal')]); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Sets the transport for Swiftmailer handlers depending on the existing * container definitions. * * @author Christian Flothmann * * @internalsince 3.9.0 */ class AddSwiftMailerTransportPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $handlers = $container->getParameter('monolog.swift_mailer.handlers'); foreach ($handlers as $id) { $definition = $container->getDefinition($id); $mailerId = (string) $definition->getArgument(0); // Try to fetch the transport for a non-default mailer first, then go with the default swiftmailer $possibleServices = [$mailerId . '.transport.real', $mailerId . '.transport', 'swiftmailer.transport.real', 'swiftmailer.transport']; foreach ($possibleServices as $serviceId) { if ($container->hasAlias($serviceId) || $container->hasDefinition($serviceId)) { $definition->addMethodCall('setTransport', [new Reference($serviceId)]); break; } } } } } { "name": "symfony\/monolog-bundle", "type": "symfony-bundle", "description": "Symfony MonologBundle", "keywords": [ "log", "logging" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/monolog-bridge": "^5.4 || ^6.0 || ^7.0", "symfony\/dependency-injection": "^5.4 || ^6.0 || ^7.0", "symfony\/config": "^5.4 || ^6.0 || ^7.0", "symfony\/http-kernel": "^5.4 || ^6.0 || ^7.0", "monolog\/monolog": "^1.25.1 || ^2.0 || ^3.0" }, "require-dev": { "symfony\/yaml": "^5.4 || ^6.0 || ^7.0", "symfony\/console": "^5.4 || ^6.0 || ^7.0", "symfony\/phpunit-bridge": "^6.3 || ^7.0" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Bundle\\MonologBundle\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "extra": { "branch-alias": { "dev-master": "3.x-dev" } } }Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Php73; /** * @author Gabriel Caruso * @author Ion Bazan * * @internal */ final class Php73 { public static $startAt = 1533462603; /** * @param bool $asNum * * @return array|float|int */ public static function hrtime($asNum = \false) { $ns = \microtime(\false); $s = \substr($ns, 11) - self::$startAt; $ns = 1000000000.0 * (float) $ns; if ($asNum) { $ns += $s * 1000000000.0; return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; } return [$s, (int) $ns]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID < 70300) { class JsonException extends Exception { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Php73 as p; if (\PHP_VERSION_ID >= 70300) { return; } if (!function_exists('is_countable')) { function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } } if (!function_exists('hrtime')) { require_once __DIR__.'/Php73.php'; p\Php73::$startAt = (int) microtime(true); function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } } if (!function_exists('array_key_first')) { function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } } if (!function_exists('array_key_last')) { function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } } Symfony Polyfill / Php73 ======================== This component provides functions added to PHP 7.3 core: - [`array_key_first`](https://php.net/array_key_first) - [`array_key_last`](https://php.net/array_key_last) - [`hrtime`](https://php.net/function.hrtime) - [`is_countable`](https://php.net/is_countable) - [`JsonException`](https://php.net/JsonException) More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). { "name": "symfony\/polyfill-php73", "type": "library", "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "keywords": [ "polyfill", "shim", "compatibility", "portable" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources\/stubs" ] }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Php80; /** * @author Fedonyuk Anton * * @internal */ class PhpToken implements \Stringable { /** * @var int */ public $id; /** * @var string */ public $text; /** * @var int */ public $line; /** * @var int */ public $pos; public function __construct(int $id, string $text, int $line = -1, int $position = -1) { $this->id = $id; $this->text = $text; $this->line = $line; $this->pos = $position; } public function getTokenName() : ?string { if ('UNKNOWN' === ($name = \token_name($this->id))) { $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; } return $name; } /** * @param int|string|array $kind */ public function is($kind) : bool { foreach ((array) $kind as $value) { if (\in_array($value, [$this->id, $this->text], \true)) { return \true; } } return \false; } public function isIgnorable() : bool { return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], \true); } public function __toString() : string { return (string) $this->text; } /** * @return static[] */ public static function tokenize(string $code, int $flags = 0) : array { $line = 1; $position = 0; $tokens = \token_get_all($code, $flags); foreach ($tokens as $index => $token) { if (\is_string($token)) { $id = \ord($token); $text = $token; } else { [$id, $text, $line] = $token; } $tokens[$index] = new static($id, $text, $line, $position); $position += \strlen($text); } return $tokens; } } Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { class PhpToken extends Symfony\Polyfill\Php80\PhpToken { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID < 80000) { class ValueError extends Error { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #[Attribute(Attribute::TARGET_CLASS)] final class Attribute { public const TARGET_CLASS = 1; public const TARGET_FUNCTION = 2; public const TARGET_METHOD = 4; public const TARGET_PROPERTY = 8; public const TARGET_CLASS_CONSTANT = 16; public const TARGET_PARAMETER = 32; public const TARGET_ALL = 63; public const IS_REPEATABLE = 64; /** @var int */ public $flags; public function __construct(int $flags = self::TARGET_ALL) { $this->flags = $flags; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID < 80000) { class UnhandledMatchError extends Error { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID < 80000) { interface Stringable { /** * @return string */ public function __toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Php80 as p; if (\PHP_VERSION_ID >= 80000) { return; } if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); } if (!function_exists('fdiv')) { function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } } if (!function_exists('preg_last_error_msg')) { function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } } if (!function_exists('str_contains')) { function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } } if (!function_exists('str_starts_with')) { function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } } if (!function_exists('str_ends_with')) { function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } } if (!function_exists('get_debug_type')) { function get_debug_type($value): string { return p\Php80::get_debug_type($value); } } if (!function_exists('get_resource_id')) { function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } } Symfony Polyfill / Php80 ======================== This component provides features added to PHP 8.0 core: - [`Stringable`](https://php.net/stringable) interface - [`fdiv`](https://php.net/fdiv) - [`ValueError`](https://php.net/valueerror) class - [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class - `FILTER_VALIDATE_BOOL` constant - [`get_debug_type`](https://php.net/get_debug_type) - [`PhpToken`](https://php.net/phptoken) class - [`preg_last_error_msg`](https://php.net/preg_last_error_msg) - [`str_contains`](https://php.net/str_contains) - [`str_starts_with`](https://php.net/str_starts_with) - [`str_ends_with`](https://php.net/str_ends_with) - [`get_resource_id`](https://php.net/get_resource_id) More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Php80; /** * @author Ion Bazan * @author Nico Oelgart * @author Nicolas Grekas * * @internal */ final class Php80 { public static function fdiv(float $dividend, float $divisor) : float { return @($dividend / $divisor); } public static function get_debug_type($value) : string { switch (\true) { case null === $value: return 'null'; case \is_bool($value): return 'bool'; case \is_string($value): return 'string'; case \is_array($value): return 'array'; case \is_int($value): return 'int'; case \is_float($value): return 'float'; case \is_object($value): break; case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; default: if (null === ($type = @\get_resource_type($value))) { return 'unknown'; } if ('Unknown' === $type) { $type = 'closed'; } return "resource ({$type})"; } $class = \get_class($value); if (\false === \strpos($class, '@')) { return $class; } return ((\get_parent_class($class) ?: \key(\class_implements($class))) ?: 'class') . '@anonymous'; } public static function get_resource_id($res) : int { if (!\is_resource($res) && null === @\get_resource_type($res)) { throw new \TypeError(\sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', \get_debug_type($res))); } return (int) $res; } public static function preg_last_error_msg() : string { switch (\preg_last_error()) { case \PREG_INTERNAL_ERROR: return 'Internal error'; case \PREG_BAD_UTF8_ERROR: return 'Malformed UTF-8 characters, possibly incorrectly encoded'; case \PREG_BAD_UTF8_OFFSET_ERROR: return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; case \PREG_BACKTRACK_LIMIT_ERROR: return 'Backtrack limit exhausted'; case \PREG_RECURSION_LIMIT_ERROR: return 'Recursion limit exhausted'; case \PREG_JIT_STACKLIMIT_ERROR: return 'JIT stack limit exhausted'; case \PREG_NO_ERROR: return 'No error'; default: return 'Unknown error'; } } public static function str_contains(string $haystack, string $needle) : bool { return '' === $needle || \false !== \strpos($haystack, $needle); } public static function str_starts_with(string $haystack, string $needle) : bool { return 0 === \strncmp($haystack, $needle, \strlen($needle)); } public static function str_ends_with(string $haystack, string $needle) : bool { if ('' === $needle || $needle === $haystack) { return \true; } if ('' === $haystack) { return \false; } $needleLength = \strlen($needle); return $needleLength <= \strlen($haystack) && 0 === \substr_compare($haystack, $needle, -$needleLength); } } { "name": "symfony\/polyfill-php80", "type": "library", "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "keywords": [ "polyfill", "shim", "compatibility", "portable" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Ion Bazan", "email": "ion.bazan@gmail.com" }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources\/stubs" ] }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\HtmlFormatter; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Handler\AbstractProcessingHandler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Component\Mailer\MailerInterface; use _ContaoManager\Symfony\Component\Mime\Email; /** * @author Alexander Borisov */ class MailerHandler extends AbstractProcessingHandler { private $mailer; private $messageTemplate; /** * @param callable|Email $messageTemplate * @param string|int $level The minimum logging level at which this handler will be triggered */ public function __construct(MailerInterface $mailer, $messageTemplate, $level = Logger::DEBUG, bool $bubble = \true) { parent::__construct($level, $bubble); $this->mailer = $mailer; $this->messageTemplate = $messageTemplate; } /** * {@inheritdoc} */ public function handleBatch(array $records) : void { $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { continue; } $messages[] = $this->processRecord($record); } if (!empty($messages)) { $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); } } /** * {@inheritdoc} */ protected function write(array $record) : void { $this->send((string) $record['formatted'], [$record]); } /** * Send a mail with the given content. * * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content */ protected function send(string $content, array $records) { $this->mailer->send($this->buildMessage($content, $records)); } /** * Gets the formatter for the Message subject. * * @param string $format The format of the subject */ protected function getSubjectFormatter(string $format) : FormatterInterface { return new LineFormatter($format); } /** * Creates instance of Message to be sent. * * @param string $content formatted email body to be sent * @param array $records Log records that formed the content */ protected function buildMessage(string $content, array $records) : Email { $message = null; if ($this->messageTemplate instanceof Email) { $message = clone $this->messageTemplate; } elseif (\is_callable($this->messageTemplate)) { $message = \call_user_func($this->messageTemplate, $content, $records); if (!$message instanceof Email) { throw new \InvalidArgumentException(\sprintf('Could not resolve message from a callable. Instance of "%s" is expected.', Email::class)); } } else { throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it.'); } if ($records) { $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); $message->subject($subjectFormatter->format($this->getHighestRecord($records))); } if ($this->getFormatter() instanceof HtmlFormatter) { if ($message->getHtmlCharset()) { $message->html($content, $message->getHtmlCharset()); } else { $message->html($content); } } else { if ($message->getTextCharset()) { $message->text($content, $message->getTextCharset()); } else { $message->text($content); } } return $message; } protected function getHighestRecord(array $records) : array { $highestRecord = null; foreach ($records as $record) { if (null === $highestRecord || $highestRecord['level'] < $record['level']) { $highestRecord = $record; } } return $highestRecord; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LineFormatter; use _ContaoManager\Monolog\Handler\AbstractProcessingHandler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\Console\Event\ConsoleCommandEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleTerminateEvent; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; /** * Writes logs to the console output depending on its verbosity setting. * * It is disabled by default and gets activated as soon as a command is executed. * Instead of listening to the console events, the output can also be set manually. * * The minimum logging level at which this handler will be triggered depends on the * verbosity setting of the console output. The default mapping is: * - OutputInterface::VERBOSITY_NORMAL will show all WARNING and higher logs * - OutputInterface::VERBOSITY_VERBOSE (-v) will show all NOTICE and higher logs * - OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) will show all INFO and higher logs * - OutputInterface::VERBOSITY_DEBUG (-vvv) will show all DEBUG and higher logs, i.e. all logs * * This mapping can be customized with the $verbosityLevelMap constructor parameter. * * @author Tobias Schultze */ class ConsoleHandler extends AbstractProcessingHandler implements EventSubscriberInterface { private $output; private $verbosityLevelMap = [OutputInterface::VERBOSITY_QUIET => Logger::ERROR, OutputInterface::VERBOSITY_NORMAL => Logger::WARNING, OutputInterface::VERBOSITY_VERBOSE => Logger::NOTICE, OutputInterface::VERBOSITY_VERY_VERBOSE => Logger::INFO, OutputInterface::VERBOSITY_DEBUG => Logger::DEBUG]; private $consoleFormatterOptions; /** * @param OutputInterface|null $output The console output to use (the handler remains disabled when passing null * until the output is set, e.g. by using console events) * @param bool $bubble Whether the messages that are handled can bubble up the stack * @param array $verbosityLevelMap Array that maps the OutputInterface verbosity to a minimum logging * level (leave empty to use the default mapping) */ public function __construct(?OutputInterface $output = null, bool $bubble = \true, array $verbosityLevelMap = [], array $consoleFormatterOptions = []) { parent::__construct(Logger::DEBUG, $bubble); $this->output = $output; if ($verbosityLevelMap) { $this->verbosityLevelMap = $verbosityLevelMap; } $this->consoleFormatterOptions = $consoleFormatterOptions; } /** * {@inheritdoc} */ public function isHandling(array $record) : bool { return $this->updateLevel() && parent::isHandling($record); } /** * {@inheritdoc} */ public function handle(array $record) : bool { // we have to update the logging level each time because the verbosity of the // console output might have changed in the meantime (it is not immutable) return $this->updateLevel() && parent::handle($record); } /** * Sets the console output to use for printing logs. */ public function setOutput(OutputInterface $output) { $this->output = $output; } /** * Disables the output. */ public function close() : void { $this->output = null; parent::close(); } /** * Before a command is executed, the handler gets activated and the console output * is set in order to know where to write the logs. */ public function onCommand(ConsoleCommandEvent $event) { $output = $event->getOutput(); if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->setOutput($output); } /** * After a command has been executed, it disables the output. */ public function onTerminate(ConsoleTerminateEvent $event) { $this->close(); } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ConsoleEvents::COMMAND => ['onCommand', 255], ConsoleEvents::TERMINATE => ['onTerminate', -255]]; } /** * {@inheritdoc} */ protected function write(array $record) : void { // at this point we've determined for sure that we want to output the record, so use the output's own verbosity $this->output->write((string) $record['formatted'], \false, $this->output->getVerbosity()); } /** * {@inheritdoc} */ protected function getDefaultFormatter() : FormatterInterface { if (!\class_exists(CliDumper::class)) { return new LineFormatter(); } if (!$this->output) { return new ConsoleFormatter($this->consoleFormatterOptions); } return new ConsoleFormatter(\array_replace(['colors' => $this->output->isDecorated(), 'multiline' => OutputInterface::VERBOSITY_DEBUG <= $this->output->getVerbosity()], $this->consoleFormatterOptions)); } /** * Updates the logging level based on the verbosity setting of the console output. * * @return bool Whether the handler is enabled and verbosity is not set to quiet */ private function updateLevel() : bool { if (null === $this->output) { return \false; } $verbosity = $this->output->getVerbosity(); if (isset($this->verbosityLevelMap[$verbosity])) { $this->setLevel($this->verbosityLevelMap[$verbosity]); } else { $this->setLevel(Logger::DEBUG); } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler\FingersCrossed; use _ContaoManager\Monolog\Handler\FingersCrossed\ActivationStrategyInterface; use _ContaoManager\Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; /** * Activation strategy that ignores certain HTTP codes. * * @author Shaun Simmons * @author Pierrick Vignand * * @final */ class HttpCodeActivationStrategy extends ErrorLevelActivationStrategy implements ActivationStrategyInterface { private $inner; private $exclusions; private $requestStack; /** * @param array $exclusions each exclusion must have a "code" and "urls" keys * @param ActivationStrategyInterface|int|string $inner an ActivationStrategyInterface to decorate */ public function __construct(RequestStack $requestStack, array $exclusions, $inner) { if (!$inner instanceof ActivationStrategyInterface) { \trigger_deprecation('symfony/monolog-bridge', '5.2', 'Passing an actionLevel (int|string) as constructor\'s 3rd argument of "%s" is deprecated, "%s" expected.', __CLASS__, ActivationStrategyInterface::class); $actionLevel = $inner; $inner = new ErrorLevelActivationStrategy($actionLevel); } foreach ($exclusions as $exclusion) { if (!\array_key_exists('code', $exclusion)) { throw new \LogicException('An exclusion must have a "code" key.'); } if (!\array_key_exists('urls', $exclusion)) { throw new \LogicException('An exclusion must have a "urls" key.'); } } $this->inner = $inner; $this->requestStack = $requestStack; $this->exclusions = $exclusions; } public function isHandlerActivated(array $record) : bool { $isActivated = $this->inner->isHandlerActivated($record); if ($isActivated && isset($record['context']['exception']) && $record['context']['exception'] instanceof HttpException && ($request = $this->requestStack->getMainRequest())) { foreach ($this->exclusions as $exclusion) { if ($record['context']['exception']->getStatusCode() !== $exclusion['code']) { continue; } if (\count($exclusion['urls'])) { return !\preg_match('{(' . \implode('|', $exclusion['urls']) . ')}i', $request->getPathInfo()); } return \false; } } return $isActivated; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler\FingersCrossed; use _ContaoManager\Monolog\Handler\FingersCrossed\ActivationStrategyInterface; use _ContaoManager\Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; /** * Activation strategy that ignores 404s for certain URLs. * * @author Jordi Boggiano * @author Fabien Potencier * @author Pierrick Vignand * * @final */ class NotFoundActivationStrategy extends ErrorLevelActivationStrategy implements ActivationStrategyInterface { private $inner; private $exclude; private $requestStack; /** * @param ActivationStrategyInterface|int|string $inner an ActivationStrategyInterface to decorate */ public function __construct(RequestStack $requestStack, array $excludedUrls, $inner) { if (!$inner instanceof ActivationStrategyInterface) { \trigger_deprecation('symfony/monolog-bridge', '5.2', 'Passing an actionLevel (int|string) as constructor\'s 3rd argument of "%s" is deprecated, "%s" expected.', __CLASS__, ActivationStrategyInterface::class); $actionLevel = $inner; $inner = new ErrorLevelActivationStrategy($actionLevel); } $this->inner = $inner; $this->requestStack = $requestStack; $this->exclude = '{(' . \implode('|', $excludedUrls) . ')}i'; } public function isHandlerActivated(array $record) : bool { $isActivated = $this->inner->isHandlerActivated($record); if ($isActivated && isset($record['context']['exception']) && $record['context']['exception'] instanceof HttpException && 404 == $record['context']['exception']->getStatusCode() && ($request = $this->requestStack->getMainRequest())) { return !\preg_match($this->exclude, $request->getPathInfo()); } return $isActivated; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Handler\SwiftMailerHandler as BaseSwiftMailerHandler; use _ContaoManager\Symfony\Component\Console\Event\ConsoleTerminateEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\TerminateEvent; \trigger_deprecation('symfony/monolog-bridge', '5.4', '"%s" is deprecated and will be removed in 6.0.', SwiftMailerHandler::class); /** * Extended SwiftMailerHandler that flushes mail queue if necessary. * * @author Philipp Kräutli * * @final * * @deprecated since Symfony 5.4 */ class SwiftMailerHandler extends BaseSwiftMailerHandler { protected $transport; protected $instantFlush = \false; public function setTransport(\_ContaoManager\Swift_Transport $transport) { $this->transport = $transport; } /** * After the kernel has been terminated we will always flush messages. */ public function onKernelTerminate(TerminateEvent $event) { $this->instantFlush = \true; } /** * After the CLI application has been terminated we will always flush messages. */ public function onCliTerminate(ConsoleTerminateEvent $event) { $this->instantFlush = \true; } /** * {@inheritdoc} */ protected function send($content, array $records) : void { parent::send($content, $records); if ($this->instantFlush) { $this->flushMemorySpool(); } } /** * {@inheritdoc} */ public function reset() : void { $this->flushMemorySpool(); } /** * Flushes the mail queue if a memory spool is used. */ private function flushMemorySpool() { $mailerTransport = $this->mailer->getTransport(); if (!$mailerTransport instanceof \_ContaoManager\Swift_Transport_SpoolTransport) { return; } $spool = $mailerTransport->getSpool(); if (!$spool instanceof \_ContaoManager\Swift_MemorySpool) { return; } if (null === $this->transport) { throw new \Exception('No transport available to flush mail queue.'); } $spool->flushQueue($this->transport); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Handler\AbstractProcessingHandler; use _ContaoManager\Monolog\Handler\FormattableHandlerTrait; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Bridge\Monolog\Formatter\VarDumperFormatter; if (\trait_exists(FormattableHandlerTrait::class)) { class ServerLogHandler extends AbstractProcessingHandler { use ServerLogHandlerTrait; /** * {@inheritdoc} */ protected function getDefaultFormatter() : FormatterInterface { return new VarDumperFormatter(); } } } else { class ServerLogHandler extends AbstractProcessingHandler { use ServerLogHandlerTrait; /** * {@inheritdoc} */ protected function getDefaultFormatter() { return new VarDumperFormatter(); } } } /** * @author Grégoire Pineau */ trait ServerLogHandlerTrait { private $host; private $context; private $socket; /** * @param string|int $level The minimum logging level at which this handler will be triggered */ public function __construct(string $host, $level = Logger::DEBUG, bool $bubble = \true, array $context = []) { parent::__construct($level, $bubble); if (!\str_contains($host, '://')) { $host = 'tcp://' . $host; } $this->host = $host; $this->context = \stream_context_create($context); } /** * {@inheritdoc} */ public function handle(array $record) : bool { if (!$this->isHandling($record)) { return \false; } \set_error_handler(self::class . '::nullErrorHandler'); try { if (!($this->socket = $this->socket ?: $this->createSocket())) { return \false === $this->bubble; } } finally { \restore_error_handler(); } return parent::handle($record); } protected function write(array $record) : void { $recordFormatted = $this->formatRecord($record); \set_error_handler(self::class . '::nullErrorHandler'); try { if (-1 === \stream_socket_sendto($this->socket, $recordFormatted)) { \stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); // Let's retry: the persistent connection might just be stale if ($this->socket = $this->createSocket()) { \stream_socket_sendto($this->socket, $recordFormatted); } } } finally { \restore_error_handler(); } } /** * {@inheritdoc} */ protected function getDefaultFormatter() : FormatterInterface { return new VarDumperFormatter(); } private static function nullErrorHandler() { } private function createSocket() { $socket = \stream_socket_client($this->host, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT | \STREAM_CLIENT_PERSISTENT, $this->context); if ($socket) { \stream_set_blocking($socket, \false); } return $socket; } private function formatRecord(array $record) : string { $recordFormatted = $record['formatted']; foreach (['log_uuid', 'uuid', 'uid'] as $key) { if (isset($record['extra'][$key])) { $recordFormatted['log_id'] = $record['extra'][$key]; break; } } return \base64_encode(\serialize($recordFormatted)) . "\n"; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Handler\AbstractHandler; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Component\Notifier\Notification\Notification; use _ContaoManager\Symfony\Component\Notifier\Notifier; use _ContaoManager\Symfony\Component\Notifier\NotifierInterface; /** * Uses Notifier as a log handler. * * @author Fabien Potencier */ class NotifierHandler extends AbstractHandler { private $notifier; /** * @param string|int $level The minimum logging level at which this handler will be triggered */ public function __construct(NotifierInterface $notifier, $level = Logger::ERROR, bool $bubble = \true) { $this->notifier = $notifier; parent::__construct(Logger::toMonologLevel($level) < Logger::ERROR ? Logger::ERROR : $level, $bubble); } public function handle(array $record) : bool { if (!$this->isHandling($record)) { return \false; } $this->notify([$record]); return !$this->bubble; } public function handleBatch(array $records) : void { if ($records = \array_filter($records, [$this, 'isHandling'])) { $this->notify($records); } } private function notify(array $records) : void { $record = $this->getHighestRecord($records); if (($record['context']['exception'] ?? null) instanceof \Throwable) { $notification = Notification::fromThrowable($record['context']['exception']); } else { $notification = new Notification($record['message']); } $notification->importanceFromLogLevelName(Logger::getLevelName($record['level'])); $this->notifier->send($notification, ...$this->notifier->getAdminRecipients()); } private function getHighestRecord(array $records) { $highestRecord = null; foreach ($records as $record) { if (null === $highestRecord || $highestRecord['level'] < $record['level']) { $highestRecord = $record; } } return $highestRecord; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Handler\FirePHPHandler as BaseFirePHPHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; /** * FirePHPHandler. * * @author Jordi Boggiano * * @final */ class FirePHPHandler extends BaseFirePHPHandler { private $headers = []; /** * @var Response */ private $response; /** * Adds the headers to the response once it's created. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); if (!\preg_match('{\\bFirePHP/\\d+\\.\\d+\\b}', $request->headers->get('User-Agent', '')) && !$request->headers->has('X-FirePHP-Version')) { self::$sendHeaders = \false; $this->headers = []; return; } $this->response = $event->getResponse(); foreach ($this->headers as $header => $content) { $this->response->headers->set($header, $content); } $this->headers = []; } /** * {@inheritdoc} */ protected function sendHeader($header, $content) : void { if (!self::$sendHeaders) { return; } if ($this->response) { $this->response->headers->set($header, $content); } else { $this->headers[$header] = $content; } } /** * Override default behavior since we check the user agent in onKernelResponse. */ protected function headersAccepted() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Handler\ChromePHPHandler as BaseChromePhpHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; /** * ChromePhpHandler. * * @author Christophe Coevoet * * @final */ class ChromePhpHandler extends BaseChromePhpHandler { private $headers = []; /** * @var Response */ private $response; /** * Adds the headers to the response once it's created. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } if (!\preg_match(static::USER_AGENT_REGEX, $event->getRequest()->headers->get('User-Agent'))) { self::$sendHeaders = \false; $this->headers = []; return; } $this->response = $event->getResponse(); foreach ($this->headers as $header => $content) { $this->response->headers->set($header, $content); } $this->headers = []; } /** * {@inheritdoc} */ protected function sendHeader($header, $content) : void { if (!self::$sendHeaders) { return; } if ($this->response) { $this->response->headers->set($header, $content); } else { $this->headers[$header] = $content; } } /** * Override default behavior since we check it in onKernelResponse. */ protected function headersAccepted() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Handler; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Formatter\LogstashFormatter; use _ContaoManager\Monolog\Handler\AbstractHandler; use _ContaoManager\Monolog\Handler\FormattableHandlerTrait; use _ContaoManager\Monolog\Handler\ProcessableHandlerTrait; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Component\HttpClient\HttpClient; use _ContaoManager\Symfony\Contracts\HttpClient\Exception\ExceptionInterface; use _ContaoManager\Symfony\Contracts\HttpClient\HttpClientInterface; use _ContaoManager\Symfony\Contracts\HttpClient\ResponseInterface; /** * Push logs directly to Elasticsearch and format them according to Logstash specification. * * This handler dials directly with the HTTP interface of Elasticsearch. This * means it will slow down your application if Elasticsearch takes times to * answer. Even if all HTTP calls are done asynchronously. * * In a development environment, it's fine to keep the default configuration: * for each log, an HTTP request will be made to push the log to Elasticsearch. * * In a production environment, it's highly recommended to wrap this handler * in a handler with buffering capabilities (like the FingersCrossedHandler, or * BufferHandler) in order to call Elasticsearch only once with a bulk push. For * even better performance and fault tolerance, a proper ELK (https://www.elastic.co/what-is/elk-stack) * stack is recommended. * * @author Grégoire Pineau */ class ElasticsearchLogstashHandler extends AbstractHandler { use FormattableHandlerTrait; use ProcessableHandlerTrait; private $endpoint; private $index; private $client; /** * @var \SplObjectStorage */ private $responses; private $elasticsearchVersion; /** * @param string|int $level The minimum logging level at which this handler will be triggered */ public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', ?HttpClientInterface $client = null, $level = Logger::DEBUG, bool $bubble = \true, string $elasticsearchVersion = '1.0.0') { if (!\interface_exists(HttpClientInterface::class)) { throw new \LogicException(\sprintf('The "%s" handler needs an HTTP client. Try running "composer require symfony/http-client".', __CLASS__)); } parent::__construct($level, $bubble); $this->endpoint = $endpoint; $this->index = $index; $this->client = $client ?: HttpClient::create(['timeout' => 1]); $this->responses = new \SplObjectStorage(); $this->elasticsearchVersion = $elasticsearchVersion; } public function handle(array $record) : bool { if (!$this->isHandling($record)) { return \false; } $this->sendToElasticsearch([$record]); return !$this->bubble; } public function handleBatch(array $records) : void { $records = \array_filter($records, [$this, 'isHandling']); if ($records) { $this->sendToElasticsearch($records); } } protected function getDefaultFormatter() : FormatterInterface { // Monolog 1.X if (\defined(LogstashFormatter::class . '::V1')) { return new LogstashFormatter('application', null, null, 'ctxt_', LogstashFormatter::V1); } // Monolog 2.X return new LogstashFormatter('application'); } private function sendToElasticsearch(array $records) { $formatter = $this->getFormatter(); if (\version_compare($this->elasticsearchVersion, '7', '>=')) { $headers = \json_encode(['index' => ['_index' => $this->index]]); } else { $headers = \json_encode(['index' => ['_index' => $this->index, '_type' => '_doc']]); } $body = ''; foreach ($records as $record) { foreach ($this->processors as $processor) { $record = $processor($record); } $body .= $headers; $body .= "\n"; $body .= $formatter->format($record); $body .= "\n"; } $response = $this->client->request('POST', $this->endpoint . '/_bulk', ['body' => $body, 'headers' => ['Content-Type' => 'application/json']]); $this->responses->attach($response); $this->wait(\false); } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { $this->wait(\true); } private function wait(bool $blocking) { foreach ($this->client->stream($this->responses, $blocking ? null : 0.0) as $response => $chunk) { try { if ($chunk->isTimeout() && !$blocking) { continue; } if (!$chunk->isFirst() && !$chunk->isLast()) { continue; } if ($chunk->isLast()) { $this->responses->detach($response); } } catch (ExceptionInterface $e) { $this->responses->detach($response); \error_log(\sprintf("Could not push logs to Elasticsearch:\n%s", (string) $e)); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog; use _ContaoManager\Monolog\Logger as BaseLogger; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * @author Fabien Potencier */ class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface { /** * {@inheritdoc} */ public function getLogs(?Request $request = null) { if ($logger = $this->getDebugLogger()) { return $logger->getLogs($request); } return []; } /** * {@inheritdoc} */ public function countErrors(?Request $request = null) { if ($logger = $this->getDebugLogger()) { return $logger->countErrors($request); } return 0; } /** * {@inheritdoc} */ public function clear() { if ($logger = $this->getDebugLogger()) { $logger->clear(); } } /** * {@inheritdoc} */ public function reset() : void { $this->clear(); if ($this instanceof ResettableInterface) { parent::reset(); } } public function removeDebugLogger() { foreach ($this->processors as $k => $processor) { if ($processor instanceof DebugLoggerInterface) { unset($this->processors[$k]); } } foreach ($this->handlers as $k => $handler) { if ($handler instanceof DebugLoggerInterface) { unset($this->handlers[$k]); } } } /** * Returns a DebugLoggerInterface instance if one is registered with this logger. */ private function getDebugLogger() : ?DebugLoggerInterface { foreach ($this->processors as $processor) { if ($processor instanceof DebugLoggerInterface) { return $processor; } } foreach ($this->handlers as $handler) { if ($handler instanceof DebugLoggerInterface) { return $handler; } } return null; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Deprecate `ResetLoggersWorkerSubscriber` to reset buffered logs in messenger workers, use "reset_on_message" option in messenger configuration instead. 5.3 --- * Add `ResetLoggersWorkerSubscriber` to reset buffered logs in messenger workers 5.2.0 ----- * The `$actionLevel` constructor argument of `Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy` has been deprecated and replaced by the `$inner` one which expects an ActivationStrategyInterface to decorate instead. `Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy` will become final in 6.0. * The `$actionLevel` constructor argument of `Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy` has been deprecated and replaced by the `$inner` one which expects an ActivationStrategyInterface to decorate instead. `Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy` will become final in 6.0 5.1.0 ----- * Added `MailerHandler` 5.0.0 ----- * The methods `DebugProcessor::getLogs()`, `DebugProcessor::countErrors()`, `Logger::getLogs()` and `Logger::countErrors()` have a new `$request` argument. * Added support for Monolog 2. 4.4.0 ----- * The `RouteProcessor` class has been made final * Added `ElasticsearchLogstashHandler` * Added the `ServerLogCommand`. Backport from the deprecated WebServerBundle 4.3.0 ----- * added `ConsoleCommandProcessor`: monolog processor that adds command name and arguments * added `RouteProcessor`: monolog processor that adds route name, controller::action and route params 4.2.0 ----- * The methods `DebugProcessor::getLogs()`, `DebugProcessor::countErrors()`, `Logger::getLogs()` and `Logger::countErrors()` will have a new `$request` argument in version 5.0, not defining it is deprecated 4.1.0 ----- * `WebProcessor` now implements `EventSubscriberInterface` in order to be easily autoconfigured 4.0.0 ----- * the `$format`, `$dateFormat`, `$allowInlineLineBreaks`, and `$ignoreEmptyContextAndExtra` constructor arguments of the `ConsoleFormatter` class have been removed, use `$options` instead * the `DebugHandler` class has been removed 3.3.0 ----- * Improved the console handler output formatting by adding var-dumper support 3.0.0 ----- * deprecated interface `Symfony\Component\HttpKernel\Log\LoggerInterface` has been removed * deprecated methods `Logger::crit()`, `Logger::emerg()`, `Logger::err()` and `Logger::warn()` have been removed 2.4.0 ----- * added ConsoleHandler and ConsoleFormatter which can be used to show log messages in the console output depending on the verbosity settings 2.1.0 ----- * added ChromePhpHandler * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * Adds the current security token to the log entry. * * @author Dany Maillard * @author Igor Timoshenko */ class TokenProcessor extends AbstractTokenProcessor { /** * {@inheritdoc} */ protected function getKey() : string { return 'token'; } /** * {@inheritdoc} */ protected function getToken() : ?TokenInterface { return $this->tokenStorage->getToken(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * Adds the original security token to the log entry. * * @author Igor Timoshenko */ class SwitchUserTokenProcessor extends AbstractTokenProcessor { /** * {@inheritdoc} */ protected function getKey() : string { return 'impersonator_token'; } /** * {@inheritdoc} */ protected function getToken() : ?TokenInterface { $token = $this->tokenStorage->getToken(); if ($token instanceof SwitchUserToken) { return $token->getOriginalToken(); } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Monolog\Processor\WebProcessor as BaseWebProcessor; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * WebProcessor override to read from the HttpFoundation's Request. * * @author Jordi Boggiano * * @final */ class WebProcessor extends BaseWebProcessor implements EventSubscriberInterface { public function __construct(?array $extraFields = null) { // Pass an empty array as the default null value would access $_SERVER parent::__construct([], $extraFields); } public function onKernelRequest(RequestEvent $event) { if ($event->isMainRequest()) { $this->serverData = $event->getRequest()->server->all(); $this->serverData['REMOTE_ADDR'] = $event->getRequest()->getClientIp(); } } public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => ['onKernelRequest', 4096]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Adds the current route information to the log entry. * * @author Piotr Stankowski * * @final */ class RouteProcessor implements EventSubscriberInterface, ResetInterface { private $routeData; private $includeParams; public function __construct(bool $includeParams = \true) { $this->includeParams = $includeParams; $this->reset(); } public function __invoke(array $records) : array { if ($this->routeData && !isset($records['extra']['requests'])) { $records['extra']['requests'] = \array_values($this->routeData); } return $records; } public function reset() { $this->routeData = []; } public function addRouteData(RequestEvent $event) { if ($event->isMainRequest()) { $this->reset(); } $request = $event->getRequest(); if (!$request->attributes->has('_controller')) { return; } $currentRequestData = ['controller' => $request->attributes->get('_controller'), 'route' => $request->attributes->get('_route')]; if ($this->includeParams) { $currentRequestData['route_params'] = $request->attributes->get('_route_params'); } $this->routeData[\spl_object_id($request)] = $currentRequestData; } public function removeRouteData(FinishRequestEvent $event) { $requestId = \spl_object_id($event->getRequest()); unset($this->routeData[$requestId]); } public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => ['addRouteData', 1], KernelEvents::FINISH_REQUEST => ['removeRouteData', 1]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\Console\Event\ConsoleEvent; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Adds the current console command information to the log entry. * * @author Piotr Stankowski */ class ConsoleCommandProcessor implements EventSubscriberInterface, ResetInterface { private $commandData; private $includeArguments; private $includeOptions; public function __construct(bool $includeArguments = \true, bool $includeOptions = \false) { $this->includeArguments = $includeArguments; $this->includeOptions = $includeOptions; } public function __invoke(array $records) { if (null !== $this->commandData && !isset($records['extra']['command'])) { $records['extra']['command'] = $this->commandData; } return $records; } public function reset() { $this->commandData = null; } public function addCommandData(ConsoleEvent $event) { $this->commandData = ['name' => $event->getCommand()->getName()]; if ($this->includeArguments) { $this->commandData['arguments'] = $event->getInput()->getArguments(); } if ($this->includeOptions) { $this->commandData['options'] = $event->getInput()->getOptions(); } } public static function getSubscribedEvents() { return [ConsoleEvents::COMMAND => ['addCommandData', 1]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * The base class for security token processors. * * @author Dany Maillard * @author Igor Timoshenko */ abstract class AbstractTokenProcessor { /** * @var TokenStorageInterface */ protected $tokenStorage; public function __construct(TokenStorageInterface $tokenStorage) { $this->tokenStorage = $tokenStorage; } protected abstract function getKey() : string; protected abstract function getToken() : ?TokenInterface; public function __invoke(array $record) : array { $record['extra'][$this->getKey()] = null; if (null !== ($token = $this->getToken())) { $record['extra'][$this->getKey()] = ['authenticated' => \method_exists($token, 'isAuthenticated') ? $token->isAuthenticated(\false) : (bool) $token->getUser(), 'roles' => $token->getRoleNames()]; // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 if (\method_exists($token, 'getUserIdentifier')) { $record['extra'][$this->getKey()]['username'] = $record['extra'][$this->getKey()]['user_identifier'] = $token->getUserIdentifier(); } else { $record['extra'][$this->getKey()]['username'] = $token->getUsername(); } } return $record; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Processor; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; class DebugProcessor implements DebugLoggerInterface, ResetInterface { private $records = []; private $errorCount = []; private $requestStack; public function __construct(?RequestStack $requestStack = null) { $this->requestStack = $requestStack; } public function __invoke(array $record) { $hash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? \spl_object_hash($request) : ''; $timestamp = $timestampRfc3339 = \false; if ($record['datetime'] instanceof \DateTimeInterface) { $timestamp = $record['datetime']->getTimestamp(); $timestampRfc3339 = $record['datetime']->format(\DateTimeInterface::RFC3339_EXTENDED); } elseif (\false !== ($timestamp = \strtotime($record['datetime']))) { $timestampRfc3339 = (new \DateTimeImmutable($record['datetime']))->format(\DateTimeInterface::RFC3339_EXTENDED); } $this->records[$hash][] = ['timestamp' => $timestamp, 'timestamp_rfc3339' => $timestampRfc3339, 'message' => $record['message'], 'priority' => $record['level'], 'priorityName' => $record['level_name'], 'context' => $record['context'], 'channel' => $record['channel'] ?? '']; if (!isset($this->errorCount[$hash])) { $this->errorCount[$hash] = 0; } switch ($record['level']) { case Logger::ERROR: case Logger::CRITICAL: case Logger::ALERT: case Logger::EMERGENCY: ++$this->errorCount[$hash]; } return $record; } /** * {@inheritdoc} */ public function getLogs(?Request $request = null) { if (null !== $request) { return $this->records[\spl_object_hash($request)] ?? []; } if (0 === \count($this->records)) { return []; } return \array_merge(...\array_values($this->records)); } /** * {@inheritdoc} */ public function countErrors(?Request $request = null) { if (null !== $request) { return $this->errorCount[\spl_object_hash($request)] ?? 0; } return \array_sum($this->errorCount); } /** * {@inheritdoc} */ public function clear() { $this->records = []; $this->errorCount = []; } /** * {@inheritdoc} */ public function reset() { $this->clear(); } } Monolog Bridge ============== The Monolog bridge provides integration for [Monolog](https://seldaek.github.io/monolog/) with various Symfony components. Resources --------- * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Formatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; /** * Formats incoming records for console output by coloring them depending on log level. * * @author Tobias Schultze * @author Grégoire Pineau */ class ConsoleFormatter implements FormatterInterface { public const SIMPLE_FORMAT = "%datetime% %start_tag%%level_name%%end_tag% [%channel%] %message%%context%%extra%\n"; public const SIMPLE_DATE = 'H:i:s'; private const LEVEL_COLOR_MAP = [Logger::DEBUG => 'fg=white', Logger::INFO => 'fg=green', Logger::NOTICE => 'fg=blue', Logger::WARNING => 'fg=cyan', Logger::ERROR => 'fg=yellow', Logger::CRITICAL => 'fg=red', Logger::ALERT => 'fg=red', Logger::EMERGENCY => 'fg=white;bg=red']; private $options; private $cloner; private $outputBuffer; private $dumper; /** * Available options: * * format: The format of the outputted log string. The following placeholders are supported: %datetime%, %start_tag%, %level_name%, %end_tag%, %channel%, %message%, %context%, %extra%; * * date_format: The format of the outputted date string; * * colors: If true, the log string contains ANSI code to add color; * * multiline: If false, "context" and "extra" are dumped on one line. */ public function __construct(array $options = []) { $this->options = \array_replace(['format' => self::SIMPLE_FORMAT, 'date_format' => self::SIMPLE_DATE, 'colors' => \true, 'multiline' => \false, 'level_name_format' => '%-9s', 'ignore_empty_context_and_extra' => \true], $options); if (\class_exists(VarCloner::class)) { $this->cloner = new VarCloner(); $this->cloner->addCasters(['*' => [$this, 'castObject']]); $this->outputBuffer = \fopen('php://memory', 'r+'); if ($this->options['multiline']) { $output = $this->outputBuffer; } else { $output = [$this, 'echoLine']; } $this->dumper = new CliDumper($output, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); } } /** * {@inheritdoc} * * @return mixed */ public function formatBatch(array $records) { foreach ($records as $key => $record) { $records[$key] = $this->format($record); } return $records; } /** * {@inheritdoc} * * @return mixed */ public function format(array $record) { $record = $this->replacePlaceHolder($record); if (!$this->options['ignore_empty_context_and_extra'] || !empty($record['context'])) { $context = ($this->options['multiline'] ? "\n" : ' ') . $this->dumpData($record['context']); } else { $context = ''; } if (!$this->options['ignore_empty_context_and_extra'] || !empty($record['extra'])) { $extra = ($this->options['multiline'] ? "\n" : ' ') . $this->dumpData($record['extra']); } else { $extra = ''; } $formatted = \strtr($this->options['format'], ['%datetime%' => $record['datetime'] instanceof \DateTimeInterface ? $record['datetime']->format($this->options['date_format']) : $record['datetime'], '%start_tag%' => \sprintf('<%s>', self::LEVEL_COLOR_MAP[$record['level']]), '%level_name%' => \sprintf($this->options['level_name_format'], $record['level_name']), '%end_tag%' => '', '%channel%' => $record['channel'], '%message%' => $this->replacePlaceHolder($record)['message'], '%context%' => $context, '%extra%' => $extra]); return $formatted; } /** * @internal */ public function echoLine(string $line, int $depth, string $indentPad) { if (-1 !== $depth) { \fwrite($this->outputBuffer, $line); } } /** * @internal */ public function castObject($v, array $a, Stub $s, bool $isNested) : array { if ($this->options['multiline']) { return $a; } if ($isNested && !$v instanceof \DateTimeInterface) { $s->cut = -1; $a = []; } return $a; } private function replacePlaceHolder(array $record) : array { $message = $record['message']; if (!\str_contains($message, '{')) { return $record; } $context = $record['context']; $replacements = []; foreach ($context as $k => $v) { // Remove quotes added by the dumper around string. $v = \trim($this->dumpData($v, \false), '"'); $v = OutputFormatter::escape($v); $replacements['{' . $k . '}'] = \sprintf('%s', $v); } $record['message'] = \strtr($message, $replacements); return $record; } private function dumpData($data, ?bool $colors = null) : string { if (null === $this->dumper) { return ''; } if (null === $colors) { $this->dumper->setColors($this->options['colors']); } else { $this->dumper->setColors($colors); } if (!$data instanceof Data) { $data = $this->cloner->cloneVar($data); } $data = $data->withRefHandles(\false); $this->dumper->dump($data); $dump = \stream_get_contents($this->outputBuffer, -1, 0); \rewind($this->outputBuffer); \ftruncate($this->outputBuffer, 0); return \rtrim($dump); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Formatter; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; /** * @author Grégoire Pineau */ class VarDumperFormatter implements FormatterInterface { private $cloner; public function __construct(?VarCloner $cloner = null) { $this->cloner = $cloner ?? new VarCloner(); } /** * {@inheritdoc} * * @return mixed */ public function format(array $record) { $record['context'] = $this->cloner->cloneVar($record['context']); $record['extra'] = $this->cloner->cloneVar($record['extra']); return $record; } /** * {@inheritdoc} * * @return mixed */ public function formatBatch(array $records) { foreach ($records as $k => $record) { $record[$k] = $this->format($record); } return $records; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Messenger; use _ContaoManager\Monolog\ResettableInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use _ContaoManager\Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; \trigger_deprecation('symfony/monolog-bridge', '5.4', 'The "%s" class is deprecated, use "reset_on_message" option in messenger configuration instead.', ResetLoggersWorkerSubscriber::class); /** * Reset loggers between messages being handled to release buffered handler logs. * * @author Laurent VOULLEMIER * * @deprecated since Symfony 5.4, use "reset_on_message" option in messenger configuration instead. */ class ResetLoggersWorkerSubscriber implements EventSubscriberInterface { private $loggers; public function __construct(iterable $loggers) { $this->loggers = $loggers; } public static function getSubscribedEvents() : array { return [WorkerMessageHandledEvent::class => 'resetLoggers', WorkerMessageFailedEvent::class => 'resetLoggers']; } public function resetLoggers() : void { foreach ($this->loggers as $logger) { if ($logger instanceof ResettableInterface) { $logger->reset(); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bridge\Monolog\Command; use _ContaoManager\Monolog\Formatter\FormatterInterface; use _ContaoManager\Monolog\Logger; use _ContaoManager\Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use _ContaoManager\Symfony\Bridge\Monolog\Handler\ConsoleHandler; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; /** * @author Grégoire Pineau */ class ServerLogCommand extends Command { private const BG_COLOR = ['black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow']; private $el; private $handler; protected static $defaultName = 'server:log'; protected static $defaultDescription = 'Start a log server that displays logs in real time'; public function isEnabled() { if (!\class_exists(ConsoleFormatter::class)) { return \false; } // based on a symfony/symfony package, it crashes due a missing FormatterInterface from monolog/monolog if (!\interface_exists(FormatterInterface::class)) { return \false; } return parent::isEnabled(); } protected function configure() { if (!\class_exists(ConsoleFormatter::class)) { return; } $this->addOption('host', null, InputOption::VALUE_REQUIRED, 'The server host', '0.0.0.0:9911')->addOption('format', null, InputOption::VALUE_REQUIRED, 'The line format', ConsoleFormatter::SIMPLE_FORMAT)->addOption('date-format', null, InputOption::VALUE_REQUIRED, 'The date format', ConsoleFormatter::SIMPLE_DATE)->addOption('filter', null, InputOption::VALUE_REQUIRED, 'An expression to filter log. Example: "level > 200 or channel in [\'app\', \'doctrine\']"')->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' %command.name% starts a log server to display in real time the log messages generated by your application: php %command.full_name% To filter the log messages using any ExpressionLanguage compatible expression, use the --filter option: php %command.full_name% --filter="level > 200 or channel in ['app', 'doctrine']" EOF ); } protected function execute(InputInterface $input, OutputInterface $output) { $filter = $input->getOption('filter'); if ($filter) { if (!\class_exists(ExpressionLanguage::class)) { throw new LogicException('Package "symfony/expression-language" is required to use the "filter" option.'); } $this->el = new ExpressionLanguage(); } $this->handler = new ConsoleHandler($output, \true, [OutputInterface::VERBOSITY_NORMAL => Logger::DEBUG]); $this->handler->setFormatter(new ConsoleFormatter(['format' => \str_replace('\\n', "\n", $input->getOption('format')), 'date_format' => $input->getOption('date-format'), 'colors' => $output->isDecorated(), 'multiline' => OutputInterface::VERBOSITY_DEBUG <= $output->getVerbosity()])); if (!\str_contains($host = $input->getOption('host'), '://')) { $host = 'tcp://' . $host; } if (!($socket = \stream_socket_server($host, $errno, $errstr))) { throw new RuntimeException(\sprintf('Server start failed on "%s": ', $host) . $errstr . ' ' . $errno); } foreach ($this->getLogs($socket) as $clientId => $message) { $record = \unserialize(\base64_decode($message)); // Impossible to decode the message, give up. if (\false === $record) { continue; } if ($filter && !$this->el->evaluate($filter, $record)) { continue; } $this->displayLog($output, $clientId, $record); } return 0; } private function getLogs($socket) : iterable { $sockets = [(int) $socket => $socket]; $write = []; while (\true) { $read = $sockets; \stream_select($read, $write, $write, null); foreach ($read as $stream) { if ($socket === $stream) { $stream = \stream_socket_accept($socket); $sockets[(int) $stream] = $stream; } elseif (\feof($stream)) { unset($sockets[(int) $stream]); \fclose($stream); } else { (yield (int) $stream => \fgets($stream)); } } } } private function displayLog(OutputInterface $output, int $clientId, array $record) { if (isset($record['log_id'])) { $clientId = \unpack('H*', $record['log_id'])[1]; } $logBlock = \sprintf(' ', self::BG_COLOR[$clientId % 8]); $output->write($logBlock); $this->handler->handle($record); } } { "name": "symfony\/monolog-bridge", "type": "symfony-bridge", "description": "Provides integration for Monolog with various Symfony components", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "monolog\/monolog": "^1.25.1|^2", "symfony\/service-contracts": "^1.1|^2|^3", "symfony\/http-kernel": "^5.3|^6.0", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16" }, "require-dev": { "symfony\/console": "^4.4|^5.0|^6.0", "symfony\/http-client": "^4.4|^5.0|^6.0", "symfony\/security-core": "^4.4|^5.0|^6.0", "symfony\/var-dumper": "^4.4|^5.0|^6.0", "symfony\/mailer": "^4.4|^5.0|^6.0", "symfony\/mime": "^4.4|^5.0|^6.0", "symfony\/messenger": "^4.4|^5.0|^6.0" }, "conflict": { "symfony\/console": "<4.4", "symfony\/http-foundation": "<5.3" }, "suggest": { "symfony\/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", "symfony\/console": "For the possibility to show log messages in console commands depending on verbosity settings.", "symfony\/var-dumper": "For using the debugging handlers like the console handler or the log server handler." }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Bridge\\Monolog\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\DataCollector; use _ContaoManager\Symfony\Component\Cache\Adapter\TraceableAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\TraceableAdapterEvent; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; /** * @author Aaron Scherer * @author Tobias Nyholm * * @final */ class CacheDataCollector extends DataCollector implements LateDataCollectorInterface { /** * @var TraceableAdapter[] */ private $instances = []; public function addInstance(string $name, TraceableAdapter $instance) { $this->instances[$name] = $instance; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []]; $this->data = ['instances' => $empty, 'total' => $empty]; foreach ($this->instances as $name => $instance) { $this->data['instances']['calls'][$name] = $instance->getCalls(); } $this->data['instances']['statistics'] = $this->calculateStatistics(); $this->data['total']['statistics'] = $this->calculateTotalStatistics(); } public function reset() { $this->data = []; foreach ($this->instances as $instance) { $instance->clearCalls(); } } public function lateCollect() { $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']); } /** * {@inheritdoc} */ public function getName() : string { return 'cache'; } /** * Method returns amount of logged Cache reads: "get" calls. */ public function getStatistics() : array { return $this->data['instances']['statistics']; } /** * Method returns the statistic totals. */ public function getTotals() : array { return $this->data['total']['statistics']; } /** * Method returns all logged Cache call objects. * * @return mixed */ public function getCalls() { return $this->data['instances']['calls']; } private function calculateStatistics() : array { $statistics = []; foreach ($this->data['instances']['calls'] as $name => $calls) { $statistics[$name] = ['calls' => 0, 'time' => 0, 'reads' => 0, 'writes' => 0, 'deletes' => 0, 'hits' => 0, 'misses' => 0]; /** @var TraceableAdapterEvent $call */ foreach ($calls as $call) { ++$statistics[$name]['calls']; $statistics[$name]['time'] += ($call->end ?? \microtime(\true)) - $call->start; if ('get' === $call->name) { ++$statistics[$name]['reads']; if ($call->hits) { ++$statistics[$name]['hits']; } else { ++$statistics[$name]['misses']; ++$statistics[$name]['writes']; } } elseif ('getItem' === $call->name) { ++$statistics[$name]['reads']; if ($call->hits) { ++$statistics[$name]['hits']; } else { ++$statistics[$name]['misses']; } } elseif ('getItems' === $call->name) { $statistics[$name]['reads'] += $call->hits + $call->misses; $statistics[$name]['hits'] += $call->hits; $statistics[$name]['misses'] += $call->misses; } elseif ('hasItem' === $call->name) { ++$statistics[$name]['reads']; foreach ($call->result ?? [] as $result) { ++$statistics[$name][$result ? 'hits' : 'misses']; } } elseif ('save' === $call->name) { ++$statistics[$name]['writes']; } elseif ('deleteItem' === $call->name) { ++$statistics[$name]['deletes']; } } if ($statistics[$name]['reads']) { $statistics[$name]['hit_read_ratio'] = \round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2); } else { $statistics[$name]['hit_read_ratio'] = null; } } return $statistics; } private function calculateTotalStatistics() : array { $statistics = $this->getStatistics(); $totals = ['calls' => 0, 'time' => 0, 'reads' => 0, 'writes' => 0, 'deletes' => 0, 'hits' => 0, 'misses' => 0]; foreach ($statistics as $name => $values) { foreach ($totals as $key => $value) { $totals[$key] += $statistics[$name][$key]; } } if ($totals['reads']) { $totals['hit_read_ratio'] = \round(100 * $totals['hits'] / $totals['reads'], 2); } else { $totals['hit_read_ratio'] = null; } return $totals; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Exception\LogicException; use _ContaoManager\Symfony\Contracts\Cache\ItemInterface; /** * @author Nicolas Grekas */ final class CacheItem implements ItemInterface { private const METADATA_EXPIRY_OFFSET = 1527506807; protected $key; protected $value; protected $isHit = \false; protected $expiry; protected $metadata = []; protected $newMetadata = []; protected $innerItem; protected $poolHash; protected $isTaggable = \false; /** * {@inheritdoc} */ public function getKey() : string { return $this->key; } /** * {@inheritdoc} * * @return mixed */ public function get() { return $this->value; } /** * {@inheritdoc} */ public function isHit() : bool { return $this->isHit; } /** * {@inheritdoc} * * @return $this */ public function set($value) : self { $this->value = $value; return $this; } /** * {@inheritdoc} * * @return $this */ public function expiresAt($expiration) : self { if (null === $expiration) { $this->expiry = null; } elseif ($expiration instanceof \DateTimeInterface) { $this->expiry = (float) $expiration->format('U.u'); } else { throw new InvalidArgumentException(\sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \get_debug_type($expiration))); } return $this; } /** * {@inheritdoc} * * @return $this */ public function expiresAfter($time) : self { if (null === $time) { $this->expiry = null; } elseif ($time instanceof \DateInterval) { $this->expiry = \microtime(\true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (\is_int($time)) { $this->expiry = $time + \microtime(\true); } else { throw new InvalidArgumentException(\sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \get_debug_type($time))); } return $this; } /** * {@inheritdoc} */ public function tag($tags) : ItemInterface { if (!$this->isTaggable) { throw new LogicException(\sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); } if (!\is_iterable($tags)) { $tags = [$tags]; } foreach ($tags as $tag) { if (!\is_string($tag) && !(\is_object($tag) && \method_exists($tag, '__toString'))) { throw new InvalidArgumentException(\sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag))); } $tag = (string) $tag; if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { continue; } if ('' === $tag) { throw new InvalidArgumentException('Cache tag length must be greater than zero.'); } if (\false !== \strpbrk($tag, self::RESERVED_CHARACTERS)) { throw new InvalidArgumentException(\sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); } $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; } return $this; } /** * {@inheritdoc} */ public function getMetadata() : array { return $this->metadata; } /** * Validates a cache key according to PSR-6. * * @param mixed $key The key to validate * * @throws InvalidArgumentException When $key is not valid */ public static function validateKey($key) : string { if (!\is_string($key)) { throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', \get_debug_type($key))); } if ('' === $key) { throw new InvalidArgumentException('Cache key length must be greater than zero.'); } if (\false !== \strpbrk($key, self::RESERVED_CHARACTERS)) { throw new InvalidArgumentException(\sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); } return $key; } /** * Internal logging helper. * * @internal */ public static function log(?LoggerInterface $logger, string $message, array $context = []) { if ($logger) { $logger->warning($message, $context); } else { $replace = []; foreach ($context as $k => $v) { if (\is_scalar($v)) { $replace['{' . $k . '}'] = $v; } } @\trigger_error(\strtr($message, $replace), \E_USER_WARNING); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache; use _ContaoManager\Psr\Cache\CacheException as Psr6CacheException; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Psr\SimpleCache\CacheException as SimpleCacheException; use _ContaoManager\Psr\SimpleCache\CacheInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Traits\ProxyTrait; if (null !== (new \ReflectionMethod(CacheInterface::class, 'get'))->getReturnType()) { throw new \LogicException('psr/simple-cache 3.0+ is not compatible with this version of symfony/cache. Please upgrade symfony/cache to 6.0+ or downgrade psr/simple-cache to 1.x or 2.x.'); } /** * Turns a PSR-6 cache into a PSR-16 one. * * @author Nicolas Grekas */ class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface { use ProxyTrait; private const METADATA_EXPIRY_OFFSET = 1527506807; private $createCacheItem; private $cacheItemPrototype; public function __construct(CacheItemPoolInterface $pool) { $this->pool = $pool; if (!$pool instanceof AdapterInterface) { return; } $cacheItemPrototype =& $this->cacheItemPrototype; $createCacheItem = \Closure::bind(static function ($key, $value, $allowInt = \false) use(&$cacheItemPrototype) { $item = clone $cacheItemPrototype; $item->poolHash = $item->innerItem = null; if ($allowInt && \is_int($key)) { $item->key = (string) $key; } else { \assert('' !== CacheItem::validateKey($key)); $item->key = $key; } $item->value = $value; $item->isHit = \false; return $item; }, null, CacheItem::class); $this->createCacheItem = function ($key, $value, $allowInt = \false) use($createCacheItem) { if (null === $this->cacheItemPrototype) { $this->get($allowInt && \is_int($key) ? (string) $key : $key); } $this->createCacheItem = $createCacheItem; return $createCacheItem($key, null, $allowInt)->set($value); }; } /** * {@inheritdoc} * * @return mixed */ public function get($key, $default = null) { try { $item = $this->pool->getItem($key); } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } if (null === $this->cacheItemPrototype) { $this->cacheItemPrototype = clone $item; $this->cacheItemPrototype->set(null); } return $item->isHit() ? $item->get() : $default; } /** * {@inheritdoc} * * @return bool */ public function set($key, $value, $ttl = null) { try { if (null !== ($f = $this->createCacheItem)) { $item = $f($key, $value); } else { $item = $this->pool->getItem($key)->set($value); } } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } if (null !== $ttl) { $item->expiresAfter($ttl); } return $this->pool->save($item); } /** * {@inheritdoc} * * @return bool */ public function delete($key) { try { return $this->pool->deleteItem($key); } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } } /** * {@inheritdoc} * * @return bool */ public function clear() { return $this->pool->clear(); } /** * {@inheritdoc} * * @return iterable */ public function getMultiple($keys, $default = null) { if ($keys instanceof \Traversable) { $keys = \iterator_to_array($keys, \false); } elseif (!\is_array($keys)) { throw new InvalidArgumentException(\sprintf('Cache keys must be array or Traversable, "%s" given.', \get_debug_type($keys))); } try { $items = $this->pool->getItems($keys); } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } $values = []; if (!$this->pool instanceof AdapterInterface) { foreach ($items as $key => $item) { $values[$key] = $item->isHit() ? $item->get() : $default; } return $values; } foreach ($items as $key => $item) { if (!$item->isHit()) { $values[$key] = $default; continue; } $values[$key] = $item->get(); if (!($metadata = $item->getMetadata())) { continue; } unset($metadata[CacheItem::METADATA_TAGS]); if ($metadata) { $values[$key] = ["\x9d" . \pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME]) . "_" => $values[$key]]; } } return $values; } /** * {@inheritdoc} * * @return bool */ public function setMultiple($values, $ttl = null) { $valuesIsArray = \is_array($values); if (!$valuesIsArray && !$values instanceof \Traversable) { throw new InvalidArgumentException(\sprintf('Cache values must be array or Traversable, "%s" given.', \get_debug_type($values))); } $items = []; try { if (null !== ($f = $this->createCacheItem)) { $valuesIsArray = \false; foreach ($values as $key => $value) { $items[$key] = $f($key, $value, \true); } } elseif ($valuesIsArray) { $items = []; foreach ($values as $key => $value) { $items[] = (string) $key; } $items = $this->pool->getItems($items); } else { foreach ($values as $key => $value) { if (\is_int($key)) { $key = (string) $key; } $items[$key] = $this->pool->getItem($key)->set($value); } } } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } $ok = \true; foreach ($items as $key => $item) { if ($valuesIsArray) { $item->set($values[$key]); } if (null !== $ttl) { $item->expiresAfter($ttl); } $ok = $this->pool->saveDeferred($item) && $ok; } return $this->pool->commit() && $ok; } /** * {@inheritdoc} * * @return bool */ public function deleteMultiple($keys) { if ($keys instanceof \Traversable) { $keys = \iterator_to_array($keys, \false); } elseif (!\is_array($keys)) { throw new InvalidArgumentException(\sprintf('Cache keys must be array or Traversable, "%s" given.', \get_debug_type($keys))); } try { return $this->pool->deleteItems($keys); } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } } /** * {@inheritdoc} * * @return bool */ public function has($key) { try { return $this->pool->hasItem($key); } catch (SimpleCacheException $e) { throw $e; } catch (Psr6CacheException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } } } Copyright (c) 2016-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache; /** * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. */ interface PruneableInterface { /** * @return bool */ public function prune(); } CHANGELOG ========= 5.4 --- * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL 5.3 --- * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension * add support for a custom serializer to the `ApcuAdapter` class 5.2.0 ----- * added integration with Messenger to allow computing cached values in a worker * allow ISO 8601 time intervals to specify default lifetime 5.1.0 ----- * added max-items + LRU + max-lifetime capabilities to `ArrayCache` * added `CouchbaseBucketAdapter` * added context `cache-adapter` to log messages 5.0.0 ----- * removed all PSR-16 implementations in the `Simple` namespace * removed `SimpleCacheAdapter` * removed `AbstractAdapter::unserialize()` * removed `CacheItem::getPreviousTags()` 4.4.0 ----- * added support for connecting to Redis Sentinel clusters * added argument `$prefix` to `AdapterInterface::clear()` * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter` * added `DeflateMarshaller` to compress serialized values * removed support for phpredis 4 `compression` * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead * Marked the `CacheDataCollector` class as `@final`. * added `SodiumMarshaller` to encrypt/decrypt values using libsodium 4.3.0 ----- * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead 4.2.0 ----- * added support for connecting to Redis clusters via DSN * added support for configuring multiple Memcached servers via DSN * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache * added sub-second expiry accuracy for backends that support it * added support for phpredis 4 `compression` and `tcp_keepalive` options * added automatic table creation when using Doctrine DBAL with PDO-based backends * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods * added `CacheCollectorPass` (originally in `FrameworkBundle`) * added `CachePoolClearerPass` (originally in `FrameworkBundle`) * added `CachePoolPass` (originally in `FrameworkBundle`) * added `CachePoolPrunerPass` (originally in `FrameworkBundle`) 3.4.0 ----- * added using options from Memcached DSN * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and ChainCache implement PruneableInterface and support manual stale cache pruning 3.3.0 ----- * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16) 3.2.0 ----- * added TagAwareAdapter for tags-based invalidation * added PdoAdapter with PDO and Doctrine DBAL support * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only) * added NullAdapter 3.1.0 ----- * added the component with strict PSR-6 implementations * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter * added AbstractAdapter, ChainAdapter and ProxyAdapter * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; /** * This file acts as a wrapper to the \RedisCluster implementation so it can accept the same type of calls as * individual \Redis objects. * * Calls are made to individual nodes via: RedisCluster->{method}($host, ...args)' * according to https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands * * @author Jack Thomas * * @internal */ class RedisClusterNodeProxy { private $host; private $redis; /** * @param \RedisCluster|RedisClusterProxy $redis */ public function __construct(array $host, $redis) { $this->host = $host; $this->redis = $redis; } public function __call(string $method, array $args) { return $this->redis->{$method}($this->host, ...$args); } public function scan(&$iIterator, $strPattern = null, $iCount = null) { return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount); } public function getOption($name) { return $this->redis->getOption($name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; use _ContaoManager\Predis\Command\Redis\UNLINK; use _ContaoManager\Predis\Connection\Aggregate\ClusterInterface; use _ContaoManager\Predis\Connection\Aggregate\RedisCluster; use _ContaoManager\Predis\Connection\Aggregate\ReplicationInterface; use _ContaoManager\Predis\Response\ErrorInterface; use _ContaoManager\Predis\Response\Status; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Aurimas Niekis * @author Nicolas Grekas * * @internal */ trait RedisTrait { private static $defaultConnectionOptions = ['class' => null, 'persistent' => 0, 'persistent_id' => null, 'timeout' => 30, 'read_timeout' => 0, 'retry_interval' => 0, 'tcp_keepalive' => 0, 'lazy' => null, 'redis_cluster' => \false, 'redis_sentinel' => null, 'dbindex' => 0, 'failover' => 'none', 'ssl' => null]; private $redis; private $marshaller; /** * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis */ private function init($redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) { parent::__construct($namespace, $defaultLifetime); if (\preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { throw new InvalidArgumentException(\sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); } if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \_ContaoManager\Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) { throw new InvalidArgumentException(\sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\\ClientInterface, "%s" given.', __METHOD__, \get_debug_type($redis))); } if ($redis instanceof \_ContaoManager\Predis\ClientInterface && $redis->getOptions()->exceptions) { $options = clone $redis->getOptions(); \Closure::bind(function () { $this->options['exceptions'] = \false; }, $options, $options)(); $redis = new $redis($redis->getConnection(), $options); } $this->redis = $redis; $this->marshaller = $marshaller ?? new DefaultMarshaller(); } /** * Creates a Redis connection using a DSN configuration. * * Example DSN: * - redis://localhost * - redis://example.com:1234 * - redis://secret@example.com/13 * - redis:///var/run/redis.sock * - redis://secret@/var/run/redis.sock/13 * * @param array $options See self::$defaultConnectionOptions * * @return \Redis|\RedisArray|\RedisCluster|RedisClusterProxy|RedisProxy|\Predis\ClientInterface According to the "class" option * * @throws InvalidArgumentException when the DSN is invalid */ public static function createConnection(string $dsn, array $options = []) { if (\str_starts_with($dsn, 'redis:')) { $scheme = 'redis'; } elseif (\str_starts_with($dsn, 'rediss:')) { $scheme = 'rediss'; } else { throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".'); } if (!\extension_loaded('redis') && !\class_exists(\_ContaoManager\Predis\Client::class)) { throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.'); } $params = \preg_replace_callback('#^' . $scheme . ':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use(&$auth) { if (isset($m[2])) { $auth = \rawurldecode($m[2]); if ('' === $auth) { $auth = null; } } return 'file:' . ($m[1] ?? ''); }, $dsn); if (\false === ($params = \parse_url($params))) { throw new InvalidArgumentException('Invalid Redis DSN.'); } $query = $hosts = []; $tls = 'rediss' === $scheme; $tcpScheme = $tls ? 'tls' : 'tcp'; if (isset($params['query'])) { \parse_str($params['query'], $query); if (isset($query['host'])) { if (!\is_array($hosts = $query['host'])) { throw new InvalidArgumentException('Invalid Redis DSN: query parameter "host" must be an array.'); } foreach ($hosts as $host => $parameters) { if (\is_string($parameters)) { \parse_str($parameters, $parameters); } if (\false === ($i = \strrpos($host, ':'))) { $hosts[$host] = ['scheme' => $tcpScheme, 'host' => $host, 'port' => 6379] + $parameters; } elseif ($port = (int) \substr($host, 1 + $i)) { $hosts[$host] = ['scheme' => $tcpScheme, 'host' => \substr($host, 0, $i), 'port' => $port] + $parameters; } else { $hosts[$host] = ['scheme' => 'unix', 'path' => \substr($host, 0, $i)] + $parameters; } } $hosts = \array_values($hosts); } } if (isset($params['host']) || isset($params['path'])) { if (!isset($params['dbindex']) && isset($params['path'])) { if (\preg_match('#/(\\d+)?$#', $params['path'], $m)) { $params['dbindex'] = $m[1] ?? '0'; $params['path'] = \substr($params['path'], 0, -\strlen($m[0])); } elseif (isset($params['host'])) { throw new InvalidArgumentException('Invalid Redis DSN: query parameter "dbindex" must be a number.'); } } if (isset($params['host'])) { \array_unshift($hosts, ['scheme' => $tcpScheme, 'host' => $params['host'], 'port' => $params['port'] ?? 6379]); } else { \array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]); } } if (!$hosts) { throw new InvalidArgumentException('Invalid Redis DSN: missing host.'); } $params += $query + $options + self::$defaultConnectionOptions; if (isset($params['redis_sentinel']) && !\class_exists(\_ContaoManager\Predis\Client::class) && !\class_exists(\RedisSentinel::class)) { throw new CacheException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.'); } if (isset($params['lazy'])) { $params['lazy'] = \filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); } $params['redis_cluster'] = \filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); } if (null === $params['class'] && \extension_loaded('redis')) { $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) && !isset($params['redis_sentinel']) ? \RedisArray::class : \Redis::class); } else { $class = $params['class'] ?? \_ContaoManager\Predis\Client::class; if (isset($params['redis_sentinel']) && !\is_a($class, \_ContaoManager\Predis\Client::class, \true) && !\class_exists(\RedisSentinel::class)) { throw new CacheException(\sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\\Client" and ext-redis >= 5.2 not found.', $class)); } } if (\is_a($class, \Redis::class, \true)) { $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; $redis = new $class(); $initializer = static function ($redis) use($connect, $params, $auth, $hosts, $tls) { $hostIndex = 0; do { $host = $hosts[$hostIndex]['host'] ?? $hosts[$hostIndex]['path']; $port = $hosts[$hostIndex]['port'] ?? 0; $passAuth = \defined('Redis::OPT_NULL_MULTIBULK_AS_NULL') && isset($params['auth']); $address = \false; if (isset($hosts[$hostIndex]['host']) && $tls) { $host = 'tls://' . $host; } if (!isset($params['redis_sentinel'])) { break; } if (\version_compare(\phpversion('redis'), '6.0.0', '>=')) { $options = ['host' => $host, 'port' => $port, 'connectTimeout' => $params['timeout'], 'persistent' => $params['persistent_id'], 'retryInterval' => $params['retry_interval'], 'readTimeout' => $params['read_timeout']]; if ($passAuth) { $options['auth'] = $params['auth']; } $sentinel = new \RedisSentinel($options); } else { $extra = $passAuth ? [$params['auth']] : []; $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); } try { if ($address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { [$host, $port] = $address; } } catch (\RedisException $e) { } } while (++$hostIndex < \count($hosts) && !$address); if (isset($params['redis_sentinel']) && !$address) { throw new InvalidArgumentException(\sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel'])); } try { $extra = ['stream' => $params['ssl'] ?? null]; if (isset($params['auth'])) { $extra['auth'] = $params['auth']; } @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [$extra] : []); \set_error_handler(function ($type, $msg) use(&$error) { $error = $msg; }); try { $isConnected = $redis->isConnected(); } finally { \restore_error_handler(); } if (!$isConnected) { $error = \preg_match('/^Redis::p?connect\\(\\): (.*)/', $error ?? $redis->getLastError() ?? '', $error) ? \sprintf(' (%s)', $error[1]) : ''; throw new InvalidArgumentException('Redis connection failed: ' . $error . '.'); } if (null !== $auth && !$redis->auth($auth) || $params['dbindex'] && !$redis->select($params['dbindex'])) { $e = \preg_replace('/^ERR /', '', $redis->getLastError()); throw new InvalidArgumentException('Redis connection failed: ' . $e . '.'); } if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } } catch (\RedisException $e) { throw new InvalidArgumentException('Redis connection failed: ' . $e->getMessage()); } return \true; }; if ($params['lazy']) { $redis = new RedisProxy($redis, $initializer); } else { $initializer($redis); } } elseif (\is_a($class, \RedisArray::class, \true)) { foreach ($hosts as $i => $host) { switch ($host['scheme']) { case 'tcp': $hosts[$i] = $host['host'] . ':' . $host['port']; break; case 'tls': $hosts[$i] = 'tls://' . $host['host'] . ':' . $host['port']; break; default: $hosts[$i] = $host['path']; } } $params['lazy_connect'] = $params['lazy'] ?? \true; $params['connect_timeout'] = $params['timeout']; try { $redis = new $class($hosts, $params); } catch (\RedisClusterException $e) { throw new InvalidArgumentException('Redis connection failed: ' . $e->getMessage()); } if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } } elseif (\is_a($class, \RedisCluster::class, \true)) { $initializer = static function () use($class, $params, $hosts) { foreach ($hosts as $i => $host) { switch ($host['scheme']) { case 'tcp': $hosts[$i] = $host['host'] . ':' . $host['port']; break; case 'tls': $hosts[$i] = 'tls://' . $host['host'] . ':' . $host['port']; break; default: $hosts[$i] = $host['path']; } } try { $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); } catch (\RedisClusterException $e) { throw new InvalidArgumentException('Redis connection failed: ' . $e->getMessage()); } if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } switch ($params['failover']) { case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break; case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break; case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break; } return $redis; }; $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer(); } elseif (\is_a($class, \_ContaoManager\Predis\ClientInterface::class, \true)) { if ($params['redis_cluster']) { $params['cluster'] = 'redis'; } elseif (isset($params['redis_sentinel'])) { $params['replication'] = 'sentinel'; $params['service'] = $params['redis_sentinel']; } $params += ['parameters' => []]; $params['parameters'] += ['persistent' => $params['persistent'], 'timeout' => $params['timeout'], 'read_write_timeout' => $params['read_timeout'], 'tcp_nodelay' => \true]; if ($params['dbindex']) { $params['parameters']['database'] = $params['dbindex']; } if (null !== $auth) { $params['parameters']['password'] = $auth; } if (isset($params['ssl'])) { foreach ($hosts as $i => $host) { if (!isset($host['ssl'])) { $hosts[$i]['ssl'] = $params['ssl']; } } } if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { $hosts = $hosts[0]; } elseif (\in_array($params['failover'], ['slaves', 'distribute'], \true) && !isset($params['replication'])) { $params['replication'] = \true; $hosts[0] += ['alias' => 'master']; } $params['exceptions'] = \false; $redis = new $class($hosts, \array_diff_key($params, self::$defaultConnectionOptions)); if (isset($params['redis_sentinel'])) { $redis->getConnection()->setSentinelTimeout($params['timeout']); } } elseif (\class_exists($class, \false)) { throw new InvalidArgumentException(\sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\\ClientInterface".', $class)); } else { throw new InvalidArgumentException(\sprintf('Class "%s" does not exist.', $class)); } return $redis; } /** * {@inheritdoc} */ protected function doFetch(array $ids) { if (!$ids) { return []; } $result = []; if ($this->redis instanceof \_ContaoManager\Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { $values = $this->pipeline(function () use($ids) { foreach ($ids as $id) { (yield 'get' => [$id]); } }); } else { $values = $this->redis->mget($ids); if (!\is_array($values) || \count($values) !== \count($ids)) { return []; } $values = \array_combine($ids, $values); } foreach ($values as $id => $v) { if ($v) { $result[$id] = $this->marshaller->unmarshall($v); } } return $result; } /** * {@inheritdoc} */ protected function doHave(string $id) { return (bool) $this->redis->exists($id); } /** * {@inheritdoc} */ protected function doClear(string $namespace) { if ($this->redis instanceof \_ContaoManager\Predis\ClientInterface) { $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; $prefixLen = \strlen($prefix ?? ''); } $cleared = \true; $hosts = $this->getHosts(); $host = \reset($hosts); if ($host instanceof \_ContaoManager\Predis\Client && $host->getConnection() instanceof ReplicationInterface) { // Predis supports info command only on the master in replication environments $hosts = [$host->getClientFor('master')]; } foreach ($hosts as $host) { if (!isset($namespace[0])) { $cleared = $host->flushDb() && $cleared; continue; } $info = $host->info('Server'); $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0']; if (!$host instanceof \_ContaoManager\Predis\ClientInterface) { $prefix = \defined('Redis::SCAN_PREFIX') && \Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN) ? '' : $host->getOption(\Redis::OPT_PREFIX); $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); } $pattern = $prefix . $namespace . '*'; if (!\version_compare($info['redis_version'], '2.8', '>=')) { // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS // can hang your server when it is executed against large databases (millions of items). // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above. $unlink = \version_compare($info['redis_version'], '4.0', '>=') ? 'UNLINK' : 'DEL'; $args = $this->redis instanceof \_ContaoManager\Predis\ClientInterface ? [0, $pattern] : [[$pattern], 0]; $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('{$unlink}',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared; continue; } $cursor = null; do { $keys = $host instanceof \_ContaoManager\Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $pattern, 'COUNT', 1000) : $host->scan($cursor, $pattern, 1000); if (isset($keys[1]) && \is_array($keys[1])) { $cursor = $keys[0]; $keys = $keys[1]; } if ($keys) { if ($prefixLen) { foreach ($keys as $i => $key) { $keys[$i] = \substr($key, $prefixLen); } } $this->doDelete($keys); } } while ($cursor = (int) $cursor); } return $cleared; } /** * {@inheritdoc} */ protected function doDelete(array $ids) { if (!$ids) { return \true; } if ($this->redis instanceof \_ContaoManager\Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { static $del; $del = $del ?? (\class_exists(UNLINK::class) ? 'unlink' : 'del'); $this->pipeline(function () use($ids, $del) { foreach ($ids as $id) { (yield $del => [$id]); } })->rewind(); } else { static $unlink = \true; if ($unlink) { try { $unlink = \false !== $this->redis->unlink($ids); } catch (\Throwable $e) { $unlink = \false; } } if (!$unlink) { $this->redis->del($ids); } } return \true; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { if (!($values = $this->marshaller->marshall($values, $failed))) { return $failed; } $results = $this->pipeline(function () use($values, $lifetime) { foreach ($values as $id => $value) { if (0 >= $lifetime) { (yield 'set' => [$id, $value]); } else { (yield 'setEx' => [$id, $lifetime, $value]); } } }); foreach ($results as $id => $result) { if (\true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { $failed[] = $id; } } return $failed; } private function pipeline(\Closure $generator, ?object $redis = null) : \Generator { $ids = []; $redis = $redis ?? $this->redis; if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || $redis instanceof \_ContaoManager\Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster) { // phpredis & predis don't support pipelining with RedisCluster // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 $results = []; foreach ($generator() as $command => $args) { $results[] = $redis->{$command}(...$args); $ids[] = 'eval' === $command ? $redis instanceof \_ContaoManager\Predis\ClientInterface ? $args[2] : $args[1][0] : $args[0]; } } elseif ($redis instanceof \_ContaoManager\Predis\ClientInterface) { $results = $redis->pipeline(static function ($redis) use($generator, &$ids) { foreach ($generator() as $command => $args) { $redis->{$command}(...$args); $ids[] = 'eval' === $command ? $args[2] : $args[0]; } }); } elseif ($redis instanceof \RedisArray) { $connections = $results = $ids = []; foreach ($generator() as $command => $args) { $id = 'eval' === $command ? $args[1][0] : $args[0]; if (!isset($connections[$h = $redis->_target($id)])) { $connections[$h] = [$redis->_instance($h), -1]; $connections[$h][0]->multi(\Redis::PIPELINE); } $connections[$h][0]->{$command}(...$args); $results[] = [$h, ++$connections[$h][1]]; $ids[] = $id; } foreach ($connections as $h => $c) { $connections[$h] = $c[0]->exec(); } foreach ($results as $k => [$h, $c]) { $results[$k] = $connections[$h][$c]; } } else { $redis->multi(\Redis::PIPELINE); foreach ($generator() as $command => $args) { $redis->{$command}(...$args); $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; } $results = $redis->exec(); } if (!$redis instanceof \_ContaoManager\Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { $e = new \RedisException($redis->getLastError()); $results = \array_map(function ($v) use($e) { return \false === $v ? $e : $v; }, (array) $results); } if (\is_bool($results)) { return; } foreach ($ids as $k => $id) { (yield $id => $results[$k]); } } private function getHosts() : array { $hosts = [$this->redis]; if ($this->redis instanceof \_ContaoManager\Predis\ClientInterface) { $connection = $this->redis->getConnection(); if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) { $hosts = []; foreach ($connection as $c) { $hosts[] = new \_ContaoManager\Predis\Client($c); } } } elseif ($this->redis instanceof \RedisArray) { $hosts = []; foreach ($this->redis->_hosts() as $host) { $hosts[] = $this->redis->_instance($host); } } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) { $hosts = []; foreach ($this->redis->_masters() as $host) { $hosts[] = new RedisClusterNodeProxy($host, $this->redis); } } return $hosts; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * @author Nicolas Grekas * * @internal */ trait ProxyTrait { private $pool; /** * {@inheritdoc} */ public function prune() { return $this->pool instanceof PruneableInterface && $this->pool->prune(); } /** * {@inheritdoc} */ public function reset() { if ($this->pool instanceof ResetInterface) { $this->pool->reset(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; /** * @author Nicolas Grekas * * @internal */ trait FilesystemCommonTrait { private $directory; private $tmp; private function init(string $namespace, ?string $directory) { if (!isset($directory[0])) { $directory = \sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'symfony-cache'; } else { $directory = \realpath($directory) ?: $directory; } if (isset($namespace[0])) { if (\preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); } $directory .= \DIRECTORY_SEPARATOR . $namespace; } else { $directory .= \DIRECTORY_SEPARATOR . '@'; } if (!\is_dir($directory)) { @\mkdir($directory, 0777, \true); } $directory .= \DIRECTORY_SEPARATOR; // On Windows the whole path is limited to 258 chars if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) { throw new InvalidArgumentException(\sprintf('Cache directory too long (%s).', $directory)); } $this->directory = $directory; } /** * {@inheritdoc} */ protected function doClear(string $namespace) { $ok = \true; foreach ($this->scanHashDir($this->directory) as $file) { if ('' !== $namespace && !\str_starts_with($this->getFileKey($file), $namespace)) { continue; } $ok = ($this->doUnlink($file) || !\file_exists($file)) && $ok; } return $ok; } /** * {@inheritdoc} */ protected function doDelete(array $ids) { $ok = \true; foreach ($ids as $id) { $file = $this->getFile($id); $ok = (!\is_file($file) || $this->doUnlink($file) || !\file_exists($file)) && $ok; } return $ok; } protected function doUnlink(string $file) { return @\unlink($file); } private function write(string $file, string $data, ?int $expiresAt = null) { $unlink = \false; \set_error_handler(__CLASS__ . '::throwError'); try { if (null === $this->tmp) { $this->tmp = $this->directory . \bin2hex(\random_bytes(6)); } try { $h = \fopen($this->tmp, 'x'); } catch (\ErrorException $e) { if (!\str_contains($e->getMessage(), 'File exists')) { throw $e; } $this->tmp = $this->directory . \bin2hex(\random_bytes(6)); $h = \fopen($this->tmp, 'x'); } \fwrite($h, $data); \fclose($h); $unlink = \true; if (null !== $expiresAt) { \touch($this->tmp, $expiresAt ?: \time() + 31556952); // 1 year in seconds } $success = \rename($this->tmp, $file); $unlink = !$success; return $success; } finally { \restore_error_handler(); if ($unlink) { @\unlink($this->tmp); } } } private function getFile(string $id, bool $mkdir = \false, ?string $directory = null) { // Use MD5 to favor speed over security, which is not an issue here $hash = \str_replace('/', '-', \base64_encode(\hash('md5', static::class . $id, \true))); $dir = ($directory ?? $this->directory) . \strtoupper($hash[0] . \DIRECTORY_SEPARATOR . $hash[1] . \DIRECTORY_SEPARATOR); if ($mkdir && !\is_dir($dir)) { @\mkdir($dir, 0777, \true); } return $dir . \substr($hash, 2, 20); } private function getFileKey(string $file) : string { return ''; } private function scanHashDir(string $directory) : \Generator { if (!\is_dir($directory)) { return; } $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; for ($i = 0; $i < 38; ++$i) { if (!\is_dir($directory . $chars[$i])) { continue; } for ($j = 0; $j < 38; ++$j) { if (!\is_dir($dir = $directory . $chars[$i] . \DIRECTORY_SEPARATOR . $chars[$j])) { continue; } foreach (@\scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) { if ('.' !== $file && '..' !== $file) { (yield $dir . \DIRECTORY_SEPARATOR . $file); } } } } } /** * @internal */ public static function throwError(int $type, string $message, string $file, int $line) { throw new \ErrorException($message, 0, $type, $file, $line); } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { if (\method_exists(parent::class, '__destruct')) { parent::__destruct(); } if (null !== $this->tmp && \is_file($this->tmp)) { \unlink($this->tmp); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; /** * @author Alessandro Chitolina * * @internal */ class RedisClusterProxy { private $redis; private $initializer; public function __construct(\Closure $initializer) { $this->initializer = $initializer; } public function __call(string $method, array $args) { $this->redis ?: ($this->redis = $this->initializer->__invoke()); return $this->redis->{$method}(...$args); } public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) { $this->redis ?: ($this->redis = $this->initializer->__invoke()); return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); } public function scan(&$iIterator, $strPattern = null, $iCount = null) { $this->redis ?: ($this->redis = $this->initializer->__invoke()); return $this->redis->scan($iIterator, $strPattern, $iCount); } public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) { $this->redis ?: ($this->redis = $this->initializer->__invoke()); return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); } public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) { $this->redis ?: ($this->redis = $this->initializer->__invoke()); return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; /** * @author Nicolas Grekas * * @internal */ class RedisProxy { private $redis; private $initializer; private $ready = \false; public function __construct(\Redis $redis, \Closure $initializer) { $this->redis = $redis; $this->initializer = $initializer; } public function __call(string $method, array $args) { $this->ready ?: ($this->ready = $this->initializer->__invoke($this->redis)); return $this->redis->{$method}(...$args); } public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) { $this->ready ?: ($this->ready = $this->initializer->__invoke($this->redis)); return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); } public function scan(&$iIterator, $strPattern = null, $iCount = null) { $this->ready ?: ($this->ready = $this->initializer->__invoke($this->redis)); return $this->redis->scan($iIterator, $strPattern, $iCount); } public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) { $this->ready ?: ($this->ready = $this->initializer->__invoke($this->redis)); return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); } public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) { $this->ready ?: ($this->ready = $this->initializer->__invoke($this->redis)); return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\LockRegistry; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; use _ContaoManager\Symfony\Contracts\Cache\CacheTrait; use _ContaoManager\Symfony\Contracts\Cache\ItemInterface; /** * @author Nicolas Grekas * * @internal */ trait ContractsTrait { use CacheTrait { doGet as private contractsGet; } private $callbackWrapper; private $computing = []; /** * Wraps the callback passed to ->get() in a callable. * * @return callable the previous callback wrapper */ public function setCallbackWrapper(?callable $callbackWrapper) : callable { if (!isset($this->callbackWrapper)) { $this->callbackWrapper = \Closure::fromCallable([LockRegistry::class, 'compute']); if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true)) { $this->setCallbackWrapper(null); } } $previousWrapper = $this->callbackWrapper; $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { return $callback($item, $save); }; return $previousWrapper; } private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null) { if (0 > ($beta = $beta ?? 1.0)) { throw new InvalidArgumentException(\sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); } static $setMetadata; $setMetadata ?? ($setMetadata = \Closure::bind(static function (CacheItem $item, float $startTime, ?array &$metadata) { if ($item->expiry > ($endTime = \microtime(\true))) { $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) \ceil(1000 * ($endTime - $startTime)); } else { unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]); } }, null, CacheItem::class)); return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use($pool, $callback, $setMetadata, &$metadata, $key) { // don't wrap nor save recursive calls if (isset($this->computing[$key])) { $value = $callback($item, $save); $save = \false; return $value; } $this->computing[$key] = $key; $startTime = \microtime(\true); if (!isset($this->callbackWrapper)) { $this->setCallbackWrapper($this->setCallbackWrapper(null)); } try { $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use($setMetadata, $startTime, &$metadata) { $setMetadata($item, $startTime, $metadata); }, $this->logger ?? null); $setMetadata($item, $startTime, $metadata); return $value; } finally { unset($this->computing[$key]); } }, $beta, $metadata, $this->logger ?? null); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; /** * @author Nicolas Grekas * @author Rob Frawley 2nd * * @internal */ trait FilesystemTrait { use FilesystemCommonTrait; private $marshaller; /** * @return bool */ public function prune() { $time = \time(); $pruned = \true; foreach ($this->scanHashDir($this->directory) as $file) { if (!($h = @\fopen($file, 'r'))) { continue; } if (($expiresAt = (int) \fgets($h)) && $time >= $expiresAt) { \fclose($h); $pruned = (@\unlink($file) || !\file_exists($file)) && $pruned; } else { \fclose($h); } } return $pruned; } /** * {@inheritdoc} */ protected function doFetch(array $ids) { $values = []; $now = \time(); foreach ($ids as $id) { $file = $this->getFile($id); if (!\is_file($file) || !($h = @\fopen($file, 'r'))) { continue; } if (($expiresAt = (int) \fgets($h)) && $now >= $expiresAt) { \fclose($h); @\unlink($file); } else { $i = \rawurldecode(\rtrim(\fgets($h))); $value = \stream_get_contents($h); \fclose($h); if ($i === $id) { $values[$id] = $this->marshaller->unmarshall($value); } } } return $values; } /** * {@inheritdoc} */ protected function doHave(string $id) { $file = $this->getFile($id); return \is_file($file) && (@\filemtime($file) > \time() || $this->doFetch([$id])); } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { $expiresAt = $lifetime ? \time() + $lifetime : 0; $values = $this->marshaller->marshall($values, $failed); foreach ($values as $id => $value) { if (!$this->write($this->getFile($id, \true), $expiresAt . "\n" . \rawurlencode($id) . "\n" . $value, $expiresAt)) { $failed[] = $id; } } if ($failed && !\is_writable($this->directory)) { throw new CacheException(\sprintf('Cache directory is not writable (%s).', $this->directory)); } return $failed; } private function getFileKey(string $file) : string { if (!($h = @\fopen($file, 'r'))) { return ''; } \fgets($h); // expiry $encodedKey = \fgets($h); \fclose($h); return \rawurldecode(\rtrim($encodedKey)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Traits; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Log\LoggerAwareTrait; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; /** * @author Nicolas Grekas * * @internal */ trait AbstractAdapterTrait { use LoggerAwareTrait; /** * @var \Closure needs to be set by class, signature is function(string , mixed , bool ) */ private static $createCacheItem; /** * @var \Closure needs to be set by class, signature is function(array , string , array <&expiredIds>) */ private static $mergeByLifetime; private $namespace = ''; private $defaultLifetime; private $namespaceVersion = ''; private $versioningIsEnabled = \false; private $deferred = []; private $ids = []; /** * @var int|null The maximum length to enforce for identifiers or null when no limit applies */ protected $maxIdLength; /** * Fetches several cache items. * * @param array $ids The cache identifiers to fetch * * @return array|\Traversable */ protected abstract function doFetch(array $ids); /** * Confirms if the cache contains specified cache item. * * @param string $id The identifier for which to check existence * * @return bool */ protected abstract function doHave(string $id); /** * Deletes all items in the pool. * * @param string $namespace The prefix used for all identifiers managed by this pool * * @return bool */ protected abstract function doClear(string $namespace); /** * Removes multiple items from the pool. * * @param array $ids An array of identifiers that should be removed from the pool * * @return bool */ protected abstract function doDelete(array $ids); /** * Persists several cache items immediately. * * @param array $values The values to cache, indexed by their cache identifier * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning * * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not */ protected abstract function doSave(array $values, int $lifetime); /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { $id = $this->getId($key); if (isset($this->deferred[$key])) { $this->commit(); } try { return $this->doHave($id); } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: ' . $e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); return \false; } } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { $this->deferred = []; if ($cleared = $this->versioningIsEnabled) { if ('' === ($namespaceVersionToClear = $this->namespaceVersion)) { foreach ($this->doFetch([static::NS_SEPARATOR . $this->namespace]) as $v) { $namespaceVersionToClear = $v; } } $namespaceToClear = $this->namespace . $namespaceVersionToClear; $namespaceVersion = self::formatNamespaceVersion(\mt_rand()); try { $e = $this->doSave([static::NS_SEPARATOR . $this->namespace => $namespaceVersion], 0); } catch (\Exception $e) { } if (\true !== $e && [] !== $e) { $cleared = \false; $message = 'Failed to save the new namespace' . ($e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => \get_debug_type($this)]); } else { $this->namespaceVersion = $namespaceVersion; $this->ids = []; } } else { $namespaceToClear = $this->namespace . $prefix; } try { return $this->doClear($namespaceToClear) || $cleared; } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to clear the cache: ' . $e->getMessage(), ['exception' => $e, 'cache-adapter' => \get_debug_type($this)]); return \false; } } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { return $this->deleteItems([$key]); } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { $ids = []; foreach ($keys as $key) { $ids[$key] = $this->getId($key); unset($this->deferred[$key]); } try { if ($this->doDelete($ids)) { return \true; } } catch (\Exception $e) { } $ok = \true; // When bulk-delete failed, retry each item individually foreach ($ids as $key => $id) { try { $e = null; if ($this->doDelete([$id])) { continue; } } catch (\Exception $e) { } $message = 'Failed to delete key "{key}"' . ($e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); $ok = \false; } return $ok; } /** * {@inheritdoc} */ public function getItem($key) { $id = $this->getId($key); if (isset($this->deferred[$key])) { $this->commit(); } $isHit = \false; $value = null; try { foreach ($this->doFetch([$id]) as $value) { $isHit = \true; } return (self::$createCacheItem)($key, $value, $isHit); } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to fetch key "{key}": ' . $e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); } return (self::$createCacheItem)($key, null, \false); } /** * {@inheritdoc} */ public function getItems(array $keys = []) { $ids = []; $commit = \false; foreach ($keys as $key) { $ids[] = $this->getId($key); $commit = $commit || isset($this->deferred[$key]); } if ($commit) { $this->commit(); } try { $items = $this->doFetch($ids); } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to fetch items: ' . $e->getMessage(), ['keys' => $keys, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); $items = []; } $ids = \array_combine($ids, $keys); return $this->generateItems($items, $ids); } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { if (!$item instanceof CacheItem) { return \false; } $this->deferred[$item->getKey()] = $item; return $this->commit(); } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { if (!$item instanceof CacheItem) { return \false; } $this->deferred[$item->getKey()] = $item; return \true; } /** * Enables/disables versioning of items. * * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed, * but old keys may need garbage collection and extra round-trips to the back-end are required. * * Calling this method also clears the memoized namespace version and thus forces a resynchronization of it. * * @return bool the previous state of versioning */ public function enableVersioning(bool $enable = \true) { $wasEnabled = $this->versioningIsEnabled; $this->versioningIsEnabled = $enable; $this->namespaceVersion = ''; $this->ids = []; return $wasEnabled; } /** * {@inheritdoc} */ public function reset() { if ($this->deferred) { $this->commit(); } $this->namespaceVersion = ''; $this->ids = []; } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { if ($this->deferred) { $this->commit(); } } private function generateItems(iterable $items, array &$keys) : \Generator { $f = self::$createCacheItem; try { foreach ($items as $id => $value) { if (!isset($keys[$id])) { throw new InvalidArgumentException(\sprintf('Could not match value id "%s" to keys "%s".', $id, \implode('", "', $keys))); } $key = $keys[$id]; unset($keys[$id]); (yield $key => $f($key, $value, \true)); } } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to fetch items: ' . $e->getMessage(), ['keys' => \array_values($keys), 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); } foreach ($keys as $key) { (yield $key => $f($key, null, \false)); } } /** * @internal */ protected function getId($key) { if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { $this->ids = []; $this->namespaceVersion = '1' . static::NS_SEPARATOR; try { foreach ($this->doFetch([static::NS_SEPARATOR . $this->namespace]) as $v) { $this->namespaceVersion = $v; } $e = \true; if ('1' . static::NS_SEPARATOR === $this->namespaceVersion) { $this->namespaceVersion = self::formatNamespaceVersion(\time()); $e = $this->doSave([static::NS_SEPARATOR . $this->namespace => $this->namespaceVersion], 0); } } catch (\Exception $e) { } if (\true !== $e && [] !== $e) { $message = 'Failed to save the new namespace' . ($e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => \get_debug_type($this)]); } } if (\is_string($key) && isset($this->ids[$key])) { return $this->namespace . $this->namespaceVersion . $this->ids[$key]; } \assert('' !== CacheItem::validateKey($key)); $this->ids[$key] = $key; if (\count($this->ids) > 1000) { $this->ids = \array_slice($this->ids, 500, null, \true); // stop memory leak if there are many keys } if (null === $this->maxIdLength) { return $this->namespace . $this->namespaceVersion . $key; } if (\strlen($id = $this->namespace . $this->namespaceVersion . $key) > $this->maxIdLength) { // Use MD5 to favor speed over security, which is not an issue here $this->ids[$key] = $id = \substr_replace(\base64_encode(\hash('md5', $key, \true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); $id = $this->namespace . $this->namespaceVersion . $id; } return $id; } /** * @internal */ public static function handleUnserializeCallback(string $class) { throw new \DomainException('Class not found: ' . $class); } private static function formatNamespaceVersion(int $value) : string { return \strtr(\substr_replace(\base64_encode(\pack('V', $value)), static::NS_SEPARATOR, 5), '/', '_'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache; use _ContaoManager\Doctrine\Common\Cache\CacheProvider; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; if (!\class_exists(CacheProvider::class)) { return; } /** * @author Nicolas Grekas * * @deprecated Use Doctrine\Common\Cache\Psr6\DoctrineProvider instead */ class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface { private $pool; public function __construct(CacheItemPoolInterface $pool) { \trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "Doctrine\\Common\\Cache\\Psr6\\DoctrineProvider" instead.', __CLASS__); $this->pool = $pool; } /** * {@inheritdoc} */ public function prune() { return $this->pool instanceof PruneableInterface && $this->pool->prune(); } /** * {@inheritdoc} */ public function reset() { if ($this->pool instanceof ResetInterface) { $this->pool->reset(); } $this->setNamespace($this->getNamespace()); } /** * {@inheritdoc} * * @return mixed */ protected function doFetch($id) { $item = $this->pool->getItem(\rawurlencode($id)); return $item->isHit() ? $item->get() : \false; } /** * {@inheritdoc} * * @return bool */ protected function doContains($id) { return $this->pool->hasItem(\rawurlencode($id)); } /** * {@inheritdoc} * * @return bool */ protected function doSave($id, $data, $lifeTime = 0) { $item = $this->pool->getItem(\rawurlencode($id)); if (0 < $lifeTime) { $item->expiresAfter($lifeTime); } return $this->pool->save($item->set($data)); } /** * {@inheritdoc} * * @return bool */ protected function doDelete($id) { return $this->pool->deleteItem(\rawurlencode($id)); } /** * {@inheritdoc} * * @return bool */ protected function doFlush() { return $this->pool->clear(); } /** * {@inheritdoc} * * @return array|null */ protected function doGetStats() { return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\ContractsTrait; use _ContaoManager\Symfony\Component\Cache\Traits\ProxyTrait; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; /** * @author Nicolas Grekas */ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use ContractsTrait; use ProxyTrait; private $namespace = ''; private $namespaceLen; private $poolHash; private $defaultLifetime; private static $createCacheItem; private static $setInnerItem; public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) { $this->pool = $pool; $this->poolHash = $poolHash = \spl_object_hash($pool); if ('' !== $namespace) { \assert('' !== CacheItem::validateKey($namespace)); $this->namespace = $namespace; } $this->namespaceLen = \strlen($namespace); $this->defaultLifetime = $defaultLifetime; self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key, $innerItem, $poolHash) { $item = new CacheItem(); $item->key = $key; if (null === $innerItem) { return $item; } $item->value = $v = $innerItem->get(); $item->isHit = $innerItem->isHit(); $item->innerItem = $innerItem; $item->poolHash = $poolHash; // Detect wrapped values that encode for their expiry and creation duration // For compactness, these values are packed in the key of an array using // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) \array_key_first($v)) && "\x9d" === $k[0] && "\x00" === $k[5] && "_" === $k[9]) { $item->value = $v[$k]; $v = \unpack('Ve/Nc', \substr($k, 1, -1)); $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; } elseif ($innerItem instanceof CacheItem) { $item->metadata = $innerItem->metadata; } $innerItem->set(null); return $item; }, null, CacheItem::class)); self::$setInnerItem ?? (self::$setInnerItem = \Closure::bind( /** * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix */ static function (CacheItemInterface $innerItem, array $item) { // Tags are stored separately, no need to account for them when considering this item's newly set metadata if (isset(($metadata = $item["\x00*\x00newMetadata"])[CacheItem::METADATA_TAGS])) { unset($metadata[CacheItem::METADATA_TAGS]); } if ($metadata) { // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators $item["\x00*\x00value"] = ["\x9d" . \pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]) . "_" => $item["\x00*\x00value"]]; } $innerItem->set($item["\x00*\x00value"]); $innerItem->expiresAt(null !== $item["\x00*\x00expiry"] ? \DateTime::createFromFormat('U.u', \sprintf('%.6F', $item["\x00*\x00expiry"])) : null); }, null, CacheItem::class )); } /** * {@inheritdoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { if (!$this->pool instanceof CacheInterface) { return $this->doGet($this, $key, $callback, $beta, $metadata); } return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use($key, $callback) { $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash); $item->set($value = $callback($item, $save)); (self::$setInnerItem)($innerItem, (array) $item); return $value; }, $beta, $metadata); } /** * {@inheritdoc} */ public function getItem($key) { $item = $this->pool->getItem($this->getId($key)); return (self::$createCacheItem)($key, $item, $this->poolHash); } /** * {@inheritdoc} */ public function getItems(array $keys = []) { if ($this->namespaceLen) { foreach ($keys as $i => $key) { $keys[$i] = $this->getId($key); } } return $this->generateItems($this->pool->getItems($keys)); } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { return $this->pool->hasItem($this->getId($key)); } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { if ($this->pool instanceof AdapterInterface) { return $this->pool->clear($this->namespace . $prefix); } return $this->pool->clear(); } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { return $this->pool->deleteItem($this->getId($key)); } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { if ($this->namespaceLen) { foreach ($keys as $i => $key) { $keys[$i] = $this->getId($key); } } return $this->pool->deleteItems($keys); } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { return $this->doSave($item, __FUNCTION__); } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { return $this->doSave($item, __FUNCTION__); } /** * {@inheritdoc} * * @return bool */ public function commit() { return $this->pool->commit(); } private function doSave(CacheItemInterface $item, string $method) { if (!$item instanceof CacheItem) { return \false; } $item = (array) $item; if (null === $item["\x00*\x00expiry"] && 0 < $this->defaultLifetime) { $item["\x00*\x00expiry"] = \microtime(\true) + $this->defaultLifetime; } if ($item["\x00*\x00poolHash"] === $this->poolHash && $item["\x00*\x00innerItem"]) { $innerItem = $item["\x00*\x00innerItem"]; } elseif ($this->pool instanceof AdapterInterface) { // this is an optimization specific for AdapterInterface implementations // so we can save a round-trip to the backend by just creating a new item $innerItem = (self::$createCacheItem)($this->namespace . $item["\x00*\x00key"], null, $this->poolHash); } else { $innerItem = $this->pool->getItem($this->namespace . $item["\x00*\x00key"]); } (self::$setInnerItem)($innerItem, $item); return $this->pool->{$method}($innerItem); } private function generateItems(iterable $items) : \Generator { $f = self::$createCacheItem; foreach ($items as $key => $item) { if ($this->namespaceLen) { $key = \substr($key, $this->namespaceLen); } (yield $key => $f($key, $item, $this->poolHash)); } } private function getId($key) : string { \assert('' !== CacheItem::validateKey($key)); return $this->namespace . $key; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Nicolas Grekas */ class ApcuAdapter extends AbstractAdapter { private $marshaller; /** * @throws CacheException if APCu is not enabled */ public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $version = null, ?MarshallerInterface $marshaller = null) { if (!static::isSupported()) { throw new CacheException('APCu is not enabled.'); } if ('cli' === \PHP_SAPI) { \ini_set('apc.use_request_time', 0); } parent::__construct($namespace, $defaultLifetime); if (null !== $version) { CacheItem::validateKey($version); if (!\apcu_exists($version . '@' . $namespace)) { $this->doClear($namespace); \apcu_add($version . '@' . $namespace, null); } } $this->marshaller = $marshaller; } public static function isSupported() { return \function_exists('apcu_fetch') && \filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN); } /** * {@inheritdoc} */ protected function doFetch(array $ids) { $unserializeCallbackHandler = \ini_set('unserialize_callback_func', __CLASS__ . '::handleUnserializeCallback'); try { $values = []; $ids = \array_flip($ids); foreach (\apcu_fetch(\array_keys($ids), $ok) ?: [] as $k => $v) { if (!isset($ids[$k])) { // work around https://github.com/krakjoe/apcu/issues/247 $k = \key($ids); } unset($ids[$k]); if (null !== $v || $ok) { $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v; } } return $values; } catch (\Error $e) { throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); } finally { \ini_set('unserialize_callback_func', $unserializeCallbackHandler); } } /** * {@inheritdoc} */ protected function doHave(string $id) { return \apcu_exists($id); } /** * {@inheritdoc} */ protected function doClear(string $namespace) { return isset($namespace[0]) && \class_exists(\APCUIterator::class, \false) && ('cli' !== \PHP_SAPI || \filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) ? \apcu_delete(new \APCUIterator(\sprintf('/^%s/', \preg_quote($namespace, '/')), \APC_ITER_KEY)) : \apcu_clear_cache(); } /** * {@inheritdoc} */ protected function doDelete(array $ids) { foreach ($ids as $id) { \apcu_delete($id); } return \true; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { if (null !== $this->marshaller && !($values = $this->marshaller->marshall($values, $failed))) { return $failed; } try { if (\false === ($failures = \apcu_store($values, null, $lifetime))) { $failures = $values; } return \array_keys($failures); } catch (\Throwable $e) { if (1 === \count($values)) { // Workaround https://github.com/krakjoe/apcu/issues/170 \apcu_delete(\array_key_first($values)); } throw $e; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use Couchbase\Bucket; use Couchbase\Cluster; use Couchbase\ClusterOptions; use Couchbase\Collection; use Couchbase\DocumentNotFoundException; use Couchbase\UpsertOptions; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Antonio Jose Cerezo Aranda */ class CouchbaseCollectionAdapter extends AbstractAdapter { private const MAX_KEY_LENGTH = 250; /** @var Collection */ private $connection; private $marshaller; public function __construct(Collection $connection, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { if (!static::isSupported()) { throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.'); } $this->maxIdLength = static::MAX_KEY_LENGTH; $this->connection = $connection; parent::__construct($namespace, $defaultLifetime); $this->enableVersioning(); $this->marshaller = $marshaller ?? new DefaultMarshaller(); } /** * @param array|string $dsn * * @return Bucket|Collection */ public static function createConnection($dsn, array $options = []) { if (\is_string($dsn)) { $dsn = [$dsn]; } elseif (!\is_array($dsn)) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, \get_debug_type($dsn))); } if (!static::isSupported()) { throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.'); } \set_error_handler(function ($type, $msg, $file, $line) : bool { throw new \ErrorException($msg, 0, $type, $file, $line); }); $dsnPattern = '/^(?couchbase(?:s)?)\\:\\/\\/(?:(?[^\\:]+)\\:(?[^\\@]{6,})@)?' . '(?[^\\:]+(?:\\:\\d+)?)(?:\\/(?[^\\/\\?]+))(?:(?:\\/(?[^\\/]+))' . '(?:\\/(?[^\\/\\?]+)))?(?:\\/)?(?:\\?(?.*))?$/i'; $newServers = []; $protocol = 'couchbase'; try { $username = $options['username'] ?? ''; $password = $options['password'] ?? ''; foreach ($dsn as $server) { if (0 !== \strpos($server, 'couchbase:')) { throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); } \preg_match($dsnPattern, $server, $matches); $username = $matches['username'] ?: $username; $password = $matches['password'] ?: $password; $protocol = $matches['protocol'] ?: $protocol; if (isset($matches['options'])) { $optionsInDsn = self::getOptions($matches['options']); foreach ($optionsInDsn as $parameter => $value) { $options[$parameter] = $value; } } $newServers[] = $matches['host']; } $option = isset($matches['options']) ? '?' . $matches['options'] : ''; $connectionString = $protocol . '://' . \implode(',', $newServers) . $option; $clusterOptions = new ClusterOptions(); $clusterOptions->credentials($username, $password); $client = new Cluster($connectionString, $clusterOptions); $bucket = $client->bucket($matches['bucketName']); $collection = $bucket->defaultCollection(); if (!empty($matches['scopeName'])) { $scope = $bucket->scope($matches['scopeName']); $collection = $scope->collection($matches['collectionName']); } return $collection; } finally { \restore_error_handler(); } } public static function isSupported() : bool { return \extension_loaded('couchbase') && \version_compare(\phpversion('couchbase'), '3.0.5', '>=') && \version_compare(\phpversion('couchbase'), '4.0', '<'); } private static function getOptions(string $options) : array { $results = []; $optionsInArray = \explode('&', $options); foreach ($optionsInArray as $option) { [$key, $value] = \explode('=', $option); $results[$key] = $value; } return $results; } /** * {@inheritdoc} */ protected function doFetch(array $ids) : array { $results = []; foreach ($ids as $id) { try { $resultCouchbase = $this->connection->get($id); } catch (DocumentNotFoundException $exception) { continue; } $content = $resultCouchbase->value ?? $resultCouchbase->content(); $results[$id] = $this->marshaller->unmarshall($content); } return $results; } /** * {@inheritdoc} */ protected function doHave($id) : bool { return $this->connection->exists($id)->exists(); } /** * {@inheritdoc} */ protected function doClear($namespace) : bool { return \false; } /** * {@inheritdoc} */ protected function doDelete(array $ids) : bool { $idsErrors = []; foreach ($ids as $id) { try { $result = $this->connection->remove($id); if (null === $result->mutationToken()) { $idsErrors[] = $id; } } catch (DocumentNotFoundException $exception) { } } return 0 === \count($idsErrors); } /** * {@inheritdoc} */ protected function doSave(array $values, $lifetime) { if (!($values = $this->marshaller->marshall($values, $failed))) { return $failed; } $upsertOptions = new UpsertOptions(); $upsertOptions->expiry($lifetime); $ko = []; foreach ($values as $key => $value) { try { $this->connection->upsert($key, $value, $upsertOptions); } catch (\Exception $exception) { $ko[$key] = ''; } } return [] === $ko ? \true : $ko; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * An adapter that collects data about all cache calls. * * @author Aaron Scherer * @author Tobias Nyholm * @author Nicolas Grekas */ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { protected $pool; private $calls = []; public function __construct(AdapterInterface $pool) { $this->pool = $pool; } /** * {@inheritdoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { if (!$this->pool instanceof CacheInterface) { throw new \BadMethodCallException(\sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_debug_type($this->pool), CacheInterface::class)); } $isHit = \true; $callback = function (CacheItem $item, bool &$save) use($callback, &$isHit) { $isHit = $item->isHit(); return $callback($item, $save); }; $event = $this->start(__FUNCTION__); try { $value = $this->pool->get($key, $callback, $beta, $metadata); $event->result[$key] = \get_debug_type($value); } finally { $event->end = \microtime(\true); } if ($isHit) { ++$event->hits; } else { ++$event->misses; } return $value; } /** * {@inheritdoc} */ public function getItem($key) { $event = $this->start(__FUNCTION__); try { $item = $this->pool->getItem($key); } finally { $event->end = \microtime(\true); } if ($event->result[$key] = $item->isHit()) { ++$event->hits; } else { ++$event->misses; } return $item; } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->hasItem($key); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->deleteItem($key); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { $event = $this->start(__FUNCTION__); try { return $event->result[$item->getKey()] = $this->pool->save($item); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { $event = $this->start(__FUNCTION__); try { return $event->result[$item->getKey()] = $this->pool->saveDeferred($item); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} */ public function getItems(array $keys = []) { $event = $this->start(__FUNCTION__); try { $result = $this->pool->getItems($keys); } finally { $event->end = \microtime(\true); } $f = function () use($result, $event) { $event->result = []; foreach ($result as $key => $item) { if ($event->result[$key] = $item->isHit()) { ++$event->hits; } else { ++$event->misses; } (yield $key => $item); } }; return $f(); } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { $event = $this->start(__FUNCTION__); try { if ($this->pool instanceof AdapterInterface) { return $event->result = $this->pool->clear($prefix); } return $event->result = $this->pool->clear(); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { $event = $this->start(__FUNCTION__); $event->result['keys'] = $keys; try { return $event->result['result'] = $this->pool->deleteItems($keys); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} * * @return bool */ public function commit() { $event = $this->start(__FUNCTION__); try { return $event->result = $this->pool->commit(); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} */ public function prune() { if (!$this->pool instanceof PruneableInterface) { return \false; } $event = $this->start(__FUNCTION__); try { return $event->result = $this->pool->prune(); } finally { $event->end = \microtime(\true); } } /** * {@inheritdoc} */ public function reset() { if ($this->pool instanceof ResetInterface) { $this->pool->reset(); } $this->clearCalls(); } /** * {@inheritdoc} */ public function delete(string $key) : bool { $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->deleteItem($key); } finally { $event->end = \microtime(\true); } } public function getCalls() { return $this->calls; } public function clearCalls() { $this->calls = []; } protected function start(string $name) { $this->calls[] = $event = new TraceableAdapterEvent(); $event->name = $name; $event->start = \microtime(\true); return $event; } } class TraceableAdapterEvent { public $name; public $start; public $end; public $result; public $hits = 0; public $misses = 0; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Doctrine\DBAL\Connection; use _ContaoManager\Doctrine\DBAL\Schema\Schema; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; class PdoAdapter extends AbstractAdapter implements PruneableInterface { protected $maxIdLength = 255; private $marshaller; private $conn; private $dsn; private $driver; private $serverVersion; private $table = 'cache_items'; private $idCol = 'item_id'; private $dataCol = 'item_data'; private $lifetimeCol = 'item_lifetime'; private $timeCol = 'item_time'; private $username = null; private $password = null; private $connectionOptions = []; private $namespace; private $dbalAdapter; /** * You can either pass an existing database connection as PDO instance or * a DSN string that will be used to lazy-connect to the database when the * cache is actually used. * * List of available options: * * db_table: The name of the table [default: cache_items] * * db_id_col: The column where to store the cache id [default: item_id] * * db_data_col: The column where to store the cache data [default: item_data] * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] * * db_time_col: The column where to store the timestamp [default: item_time] * * db_username: The username when lazy-connect [default: ''] * * db_password: The password when lazy-connect [default: ''] * * db_connection_options: An array of driver-specific connection options [default: []] * * @param \PDO|string $connOrDsn * * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION * @throws InvalidArgumentException When namespace contains invalid characters */ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], ?MarshallerInterface $marshaller = null) { if ($connOrDsn instanceof Connection || \is_string($connOrDsn) && \str_contains($connOrDsn, '://')) { \trigger_deprecation('symfony/cache', '5.4', 'Usage of a DBAL Connection with "%s" is deprecated and will be removed in symfony 6.0. Use "%s" instead.', __CLASS__, DoctrineDbalAdapter::class); $this->dbalAdapter = new DoctrineDbalAdapter($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller); return; } if (isset($namespace[0]) && \preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); } if ($connOrDsn instanceof \PDO) { if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { throw new InvalidArgumentException(\sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); } $this->conn = $connOrDsn; } elseif (\is_string($connOrDsn)) { $this->dsn = $connOrDsn; } else { throw new InvalidArgumentException(\sprintf('"%s" requires PDO or Doctrine\\DBAL\\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \get_debug_type($connOrDsn))); } $this->table = $options['db_table'] ?? $this->table; $this->idCol = $options['db_id_col'] ?? $this->idCol; $this->dataCol = $options['db_data_col'] ?? $this->dataCol; $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; $this->timeCol = $options['db_time_col'] ?? $this->timeCol; $this->username = $options['db_username'] ?? $this->username; $this->password = $options['db_password'] ?? $this->password; $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; $this->namespace = $namespace; $this->marshaller = $marshaller ?? new DefaultMarshaller(); parent::__construct($namespace, $defaultLifetime); } /** * {@inheritDoc} */ public function getItem($key) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->getItem($key); } return parent::getItem($key); } /** * {@inheritDoc} */ public function getItems(array $keys = []) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->getItems($keys); } return parent::getItems($keys); } /** * {@inheritDoc} */ public function hasItem($key) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->hasItem($key); } return parent::hasItem($key); } /** * {@inheritDoc} */ public function deleteItem($key) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->deleteItem($key); } return parent::deleteItem($key); } /** * {@inheritDoc} */ public function deleteItems(array $keys) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->deleteItems($keys); } return parent::deleteItems($keys); } /** * {@inheritDoc} */ public function clear(string $prefix = '') { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->clear($prefix); } return parent::clear($prefix); } /** * {@inheritDoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->get($key, $callback, $beta, $metadata); } return parent::get($key, $callback, $beta, $metadata); } /** * {@inheritDoc} */ public function delete(string $key) : bool { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->delete($key); } return parent::delete($key); } /** * {@inheritDoc} */ public function save(CacheItemInterface $item) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->save($item); } return parent::save($item); } /** * {@inheritDoc} */ public function saveDeferred(CacheItemInterface $item) { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->saveDeferred($item); } return parent::saveDeferred($item); } /** * {@inheritDoc} */ public function setLogger(LoggerInterface $logger) : void { if (isset($this->dbalAdapter)) { $this->dbalAdapter->setLogger($logger); return; } parent::setLogger($logger); } /** * {@inheritDoc} */ public function commit() { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->commit(); } return parent::commit(); } /** * {@inheritDoc} */ public function reset() { if (isset($this->dbalAdapter)) { $this->dbalAdapter->reset(); return; } parent::reset(); } /** * Creates the table to store cache items which can be called once for setup. * * Cache ID are saved in a column of maximum length 255. Cache data is * saved in a BLOB. * * @throws \PDOException When the table already exists * @throws \DomainException When an unsupported PDO driver is used */ public function createTable() { if (isset($this->dbalAdapter)) { $this->dbalAdapter->createTable(); return; } // connect if we are not yet $conn = $this->getConnection(); switch ($this->driver) { case 'mysql': // We use varbinary for the ID column because it prevents unwanted conversions: // - character set conversions between server and client // - trailing space removal // - case-insensitivity // - language processing like é == e $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARBINARY(255) NOT NULL PRIMARY KEY, {$this->dataCol} MEDIUMBLOB NOT NULL, {$this->lifetimeCol} INTEGER UNSIGNED, {$this->timeCol} INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; break; case 'sqlite': $sql = "CREATE TABLE {$this->table} ({$this->idCol} TEXT NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER, {$this->timeCol} INTEGER NOT NULL)"; break; case 'pgsql': $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR(255) NOT NULL PRIMARY KEY, {$this->dataCol} BYTEA NOT NULL, {$this->lifetimeCol} INTEGER, {$this->timeCol} INTEGER NOT NULL)"; break; case 'oci': $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR2(255) NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER, {$this->timeCol} INTEGER NOT NULL)"; break; case 'sqlsrv': $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR(255) NOT NULL PRIMARY KEY, {$this->dataCol} VARBINARY(MAX) NOT NULL, {$this->lifetimeCol} INTEGER, {$this->timeCol} INTEGER NOT NULL)"; break; default: throw new \DomainException(\sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver)); } $conn->exec($sql); } /** * Adds the Table to the Schema if the adapter uses this Connection. * * @deprecated since symfony/cache 5.4 use DoctrineDbalAdapter instead */ public function configureSchema(Schema $schema, Connection $forConnection) : void { if (isset($this->dbalAdapter)) { $this->dbalAdapter->configureSchema($schema, $forConnection); } } /** * {@inheritdoc} */ public function prune() { if (isset($this->dbalAdapter)) { return $this->dbalAdapter->prune(); } $deleteSql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} + {$this->timeCol} <= :time"; if ('' !== $this->namespace) { $deleteSql .= " AND {$this->idCol} LIKE :namespace"; } $connection = $this->getConnection(); try { $delete = $connection->prepare($deleteSql); } catch (\PDOException $e) { return \true; } $delete->bindValue(':time', \time(), \PDO::PARAM_INT); if ('' !== $this->namespace) { $delete->bindValue(':namespace', \sprintf('%s%%', $this->namespace), \PDO::PARAM_STR); } try { return $delete->execute(); } catch (\PDOException $e) { return \true; } } /** * {@inheritdoc} */ protected function doFetch(array $ids) { $connection = $this->getConnection(); $now = \time(); $expired = []; $sql = \str_pad('', (\count($ids) << 1) - 1, '?,'); $sql = "SELECT {$this->idCol}, CASE WHEN {$this->lifetimeCol} IS NULL OR {$this->lifetimeCol} + {$this->timeCol} > ? THEN {$this->dataCol} ELSE NULL END FROM {$this->table} WHERE {$this->idCol} IN ({$sql})"; $stmt = $connection->prepare($sql); $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); foreach ($ids as $id) { $stmt->bindValue(++$i, $id); } $result = $stmt->execute(); if (\is_object($result)) { $result = $result->iterateNumeric(); } else { $stmt->setFetchMode(\PDO::FETCH_NUM); $result = $stmt; } foreach ($result as $row) { if (null === $row[1]) { $expired[] = $row[0]; } else { (yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? \stream_get_contents($row[1]) : $row[1])); } } if ($expired) { $sql = \str_pad('', (\count($expired) << 1) - 1, '?,'); $sql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} + {$this->timeCol} <= ? AND {$this->idCol} IN ({$sql})"; $stmt = $connection->prepare($sql); $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); foreach ($expired as $id) { $stmt->bindValue(++$i, $id); } $stmt->execute(); } } /** * {@inheritdoc} */ protected function doHave(string $id) { $connection = $this->getConnection(); $sql = "SELECT 1 FROM {$this->table} WHERE {$this->idCol} = :id AND ({$this->lifetimeCol} IS NULL OR {$this->lifetimeCol} + {$this->timeCol} > :time)"; $stmt = $connection->prepare($sql); $stmt->bindValue(':id', $id); $stmt->bindValue(':time', \time(), \PDO::PARAM_INT); $stmt->execute(); return (bool) $stmt->fetchColumn(); } /** * {@inheritdoc} */ protected function doClear(string $namespace) { $conn = $this->getConnection(); if ('' === $namespace) { if ('sqlite' === $this->driver) { $sql = "DELETE FROM {$this->table}"; } else { $sql = "TRUNCATE TABLE {$this->table}"; } } else { $sql = "DELETE FROM {$this->table} WHERE {$this->idCol} LIKE '{$namespace}%'"; } try { $conn->exec($sql); } catch (\PDOException $e) { } return \true; } /** * {@inheritdoc} */ protected function doDelete(array $ids) { $sql = \str_pad('', (\count($ids) << 1) - 1, '?,'); $sql = "DELETE FROM {$this->table} WHERE {$this->idCol} IN ({$sql})"; try { $stmt = $this->getConnection()->prepare($sql); $stmt->execute(\array_values($ids)); } catch (\PDOException $e) { } return \true; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { if (!($values = $this->marshaller->marshall($values, $failed))) { return $failed; } $conn = $this->getConnection(); $driver = $this->driver; $insertSql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time)"; switch (\true) { case 'mysql' === $driver: $sql = $insertSql . " ON DUPLICATE KEY UPDATE {$this->dataCol} = VALUES({$this->dataCol}), {$this->lifetimeCol} = VALUES({$this->lifetimeCol}), {$this->timeCol} = VALUES({$this->timeCol})"; break; case 'oci' === $driver: // DUAL is Oracle specific dummy table $sql = "MERGE INTO {$this->table} USING DUAL ON ({$this->idCol} = ?) " . "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " . "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?"; break; case 'sqlsrv' === $driver && \version_compare($this->getServerVersion(), '10', '>='): // MERGE is only available since SQL Server 2008 and must be terminated by semicolon // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx $sql = "MERGE INTO {$this->table} WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ({$this->idCol} = ?) " . "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " . "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?;"; break; case 'sqlite' === $driver: $sql = 'INSERT OR REPLACE' . \substr($insertSql, 6); break; case 'pgsql' === $driver && \version_compare($this->getServerVersion(), '9.5', '>='): $sql = $insertSql . " ON CONFLICT ({$this->idCol}) DO UPDATE SET ({$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) = (EXCLUDED.{$this->dataCol}, EXCLUDED.{$this->lifetimeCol}, EXCLUDED.{$this->timeCol})"; break; default: $driver = null; $sql = "UPDATE {$this->table} SET {$this->dataCol} = :data, {$this->lifetimeCol} = :lifetime, {$this->timeCol} = :time WHERE {$this->idCol} = :id"; break; } $now = \time(); $lifetime = $lifetime ?: null; try { $stmt = $conn->prepare($sql); } catch (\PDOException $e) { if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], \true))) { $this->createTable(); } $stmt = $conn->prepare($sql); } // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution. if ('sqlsrv' === $driver || 'oci' === $driver) { $stmt->bindParam(1, $id); $stmt->bindParam(2, $id); $stmt->bindParam(3, $data, \PDO::PARAM_LOB); $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT); $stmt->bindValue(5, $now, \PDO::PARAM_INT); $stmt->bindParam(6, $data, \PDO::PARAM_LOB); $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT); $stmt->bindValue(8, $now, \PDO::PARAM_INT); } else { $stmt->bindParam(':id', $id); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', $now, \PDO::PARAM_INT); } if (null === $driver) { $insertStmt = $conn->prepare($insertSql); $insertStmt->bindParam(':id', $id); $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT); } foreach ($values as $id => $data) { try { $stmt->execute(); } catch (\PDOException $e) { if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], \true))) { $this->createTable(); } $stmt->execute(); } if (null === $driver && !$stmt->rowCount()) { try { $insertStmt->execute(); } catch (\PDOException $e) { // A concurrent write won, let it be } } } return $failed; } /** * @internal */ protected function getId($key) { if ('pgsql' !== $this->driver ?? ($this->getConnection() ? $this->driver : null)) { return parent::getId($key); } if (\str_contains($key, "\x00") || \str_contains($key, '%') || !\preg_match('//u', $key)) { $key = \rawurlencode($key); } return parent::getId($key); } private function getConnection() : \PDO { if (null === $this->conn) { $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions); $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); } if (null === $this->driver) { $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME); } return $this->conn; } private function getServerVersion() : string { if (null === $this->serverVersion) { $this->serverVersion = $this->conn->getAttribute(\PDO::ATTR_SERVER_VERSION); } return $this->serverVersion; } private function isTableMissing(\PDOException $exception) : bool { $driver = $this->driver; $code = $exception->getCode(); switch (\true) { case 'pgsql' === $driver && '42P01' === $code: case 'sqlite' === $driver && \str_contains($exception->getMessage(), 'no such table:'): case 'oci' === $driver && 942 === $code: case 'sqlsrv' === $driver && 208 === $code: case 'mysql' === $driver && 1146 === $code: return \true; default: return \false; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Log\LoggerAwareInterface; use _ContaoManager\Psr\Log\LoggerAwareTrait; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; /** * An in-memory cache storage. * * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items. * * @author Nicolas Grekas */ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use LoggerAwareTrait; private $storeSerialized; private $values = []; private $expiries = []; private $defaultLifetime; private $maxLifetime; private $maxItems; private static $createCacheItem; /** * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise */ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = \true, float $maxLifetime = 0, int $maxItems = 0) { if (0 > $maxLifetime) { throw new InvalidArgumentException(\sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime)); } if (0 > $maxItems) { throw new InvalidArgumentException(\sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems)); } $this->defaultLifetime = $defaultLifetime; $this->storeSerialized = $storeSerialized; $this->maxLifetime = $maxLifetime; $this->maxItems = $maxItems; self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; $item->value = $value; $item->isHit = $isHit; return $item; }, null, CacheItem::class)); } /** * {@inheritdoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { $item = $this->getItem($key); $metadata = $item->getMetadata(); // ArrayAdapter works in memory, we don't care about stampede protection if (\INF === $beta || !$item->isHit()) { $save = \true; $item->set($callback($item, $save)); if ($save) { $this->save($item); } } return $item->get(); } /** * {@inheritdoc} */ public function delete(string $key) : bool { return $this->deleteItem($key); } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > \microtime(\true)) { if ($this->maxItems) { // Move the item last in the storage $value = $this->values[$key]; unset($this->values[$key]); $this->values[$key] = $value; } return \true; } \assert('' !== CacheItem::validateKey($key)); return isset($this->expiries[$key]) && !$this->deleteItem($key); } /** * {@inheritdoc} */ public function getItem($key) { if (!($isHit = $this->hasItem($key))) { $value = null; if (!$this->maxItems) { // Track misses in non-LRU mode only $this->values[$key] = null; } } else { $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; } return (self::$createCacheItem)($key, $value, $isHit); } /** * {@inheritdoc} */ public function getItems(array $keys = []) { \assert(self::validateKeys($keys)); return $this->generateItems($keys, \microtime(\true), self::$createCacheItem); } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { \assert('' !== CacheItem::validateKey($key)); unset($this->values[$key], $this->expiries[$key]); return \true; } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { foreach ($keys as $key) { $this->deleteItem($key); } return \true; } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { if (!$item instanceof CacheItem) { return \false; } $item = (array) $item; $key = $item["\x00*\x00key"]; $value = $item["\x00*\x00value"]; $expiry = $item["\x00*\x00expiry"]; $now = \microtime(\true); if (null !== $expiry) { if (!$expiry) { $expiry = \PHP_INT_MAX; } elseif ($expiry <= $now) { $this->deleteItem($key); return \true; } } if ($this->storeSerialized && null === ($value = $this->freeze($value, $key))) { return \false; } if (null === $expiry && 0 < $this->defaultLifetime) { $expiry = $this->defaultLifetime; $expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry); } elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) { $expiry = $now + $this->maxLifetime; } if ($this->maxItems) { unset($this->values[$key]); // Iterate items and vacuum expired ones while we are at it foreach ($this->values as $k => $v) { if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) { break; } unset($this->values[$k], $this->expiries[$k]); } } $this->values[$key] = $value; $this->expiries[$key] = $expiry ?? \PHP_INT_MAX; return \true; } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { return $this->save($item); } /** * {@inheritdoc} * * @return bool */ public function commit() { return \true; } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { if ('' !== $prefix) { $now = \microtime(\true); foreach ($this->values as $key => $value) { if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || 0 === \strpos($key, $prefix)) { unset($this->values[$key], $this->expiries[$key]); } } if ($this->values) { return \true; } } $this->values = $this->expiries = []; return \true; } /** * Returns all cached values, with cache miss as null. * * @return array */ public function getValues() { if (!$this->storeSerialized) { return $this->values; } $values = $this->values; foreach ($values as $k => $v) { if (null === $v || 'N;' === $v) { continue; } if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) { $values[$k] = \serialize($v); } } return $values; } /** * {@inheritdoc} */ public function reset() { $this->clear(); } private function generateItems(array $keys, float $now, \Closure $f) : \Generator { foreach ($keys as $i => $key) { if (!($isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key)))) { $value = null; if (!$this->maxItems) { // Track misses in non-LRU mode only $this->values[$key] = null; } } else { if ($this->maxItems) { // Move the item last in the storage $value = $this->values[$key]; unset($this->values[$key]); $this->values[$key] = $value; } $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; } unset($keys[$i]); (yield $key => $f($key, $value, $isHit)); } foreach ($keys as $key) { (yield $key => $f($key, null, \false)); } } private function freeze($value, string $key) { if (null === $value) { return 'N;'; } if (\is_string($value)) { // Serialize strings if they could be confused with serialized objects or arrays if ('N;' === $value || isset($value[2]) && ':' === $value[1]) { return \serialize($value); } } elseif (!\is_scalar($value)) { try { $serialized = \serialize($value); } catch (\Exception $e) { unset($this->values[$key]); $type = \get_debug_type($value); $message = \sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); return; } // Keep value serialized if it contains any objects or any internal references if ('C' === $serialized[0] || 'O' === $serialized[0] || \preg_match('/;[OCRr]:[1-9]/', $serialized)) { return $serialized; } } return $value; } private function unfreeze(string $key, bool &$isHit) { if ('N;' === ($value = $this->values[$key])) { return null; } if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { $value = \unserialize($value); } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to unserialize key "{key}": ' . $e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); $value = \false; } if (\false === $value) { $value = null; $isHit = \false; if (!$this->maxItems) { $this->values[$key] = null; } } } return $value; } private function validateKeys(array $keys) : bool { foreach ($keys as $key) { if (!\is_string($key) || !isset($this->expiries[$key])) { CacheItem::validateKey($key); } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Predis\Connection\Aggregate\ClusterInterface; use _ContaoManager\Predis\Connection\Aggregate\PredisCluster; use _ContaoManager\Predis\Connection\Aggregate\ReplicationInterface; use _ContaoManager\Predis\Response\ErrorInterface; use _ContaoManager\Predis\Response\Status; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Exception\LogicException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DeflateMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\Marshaller\TagAwareMarshaller; use _ContaoManager\Symfony\Component\Cache\Traits\RedisClusterProxy; use _ContaoManager\Symfony\Component\Cache\Traits\RedisProxy; use _ContaoManager\Symfony\Component\Cache\Traits\RedisTrait; /** * Stores tag id <> cache id relationship as a Redis Set. * * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache * relationship survives eviction (cache cleanup when Redis runs out of memory). * * Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up * * Design limitations: * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype. * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also. * * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies. * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype. * * @author Nicolas Grekas * @author André Rømcke */ class RedisTagAwareAdapter extends AbstractTagAwareAdapter { use RedisTrait; /** * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements. */ private const DEFAULT_CACHE_TTL = 8640000; /** * @var string|null detected eviction policy used on Redis server */ private $redisEvictionPolicy; private $namespace; /** * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client * @param string $namespace The default namespace * @param int $defaultLifetime The default lifetime */ public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { if ($redis instanceof \_ContaoManager\Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { throw new InvalidArgumentException(\sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_debug_type($redis->getConnection()))); } if (\defined('Redis::OPT_COMPRESSION') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) { $compression = $redis->getOption(\Redis::OPT_COMPRESSION); foreach (\is_array($compression) ? $compression : [$compression] as $c) { if (\Redis::COMPRESSION_NONE !== $c) { throw new InvalidArgumentException(\sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); } } } $this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller)); $this->namespace = $namespace; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []) : array { $eviction = $this->getRedisEvictionPolicy(); if ('noeviction' !== $eviction && !\str_starts_with($eviction, 'volatile-')) { throw new LogicException(\sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction)); } // serialize values if (!($serialized = $this->marshaller->marshall($values, $failed))) { return $failed; } // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op $results = $this->pipeline(static function () use($serialized, $lifetime, $addTagData, $delTagData, $failed) { // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one foreach ($serialized as $id => $value) { (yield 'setEx' => [$id, 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime, $value]); } // Add and Remove Tags foreach ($addTagData as $tagId => $ids) { if (!$failed || ($ids = \array_diff($ids, $failed))) { (yield 'sAdd' => \array_merge([$tagId], $ids)); } } foreach ($delTagData as $tagId => $ids) { if (!$failed || ($ids = \array_diff($ids, $failed))) { (yield 'sRem' => \array_merge([$tagId], $ids)); } } }); foreach ($results as $id => $result) { // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not if (\is_numeric($result)) { continue; } // setEx results if (\true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { $failed[] = $id; } } return $failed; } /** * {@inheritdoc} */ protected function doDeleteYieldTags(array $ids) : iterable { $lua = <<<'EOLUA' local v = redis.call('GET', KEYS[1]) local e = redis.pcall('UNLINK', KEYS[1]) if type(e) ~= 'number' then redis.call('DEL', KEYS[1]) end if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then return '' end return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536) EOLUA; $results = $this->pipeline(function () use($ids, $lua) { foreach ($ids as $id) { (yield 'eval' => $this->redis instanceof \_ContaoManager\Predis\ClientInterface ? [$lua, 1, $id] : [$lua, [$id], 1]); } }); foreach ($results as $id => $result) { if ($result instanceof \RedisException || $result instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to delete key "{key}": ' . $result->getMessage(), ['key' => \substr($id, \strlen($this->namespace)), 'exception' => $result]); continue; } try { (yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result)); } catch (\Exception $e) { (yield $id => []); } } } /** * {@inheritdoc} */ protected function doDeleteTagRelations(array $tagData) : bool { $results = $this->pipeline(static function () use($tagData) { foreach ($tagData as $tagId => $idList) { \array_unshift($idList, $tagId); (yield 'sRem' => $idList); } }); foreach ($results as $result) { // no-op } return \true; } /** * {@inheritdoc} */ protected function doInvalidate(array $tagIds) : bool { // This script scans the set of items linked to tag: it empties the set // and removes the linked items. When the set is still not empty after // the scan, it means we're in cluster mode and that the linked items // are on other nodes: we move the links to a temporary set and we // garbage collect that set from the client side. $lua = <<<'EOLUA' redis.replicate_commands() local cursor = '0' local id = KEYS[1] repeat local result = redis.call('SSCAN', id, cursor, 'COUNT', 5000); cursor = result[1]; local rems = {} for _, v in ipairs(result[2]) do local ok, _ = pcall(redis.call, 'DEL', ARGV[1]..v) if ok then table.insert(rems, v) end end if 0 < #rems then redis.call('SREM', id, unpack(rems)) end until '0' == cursor; redis.call('SUNIONSTORE', '{'..id..'}'..id, id) redis.call('DEL', id) return redis.call('SSCAN', '{'..id..'}'..id, '0', 'COUNT', 5000) EOLUA; $results = $this->pipeline(function () use($tagIds, $lua) { if ($this->redis instanceof \_ContaoManager\Predis\ClientInterface) { $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; } elseif (\is_array($prefix = $this->redis->getOption(\Redis::OPT_PREFIX) ?? '')) { $prefix = \current($prefix); } foreach ($tagIds as $id) { (yield 'eval' => $this->redis instanceof \_ContaoManager\Predis\ClientInterface ? [$lua, 1, $id, $prefix] : [$lua, [$id, $prefix], 1]); } }); $lua = <<<'EOLUA' redis.replicate_commands() local id = KEYS[1] local cursor = table.remove(ARGV) redis.call('SREM', '{'..id..'}'..id, unpack(ARGV)) return redis.call('SSCAN', '{'..id..'}'..id, cursor, 'COUNT', 5000) EOLUA; $success = \true; foreach ($results as $id => $values) { if ($values instanceof \RedisException || $values instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to invalidate key "{key}": ' . $values->getMessage(), ['key' => \substr($id, \strlen($this->namespace)), 'exception' => $values]); $success = \false; continue; } [$cursor, $ids] = $values; while ($ids || '0' !== $cursor) { $this->doDelete($ids); $evalArgs = [$id, $cursor]; \array_splice($evalArgs, 1, 0, $ids); if ($this->redis instanceof \_ContaoManager\Predis\ClientInterface) { \array_unshift($evalArgs, $lua, 1); } else { $evalArgs = [$lua, $evalArgs, 1]; } $results = $this->pipeline(function () use($evalArgs) { (yield 'eval' => $evalArgs); }); foreach ($results as [$cursor, $ids]) { // no-op } } } return $success; } private function getRedisEvictionPolicy() : string { if (null !== $this->redisEvictionPolicy) { return $this->redisEvictionPolicy; } $hosts = $this->getHosts(); $host = \reset($hosts); if ($host instanceof \_ContaoManager\Predis\Client && $host->getConnection() instanceof ReplicationInterface) { // Predis supports info command only on the master in replication environments $hosts = [$host->getClientFor('master')]; } foreach ($hosts as $host) { $info = $host->info('Memory'); if ($info instanceof ErrorInterface) { continue; } $info = $info['Memory'] ?? $info; return $this->redisEvictionPolicy = $info['maxmemory_policy']; } return $this->redisEvictionPolicy = ''; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Antonio Jose Cerezo Aranda */ class CouchbaseBucketAdapter extends AbstractAdapter { private const THIRTY_DAYS_IN_SECONDS = 2592000; private const MAX_KEY_LENGTH = 250; private const KEY_NOT_FOUND = 13; private const VALID_DSN_OPTIONS = ['operationTimeout', 'configTimeout', 'configNodeTimeout', 'n1qlTimeout', 'httpTimeout', 'configDelay', 'htconfigIdleTimeout', 'durabilityInterval', 'durabilityTimeout']; private $bucket; private $marshaller; public function __construct(\_ContaoManager\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { if (!static::isSupported()) { throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); } $this->maxIdLength = static::MAX_KEY_LENGTH; $this->bucket = $bucket; parent::__construct($namespace, $defaultLifetime); $this->enableVersioning(); $this->marshaller = $marshaller ?? new DefaultMarshaller(); } /** * @param array|string $servers */ public static function createConnection($servers, array $options = []) : \_ContaoManager\CouchbaseBucket { if (\is_string($servers)) { $servers = [$servers]; } elseif (!\is_array($servers)) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, \get_debug_type($servers))); } if (!static::isSupported()) { throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); } \set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); $dsnPattern = '/^(?couchbase(?:s)?)\\:\\/\\/(?:(?[^\\:]+)\\:(?[^\\@]{6,})@)?' . '(?[^\\:]+(?:\\:\\d+)?)(?:\\/(?[^\\?]+))(?:\\?(?.*))?$/i'; $newServers = []; $protocol = 'couchbase'; try { $options = self::initOptions($options); $username = $options['username']; $password = $options['password']; foreach ($servers as $dsn) { if (0 !== \strpos($dsn, 'couchbase:')) { throw new InvalidArgumentException('Invalid Couchbase DSN: it does not start with "couchbase:".'); } \preg_match($dsnPattern, $dsn, $matches); $username = $matches['username'] ?: $username; $password = $matches['password'] ?: $password; $protocol = $matches['protocol'] ?: $protocol; if (isset($matches['options'])) { $optionsInDsn = self::getOptions($matches['options']); foreach ($optionsInDsn as $parameter => $value) { $options[$parameter] = $value; } } $newServers[] = $matches['host']; } $connectionString = $protocol . '://' . \implode(',', $newServers); $client = new \_ContaoManager\CouchbaseCluster($connectionString); $client->authenticateAs($username, $password); $bucket = $client->openBucket($matches['bucketName']); unset($options['username'], $options['password']); foreach ($options as $option => $value) { if (!empty($value)) { $bucket->{$option} = $value; } } return $bucket; } finally { \restore_error_handler(); } } public static function isSupported() : bool { return \extension_loaded('couchbase') && \version_compare(\phpversion('couchbase'), '2.6.0', '>=') && \version_compare(\phpversion('couchbase'), '3.0', '<'); } private static function getOptions(string $options) : array { $results = []; $optionsInArray = \explode('&', $options); foreach ($optionsInArray as $option) { [$key, $value] = \explode('=', $option); if (\in_array($key, static::VALID_DSN_OPTIONS, \true)) { $results[$key] = $value; } } return $results; } private static function initOptions(array $options) : array { $options['username'] = $options['username'] ?? ''; $options['password'] = $options['password'] ?? ''; $options['operationTimeout'] = $options['operationTimeout'] ?? 0; $options['configTimeout'] = $options['configTimeout'] ?? 0; $options['configNodeTimeout'] = $options['configNodeTimeout'] ?? 0; $options['n1qlTimeout'] = $options['n1qlTimeout'] ?? 0; $options['httpTimeout'] = $options['httpTimeout'] ?? 0; $options['configDelay'] = $options['configDelay'] ?? 0; $options['htconfigIdleTimeout'] = $options['htconfigIdleTimeout'] ?? 0; $options['durabilityInterval'] = $options['durabilityInterval'] ?? 0; $options['durabilityTimeout'] = $options['durabilityTimeout'] ?? 0; return $options; } /** * {@inheritdoc} */ protected function doFetch(array $ids) { $resultsCouchbase = $this->bucket->get($ids); $results = []; foreach ($resultsCouchbase as $key => $value) { if (null !== $value->error) { continue; } $results[$key] = $this->marshaller->unmarshall($value->value); } return $results; } /** * {@inheritdoc} */ protected function doHave(string $id) : bool { return \false !== $this->bucket->get($id); } /** * {@inheritdoc} */ protected function doClear(string $namespace) : bool { if ('' === $namespace) { $this->bucket->manager()->flush(); return \true; } return \false; } /** * {@inheritdoc} */ protected function doDelete(array $ids) : bool { $results = $this->bucket->remove(\array_values($ids)); foreach ($results as $key => $result) { if (null !== $result->error && static::KEY_NOT_FOUND !== $result->error->getCode()) { continue; } unset($results[$key]); } return 0 === \count($results); } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { if (!($values = $this->marshaller->marshall($values, $failed))) { return $failed; } $lifetime = $this->normalizeExpiry($lifetime); $ko = []; foreach ($values as $key => $value) { $result = $this->bucket->upsert($key, $value, ['expiry' => $lifetime]); if (null !== $result->error) { $ko[$key] = $result; } } return [] === $ko ? \true : $ko; } private function normalizeExpiry(int $expiry) : int { if ($expiry && $expiry > static::THIRTY_DAYS_IN_SECONDS) { $expiry += \time(); } return $expiry; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; /** * @author Titouan Galopin */ class NullAdapter implements AdapterInterface, CacheInterface { private static $createCacheItem; public function __construct() { self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key) { $item = new CacheItem(); $item->key = $key; $item->isHit = \false; return $item; }, null, CacheItem::class)); } /** * {@inheritdoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { $save = \true; return $callback((self::$createCacheItem)($key), $save); } /** * {@inheritdoc} */ public function getItem($key) { return (self::$createCacheItem)($key); } /** * {@inheritdoc} */ public function getItems(array $keys = []) { return $this->generateItems($keys); } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { return \false; } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { return \true; } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { return \true; } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { return \true; } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { return \true; } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { return \true; } /** * {@inheritdoc} * * @return bool */ public function commit() { return \true; } /** * {@inheritdoc} */ public function delete(string $key) : bool { return $this->deleteItem($key); } private function generateItems(array $keys) : \Generator { $f = self::$createCacheItem; foreach ($keys as $key) { (yield $key => $f($key)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; /** * @author Lars Strojny */ final class ParameterNormalizer { public static function normalizeDuration(string $duration) : int { if (\is_numeric($duration)) { return $duration; } if (\false !== ($time = \strtotime($duration, 0))) { return $time; } try { return \DateTime::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp(); } catch (\Exception $e) { throw new \InvalidArgumentException(\sprintf('Cannot parse date interval "%s".', $duration), 0, $e); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Cache\InvalidArgumentException; use _ContaoManager\Psr\Log\LoggerAwareInterface; use _ContaoManager\Psr\Log\LoggerAwareTrait; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\ContractsTrait; use _ContaoManager\Symfony\Component\Cache\Traits\ProxyTrait; use _ContaoManager\Symfony\Contracts\Cache\TagAwareCacheInterface; /** * @author Nicolas Grekas */ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface { use ContractsTrait; use LoggerAwareTrait; use ProxyTrait; public const TAGS_PREFIX = "\x00tags\x00"; private $deferred = []; private $tags; private $knownTagVersions = []; private $knownTagVersionsTtl; private static $createCacheItem; private static $setCacheItemTags; private static $getTagsByKey; private static $saveTags; public function __construct(AdapterInterface $itemsPool, ?AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15) { $this->pool = $itemsPool; $this->tags = $tagsPool ?: $itemsPool; $this->knownTagVersionsTtl = $knownTagVersionsTtl; self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key, $value, CacheItem $protoItem) { $item = new CacheItem(); $item->key = $key; $item->value = $value; $item->expiry = $protoItem->expiry; $item->poolHash = $protoItem->poolHash; return $item; }, null, CacheItem::class)); self::$setCacheItemTags ?? (self::$setCacheItemTags = \Closure::bind(static function (CacheItem $item, $key, array &$itemTags) { $item->isTaggable = \true; if (!$item->isHit) { return $item; } if (isset($itemTags[$key])) { foreach ($itemTags[$key] as $tag => $version) { $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag; } unset($itemTags[$key]); } else { $item->value = null; $item->isHit = \false; } return $item; }, null, CacheItem::class)); self::$getTagsByKey ?? (self::$getTagsByKey = \Closure::bind(static function ($deferred) { $tagsByKey = []; foreach ($deferred as $key => $item) { $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? []; $item->metadata = $item->newMetadata; } return $tagsByKey; }, null, CacheItem::class)); self::$saveTags ?? (self::$saveTags = \Closure::bind(static function (AdapterInterface $tagsAdapter, array $tags) { \ksort($tags); foreach ($tags as $v) { $v->expiry = 0; $tagsAdapter->saveDeferred($v); } return $tagsAdapter->commit(); }, null, CacheItem::class)); } /** * {@inheritdoc} */ public function invalidateTags(array $tags) { $ids = []; foreach ($tags as $tag) { \assert('' !== CacheItem::validateKey($tag)); unset($this->knownTagVersions[$tag]); $ids[] = $tag . static::TAGS_PREFIX; } return !$tags || $this->tags->deleteItems($ids); } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { if (\is_string($key) && isset($this->deferred[$key])) { $this->commit(); } if (!$this->pool->hasItem($key)) { return \false; } $itemTags = $this->pool->getItem(static::TAGS_PREFIX . $key); if (!$itemTags->isHit()) { return \false; } if (!($itemTags = $itemTags->get())) { return \true; } foreach ($this->getTagVersions([$itemTags]) as $tag => $version) { if ($itemTags[$tag] !== $version) { return \false; } } return \true; } /** * {@inheritdoc} */ public function getItem($key) { foreach ($this->getItems([$key]) as $item) { return $item; } return null; } /** * {@inheritdoc} */ public function getItems(array $keys = []) { $tagKeys = []; $commit = \false; foreach ($keys as $key) { if ('' !== $key && \is_string($key)) { $commit = $commit || isset($this->deferred[$key]); $key = static::TAGS_PREFIX . $key; $tagKeys[$key] = $key; } } if ($commit) { $this->commit(); } try { $items = $this->pool->getItems($tagKeys + $keys); } catch (InvalidArgumentException $e) { $this->pool->getItems($keys); // Should throw an exception throw $e; } return $this->generateItems($items, $tagKeys); } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { if ('' !== $prefix) { foreach ($this->deferred as $key => $item) { if (\str_starts_with($key, $prefix)) { unset($this->deferred[$key]); } } } else { $this->deferred = []; } if ($this->pool instanceof AdapterInterface) { return $this->pool->clear($prefix); } return $this->pool->clear(); } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { return $this->deleteItems([$key]); } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { foreach ($keys as $key) { if ('' !== $key && \is_string($key)) { $keys[] = static::TAGS_PREFIX . $key; } } return $this->pool->deleteItems($keys); } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { if (!$item instanceof CacheItem) { return \false; } $this->deferred[$item->getKey()] = $item; return $this->commit(); } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { if (!$item instanceof CacheItem) { return \false; } $this->deferred[$item->getKey()] = $item; return \true; } /** * {@inheritdoc} * * @return bool */ public function commit() { if (!$this->deferred) { return \true; } $ok = \true; foreach ($this->deferred as $key => $item) { if (!$this->pool->saveDeferred($item)) { unset($this->deferred[$key]); $ok = \false; } } $items = $this->deferred; $tagsByKey = (self::$getTagsByKey)($items); $this->deferred = []; $tagVersions = $this->getTagVersions($tagsByKey); $f = self::$createCacheItem; foreach ($tagsByKey as $key => $tags) { $this->pool->saveDeferred($f(static::TAGS_PREFIX . $key, \array_intersect_key($tagVersions, $tags), $items[$key])); } return $this->pool->commit() && $ok; } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { $this->commit(); } private function generateItems(iterable $items, array $tagKeys) : \Generator { $bufferedItems = $itemTags = []; $f = self::$setCacheItemTags; foreach ($items as $key => $item) { if (!$tagKeys) { (yield $key => $f($item, static::TAGS_PREFIX . $key, $itemTags)); continue; } if (!isset($tagKeys[$key])) { $bufferedItems[$key] = $item; continue; } unset($tagKeys[$key]); if ($item->isHit()) { $itemTags[$key] = $item->get() ?: []; } if (!$tagKeys) { $tagVersions = $this->getTagVersions($itemTags); foreach ($itemTags as $key => $tags) { foreach ($tags as $tag => $version) { if ($tagVersions[$tag] !== $version) { unset($itemTags[$key]); continue 2; } } } $tagVersions = $tagKeys = null; foreach ($bufferedItems as $key => $item) { (yield $key => $f($item, static::TAGS_PREFIX . $key, $itemTags)); } $bufferedItems = null; } } } private function getTagVersions(array $tagsByKey) { $tagVersions = []; $fetchTagVersions = \false; foreach ($tagsByKey as $tags) { $tagVersions += $tags; foreach ($tags as $tag => $version) { if ($tagVersions[$tag] !== $version) { unset($this->knownTagVersions[$tag]); } } } if (!$tagVersions) { return []; } $now = \microtime(\true); $tags = []; foreach ($tagVersions as $tag => $version) { $tags[$tag . static::TAGS_PREFIX] = $tag; if ($fetchTagVersions || ($this->knownTagVersions[$tag][1] ?? null) !== $version || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) { // reuse previously fetched tag versions up to the ttl $fetchTagVersions = \true; } } if (!$fetchTagVersions) { return $tagVersions; } $newTags = []; $newVersion = null; foreach ($this->tags->getItems(\array_keys($tags)) as $tag => $version) { if (!$version->isHit()) { $newTags[$tag] = $version->set($newVersion ?? ($newVersion = \random_int(\PHP_INT_MIN, \PHP_INT_MAX))); } $tagVersions[$tag = $tags[$tag]] = $version->get(); $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]]; } if ($newTags) { (self::$saveTags)($this->tags, $newTags); } return $tagVersions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\FilesystemCommonTrait; use _ContaoManager\Symfony\Component\VarExporter\VarExporter; /** * @author Piotr Stankowski * @author Nicolas Grekas * @author Rob Frawley 2nd */ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface { use FilesystemCommonTrait { doClear as private doCommonClear; doDelete as private doCommonDelete; } private $includeHandler; private $appendOnly; private $values = []; private $files = []; private static $startTime; private static $valuesCache = []; /** * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. * Doing so is encouraged because it fits perfectly OPcache's memory model. * * @throws CacheException if OPcache is not enabled */ public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, bool $appendOnly = \false) { $this->appendOnly = $appendOnly; self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? \time(); parent::__construct('', $defaultLifetime); $this->init($namespace, $directory); $this->includeHandler = static function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }; } public static function isSupported() { self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? \time(); return \function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) || \filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN)); } /** * @return bool */ public function prune() { $time = \time(); $pruned = \true; $getExpiry = \true; \set_error_handler($this->includeHandler); try { foreach ($this->scanHashDir($this->directory) as $file) { try { if (\is_array($expiresAt = (include $file))) { $expiresAt = $expiresAt[0]; } } catch (\ErrorException $e) { $expiresAt = $time; } if ($time >= $expiresAt) { $pruned = ($this->doUnlink($file) || !\file_exists($file)) && $pruned; } } } finally { \restore_error_handler(); } return $pruned; } /** * {@inheritdoc} */ protected function doFetch(array $ids) { if ($this->appendOnly) { $now = 0; $missingIds = []; } else { $now = \time(); $missingIds = $ids; $ids = []; } $values = []; begin: $getExpiry = \false; foreach ($ids as $id) { if (null === ($value = $this->values[$id] ?? null)) { $missingIds[] = $id; } elseif ('N;' === $value) { $values[$id] = null; } elseif (!\is_object($value)) { $values[$id] = $value; } elseif (!$value instanceof LazyValue) { $values[$id] = $value(); } elseif (\false === ($values[$id] = (include $value->file))) { unset($values[$id], $this->values[$id]); $missingIds[] = $id; } if (!$this->appendOnly) { unset($this->values[$id]); } } if (!$missingIds) { return $values; } \set_error_handler($this->includeHandler); try { $getExpiry = \true; foreach ($missingIds as $k => $id) { try { $file = $this->files[$id] ?? ($this->files[$id] = $this->getFile($id)); if (isset(self::$valuesCache[$file])) { [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; } elseif (\is_array($expiresAt = (include $file))) { if ($this->appendOnly) { self::$valuesCache[$file] = $expiresAt; } [$expiresAt, $this->values[$id]] = $expiresAt; } elseif ($now < $expiresAt) { $this->values[$id] = new LazyValue($file); } if ($now >= $expiresAt) { unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); } } catch (\ErrorException $e) { unset($missingIds[$k]); } } } finally { \restore_error_handler(); } $ids = $missingIds; $missingIds = []; goto begin; } /** * {@inheritdoc} */ protected function doHave(string $id) { if ($this->appendOnly && isset($this->values[$id])) { return \true; } \set_error_handler($this->includeHandler); try { $file = $this->files[$id] ?? ($this->files[$id] = $this->getFile($id)); $getExpiry = \true; if (isset(self::$valuesCache[$file])) { [$expiresAt, $value] = self::$valuesCache[$file]; } elseif (\is_array($expiresAt = (include $file))) { if ($this->appendOnly) { self::$valuesCache[$file] = $expiresAt; } [$expiresAt, $value] = $expiresAt; } elseif ($this->appendOnly) { $value = new LazyValue($file); } } catch (\ErrorException $e) { return \false; } finally { \restore_error_handler(); } if ($this->appendOnly) { $now = 0; $this->values[$id] = $value; } else { $now = \time(); } return $now < $expiresAt; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { $ok = \true; $expiry = $lifetime ? \time() + $lifetime : 'PHP_INT_MAX'; $allowCompile = self::isSupported(); foreach ($values as $key => $value) { unset($this->values[$key]); $isStaticValue = \true; if (null === $value) { $value = "'N;'"; } elseif (\is_object($value) || \is_array($value)) { try { $value = VarExporter::export($value, $isStaticValue); } catch (\Exception $e) { throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \get_debug_type($value)), 0, $e); } } elseif (\is_string($value)) { // Wrap "N;" in a closure to not confuse it with an encoded `null` if ('N;' === $value) { $isStaticValue = \false; } $value = \var_export($value, \true); } elseif (!\is_scalar($value)) { throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \get_debug_type($value))); } else { $value = \var_export($value, \true); } $encodedKey = \rawurlencode($key); if ($isStaticValue) { $value = "return [{$expiry}, {$value}];"; } elseif ($this->appendOnly) { $value = "return [{$expiry}, static function () { return {$value}; }];"; } else { // We cannot use a closure here because of https://bugs.php.net/76982 $value = \str_replace('\\Symfony\\Component\\VarExporter\\Internal\\', '', $value); $value = "namespace Symfony\\Component\\VarExporter\\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};"; } $file = $this->files[$key] = $this->getFile($key, \true); // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past $ok = $this->write($file, "directory)) { throw new CacheException(\sprintf('Cache directory is not writable (%s).', $this->directory)); } return $ok; } /** * {@inheritdoc} */ protected function doClear(string $namespace) { $this->values = []; return $this->doCommonClear($namespace); } /** * {@inheritdoc} */ protected function doDelete(array $ids) { foreach ($ids as $id) { unset($this->values[$id]); } return $this->doCommonDelete($ids); } protected function doUnlink(string $file) { unset(self::$valuesCache[$file]); if (self::isSupported()) { @\opcache_invalidate($file, \true); } return @\unlink($file); } private function getFileKey(string $file) : string { if (!($h = @\fopen($file, 'r'))) { return ''; } $encodedKey = \substr(\fgets($h), 8); \fclose($h); return \rawurldecode(\rtrim($encodedKey)); } } /** * @internal */ class LazyValue { public $file; public function __construct(string $file) { $this->file = $file; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Doctrine\DBAL\ArrayParameterType; use _ContaoManager\Doctrine\DBAL\Configuration; use _ContaoManager\Doctrine\DBAL\Connection; use _ContaoManager\Doctrine\DBAL\Driver\ServerInfoAwareConnection; use _ContaoManager\Doctrine\DBAL\DriverManager; use _ContaoManager\Doctrine\DBAL\Exception as DBALException; use _ContaoManager\Doctrine\DBAL\Exception\TableNotFoundException; use _ContaoManager\Doctrine\DBAL\ParameterType; use _ContaoManager\Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use _ContaoManager\Doctrine\DBAL\Schema\Schema; use _ContaoManager\Doctrine\DBAL\ServerVersionProvider; use _ContaoManager\Doctrine\DBAL\Tools\DsnParser; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface { protected $maxIdLength = 255; private $marshaller; private $conn; private $platformName; private $serverVersion; private $table = 'cache_items'; private $idCol = 'item_id'; private $dataCol = 'item_data'; private $lifetimeCol = 'item_lifetime'; private $timeCol = 'item_time'; private $namespace; /** * You can either pass an existing database Doctrine DBAL Connection or * a DSN string that will be used to connect to the database. * * The cache table is created automatically when possible. * Otherwise, use the createTable() method. * * List of available options: * * db_table: The name of the table [default: cache_items] * * db_id_col: The column where to store the cache id [default: item_id] * * db_data_col: The column where to store the cache data [default: item_data] * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] * * db_time_col: The column where to store the timestamp [default: item_time] * * @param Connection|string $connOrDsn * * @throws InvalidArgumentException When namespace contains invalid characters */ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], ?MarshallerInterface $marshaller = null) { if (isset($namespace[0]) && \preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); } if ($connOrDsn instanceof Connection) { $this->conn = $connOrDsn; } elseif (\is_string($connOrDsn)) { if (!\class_exists(DriverManager::class)) { throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".'); } if (\class_exists(DsnParser::class)) { $params = (new DsnParser(['db2' => 'ibm_db2', 'mssql' => 'pdo_sqlsrv', 'mysql' => 'pdo_mysql', 'mysql2' => 'pdo_mysql', 'postgres' => 'pdo_pgsql', 'postgresql' => 'pdo_pgsql', 'pgsql' => 'pdo_pgsql', 'sqlite' => 'pdo_sqlite', 'sqlite3' => 'pdo_sqlite']))->parse($connOrDsn); } else { $params = ['url' => $connOrDsn]; } $config = new Configuration(); if (\class_exists(DefaultSchemaManagerFactory::class)) { $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); } $this->conn = DriverManager::getConnection($params, $config); } else { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be "%s" or string, "%s" given.', __METHOD__, Connection::class, \get_debug_type($connOrDsn))); } $this->table = $options['db_table'] ?? $this->table; $this->idCol = $options['db_id_col'] ?? $this->idCol; $this->dataCol = $options['db_data_col'] ?? $this->dataCol; $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; $this->timeCol = $options['db_time_col'] ?? $this->timeCol; $this->namespace = $namespace; $this->marshaller = $marshaller ?? new DefaultMarshaller(); parent::__construct($namespace, $defaultLifetime); } /** * Creates the table to store cache items which can be called once for setup. * * Cache ID are saved in a column of maximum length 255. Cache data is * saved in a BLOB. * * @throws DBALException When the table already exists */ public function createTable() { $schema = new Schema(); $this->addTableToSchema($schema); foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { $this->conn->executeStatement($sql); } } /** * {@inheritdoc} */ public function configureSchema(Schema $schema, Connection $forConnection) : void { // only update the schema for this connection if ($forConnection !== $this->conn) { return; } if ($schema->hasTable($this->table)) { return; } $this->addTableToSchema($schema); } /** * {@inheritdoc} */ public function prune() : bool { $deleteSql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} + {$this->timeCol} <= ?"; $params = [\time()]; $paramTypes = [ParameterType::INTEGER]; if ('' !== $this->namespace) { $deleteSql .= " AND {$this->idCol} LIKE ?"; $params[] = \sprintf('%s%%', $this->namespace); $paramTypes[] = ParameterType::STRING; } try { $this->conn->executeStatement($deleteSql, $params, $paramTypes); } catch (TableNotFoundException $e) { } return \true; } /** * {@inheritdoc} */ protected function doFetch(array $ids) : iterable { $now = \time(); $expired = []; $sql = "SELECT {$this->idCol}, CASE WHEN {$this->lifetimeCol} IS NULL OR {$this->lifetimeCol} + {$this->timeCol} > ? THEN {$this->dataCol} ELSE NULL END FROM {$this->table} WHERE {$this->idCol} IN (?)"; $result = $this->conn->executeQuery($sql, [$now, $ids], [ParameterType::INTEGER, \class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY])->iterateNumeric(); foreach ($result as $row) { if (null === $row[1]) { $expired[] = $row[0]; } else { (yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? \stream_get_contents($row[1]) : $row[1])); } } if ($expired) { $sql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} + {$this->timeCol} <= ? AND {$this->idCol} IN (?)"; $this->conn->executeStatement($sql, [$now, $expired], [ParameterType::INTEGER, \class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY]); } } /** * {@inheritdoc} */ protected function doHave(string $id) : bool { $sql = "SELECT 1 FROM {$this->table} WHERE {$this->idCol} = ? AND ({$this->lifetimeCol} IS NULL OR {$this->lifetimeCol} + {$this->timeCol} > ?)"; $result = $this->conn->executeQuery($sql, [$id, \time()], [ParameterType::STRING, ParameterType::INTEGER]); return (bool) $result->fetchOne(); } /** * {@inheritdoc} */ protected function doClear(string $namespace) : bool { if ('' === $namespace) { if ('sqlite' === $this->getPlatformName()) { $sql = "DELETE FROM {$this->table}"; } else { $sql = "TRUNCATE TABLE {$this->table}"; } } else { $sql = "DELETE FROM {$this->table} WHERE {$this->idCol} LIKE '{$namespace}%'"; } try { $this->conn->executeStatement($sql); } catch (TableNotFoundException $e) { } return \true; } /** * {@inheritdoc} */ protected function doDelete(array $ids) : bool { $sql = "DELETE FROM {$this->table} WHERE {$this->idCol} IN (?)"; try { $this->conn->executeStatement($sql, [\array_values($ids)], [\class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY]); } catch (TableNotFoundException $e) { } return \true; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { if (!($values = $this->marshaller->marshall($values, $failed))) { return $failed; } $platformName = $this->getPlatformName(); $insertSql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?)"; switch (\true) { case 'mysql' === $platformName: $sql = $insertSql . " ON DUPLICATE KEY UPDATE {$this->dataCol} = VALUES({$this->dataCol}), {$this->lifetimeCol} = VALUES({$this->lifetimeCol}), {$this->timeCol} = VALUES({$this->timeCol})"; break; case 'oci' === $platformName: // DUAL is Oracle specific dummy table $sql = "MERGE INTO {$this->table} USING DUAL ON ({$this->idCol} = ?) " . "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " . "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?"; break; case 'sqlsrv' === $platformName && \version_compare($this->getServerVersion(), '10', '>='): // MERGE is only available since SQL Server 2008 and must be terminated by semicolon // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx $sql = "MERGE INTO {$this->table} WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ({$this->idCol} = ?) " . "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " . "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?;"; break; case 'sqlite' === $platformName: $sql = 'INSERT OR REPLACE' . \substr($insertSql, 6); break; case 'pgsql' === $platformName && \version_compare($this->getServerVersion(), '9.5', '>='): $sql = $insertSql . " ON CONFLICT ({$this->idCol}) DO UPDATE SET ({$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) = (EXCLUDED.{$this->dataCol}, EXCLUDED.{$this->lifetimeCol}, EXCLUDED.{$this->timeCol})"; break; default: $platformName = null; $sql = "UPDATE {$this->table} SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ? WHERE {$this->idCol} = ?"; break; } $now = \time(); $lifetime = $lifetime ?: null; try { $stmt = $this->conn->prepare($sql); } catch (TableNotFoundException $e) { if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], \true)) { $this->createTable(); } $stmt = $this->conn->prepare($sql); } if ('sqlsrv' === $platformName || 'oci' === $platformName) { $bind = static function ($id, $data) use($stmt) { $stmt->bindValue(1, $id); $stmt->bindValue(2, $id); $stmt->bindValue(3, $data, ParameterType::LARGE_OBJECT); $stmt->bindValue(6, $data, ParameterType::LARGE_OBJECT); }; $stmt->bindValue(4, $lifetime, ParameterType::INTEGER); $stmt->bindValue(5, $now, ParameterType::INTEGER); $stmt->bindValue(7, $lifetime, ParameterType::INTEGER); $stmt->bindValue(8, $now, ParameterType::INTEGER); } elseif (null !== $platformName) { $bind = static function ($id, $data) use($stmt) { $stmt->bindValue(1, $id); $stmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); }; $stmt->bindValue(3, $lifetime, ParameterType::INTEGER); $stmt->bindValue(4, $now, ParameterType::INTEGER); } else { $stmt->bindValue(2, $lifetime, ParameterType::INTEGER); $stmt->bindValue(3, $now, ParameterType::INTEGER); $insertStmt = $this->conn->prepare($insertSql); $insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER); $insertStmt->bindValue(4, $now, ParameterType::INTEGER); $bind = static function ($id, $data) use($stmt, $insertStmt) { $stmt->bindValue(1, $data, ParameterType::LARGE_OBJECT); $stmt->bindValue(4, $id); $insertStmt->bindValue(1, $id); $insertStmt->bindValue(2, $data, ParameterType::LARGE_OBJECT); }; } foreach ($values as $id => $data) { $bind($id, $data); try { $rowCount = $stmt->executeStatement(); } catch (TableNotFoundException $e) { if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], \true)) { $this->createTable(); } $rowCount = $stmt->executeStatement(); } if (null === $platformName && 0 === $rowCount) { try { $insertStmt->executeStatement(); } catch (DBALException $e) { // A concurrent write won, let it be } } } return $failed; } /** * @internal */ protected function getId($key) { if ('pgsql' !== $this->getPlatformName()) { return parent::getId($key); } if (\str_contains($key, "\x00") || \str_contains($key, '%') || !\preg_match('//u', $key)) { $key = \rawurlencode($key); } return parent::getId($key); } private function getPlatformName() : string { if (isset($this->platformName)) { return $this->platformName; } $platform = $this->conn->getDatabasePlatform(); switch (\true) { case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\MySQLPlatform: case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\MySQL57Platform: return $this->platformName = 'mysql'; case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\SqlitePlatform: return $this->platformName = 'sqlite'; case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\PostgreSQLPlatform: case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\PostgreSQL94Platform: return $this->platformName = 'pgsql'; case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\OraclePlatform: return $this->platformName = 'oci'; case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\SQLServerPlatform: case $platform instanceof \_ContaoManager\Doctrine\DBAL\Platforms\SQLServer2012Platform: return $this->platformName = 'sqlsrv'; default: return $this->platformName = \get_class($platform); } } private function getServerVersion() : string { if (isset($this->serverVersion)) { return $this->serverVersion; } if ($this->conn instanceof ServerVersionProvider || $this->conn instanceof ServerInfoAwareConnection) { return $this->serverVersion = $this->conn->getServerVersion(); } // The condition should be removed once support for DBAL <3.3 is dropped $conn = \method_exists($this->conn, 'getNativeConnection') ? $this->conn->getNativeConnection() : $this->conn->getWrappedConnection(); return $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION); } private function addTableToSchema(Schema $schema) : void { $types = ['mysql' => 'binary', 'sqlite' => 'text']; $table = $schema->createTable($this->table); $table->addColumn($this->idCol, $types[$this->getPlatformName()] ?? 'string', ['length' => 255]); $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]); $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => \true, 'notnull' => \false]); $table->addColumn($this->timeCol, 'integer', ['unsigned' => \true]); $table->setPrimaryKey([$this->idCol]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\InvalidArgumentException; /** * Interface for invalidating cached items using tags. * * @author Nicolas Grekas */ interface TagAwareAdapterInterface extends AdapterInterface { /** * Invalidates cached items using tags. * * @param string[] $tags An array of tags to invalidate * * @return bool * * @throws InvalidArgumentException When $tags is not valid */ public function invalidateTags(array $tags); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Rob Frawley 2nd * @author Nicolas Grekas */ class MemcachedAdapter extends AbstractAdapter { /** * We are replacing characters that are illegal in Memcached keys with reserved characters from * {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached. * Note: don’t use {@see \Symfony\Component\Cache\Adapter\AbstractAdapter::NS_SEPARATOR}. */ private const RESERVED_MEMCACHED = " \n\r\t\v\f\x00"; private const RESERVED_PSR6 = '@()\\{}/'; protected $maxIdLength = 250; private $marshaller; private $client; private $lazyClient; /** * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged. * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that: * - the Memcached::OPT_BINARY_PROTOCOL must be enabled * (that's the default when using MemcachedAdapter::createConnection()); * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation; * your Memcached memory should be large enough to never trigger LRU. * * Using a MemcachedAdapter as a pure items store is fine. */ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { if (!static::isSupported()) { throw new CacheException('Memcached ' . (\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0') . ' is required.'); } if ('Memcached' === \get_class($client)) { $opt = $client->getOption(\Memcached::OPT_SERIALIZER); if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); } $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY)); $this->client = $client; } else { $this->lazyClient = $client; } parent::__construct($namespace, $defaultLifetime); $this->enableVersioning(); $this->marshaller = $marshaller ?? new DefaultMarshaller(); } public static function isSupported() { return \extension_loaded('memcached') && \version_compare(\phpversion('memcached'), \PHP_VERSION_ID >= 80100 ? '3.1.6' : '2.2.0', '>='); } /** * Creates a Memcached instance. * * By default, the binary protocol, no block, and libketama compatible options are enabled. * * Examples for servers: * - 'memcached://user:pass@localhost?weight=33' * - [['localhost', 11211, 33]] * * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs * * @return \Memcached * * @throws \ErrorException When invalid options or servers are provided */ public static function createConnection($servers, array $options = []) { if (\is_string($servers)) { $servers = [$servers]; } elseif (!\is_array($servers)) { throw new InvalidArgumentException(\sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', \get_debug_type($servers))); } if (!static::isSupported()) { throw new CacheException('Memcached ' . (\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0') . ' is required.'); } \set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); try { $client = new \Memcached($options['persistent_id'] ?? null); $username = $options['username'] ?? null; $password = $options['password'] ?? null; // parse any DSN in $servers foreach ($servers as $i => $dsn) { if (\is_array($dsn)) { continue; } if (!\str_starts_with($dsn, 'memcached:')) { throw new InvalidArgumentException('Invalid Memcached DSN: it does not start with "memcached:".'); } $params = \preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use(&$username, &$password) { if (!empty($m[2])) { [$username, $password] = \explode(':', $m[2], 2) + [1 => null]; $username = \rawurldecode($username); $password = null !== $password ? \rawurldecode($password) : null; } return 'file:' . ($m[1] ?? ''); }, $dsn); if (\false === ($params = \parse_url($params))) { throw new InvalidArgumentException('Invalid Memcached DSN.'); } $query = $hosts = []; if (isset($params['query'])) { \parse_str($params['query'], $query); if (isset($query['host'])) { if (!\is_array($hosts = $query['host'])) { throw new InvalidArgumentException('Invalid Memcached DSN: query parameter "host" must be an array.'); } foreach ($hosts as $host => $weight) { if (\false === ($port = \strrpos($host, ':'))) { $hosts[$host] = [$host, 11211, (int) $weight]; } else { $hosts[$host] = [\substr($host, 0, $port), (int) \substr($host, 1 + $port), (int) $weight]; } } $hosts = \array_values($hosts); unset($query['host']); } if ($hosts && !isset($params['host']) && !isset($params['path'])) { unset($servers[$i]); $servers = \array_merge($servers, $hosts); continue; } } if (!isset($params['host']) && !isset($params['path'])) { throw new InvalidArgumentException('Invalid Memcached DSN: missing host or path.'); } if (isset($params['path']) && \preg_match('#/(\\d+)$#', $params['path'], $m)) { $params['weight'] = $m[1]; $params['path'] = \substr($params['path'], 0, -\strlen($m[0])); } $params += ['host' => $params['host'] ?? $params['path'], 'port' => isset($params['host']) ? 11211 : null, 'weight' => 0]; if ($query) { $params += $query; $options = $query + $options; } $servers[$i] = [$params['host'], $params['port'], $params['weight']]; if ($hosts) { $servers = \array_merge($servers, $hosts); } } // set client's options unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']); $options = \array_change_key_case($options, \CASE_UPPER); $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, \true); $client->setOption(\Memcached::OPT_NO_BLOCK, \true); $client->setOption(\Memcached::OPT_TCP_NODELAY, \true); if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, \true); } foreach ($options as $name => $value) { if (\is_int($name)) { continue; } if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) { $value = \constant('Memcached::' . $name . '_' . \strtoupper($value)); } unset($options[$name]); if (\defined('Memcached::OPT_' . $name)) { $options[\constant('Memcached::OPT_' . $name)] = $value; } } $client->setOptions($options + [\Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP]); // set client's servers, taking care of persistent connections if (!$client->isPristine()) { $oldServers = []; foreach ($client->getServerList() as $server) { $oldServers[] = [$server['host'], $server['port']]; } $newServers = []; foreach ($servers as $server) { if (1 < \count($server)) { $server = \array_values($server); unset($server[2]); $server[1] = (int) $server[1]; } $newServers[] = $server; } if ($oldServers !== $newServers) { $client->resetServerList(); $client->addServers($servers); } } else { $client->addServers($servers); } if (null !== $username || null !== $password) { if (!\method_exists($client, 'setSaslAuthData')) { \trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); } $client->setSaslAuthData($username, $password); } return $client; } finally { \restore_error_handler(); } } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { if (!($values = $this->marshaller->marshall($values, $failed))) { return $failed; } if ($lifetime && $lifetime > 30 * 86400) { $lifetime += \time(); } $encodedValues = []; foreach ($values as $key => $value) { $encodedValues[self::encodeKey($key)] = $value; } return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : \false; } /** * {@inheritdoc} */ protected function doFetch(array $ids) { try { $encodedIds = \array_map([__CLASS__, 'encodeKey'], $ids); $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds)); $result = []; foreach ($encodedResult as $key => $value) { $result[self::decodeKey($key)] = $this->marshaller->unmarshall($value); } return $result; } catch (\Error $e) { throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); } } /** * {@inheritdoc} */ protected function doHave(string $id) { return \false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); } /** * {@inheritdoc} */ protected function doDelete(array $ids) { $ok = \true; $encodedIds = \array_map([__CLASS__, 'encodeKey'], $ids); foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) { if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) { $ok = \false; } } return $ok; } /** * {@inheritdoc} */ protected function doClear(string $namespace) { return '' === $namespace && $this->getClient()->flush(); } private function checkResultCode($result) { $code = $this->client->getResultCode(); if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) { return $result; } throw new CacheException('MemcachedAdapter client error: ' . \strtolower($this->client->getResultMessage())); } private function getClient() : \Memcached { if ($this->client) { return $this->client; } $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER); if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); } if ('' !== ($prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY))) { throw new CacheException(\sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix)); } return $this->client = $this->lazyClient; } private static function encodeKey(string $key) : string { return \strtr($key, self::RESERVED_MEMCACHED, self::RESERVED_PSR6); } private static function decodeKey(string $key) : string { return \strtr($key, self::RESERVED_PSR6, self::RESERVED_MEMCACHED); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\Traits\RedisClusterProxy; use _ContaoManager\Symfony\Component\Cache\Traits\RedisProxy; use _ContaoManager\Symfony\Component\Cache\Traits\RedisTrait; class RedisAdapter extends AbstractAdapter { use RedisTrait; /** * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client * @param string $namespace The default namespace * @param int $defaultLifetime The default lifetime */ public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { $this->init($redis, $namespace, $defaultLifetime, $marshaller); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; // Help opcache.preload discover always-needed symbols \class_exists(CacheItem::class); /** * Interface for adapters managing instances of Symfony's CacheItem. * * @author Kévin Dunglas */ interface AdapterInterface extends CacheItemPoolInterface { /** * {@inheritdoc} * * @return CacheItem */ public function getItem($key); /** * {@inheritdoc} * * @return \Traversable */ public function getItems(array $keys = []); /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = ''); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\Marshaller\TagAwareMarshaller; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\FilesystemTrait; /** * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls. * * @author Nicolas Grekas * @author André Rømcke */ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface { use FilesystemTrait { doClear as private doClearCache; doSave as private doSaveCache; } /** * Folder used for tag symlinks. */ private const TAG_FOLDER = 'tags'; public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) { $this->marshaller = new TagAwareMarshaller($marshaller); parent::__construct('', $defaultLifetime); $this->init($namespace, $directory); } /** * {@inheritdoc} */ protected function doClear(string $namespace) { $ok = $this->doClearCache($namespace); if ('' !== $namespace) { return $ok; } \set_error_handler(static function () { }); $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; try { foreach ($this->scanHashDir($this->directory . self::TAG_FOLDER . \DIRECTORY_SEPARATOR) as $dir) { if (\rename($dir, $renamed = \substr_replace($dir, \bin2hex(\random_bytes(4)), -8))) { $dir = $renamed . \DIRECTORY_SEPARATOR; } else { $dir .= \DIRECTORY_SEPARATOR; $renamed = null; } for ($i = 0; $i < 38; ++$i) { if (!\is_dir($dir . $chars[$i])) { continue; } for ($j = 0; $j < 38; ++$j) { if (!\is_dir($d = $dir . $chars[$i] . \DIRECTORY_SEPARATOR . $chars[$j])) { continue; } foreach (\scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { if ('.' !== $link && '..' !== $link && (null !== $renamed || !\realpath($d . \DIRECTORY_SEPARATOR . $link))) { \unlink($d . \DIRECTORY_SEPARATOR . $link); } } null === $renamed ?: \rmdir($d); } null === $renamed ?: \rmdir($dir . $chars[$i]); } null === $renamed ?: \rmdir($renamed); } } finally { \restore_error_handler(); } return $ok; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []) : array { $failed = $this->doSaveCache($values, $lifetime); // Add Tags as symlinks foreach ($addTagData as $tagId => $ids) { $tagFolder = $this->getTagFolder($tagId); foreach ($ids as $id) { if ($failed && \in_array($id, $failed, \true)) { continue; } $file = $this->getFile($id); if (!@\symlink($file, $tagLink = $this->getFile($id, \true, $tagFolder)) && !\is_link($tagLink)) { @\unlink($file); $failed[] = $id; } } } // Unlink removed Tags foreach ($removeTagData as $tagId => $ids) { $tagFolder = $this->getTagFolder($tagId); foreach ($ids as $id) { if ($failed && \in_array($id, $failed, \true)) { continue; } @\unlink($this->getFile($id, \false, $tagFolder)); } } return $failed; } /** * {@inheritdoc} */ protected function doDeleteYieldTags(array $ids) : iterable { foreach ($ids as $id) { $file = $this->getFile($id); if (!\is_file($file) || !($h = @\fopen($file, 'r'))) { continue; } if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@\unlink($file)) { \fclose($h); continue; } $meta = \explode("\n", \fread($h, 4096), 3)[2] ?? ''; // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F if (13 < \strlen($meta) && "\x9d" === $meta[0] && "\x00" === $meta[5] && "_" === $meta[9]) { $meta[9] = "\x00"; $tagLen = \unpack('Nlen', $meta, 9)['len']; $meta = \substr($meta, 13, $tagLen); if (0 < ($tagLen -= \strlen($meta))) { $meta .= \fread($h, $tagLen); } try { (yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta)); } catch (\Exception $e) { (yield $id => []); } } \fclose($h); if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) { @\unlink($file); } } } /** * {@inheritdoc} */ protected function doDeleteTagRelations(array $tagData) : bool { foreach ($tagData as $tagId => $idList) { $tagFolder = $this->getTagFolder($tagId); foreach ($idList as $id) { @\unlink($this->getFile($id, \false, $tagFolder)); } } return \true; } /** * {@inheritdoc} */ protected function doInvalidate(array $tagIds) : bool { foreach ($tagIds as $tagId) { if (!\is_dir($tagFolder = $this->getTagFolder($tagId))) { continue; } \set_error_handler(static function () { }); try { if (\rename($tagFolder, $renamed = \substr_replace($tagFolder, \bin2hex(\random_bytes(4)), -9))) { $tagFolder = $renamed . \DIRECTORY_SEPARATOR; } else { $renamed = null; } foreach ($this->scanHashDir($tagFolder) as $itemLink) { \unlink(\realpath($itemLink) ?: $itemLink); \unlink($itemLink); } if (null === $renamed) { continue; } $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; for ($i = 0; $i < 38; ++$i) { for ($j = 0; $j < 38; ++$j) { \rmdir($tagFolder . $chars[$i] . \DIRECTORY_SEPARATOR . $chars[$j]); } \rmdir($tagFolder . $chars[$i]); } \rmdir($renamed); } finally { \restore_error_handler(); } } return \true; } private function getTagFolder(string $tagId) : string { return $this->getFile($tagId, \false, $this->directory . self::TAG_FOLDER . \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Contracts\Cache\TagAwareCacheInterface; /** * @author Robin Chalas */ class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface { public function __construct(TagAwareAdapterInterface $pool) { parent::__construct($pool); } /** * {@inheritdoc} */ public function invalidateTags(array $tags) { $event = $this->start(__FUNCTION__); try { return $event->result = $this->pool->invalidateTags($tags); } finally { $event->end = \microtime(\true); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\SimpleCache\CacheInterface; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\ProxyTrait; /** * Turns a PSR-16 cache into a PSR-6 one. * * @author Nicolas Grekas */ class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface { use ProxyTrait; /** * @internal */ protected const NS_SEPARATOR = '_'; private $miss; public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0) { parent::__construct($namespace, $defaultLifetime); $this->pool = $pool; $this->miss = new \stdClass(); } /** * {@inheritdoc} */ protected function doFetch(array $ids) { foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { if ($this->miss !== $value) { (yield $key => $value); } } } /** * {@inheritdoc} */ protected function doHave(string $id) { return $this->pool->has($id); } /** * {@inheritdoc} */ protected function doClear(string $namespace) { return $this->pool->clear(); } /** * {@inheritdoc} */ protected function doDelete(array $ids) { return $this->pool->deleteMultiple($ids); } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\ContractsTrait; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Chains several adapters together. * * Cached items are fetched from the first adapter having them in its data store. * They are saved and deleted in all adapters at once. * * @author Kévin Dunglas */ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use ContractsTrait; private $adapters = []; private $adapterCount; private $defaultLifetime; private static $syncItem; /** * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones */ public function __construct(array $adapters, int $defaultLifetime = 0) { if (!$adapters) { throw new InvalidArgumentException('At least one adapter must be specified.'); } foreach ($adapters as $adapter) { if (!$adapter instanceof CacheItemPoolInterface) { throw new InvalidArgumentException(\sprintf('The class "%s" does not implement the "%s" interface.', \get_debug_type($adapter), CacheItemPoolInterface::class)); } if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) && $adapter instanceof ApcuAdapter && !\filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { continue; // skip putting APCu in the chain when the backend is disabled } if ($adapter instanceof AdapterInterface) { $this->adapters[] = $adapter; } else { $this->adapters[] = new ProxyAdapter($adapter); } } $this->adapterCount = \count($this->adapters); $this->defaultLifetime = $defaultLifetime; self::$syncItem ?? (self::$syncItem = \Closure::bind(static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { $sourceItem->isTaggable = \false; $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata; unset($sourceMetadata[CacheItem::METADATA_TAGS]); $item->value = $sourceItem->value; $item->isHit = $sourceItem->isHit; $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) { $item->expiresAt(\DateTime::createFromFormat('U.u', \sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY]))); } elseif (0 < $defaultLifetime) { $item->expiresAfter($defaultLifetime); } return $item; }, null, CacheItem::class)); } /** * {@inheritdoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { $doSave = \true; $callback = static function (CacheItem $item, bool &$save) use($callback, &$doSave) { $value = $callback($item, $save); $doSave = $save; return $value; }; $lastItem = null; $i = 0; $wrap = function (?CacheItem $item = null, bool &$save = \true) use($key, $callback, $beta, &$wrap, &$i, &$doSave, &$lastItem, &$metadata) { $adapter = $this->adapters[$i]; if (isset($this->adapters[++$i])) { $callback = $wrap; $beta = \INF === $beta ? \INF : 0; } if ($adapter instanceof CacheInterface) { $value = $adapter->get($key, $callback, $beta, $metadata); } else { $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); } if (null !== $item) { (self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata); } $save = $doSave; return $value; }; return $wrap(); } /** * {@inheritdoc} */ public function getItem($key) { $syncItem = self::$syncItem; $misses = []; foreach ($this->adapters as $i => $adapter) { $item = $adapter->getItem($key); if ($item->isHit()) { while (0 <= --$i) { $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime)); } return $item; } $misses[$i] = $item; } return $item; } /** * {@inheritdoc} */ public function getItems(array $keys = []) { return $this->generateItems($this->adapters[0]->getItems($keys), 0); } private function generateItems(iterable $items, int $adapterIndex) : \Generator { $missing = []; $misses = []; $nextAdapterIndex = $adapterIndex + 1; $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null; foreach ($items as $k => $item) { if (!$nextAdapter || $item->isHit()) { (yield $k => $item); } else { $missing[] = $k; $misses[$k] = $item; } } if ($missing) { $syncItem = self::$syncItem; $adapter = $this->adapters[$adapterIndex]; $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex); foreach ($items as $k => $item) { if ($item->isHit()) { $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime)); } (yield $k => $item); } } } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { foreach ($this->adapters as $adapter) { if ($adapter->hasItem($key)) { return \true; } } return \false; } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { $cleared = \true; $i = $this->adapterCount; while ($i--) { if ($this->adapters[$i] instanceof AdapterInterface) { $cleared = $this->adapters[$i]->clear($prefix) && $cleared; } else { $cleared = $this->adapters[$i]->clear() && $cleared; } } return $cleared; } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { $deleted = \true; $i = $this->adapterCount; while ($i--) { $deleted = $this->adapters[$i]->deleteItem($key) && $deleted; } return $deleted; } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { $deleted = \true; $i = $this->adapterCount; while ($i--) { $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted; } return $deleted; } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { $saved = \true; $i = $this->adapterCount; while ($i--) { $saved = $this->adapters[$i]->save($item) && $saved; } return $saved; } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { $saved = \true; $i = $this->adapterCount; while ($i--) { $saved = $this->adapters[$i]->saveDeferred($item) && $saved; } return $saved; } /** * {@inheritdoc} * * @return bool */ public function commit() { $committed = \true; $i = $this->adapterCount; while ($i--) { $committed = $this->adapters[$i]->commit() && $committed; } return $committed; } /** * {@inheritdoc} */ public function prune() { $pruned = \true; foreach ($this->adapters as $adapter) { if ($adapter instanceof PruneableInterface) { $pruned = $adapter->prune() && $pruned; } } return $pruned; } /** * {@inheritdoc} */ public function reset() { foreach ($this->adapters as $adapter) { if ($adapter instanceof ResetInterface) { $adapter->reset(); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Symfony\Component\Cache\Marshaller\DefaultMarshaller; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\FilesystemTrait; class FilesystemAdapter extends AbstractAdapter implements PruneableInterface { use FilesystemTrait; public function __construct(string $namespace = '', int $defaultLifetime = 0, ?string $directory = null, ?MarshallerInterface $marshaller = null) { $this->marshaller = $marshaller ?? new DefaultMarshaller(); parent::__construct('', $defaultLifetime); $this->init($namespace, $directory); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Doctrine\Common\Cache\CacheProvider; use _ContaoManager\Doctrine\Common\Cache\Psr6\CacheAdapter; /** * @author Nicolas Grekas * * @deprecated Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead */ class DoctrineAdapter extends AbstractAdapter { private $provider; public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0) { \trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "%s" instead.', __CLASS__, CacheAdapter::class); parent::__construct('', $defaultLifetime); $this->provider = $provider; $provider->setNamespace($namespace); } /** * {@inheritdoc} */ public function reset() { parent::reset(); $this->provider->setNamespace($this->provider->getNamespace()); } /** * {@inheritdoc} */ protected function doFetch(array $ids) { $unserializeCallbackHandler = \ini_set('unserialize_callback_func', parent::class . '::handleUnserializeCallback'); try { return $this->provider->fetchMultiple($ids); } catch (\Error $e) { $trace = $e->getTrace(); if (isset($trace[0]['function']) && !isset($trace[0]['class'])) { switch ($trace[0]['function']) { case 'unserialize': case 'apcu_fetch': case 'apc_fetch': throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); } } throw $e; } finally { \ini_set('unserialize_callback_func', $unserializeCallbackHandler); } } /** * {@inheritdoc} */ protected function doHave(string $id) { return $this->provider->contains($id); } /** * {@inheritdoc} */ protected function doClear(string $namespace) { $namespace = $this->provider->getNamespace(); return isset($namespace[0]) ? $this->provider->deleteAll() : $this->provider->flushAll(); } /** * {@inheritdoc} */ protected function doDelete(array $ids) { $ok = \true; foreach ($ids as $id) { $ok = $this->provider->delete($id) && $ok; } return $ok; } /** * {@inheritdoc} */ protected function doSave(array $values, int $lifetime) { return $this->provider->saveMultiple($values, $lifetime); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Log\LoggerAwareInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\AbstractAdapterTrait; use _ContaoManager\Symfony\Component\Cache\Traits\ContractsTrait; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; /** * @author Nicolas Grekas */ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use AbstractAdapterTrait; use ContractsTrait; /** * @internal */ protected const NS_SEPARATOR = ':'; private static $apcuSupported; private static $phpFilesSupported; protected function __construct(string $namespace = '', int $defaultLifetime = 0) { $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace) . static::NS_SEPARATOR; $this->defaultLifetime = $defaultLifetime; if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); } self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; $item->value = $v = $value; $item->isHit = $isHit; // Detect wrapped values that encode for their expiry and creation duration // For compactness, these values are packed in the key of an array using // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) \array_key_first($v)) && "\x9d" === $k[0] && "\x00" === $k[5] && "_" === $k[9]) { $item->value = $v[$k]; $v = \unpack('Ve/Nc', \substr($k, 1, -1)); $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; } return $item; }, null, CacheItem::class)); self::$mergeByLifetime ?? (self::$mergeByLifetime = \Closure::bind(static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) { $byLifetime = []; $now = \microtime(\true); $expiredIds = []; foreach ($deferred as $key => $item) { $key = (string) $key; if (null === $item->expiry) { $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; } elseif (!$item->expiry) { $ttl = 0; } elseif (0 >= ($ttl = (int) (0.1 + $item->expiry - $now))) { $expiredIds[] = $getId($key); continue; } if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { unset($metadata[CacheItem::METADATA_TAGS]); } // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9d" . \pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]) . "_" => $item->value] : $item->value; } return $byLifetime; }, null, CacheItem::class)); } /** * Returns the best possible adapter that your runtime supports. * * Using ApcuAdapter makes system caches compatible with read-only filesystems. * * @return AdapterInterface */ public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, ?LoggerInterface $logger = null) { $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, \true); if (null !== $logger) { $opcache->setLogger($logger); } if (!(self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported())) { return $opcache; } if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) && !\filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { return $opcache; } $apcu = new ApcuAdapter($namespace, \intdiv($defaultLifetime, 5), $version); if (null !== $logger) { $apcu->setLogger($logger); } return new ChainAdapter([$apcu, $opcache]); } public static function createConnection(string $dsn, array $options = []) { if (\str_starts_with($dsn, 'redis:') || \str_starts_with($dsn, 'rediss:')) { return RedisAdapter::createConnection($dsn, $options); } if (\str_starts_with($dsn, 'memcached:')) { return MemcachedAdapter::createConnection($dsn, $options); } if (0 === \strpos($dsn, 'couchbase:')) { if (CouchbaseBucketAdapter::isSupported()) { return CouchbaseBucketAdapter::createConnection($dsn, $options); } return CouchbaseCollectionAdapter::createConnection($dsn, $options); } throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:" nor "couchbase:".'); } /** * {@inheritdoc} * * @return bool */ public function commit() { $ok = \true; $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime); $retry = $this->deferred = []; if ($expiredIds) { try { $this->doDelete($expiredIds); } catch (\Exception $e) { $ok = \false; CacheItem::log($this->logger, 'Failed to delete expired items: ' . $e->getMessage(), ['exception' => $e, 'cache-adapter' => \get_debug_type($this)]); } } foreach ($byLifetime as $lifetime => $values) { try { $e = $this->doSave($values, $lifetime); } catch (\Exception $e) { } if (\true === $e || [] === $e) { continue; } if (\is_array($e) || 1 === \count($values)) { foreach (\is_array($e) ? $e : \array_keys($values) as $id) { $ok = \false; $v = $values[$id]; $type = \get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['key' => \substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => \get_debug_type($this)]); } } else { foreach ($values as $id => $v) { $retry[$lifetime][] = $id; } } } // When bulk-save failed, retry each item individually foreach ($retry as $lifetime => $ids) { foreach ($ids as $id) { try { $v = $byLifetime[$lifetime][$id]; $e = $this->doSave([$id => $v], $lifetime); } catch (\Exception $e) { } if (\true === $e || [] === $e) { continue; } $ok = \false; $type = \get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['key' => \substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => \get_debug_type($this)]); } } return $ok; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Log\LoggerAwareInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\AbstractAdapterTrait; use _ContaoManager\Symfony\Component\Cache\Traits\ContractsTrait; use _ContaoManager\Symfony\Contracts\Cache\TagAwareCacheInterface; /** * Abstract for native TagAware adapters. * * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate(). * * @author Nicolas Grekas * @author André Rømcke * * @internal */ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface { use AbstractAdapterTrait; use ContractsTrait; private const TAGS_PREFIX = "\x00tags\x00"; protected function __construct(string $namespace = '', int $defaultLifetime = 0) { $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace) . ':'; $this->defaultLifetime = $defaultLifetime; if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); } self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; $item->isTaggable = \true; // If structure does not match what we expect return item as is (no value and not a hit) if (!\is_array($value) || !\array_key_exists('value', $value)) { return $item; } $item->isHit = $isHit; // Extract value, tags and meta data from the cache value $item->value = $value['value']; $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? []; if (isset($value['meta'])) { // For compactness these values are packed, & expiry is offset to reduce size $v = \unpack('Ve/Nc', $value['meta']); $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; } return $item; }, null, CacheItem::class)); self::$mergeByLifetime ?? (self::$mergeByLifetime = \Closure::bind(static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) { $byLifetime = []; $now = \microtime(\true); $expiredIds = []; foreach ($deferred as $key => $item) { $key = (string) $key; if (null === $item->expiry) { $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; } elseif (!$item->expiry) { $ttl = 0; } elseif (0 >= ($ttl = (int) (0.1 + $item->expiry - $now))) { $expiredIds[] = $getId($key); continue; } // Store Value and Tags on the cache value if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]]; unset($metadata[CacheItem::METADATA_TAGS]); } else { $value = ['value' => $item->value, 'tags' => []]; } if ($metadata) { // For compactness, expiry and creation duration are packed, using magic numbers as separators $value['meta'] = \pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]); } // Extract tag changes, these should be removed from values in doSave() $value['tag-operations'] = ['add' => [], 'remove' => []]; $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; foreach (\array_diff($value['tags'], $oldTags) as $addedTag) { $value['tag-operations']['add'][] = $getId($tagPrefix . $addedTag); } foreach (\array_diff($oldTags, $value['tags']) as $removedTag) { $value['tag-operations']['remove'][] = $getId($tagPrefix . $removedTag); } $byLifetime[$ttl][$getId($key)] = $value; $item->metadata = $item->newMetadata; } return $byLifetime; }, null, CacheItem::class)); } /** * Persists several cache items immediately. * * @param array $values The values to cache, indexed by their cache identifier * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag * * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not */ protected abstract function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []) : array; /** * Removes multiple items from the pool and their corresponding tags. * * @param array $ids An array of identifiers that should be removed from the pool * * @return bool */ protected abstract function doDelete(array $ids); /** * Removes relations between tags and deleted items. * * @param array $tagData Array of tag => key identifiers that should be removed from the pool */ protected abstract function doDeleteTagRelations(array $tagData) : bool; /** * Invalidates cached items using tags. * * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id */ protected abstract function doInvalidate(array $tagIds) : bool; /** * Delete items and yields the tags they were bound to. */ protected function doDeleteYieldTags(array $ids) : iterable { foreach ($this->doFetch($ids) as $id => $value) { (yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : []); } $this->doDelete($ids); } /** * {@inheritdoc} */ public function commit() : bool { $ok = \true; $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, \Closure::fromCallable([$this, 'getId']), self::TAGS_PREFIX, $this->defaultLifetime); $retry = $this->deferred = []; if ($expiredIds) { // Tags are not cleaned up in this case, however that is done on invalidateTags(). try { $this->doDelete($expiredIds); } catch (\Exception $e) { $ok = \false; CacheItem::log($this->logger, 'Failed to delete expired items: ' . $e->getMessage(), ['exception' => $e, 'cache-adapter' => \get_debug_type($this)]); } } foreach ($byLifetime as $lifetime => $values) { try { $values = $this->extractTagData($values, $addTagData, $removeTagData); $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); } catch (\Exception $e) { } if (\true === $e || [] === $e) { continue; } if (\is_array($e) || 1 === \count($values)) { foreach (\is_array($e) ? $e : \array_keys($values) as $id) { $ok = \false; $v = $values[$id]; $type = \get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['key' => \substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => \get_debug_type($this)]); } } else { foreach ($values as $id => $v) { $retry[$lifetime][] = $id; } } } // When bulk-save failed, retry each item individually foreach ($retry as $lifetime => $ids) { foreach ($ids as $id) { try { $v = $byLifetime[$lifetime][$id]; $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData); $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); } catch (\Exception $e) { } if (\true === $e || [] === $e) { continue; } $ok = \false; $type = \get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['key' => \substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => \get_debug_type($this)]); } } return $ok; } /** * {@inheritdoc} */ public function deleteItems(array $keys) : bool { if (!$keys) { return \true; } $ok = \true; $ids = []; $tagData = []; foreach ($keys as $key) { $ids[$key] = $this->getId($key); unset($this->deferred[$key]); } try { foreach ($this->doDeleteYieldTags(\array_values($ids)) as $id => $tags) { foreach ($tags as $tag) { $tagData[$this->getId(self::TAGS_PREFIX . $tag)][] = $id; } } } catch (\Exception $e) { $ok = \false; } try { if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) { return \true; } } catch (\Exception $e) { } // When bulk-delete failed, retry each item individually foreach ($ids as $key => $id) { try { $e = null; if ($this->doDelete([$id])) { continue; } } catch (\Exception $e) { } $message = 'Failed to delete key "{key}"' . ($e instanceof \Exception ? ': ' . $e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => \get_debug_type($this)]); $ok = \false; } return $ok; } /** * {@inheritdoc} */ public function invalidateTags(array $tags) { if (empty($tags)) { return \false; } $tagIds = []; foreach (\array_unique($tags) as $tag) { $tagIds[] = $this->getId(self::TAGS_PREFIX . $tag); } try { if ($this->doInvalidate($tagIds)) { return \true; } } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to invalidate tags: ' . $e->getMessage(), ['exception' => $e, 'cache-adapter' => \get_debug_type($this)]); } return \false; } /** * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it. */ private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData) : array { $addTagData = $removeTagData = []; foreach ($values as $id => $value) { foreach ($value['tag-operations']['add'] as $tag => $tagId) { $addTagData[$tagId][] = $id; } foreach ($value['tag-operations']['remove'] as $tag => $tagId) { $removeTagData[$tagId][] = $id; } unset($values[$id]['tag-operations']); } return $values; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Adapter; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\Cache\ResettableInterface; use _ContaoManager\Symfony\Component\Cache\Traits\ContractsTrait; use _ContaoManager\Symfony\Component\Cache\Traits\ProxyTrait; use _ContaoManager\Symfony\Component\VarExporter\VarExporter; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; /** * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. * * @author Titouan Galopin * @author Nicolas Grekas */ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use ContractsTrait; use ProxyTrait; private $file; private $keys; private $values; private static $createCacheItem; private static $valuesCache = []; /** * @param string $file The PHP file were values are cached * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit */ public function __construct(string $file, AdapterInterface $fallbackPool) { $this->file = $file; $this->pool = $fallbackPool; self::$createCacheItem ?? (self::$createCacheItem = \Closure::bind(static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; $item->value = $value; $item->isHit = $isHit; return $item; }, null, CacheItem::class)); } /** * This adapter takes advantage of how PHP stores arrays in its latest versions. * * @param string $file The PHP file were values are cached * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit * * @return CacheItemPoolInterface */ public static function create(string $file, CacheItemPoolInterface $fallbackPool) { if (!$fallbackPool instanceof AdapterInterface) { $fallbackPool = new ProxyAdapter($fallbackPool); } return new static($file, $fallbackPool); } /** * {@inheritdoc} */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null) { if (null === $this->values) { $this->initialize(); } if (!isset($this->keys[$key])) { get_from_pool: if ($this->pool instanceof CacheInterface) { return $this->pool->get($key, $callback, $beta, $metadata); } return $this->doGet($this->pool, $key, $callback, $beta, $metadata); } $value = $this->values[$this->keys[$key]]; if ('N;' === $value) { return null; } try { if ($value instanceof \Closure) { return $value(); } } catch (\Throwable $e) { unset($this->keys[$key]); goto get_from_pool; } return $value; } /** * {@inheritdoc} */ public function getItem($key) { if (!\is_string($key)) { throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', \get_debug_type($key))); } if (null === $this->values) { $this->initialize(); } if (!isset($this->keys[$key])) { return $this->pool->getItem($key); } $value = $this->values[$this->keys[$key]]; $isHit = \true; if ('N;' === $value) { $value = null; } elseif ($value instanceof \Closure) { try { $value = $value(); } catch (\Throwable $e) { $value = null; $isHit = \false; } } return (self::$createCacheItem)($key, $value, $isHit); } /** * {@inheritdoc} */ public function getItems(array $keys = []) { foreach ($keys as $key) { if (!\is_string($key)) { throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', \get_debug_type($key))); } } if (null === $this->values) { $this->initialize(); } return $this->generateItems($keys); } /** * {@inheritdoc} * * @return bool */ public function hasItem($key) { if (!\is_string($key)) { throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', \get_debug_type($key))); } if (null === $this->values) { $this->initialize(); } return isset($this->keys[$key]) || $this->pool->hasItem($key); } /** * {@inheritdoc} * * @return bool */ public function deleteItem($key) { if (!\is_string($key)) { throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', \get_debug_type($key))); } if (null === $this->values) { $this->initialize(); } return !isset($this->keys[$key]) && $this->pool->deleteItem($key); } /** * {@inheritdoc} * * @return bool */ public function deleteItems(array $keys) { $deleted = \true; $fallbackKeys = []; foreach ($keys as $key) { if (!\is_string($key)) { throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', \get_debug_type($key))); } if (isset($this->keys[$key])) { $deleted = \false; } else { $fallbackKeys[] = $key; } } if (null === $this->values) { $this->initialize(); } if ($fallbackKeys) { $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted; } return $deleted; } /** * {@inheritdoc} * * @return bool */ public function save(CacheItemInterface $item) { if (null === $this->values) { $this->initialize(); } return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); } /** * {@inheritdoc} * * @return bool */ public function saveDeferred(CacheItemInterface $item) { if (null === $this->values) { $this->initialize(); } return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); } /** * {@inheritdoc} * * @return bool */ public function commit() { return $this->pool->commit(); } /** * {@inheritdoc} * * @return bool */ public function clear(string $prefix = '') { $this->keys = $this->values = []; $cleared = @\unlink($this->file) || !\file_exists($this->file); unset(self::$valuesCache[$this->file]); if ($this->pool instanceof AdapterInterface) { return $this->pool->clear($prefix) && $cleared; } return $this->pool->clear() && $cleared; } /** * Store an array of cached values. * * @param array $values The cached values * * @return string[] A list of classes to preload on PHP 7.4+ */ public function warmUp(array $values) { if (\file_exists($this->file)) { if (!\is_file($this->file)) { throw new InvalidArgumentException(\sprintf('Cache path exists and is not a file: "%s".', $this->file)); } if (!\is_writable($this->file)) { throw new InvalidArgumentException(\sprintf('Cache file is not writable: "%s".', $this->file)); } } else { $directory = \dirname($this->file); if (!\is_dir($directory) && !@\mkdir($directory, 0777, \true)) { throw new InvalidArgumentException(\sprintf('Cache directory does not exist and cannot be created: "%s".', $directory)); } if (!\is_writable($directory)) { throw new InvalidArgumentException(\sprintf('Cache directory is not writable: "%s".', $directory)); } } $preload = []; $dumpedValues = ''; $dumpedMap = []; $dump = <<<'EOF' $value) { CacheItem::validateKey(\is_int($key) ? (string) $key : $key); $isStaticValue = \true; if (null === $value) { $value = "'N;'"; } elseif (\is_object($value) || \is_array($value)) { try { $value = VarExporter::export($value, $isStaticValue, $preload); } catch (\Exception $e) { throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \get_debug_type($value)), 0, $e); } } elseif (\is_string($value)) { // Wrap "N;" in a closure to not confuse it with an encoded `null` if ('N;' === $value) { $isStaticValue = \false; } $value = \var_export($value, \true); } elseif (!\is_scalar($value)) { throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \get_debug_type($value))); } else { $value = \var_export($value, \true); } if (!$isStaticValue) { $value = \str_replace("\n", "\n ", $value); $value = "static function () {\n return {$value};\n}"; } $hash = \hash('md5', $value); if (null === ($id = $dumpedMap[$hash] ?? null)) { $id = $dumpedMap[$hash] = \count($dumpedMap); $dumpedValues .= "{$id} => {$value},\n"; } $dump .= \var_export($key, \true) . " => {$id},\n"; } $dump .= "\n], [\n\n{$dumpedValues}\n]];\n"; $tmpFile = \uniqid($this->file, \true); \file_put_contents($tmpFile, $dump); @\chmod($tmpFile, 0666 & ~\umask()); unset($serialized, $value, $dump); @\rename($tmpFile, $this->file); unset(self::$valuesCache[$this->file]); $this->initialize(); return $preload; } /** * Load the cache file. */ private function initialize() { if (isset(self::$valuesCache[$this->file])) { $values = self::$valuesCache[$this->file]; } elseif (!\is_file($this->file)) { $this->keys = $this->values = []; return; } else { $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; } if (2 !== \count($values) || !isset($values[0], $values[1])) { $this->keys = $this->values = []; } else { [$this->keys, $this->values] = $values; } } private function generateItems(array $keys) : \Generator { $f = self::$createCacheItem; $fallbackKeys = []; foreach ($keys as $key) { if (isset($this->keys[$key])) { $value = $this->values[$this->keys[$key]]; if ('N;' === $value) { (yield $key => $f($key, null, \true)); } elseif ($value instanceof \Closure) { try { (yield $key => $f($key, $value(), \true)); } catch (\Throwable $e) { (yield $key => $f($key, null, \false)); } } else { (yield $key => $f($key, $value, \true)); } } else { $fallbackKeys[] = $key; } } if ($fallbackKeys) { yield from $this->pool->getItems($fallbackKeys); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Contracts\Cache\CacheInterface; use _ContaoManager\Symfony\Contracts\Cache\ItemInterface; /** * LockRegistry is used internally by existing adapters to protect against cache stampede. * * It does so by wrapping the computation of items in a pool of locks. * Foreach each apps, there can be at most 20 concurrent processes that * compute items at the same time and only one per cache-key. * * @author Nicolas Grekas */ final class LockRegistry { private static $openedFiles = []; private static $lockedFiles; private static $signalingException; private static $signalingCallback; /** * The number of items in this list controls the max number of concurrent processes. */ private static $files = [__DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'AbstractAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'AbstractTagAwareAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'AdapterInterface.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'ApcuAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'ArrayAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'ChainAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'CouchbaseBucketAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'CouchbaseCollectionAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'DoctrineAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'DoctrineDbalAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'FilesystemAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'FilesystemTagAwareAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'MemcachedAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'NullAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'ParameterNormalizer.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'PdoAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'PhpArrayAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'PhpFilesAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'ProxyAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'Psr16Adapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'RedisAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'RedisTagAwareAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'TagAwareAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'TagAwareAdapterInterface.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'TraceableAdapter.php', __DIR__ . \DIRECTORY_SEPARATOR . 'Adapter' . \DIRECTORY_SEPARATOR . 'TraceableTagAwareAdapter.php']; /** * Defines a set of existing files that will be used as keys to acquire locks. * * @return array The previously defined set of files */ public static function setFiles(array $files) : array { $previousFiles = self::$files; self::$files = $files; foreach (self::$openedFiles as $file) { if ($file) { \flock($file, \LOCK_UN); \fclose($file); } } self::$openedFiles = self::$lockedFiles = []; return $previousFiles; } public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null) { if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { // disable locking on Windows by default self::$files = self::$lockedFiles = []; } $key = self::$files ? \abs(\crc32($item->getKey())) % \count(self::$files) : -1; if ($key < 0 || self::$lockedFiles || !($lock = self::open($key))) { return $callback($item, $save); } self::$signalingException ?? (self::$signalingException = \unserialize("O:9:\"Exception\":1:{s:16:\"\x00Exception\x00trace\";a:0:{}}")); self::$signalingCallback ?? (self::$signalingCallback = function () { throw self::$signalingException; }); while (\true) { try { $locked = \false; // race to get the lock in non-blocking mode $locked = \flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); if ($locked || !$wouldBlock) { $logger && $logger->info(\sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); self::$lockedFiles[$key] = \true; $value = $callback($item, $save); if ($save) { if ($setMetadata) { $setMetadata($item); } $pool->save($item->set($value)); $save = \false; } return $value; } // if we failed the race, retry locking in blocking mode to wait for the winner $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); \flock($lock, \LOCK_SH); } finally { \flock($lock, \LOCK_UN); unset(self::$lockedFiles[$key]); } try { $value = $pool->get($item->getKey(), self::$signalingCallback, 0); $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); $save = \false; return $value; } catch (\Exception $e) { if (self::$signalingException !== $e) { throw $e; } $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); } } return null; } private static function open(int $key) { if (null !== ($h = self::$openedFiles[$key] ?? null)) { return $h; } \set_error_handler(function () { }); try { $h = \fopen(self::$files[$key], 'r+'); } finally { \restore_error_handler(); } return self::$openedFiles[$key] = $h ?: @\fopen(self::$files[$key], 'r'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Resets a pool's local state. */ interface ResettableInterface extends ResetInterface { } Symfony PSR-6 implementation for caching ======================================== The Cache component provides extended [PSR-6](https://www.php-fig.org/psr/psr-6/) implementations for adding cache to your applications. It is designed to have a low overhead so that caching is fastest. It ships with adapters for the most widespread caching backends. It also provides a [PSR-16](https://www.php-fig.org/psr/psr-16/) adapter, and implementations for [symfony/cache-contracts](https://github.com/symfony/cache-contracts)' `CacheInterface` and `TagAwareCacheInterface`. Resources --------- * [Documentation](https://symfony.com/doc/current/components/cache.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Messenger; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\DependencyInjection\ReverseContainer; use _ContaoManager\Symfony\Component\Messenger\Handler\MessageHandlerInterface; /** * Computes cached values sent to a message bus. */ class EarlyExpirationHandler implements MessageHandlerInterface { private $reverseContainer; private $processedNonces = []; public function __construct(ReverseContainer $reverseContainer) { $this->reverseContainer = $reverseContainer; } public function __invoke(EarlyExpirationMessage $message) { $item = $message->getItem(); $metadata = $item->getMetadata(); $expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? 0; $ctime = $metadata[CacheItem::METADATA_CTIME] ?? 0; if ($expiry && $ctime) { // skip duplicate or expired messages $processingNonce = [$expiry, $ctime]; $pool = $message->getPool(); $key = $item->getKey(); if (($this->processedNonces[$pool][$key] ?? null) === $processingNonce) { return; } if (\microtime(\true) >= $expiry) { return; } $this->processedNonces[$pool] = [$key => $processingNonce] + ($this->processedNonces[$pool] ?? []); if (\count($this->processedNonces[$pool]) > 100) { \array_pop($this->processedNonces[$pool]); } } static $setMetadata; $setMetadata ?? ($setMetadata = \Closure::bind(function (CacheItem $item, float $startTime) { if ($item->expiry > ($endTime = \microtime(\true))) { $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; $item->newMetadata[CacheItem::METADATA_CTIME] = (int) \ceil(1000 * ($endTime - $startTime)); } }, null, CacheItem::class)); $startTime = \microtime(\true); $pool = $message->findPool($this->reverseContainer); $callback = $message->findCallback($this->reverseContainer); $save = \true; $value = $callback($item, $save); $setMetadata($item, $startTime); $pool->save($item->set($value)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Messenger; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\DependencyInjection\ReverseContainer; /** * Conveys a cached value that needs to be computed. */ final class EarlyExpirationMessage { private $item; private $pool; private $callback; public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool) : ?self { try { $item = clone $item; $item->set(null); } catch (\Exception $e) { return null; } $pool = $reverseContainer->getId($pool); if (\is_object($callback)) { if (null === ($id = $reverseContainer->getId($callback))) { return null; } $callback = '@' . $id; } elseif (!\is_array($callback)) { $callback = (string) $callback; } elseif (!\is_object($callback[0])) { $callback = [(string) $callback[0], (string) $callback[1]]; } else { if (null === ($id = $reverseContainer->getId($callback[0]))) { return null; } $callback = ['@' . $id, (string) $callback[1]]; } return new self($item, $pool, $callback); } public function getItem() : CacheItem { return $this->item; } public function getPool() : string { return $this->pool; } public function getCallback() { return $this->callback; } public function findPool(ReverseContainer $reverseContainer) : AdapterInterface { return $reverseContainer->getService($this->pool); } public function findCallback(ReverseContainer $reverseContainer) : callable { if (\is_string($callback = $this->callback)) { return '@' === $callback[0] ? $reverseContainer->getService(\substr($callback, 1)) : $callback; } if ('@' === $callback[0][0]) { $callback[0] = $reverseContainer->getService(\substr($callback[0], 1)); } return $callback; } private function __construct(CacheItem $item, string $pool, $callback) { $this->item = $item; $this->pool = $pool; $this->callback = $callback; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Messenger; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\CacheItem; use _ContaoManager\Symfony\Component\DependencyInjection\ReverseContainer; use _ContaoManager\Symfony\Component\Messenger\MessageBusInterface; use _ContaoManager\Symfony\Component\Messenger\Stamp\HandledStamp; /** * Sends the computation of cached values to a message bus. */ class EarlyExpirationDispatcher { private $bus; private $reverseContainer; private $callbackWrapper; public function __construct(MessageBusInterface $bus, ReverseContainer $reverseContainer, ?callable $callbackWrapper = null) { $this->bus = $bus; $this->reverseContainer = $reverseContainer; $this->callbackWrapper = $callbackWrapper; } public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null) { if (!$item->isHit() || null === ($message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool))) { // The item is stale or the callback cannot be reversed: we must compute the value now $logger && $logger->info('Computing item "{key}" online: ' . ($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save); } $envelope = $this->bus->dispatch($message); if ($logger) { if ($envelope->last(HandledStamp::class)) { $logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]); } else { $logger->info('Item "{key}" sent for recomputation', ['key' => $item->getKey()]); } } // The item's value is not stale, no need to write it to the backend $save = \false; return $message->getItem()->get() ?? $item->get(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Marshaller; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; /** * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise. * * @author Nicolas Grekas */ class DefaultMarshaller implements MarshallerInterface { private $useIgbinarySerialize = \true; private $throwOnSerializationFailure; public function __construct(?bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = \false) { if (null === $useIgbinarySerialize) { $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || \version_compare('3.1.6', \phpversion('igbinary'), '<=')); } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || \PHP_VERSION_ID >= 70400 && \version_compare('3.1.6', \phpversion('igbinary'), '>'))) { throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.'); } $this->useIgbinarySerialize = $useIgbinarySerialize; $this->throwOnSerializationFailure = $throwOnSerializationFailure; } /** * {@inheritdoc} */ public function marshall(array $values, ?array &$failed) : array { $serialized = $failed = []; foreach ($values as $id => $value) { try { if ($this->useIgbinarySerialize) { $serialized[$id] = \igbinary_serialize($value); } else { $serialized[$id] = \serialize($value); } } catch (\Exception $e) { if ($this->throwOnSerializationFailure) { throw new \ValueError($e->getMessage(), 0, $e); } $failed[] = $id; } } return $serialized; } /** * {@inheritdoc} */ public function unmarshall(string $value) { if ('b:0;' === $value) { return \false; } if ('N;' === $value) { return null; } static $igbinaryNull; if ($value === ($igbinaryNull ?? ($igbinaryNull = \extension_loaded('igbinary') ? \igbinary_serialize(null) : \false))) { return null; } $unserializeCallbackHandler = \ini_set('unserialize_callback_func', __CLASS__ . '::handleUnserializeCallback'); try { if (':' === ($value[1] ?? ':')) { if (\false !== ($value = \unserialize($value))) { return $value; } } elseif (\false === $igbinaryNull) { throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?'); } elseif (null !== ($value = \igbinary_unserialize($value))) { return $value; } throw new \DomainException(\error_get_last() ? \error_get_last()['message'] : 'Failed to unserialize values.'); } catch (\Error $e) { throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); } finally { \ini_set('unserialize_callback_func', $unserializeCallbackHandler); } } /** * @internal */ public static function handleUnserializeCallback(string $class) { throw new \DomainException('Class not found: ' . $class); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Marshaller; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; /** * Compresses values using gzdeflate(). * * @author Nicolas Grekas */ class DeflateMarshaller implements MarshallerInterface { private $marshaller; public function __construct(MarshallerInterface $marshaller) { if (!\function_exists('gzdeflate')) { throw new CacheException('The "zlib" PHP extension is not loaded.'); } $this->marshaller = $marshaller; } /** * {@inheritdoc} */ public function marshall(array $values, ?array &$failed) : array { return \array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); } /** * {@inheritdoc} */ public function unmarshall(string $value) { if (\false !== ($inflatedValue = @\gzinflate($value))) { $value = $inflatedValue; } return $this->marshaller->unmarshall($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Marshaller; /** * Serializes/unserializes PHP values. * * Implementations of this interface MUST deal with errors carefully. They MUST * also deal with forward and backward compatibility at the storage format level. * * @author Nicolas Grekas */ interface MarshallerInterface { /** * Serializes a list of values. * * When serialization fails for a specific value, no exception should be * thrown. Instead, its key should be listed in $failed. */ public function marshall(array $values, ?array &$failed) : array; /** * Unserializes a single value and throws an exception if anything goes wrong. * * @return mixed * * @throws \Exception Whenever unserialization fails */ public function unmarshall(string $value); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Marshaller; /** * A marshaller optimized for data structures generated by AbstractTagAwareAdapter. * * @author Nicolas Grekas */ class TagAwareMarshaller implements MarshallerInterface { private $marshaller; public function __construct(?MarshallerInterface $marshaller = null) { $this->marshaller = $marshaller ?? new DefaultMarshaller(); } /** * {@inheritdoc} */ public function marshall(array $values, ?array &$failed) : array { $failed = $notSerialized = $serialized = []; foreach ($values as $id => $value) { if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) { // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall() $v = $this->marshaller->marshall($value, $f); if ($f) { $f = []; $failed[] = $id; } else { if ([] === $value['tags']) { $v['tags'] = ''; } $serialized[$id] = "\x9d" . ($value['meta'] ?? "\x00\x00\x00\x00\x00\x00\x00\x00") . \pack('N', \strlen($v['tags'])) . $v['tags'] . $v['value']; $serialized[$id][9] = "_"; } } else { // other arbitrary values are serialized using the decorated marshaller below $notSerialized[$id] = $value; } } if ($notSerialized) { $serialized += $this->marshaller->marshall($notSerialized, $f); $failed = \array_merge($failed, $f); } return $serialized; } /** * {@inheritdoc} */ public function unmarshall(string $value) { // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F if (13 >= \strlen($value) || "\x9d" !== $value[0] || "\x00" !== $value[5] || "_" !== $value[9]) { return $this->marshaller->unmarshall($value); } // data consists of value, tags and metadata which we need to unpack $meta = \substr($value, 1, 12); $meta[8] = "\x00"; $tagLen = \unpack('Nlen', $meta, 8)['len']; $meta = \substr($meta, 0, 8); return ['value' => $this->marshaller->unmarshall(\substr($value, 13 + $tagLen)), 'tags' => $tagLen ? $this->marshaller->unmarshall(\substr($value, 13, $tagLen)) : [], 'meta' => "\x00\x00\x00\x00\x00\x00\x00\x00" === $meta ? null : $meta]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Marshaller; use _ContaoManager\Symfony\Component\Cache\Exception\CacheException; use _ContaoManager\Symfony\Component\Cache\Exception\InvalidArgumentException; /** * Encrypt/decrypt values using Libsodium. * * @author Ahmed TAILOULOUTE */ class SodiumMarshaller implements MarshallerInterface { private $marshaller; private $decryptionKeys; /** * @param string[] $decryptionKeys The key at index "0" is required and is used to decrypt and encrypt values; * more rotating keys can be provided to decrypt values; * each key must be generated using sodium_crypto_box_keypair() */ public function __construct(array $decryptionKeys, ?MarshallerInterface $marshaller = null) { if (!self::isSupported()) { throw new CacheException('The "sodium" PHP extension is not loaded.'); } if (!isset($decryptionKeys[0])) { throw new InvalidArgumentException('At least one decryption key must be provided at index "0".'); } $this->marshaller = $marshaller ?? new DefaultMarshaller(); $this->decryptionKeys = $decryptionKeys; } public static function isSupported() : bool { return \function_exists('sodium_crypto_box_seal'); } /** * {@inheritdoc} */ public function marshall(array $values, ?array &$failed) : array { $encryptionKey = \sodium_crypto_box_publickey($this->decryptionKeys[0]); $encryptedValues = []; foreach ($this->marshaller->marshall($values, $failed) as $k => $v) { $encryptedValues[$k] = \sodium_crypto_box_seal($v, $encryptionKey); } return $encryptedValues; } /** * {@inheritdoc} */ public function unmarshall(string $value) { foreach ($this->decryptionKeys as $k) { if (\false !== ($decryptedValue = @\sodium_crypto_box_seal_open($value, $k))) { $value = $decryptedValue; break; } } return $this->marshaller->unmarshall($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\DependencyInjection; use _ContaoManager\Symfony\Component\Cache\Adapter\AbstractAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ArrayAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ChainAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\NullAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\ParameterNormalizer; use _ContaoManager\Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Nicolas Grekas */ class CachePoolPass implements CompilerPassInterface { private $cachePoolTag; private $kernelResetTag; private $cacheClearerId; private $cachePoolClearerTag; private $cacheSystemClearerId; private $cacheSystemClearerTag; private $reverseContainerId; private $reversibleTag; private $messageHandlerId; public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer', string $reverseContainerId = 'reverse_container', string $reversibleTag = 'container.reversible', string $messageHandlerId = 'cache.early_expiration_handler') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->cachePoolTag = $cachePoolTag; $this->kernelResetTag = $kernelResetTag; $this->cacheClearerId = $cacheClearerId; $this->cachePoolClearerTag = $cachePoolClearerTag; $this->cacheSystemClearerId = $cacheSystemClearerId; $this->cacheSystemClearerTag = $cacheSystemClearerTag; $this->reverseContainerId = $reverseContainerId; $this->reversibleTag = $reversibleTag; $this->messageHandlerId = $messageHandlerId; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if ($container->hasParameter('cache.prefix.seed')) { $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); } else { $seed = '_' . $container->getParameter('kernel.project_dir'); $seed .= '.' . $container->getParameter('kernel.container_class'); } $needsMessageHandler = \false; $allPools = []; $clearers = []; $attributes = ['provider', 'name', 'namespace', 'default_lifetime', 'early_expiration_message_bus', 'reset']; foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { $adapter = $pool = $container->getDefinition($id); if ($pool->isAbstract()) { continue; } $class = $adapter->getClass(); while ($adapter instanceof ChildDefinition) { $adapter = $container->findDefinition($adapter->getParent()); $class = $class ?: $adapter->getClass(); if ($t = $adapter->getTag($this->cachePoolTag)) { $tags[0] += $t[0]; } } $name = $tags[0]['name'] ?? $id; if (!isset($tags[0]['namespace'])) { $namespaceSeed = $seed; if (null !== $class) { $namespaceSeed .= '.' . $class; } $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name); } if (isset($tags[0]['clearer'])) { $clearer = $tags[0]['clearer']; while ($container->hasAlias($clearer)) { $clearer = (string) $container->getAlias($clearer); } } else { $clearer = null; } unset($tags[0]['clearer'], $tags[0]['name']); if (isset($tags[0]['provider'])) { $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); } if (ChainAdapter::class === $class) { $adapters = []; foreach ($adapter->getArgument(0) as $provider => $adapter) { if ($adapter instanceof ChildDefinition) { $chainedPool = $adapter; } else { $chainedPool = $adapter = new ChildDefinition($adapter); } $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]]; $chainedClass = ''; while ($adapter instanceof ChildDefinition) { $adapter = $container->findDefinition($adapter->getParent()); $chainedClass = $chainedClass ?: $adapter->getClass(); if ($t = $adapter->getTag($this->cachePoolTag)) { $chainedTags[0] += $t[0]; } } if (ChainAdapter::class === $chainedClass) { throw new InvalidArgumentException(\sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent())); } $i = 0; if (isset($chainedTags[0]['provider'])) { $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider']))); } if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], \true)) { $chainedPool->replaceArgument($i++, $tags[0]['namespace']); } if (isset($tags[0]['default_lifetime'])) { $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']); } $adapters[] = $chainedPool; } $pool->replaceArgument(0, $adapters); unset($tags[0]['provider'], $tags[0]['namespace']); $i = 1; } else { $i = 0; } foreach ($attributes as $attr) { if (!isset($tags[0][$attr])) { // no-op } elseif ('reset' === $attr) { if ($tags[0][$attr]) { $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]); } } elseif ('early_expiration_message_bus' === $attr) { $needsMessageHandler = \true; $pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class))->addArgument(new Reference($tags[0]['early_expiration_message_bus']))->addArgument(new Reference($this->reverseContainerId))->addArgument((new Definition('callable'))->setFactory([new Reference($id), 'setCallbackWrapper'])->addArgument(null))]); $pool->addTag($this->reversibleTag); } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], \true)) { $argument = $tags[0][$attr]; if ('default_lifetime' === $attr && !\is_numeric($argument)) { $argument = (new Definition('int', [$argument]))->setFactory([ParameterNormalizer::class, 'normalizeDuration']); } $pool->replaceArgument($i++, $argument); } unset($tags[0][$attr]); } if (!empty($tags[0])) { throw new InvalidArgumentException(\sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $this->cachePoolTag, $id, \implode('", "', \array_keys($tags[0])))); } if (null !== $clearer) { $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } if (!$needsMessageHandler) { $container->removeDefinition($this->messageHandlerId); } $notAliasedCacheClearerId = $this->cacheClearerId; while ($container->hasAlias($notAliasedCacheClearerId)) { $notAliasedCacheClearerId = (string) $container->getAlias($notAliasedCacheClearerId); } if ($container->hasDefinition($notAliasedCacheClearerId)) { $clearers[$notAliasedCacheClearerId] = $allPools; } foreach ($clearers as $id => $pools) { $clearer = $container->getDefinition($id); if ($clearer instanceof ChildDefinition) { $clearer->replaceArgument(0, $pools); } else { $clearer->setArgument(0, $pools); } $clearer->addTag($this->cachePoolClearerTag); if ($this->cacheSystemClearerId === $id) { $clearer->addTag($this->cacheSystemClearerTag); } } $allPoolsKeys = \array_keys($allPools); if ($container->hasDefinition('console.command.cache_pool_list')) { $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, $allPoolsKeys); } if ($container->hasDefinition('console.command.cache_pool_clear')) { $container->getDefinition('console.command.cache_pool_clear')->addArgument($allPoolsKeys); } if ($container->hasDefinition('console.command.cache_pool_delete')) { $container->getDefinition('console.command.cache_pool_delete')->addArgument($allPoolsKeys); } } private function getNamespace(string $seed, string $id) { return \substr(\str_replace('/', '-', \base64_encode(\hash('sha256', $id . $seed, \true))), 0, 10); } /** * @internal */ public static function getServiceProvider(ContainerBuilder $container, string $name) { $container->resolveEnvPlaceholders($name, null, $usedEnvs); if ($usedEnvs || \preg_match('#^[a-z]++:#', $name)) { $dsn = $name; if (!$container->hasDefinition($name = '.cache_connection.' . ContainerBuilder::hash($dsn))) { $definition = new Definition(AbstractAdapter::class); $definition->setPublic(\false); $definition->setFactory([AbstractAdapter::class, 'createConnection']); $definition->setArguments([$dsn, ['lazy' => \true]]); $container->setDefinition($name, $definition); } } return $name; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\DependencyInjection; use _ContaoManager\Symfony\Component\Cache\PruneableInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Rob Frawley 2nd */ class CachePoolPrunerPass implements CompilerPassInterface { private $cacheCommandServiceId; private $cachePoolTag; public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->cacheCommandServiceId = $cacheCommandServiceId; $this->cachePoolTag = $cachePoolTag; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->cacheCommandServiceId)) { return; } $services = []; foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass()); if (!($reflection = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if ($reflection->implementsInterface(PruneableInterface::class)) { $services[$id] = new Reference($id); } } $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\DependencyInjection; use _ContaoManager\Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\TraceableAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Inject a data collector to all the cache services to be able to get detailed statistics. * * @author Tobias Nyholm */ class CacheCollectorPass implements CompilerPassInterface { private $dataCollectorCacheId; private $cachePoolTag; private $cachePoolRecorderInnerSuffix; public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->dataCollectorCacheId = $dataCollectorCacheId; $this->cachePoolTag = $cachePoolTag; $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dataCollectorCacheId)) { return; } foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) { $poolName = $attributes[0]['name'] ?? $id; $this->addToCollector($id, $poolName, $container); } } private function addToCollector(string $id, string $name, ContainerBuilder $container) { $definition = $container->getDefinition($id); if ($definition->isAbstract()) { return; } $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId); $recorder = new Definition(\is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); $recorder->setTags($definition->getTags()); if (!$definition->isPublic() || !$definition->isPrivate()) { $recorder->setPublic($definition->isPublic()); } $recorder->setArguments([new Reference($innerId = $id . $this->cachePoolRecorderInnerSuffix)]); foreach ($definition->getMethodCalls() as [$method, $args]) { if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) { continue; } if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) { $args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']); } } $definition->setTags([]); $definition->setPublic(\false); $container->setDefinition($innerId, $definition); $container->setDefinition($id, $recorder); // Tell the collector to add the new instance $collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]); $collectorDefinition->setPublic(\false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Nicolas Grekas */ class CachePoolClearerPass implements CompilerPassInterface { private $cachePoolClearerTag; public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->cachePoolClearerTag = $cachePoolClearerTag; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $container->getParameterBag()->remove('cache.prefix.seed'); foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) { $clearer = $container->getDefinition($id); $pools = []; foreach ($clearer->getArgument(0) as $name => $ref) { if ($container->hasDefinition($ref)) { $pools[$name] = new Reference($ref); } } $clearer->replaceArgument(0, $pools); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Exception; use _ContaoManager\Psr\Cache\CacheException as Psr6CacheInterface; use _ContaoManager\Psr\SimpleCache\CacheException as SimpleCacheInterface; if (\interface_exists(SimpleCacheInterface::class)) { class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface { } } else { class LogicException extends \LogicException implements Psr6CacheInterface { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Exception; use _ContaoManager\Psr\Cache\InvalidArgumentException as Psr6CacheInterface; use _ContaoManager\Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; if (\interface_exists(SimpleCacheInterface::class)) { class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface { } } else { class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Cache\Exception; use _ContaoManager\Psr\Cache\CacheException as Psr6CacheInterface; use _ContaoManager\Psr\SimpleCache\CacheException as SimpleCacheInterface; if (\interface_exists(SimpleCacheInterface::class)) { class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface { } } else { class CacheException extends \Exception implements Psr6CacheInterface { } } { "name": "symfony\/cache", "type": "library", "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "keywords": [ "caching", "psr6" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "provide": { "psr\/cache-implementation": "1.0|2.0", "psr\/simple-cache-implementation": "1.0|2.0", "symfony\/cache-implementation": "1.0|2.0" }, "require": { "php": ">=7.2.5", "psr\/cache": "^1.0|^2.0", "psr\/log": "^1.1|^2|^3", "symfony\/cache-contracts": "^1.1.7|^2", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php73": "^1.9", "symfony\/polyfill-php80": "^1.16", "symfony\/service-contracts": "^1.1|^2|^3", "symfony\/var-exporter": "^4.4|^5.0|^6.0" }, "require-dev": { "cache\/integration-tests": "dev-master", "doctrine\/cache": "^1.6|^2.0", "doctrine\/dbal": "^2.13.1|^3|^4", "predis\/predis": "^1.1", "psr\/simple-cache": "^1.0|^2.0", "symfony\/config": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^4.4|^5.0|^6.0", "symfony\/filesystem": "^4.4|^5.0|^6.0", "symfony\/http-kernel": "^4.4|^5.0|^6.0", "symfony\/messenger": "^4.4|^5.0|^6.0", "symfony\/var-dumper": "^4.4|^5.0|^6.0" }, "conflict": { "doctrine\/dbal": "<2.13.1", "symfony\/dependency-injection": "<4.4", "symfony\/http-kernel": "<4.4", "symfony\/var-dumper": "<4.4" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Cache\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Ctype as p; if (!function_exists('ctype_alnum')) { function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } } if (!function_exists('ctype_alpha')) { function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } } if (!function_exists('ctype_cntrl')) { function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } } if (!function_exists('ctype_digit')) { function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } } if (!function_exists('ctype_graph')) { function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } } if (!function_exists('ctype_lower')) { function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } } if (!function_exists('ctype_print')) { function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } } if (!function_exists('ctype_punct')) { function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } } if (!function_exists('ctype_space')) { function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } } if (!function_exists('ctype_upper')) { function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } } if (!function_exists('ctype_xdigit')) { function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } } Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Ctype as p; if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; } if (!function_exists('ctype_alnum')) { function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } } if (!function_exists('ctype_alpha')) { function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } } if (!function_exists('ctype_cntrl')) { function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } } if (!function_exists('ctype_digit')) { function ctype_digit($text) { return p\Ctype::ctype_digit($text); } } if (!function_exists('ctype_graph')) { function ctype_graph($text) { return p\Ctype::ctype_graph($text); } } if (!function_exists('ctype_lower')) { function ctype_lower($text) { return p\Ctype::ctype_lower($text); } } if (!function_exists('ctype_print')) { function ctype_print($text) { return p\Ctype::ctype_print($text); } } if (!function_exists('ctype_punct')) { function ctype_punct($text) { return p\Ctype::ctype_punct($text); } } if (!function_exists('ctype_space')) { function ctype_space($text) { return p\Ctype::ctype_space($text); } } if (!function_exists('ctype_upper')) { function ctype_upper($text) { return p\Ctype::ctype_upper($text); } } if (!function_exists('ctype_xdigit')) { function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } } Symfony Polyfill / Ctype ======================== This component provides `ctype_*` functions to users who run php versions without the ctype extension. More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Ctype; /** * Ctype implementation through regex. * * @internal * * @author Gert de Pagter */ final class Ctype { /** * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. * * @see https://php.net/ctype-alnum * * @param mixed $text * * @return bool */ public static function ctype_alnum($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^A-Za-z0-9]/', $text); } /** * Returns TRUE if every character in text is a letter, FALSE otherwise. * * @see https://php.net/ctype-alpha * * @param mixed $text * * @return bool */ public static function ctype_alpha($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^A-Za-z]/', $text); } /** * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. * * @see https://php.net/ctype-cntrl * * @param mixed $text * * @return bool */ public static function ctype_cntrl($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^\\x00-\\x1f\\x7f]/', $text); } /** * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. * * @see https://php.net/ctype-digit * * @param mixed $text * * @return bool */ public static function ctype_digit($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^0-9]/', $text); } /** * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. * * @see https://php.net/ctype-graph * * @param mixed $text * * @return bool */ public static function ctype_graph($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^!-~]/', $text); } /** * Returns TRUE if every character in text is a lowercase letter. * * @see https://php.net/ctype-lower * * @param mixed $text * * @return bool */ public static function ctype_lower($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^a-z]/', $text); } /** * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. * * @see https://php.net/ctype-print * * @param mixed $text * * @return bool */ public static function ctype_print($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^ -~]/', $text); } /** * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. * * @see https://php.net/ctype-punct * * @param mixed $text * * @return bool */ public static function ctype_punct($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^!-\\/\\:-@\\[-`\\{-~]/', $text); } /** * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. * * @see https://php.net/ctype-space * * @param mixed $text * * @return bool */ public static function ctype_space($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^\\s]/', $text); } /** * Returns TRUE if every character in text is an uppercase letter. * * @see https://php.net/ctype-upper * * @param mixed $text * * @return bool */ public static function ctype_upper($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^A-Z]/', $text); } /** * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. * * @see https://php.net/ctype-xdigit * * @param mixed $text * * @return bool */ public static function ctype_xdigit($text) { $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !\preg_match('/[^A-Fa-f0-9]/', $text); } /** * Converts integers to their char versions according to normal ctype behaviour, if needed. * * If an integer between -128 and 255 inclusive is provided, * it is interpreted as the ASCII value of a single character * (negative values have 256 added in order to allow characters in the Extended ASCII range). * Any other integer is interpreted as a string containing the decimal digits of the integer. * * @param mixed $int * @param string $function * * @return mixed */ private static function convert_int_to_char_for_ctype($int, $function) { if (!\is_int($int)) { return $int; } if ($int < -128 || $int > 255) { return (string) $int; } if (\PHP_VERSION_ID >= 80100) { @\trigger_error($function . '(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); } if ($int < 0) { $int += 256; } return \chr($int); } } { "name": "symfony\/polyfill-ctype", "type": "library", "description": "Symfony polyfill for ctype functions", "keywords": [ "polyfill", "compatibility", "portable", "ctype" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "provide": { "ext-ctype": "*" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, "files": [ "bootstrap.php" ] }, "suggest": { "ext-ctype": "For best performance" }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } }Copyright (c) 2021-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID < 80100) { #[Attribute(Attribute::TARGET_METHOD)] final class ReturnTypeWillChange { public function __construct() { } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) { /** * @property string $data */ class CURLStringFile extends CURLFile { private $data; public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') { $this->data = $data; parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); } public function __set(string $name, $value): void { if ('data' !== $name) { $this->$name = $value; return; } if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { throw new \TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); } $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); } public function __isset(string $name): bool { return isset($this->$name); } public function &__get(string $name) { return $this->$name; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Php81 as p; if (\PHP_VERSION_ID >= 80100) { return; } if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) { define('MYSQLI_REFRESH_REPLICA', 64); } if (!function_exists('array_is_list')) { function array_is_list(array $array): bool { return p\Php81::array_is_list($array); } } if (!function_exists('enum_exists')) { function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; } } Symfony Polyfill / Php81 ======================== This component provides features added to PHP 8.1 core: - [`array_is_list`](https://php.net/array_is_list) - [`enum_exists`](https://php.net/enum-exists) - [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant - [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) - [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used) More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Php81; /** * @author Nicolas Grekas * * @internal */ final class Php81 { public static function array_is_list(array $array) : bool { if ([] === $array || $array === \array_values($array)) { return \true; } $nextKey = -1; foreach ($array as $k => $v) { if ($k !== ++$nextKey) { return \false; } } return \true; } } { "name": "symfony\/polyfill-php81", "type": "library", "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "keywords": [ "polyfill", "shim", "compatibility", "portable" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources\/stubs" ] }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DataCollector; use _ContaoManager\Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallMap; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollector; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter; use _ContaoManager\Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use _ContaoManager\Symfony\Component\Security\Http\Firewall\SwitchUserListener; use _ContaoManager\Symfony\Component\Security\Http\FirewallMapInterface; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; use _ContaoManager\Symfony\Component\VarDumper\Caster\ClassStub; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; /** * @author Fabien Potencier * * @final */ class SecurityDataCollector extends DataCollector implements LateDataCollectorInterface { private $tokenStorage; private $roleHierarchy; private $logoutUrlGenerator; private $accessDecisionManager; private $firewallMap; private $firewall; private $hasVarDumper; private $authenticatorManagerEnabled; public function __construct(?TokenStorageInterface $tokenStorage = null, ?RoleHierarchyInterface $roleHierarchy = null, ?LogoutUrlGenerator $logoutUrlGenerator = null, ?AccessDecisionManagerInterface $accessDecisionManager = null, ?FirewallMapInterface $firewallMap = null, ?TraceableFirewallListener $firewall = null, bool $authenticatorManagerEnabled = \false) { if (!$authenticatorManagerEnabled) { \trigger_deprecation('symfony/security-bundle', '5.4', 'Setting the $authenticatorManagerEnabled argument of "%s" to "false" is deprecated, use the new authenticator system instead.', __METHOD__); } $this->tokenStorage = $tokenStorage; $this->roleHierarchy = $roleHierarchy; $this->logoutUrlGenerator = $logoutUrlGenerator; $this->accessDecisionManager = $accessDecisionManager; $this->firewallMap = $firewallMap; $this->firewall = $firewall; $this->hasVarDumper = \class_exists(ClassStub::class); $this->authenticatorManagerEnabled = $authenticatorManagerEnabled; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { if (null === $this->tokenStorage) { $this->data = ['enabled' => \false, 'authenticated' => \false, 'impersonated' => \false, 'impersonator_user' => null, 'impersonation_exit_path' => null, 'token' => null, 'token_class' => null, 'logout_url' => null, 'user' => '', 'roles' => [], 'inherited_roles' => [], 'supports_role_hierarchy' => null !== $this->roleHierarchy]; } elseif (null === ($token = $this->tokenStorage->getToken())) { $this->data = ['enabled' => \true, 'authenticated' => \false, 'impersonated' => \false, 'impersonator_user' => null, 'impersonation_exit_path' => null, 'token' => null, 'token_class' => null, 'logout_url' => null, 'user' => '', 'roles' => [], 'inherited_roles' => [], 'supports_role_hierarchy' => null !== $this->roleHierarchy]; } else { $inheritedRoles = []; $assignedRoles = $token->getRoleNames(); $impersonatorUser = null; if ($token instanceof SwitchUserToken) { $originalToken = $token->getOriginalToken(); // @deprecated since Symfony 5.3, change to $originalToken->getUserIdentifier() in 6.0 $impersonatorUser = \method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername(); } if (null !== $this->roleHierarchy) { foreach ($this->roleHierarchy->getReachableRoleNames($assignedRoles) as $role) { if (!\in_array($role, $assignedRoles, \true)) { $inheritedRoles[] = $role; } } } $logoutUrl = null; try { if (null !== $this->logoutUrlGenerator && !$token instanceof AnonymousToken) { $logoutUrl = $this->logoutUrlGenerator->getLogoutPath(); } } catch (\Exception $e) { // fail silently when the logout URL cannot be generated } $this->data = [ 'enabled' => \true, 'authenticated' => \method_exists($token, 'isAuthenticated') ? $token->isAuthenticated(\false) : (bool) $token->getUser(), 'impersonated' => null !== $impersonatorUser, 'impersonator_user' => $impersonatorUser, 'impersonation_exit_path' => null, 'token' => $token, 'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token), 'logout_url' => $logoutUrl, // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 'user' => \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(), 'roles' => $assignedRoles, 'inherited_roles' => \array_unique($inheritedRoles), 'supports_role_hierarchy' => null !== $this->roleHierarchy, ]; } // collect voters and access decision manager information if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) { $this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy(); foreach ($this->accessDecisionManager->getVoters() as $voter) { if ($voter instanceof TraceableVoter) { $voter = $voter->getDecoratedVoter(); } $this->data['voters'][] = $this->hasVarDumper ? new ClassStub(\get_class($voter)) : \get_class($voter); } // collect voter details $decisionLog = $this->accessDecisionManager->getDecisionLog(); foreach ($decisionLog as $key => $log) { $decisionLog[$key]['voter_details'] = []; foreach ($log['voterDetails'] as $voterDetail) { $voterClass = \get_class($voterDetail['voter']); $classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass; $decisionLog[$key]['voter_details'][] = [ 'class' => $classData, 'attributes' => $voterDetail['attributes'], // Only displayed for unanimous strategy 'vote' => $voterDetail['vote'], ]; } unset($decisionLog[$key]['voterDetails']); } $this->data['access_decision_log'] = $decisionLog; } else { $this->data['access_decision_log'] = []; $this->data['voter_strategy'] = 'unknown'; $this->data['voters'] = []; } // collect firewall context information $this->data['firewall'] = null; if ($this->firewallMap instanceof FirewallMap) { $firewallConfig = $this->firewallMap->getFirewallConfig($request); if (null !== $firewallConfig) { $this->data['firewall'] = ['name' => $firewallConfig->getName(), 'allows_anonymous' => $this->authenticatorManagerEnabled ? \false : $firewallConfig->allowsAnonymous(), 'request_matcher' => $firewallConfig->getRequestMatcher(), 'security_enabled' => $firewallConfig->isSecurityEnabled(), 'stateless' => $firewallConfig->isStateless(), 'provider' => $firewallConfig->getProvider(), 'context' => $firewallConfig->getContext(), 'entry_point' => $firewallConfig->getEntryPoint(), 'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(), 'access_denied_url' => $firewallConfig->getAccessDeniedUrl(), 'user_checker' => $firewallConfig->getUserChecker()]; // in 6.0, always fill `$this->data['authenticators'] only if ($this->authenticatorManagerEnabled) { $this->data['firewall']['authenticators'] = $firewallConfig->getAuthenticators(); } else { $this->data['firewall']['listeners'] = $firewallConfig->getAuthenticators(); } // generate exit impersonation path from current request if ($this->data['impersonated'] && null !== ($switchUserConfig = $firewallConfig->getSwitchUser())) { $exitPath = $request->getRequestUri(); $exitPath .= null === $request->getQueryString() ? '?' : '&'; $exitPath .= \sprintf('%s=%s', \urlencode($switchUserConfig['parameter']), SwitchUserListener::EXIT_VALUE); $this->data['impersonation_exit_path'] = $exitPath; } } } // collect firewall listeners information $this->data['listeners'] = []; if ($this->firewall) { $this->data['listeners'] = $this->firewall->getWrappedListeners(); } $this->data['authenticator_manager_enabled'] = $this->authenticatorManagerEnabled; $this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : []; } /** * {@inheritdoc} */ public function reset() { $this->data = []; } public function lateCollect() { $this->data = $this->cloneVar($this->data); } /** * Checks if security is enabled. */ public function isEnabled() : bool { return $this->data['enabled']; } /** * Gets the user. */ public function getUser() : string { return $this->data['user']; } /** * Gets the roles of the user. * * @return array|Data */ public function getRoles() { return $this->data['roles']; } /** * Gets the inherited roles of the user. * * @return array|Data */ public function getInheritedRoles() { return $this->data['inherited_roles']; } /** * Checks if the data contains information about inherited roles. Still the inherited * roles can be an empty array. */ public function supportsRoleHierarchy() : bool { return $this->data['supports_role_hierarchy']; } /** * Checks if the user is authenticated or not. */ public function isAuthenticated() : bool { return $this->data['authenticated']; } public function isImpersonated() : bool { return $this->data['impersonated']; } public function getImpersonatorUser() : ?string { return $this->data['impersonator_user']; } public function getImpersonationExitPath() : ?string { return $this->data['impersonation_exit_path']; } /** * Get the class name of the security token. * * @return string|Data|null */ public function getTokenClass() { return $this->data['token_class']; } /** * Get the full security token class as Data object. */ public function getToken() : ?Data { return $this->data['token']; } /** * Get the logout URL. */ public function getLogoutUrl() : ?string { return $this->data['logout_url']; } /** * Returns the FQCN of the security voters enabled in the application. * * @return string[]|Data */ public function getVoters() { return $this->data['voters']; } /** * Returns the strategy configured for the security voters. */ public function getVoterStrategy() : string { return $this->data['voter_strategy']; } /** * Returns the log of the security decisions made by the access decision manager. * * @return array|Data */ public function getAccessDecisionLog() { return $this->data['access_decision_log']; } /** * Returns the configuration of the current firewall context. * * @return array|Data|null */ public function getFirewall() { return $this->data['firewall']; } /** * @return array|Data */ public function getListeners() { return $this->data['listeners']; } /** * @return array|Data */ public function getAuthenticators() { return $this->data['authenticators']; } /** * {@inheritdoc} */ public function getName() : string { return 'security'; } public function isAuthenticatorManagerEnabled() : bool { return $this->data['authenticator_manager_enabled']; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Deprecate `FirewallConfig::getListeners()`, use `FirewallConfig::getAuthenticators()` instead * Deprecate `security.authentication.basic_entry_point` and `security.authentication.retry_entry_point` services, the logic is moved into the `HttpBasicAuthenticator` and `ChannelListener` respectively * Deprecate `FirewallConfig::allowsAnonymous()` and the `allows_anonymous` from the data collector data, there will be no anonymous concept as of version 6. * Deprecate not setting `$authenticatorManagerEnabled` to `true` in `SecurityDataCollector` and `DebugFirewallCommand` * Deprecate `SecurityFactoryInterface` and `SecurityExtension::addSecurityListenerFactory()` in favor of `AuthenticatorFactoryInterface` and `SecurityExtension::addAuthenticatorFactory()` * Add `AuthenticatorFactoryInterface::getPriority()` which replaces `SecurityFactoryInterface::getPosition()` * Deprecate passing an array of arrays as 1st argument to `MainConfiguration`, pass a sorted flat array of factories instead. * Deprecate the `always_authenticate_before_granting` option * Display the roles of the logged-in user in the Web Debug Toolbar * Add the `security.access_decision_manager.strategy_service` option * Deprecate not configuring explicitly a provider for custom_authenticators when there is more than one registered provider 5.3 --- * The authenticator system is no longer experimental * Login Link functionality is no longer experimental * Add `required_badges` firewall config option * [BC break] Add `login_throttling.lock_factory` setting defaulting to `null` (instead of `lock.factory`) * Add a `login_throttling.interval` (in `security.firewalls`) option to change the default throttling interval. * Add the `debug:firewall` command. * Deprecate `UserPasswordEncoderCommand` class and the corresponding `user:encode-password` command, use `UserPasswordHashCommand` and `user:hash-password` instead * Deprecate the `security.encoder_factory.generic` service, the `security.encoder_factory` and `Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface` aliases, use `security.password_hasher_factory` and `Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface` instead * Deprecate the `security.user_password_encoder.generic` service, the `security.password_encoder` and the `Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface` aliases, use `security.user_password_hasher`, `security.password_hasher` and `Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface` instead * Deprecate the public `security.authorization_checker` and `security.token_storage` services to private * Not setting the `enable_authenticator_manager` config option to `true` is deprecated * Deprecate the `security.authentication.provider.*` services, use the new authenticator system instead * Deprecate the `security.authentication.listener.*` services, use the new authenticator system instead * Deprecate the Guard component integration, use the new authenticator system instead * Add `form_login.form_only` option 5.2.0 ----- * Added `FirewallListenerFactoryInterface`, which can be implemented by security factories to add firewall listeners * Added `SortFirewallListenersPass` to make the execution order of firewall listeners configurable by leveraging `Symfony\Component\Security\Http\Firewall\FirewallListenerInterface` * Added ability to use comma separated ip address list for `security.access_control` * [BC break] Removed `EntryPointFactoryInterface`, authenticators must now implement `AuthenticationEntryPointInterface` if they require autoregistration of a Security entry point. 5.1.0 ----- * Added XSD for configuration * Added security configuration for priority-based access decision strategy * Marked the `AnonymousFactory`, `FormLoginFactory`, `FormLoginLdapFactory`, `GuardAuthenticationFactory`, `HttpBasicFactory`, `HttpBasicLdapFactory`, `JsonLoginFactory`, `JsonLoginLdapFactory`, `RememberMeFactory`, `RemoteUserFactory` and `X509Factory` as `@internal` * Renamed method `AbstractFactory#createEntryPoint()` to `AbstractFactory#createDefaultEntryPoint()` 5.0.0 ----- * The `switch_user.stateless` firewall option has been removed. * Removed the ability to configure encoders using `argon2i` or `bcrypt` as algorithm, use `auto` instead * The `simple_form` and `simple_preauth` authentication listeners have been removed, use Guard instead. * The `SimpleFormFactory` and `SimplePreAuthenticationFactory` classes have been removed, use Guard instead. * Removed `LogoutUrlHelper` and `SecurityHelper` templating helpers, use Twig instead * Removed the `logout_on_user_change` firewall option * Removed the `threads` encoder option * Removed the `security.authentication.trust_resolver.anonymous_class` parameter * Removed the `security.authentication.trust_resolver.rememberme_class` parameter * Removed the `security.user.provider.in_memory.user` service. 4.4.0 ----- * Added `anonymous: lazy` mode to firewalls to make them (not) start the session as late as possible * Added `migrate_from` option to encoders configuration. * Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.) * Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories. * Marked the `SecurityDataCollector` class as `@final`. 4.3.0 ----- * Added new encoder types: `auto` (recommended), `native` and `sodium` * The normalization of the cookie names configured in the `logout.delete_cookies` option is deprecated and will be disabled in Symfony 5.0. This affects to cookies with dashes in their names. For example, starting from Symfony 5.0, the `my-cookie` name will delete `my-cookie` (with a dash) instead of `my_cookie` (with an underscore). 4.2.0 ----- * Using the `security.authentication.trust_resolver.anonymous_class` and `security.authentication.trust_resolver.rememberme_class` parameters to define the token classes is deprecated. To use custom tokens extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken`. or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. * Added `Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass` * Added `json_login_ldap` authentication provider to use LDAP authentication with a REST API. * Made remember-me cookies inherit their default config from `framework.session.cookie_*` and added an "auto" mode to their "secure" config option to make them secure on HTTPS automatically. * Deprecated the `simple_form` and `simple_preauth` authentication listeners, use Guard instead. * Deprecated the `SimpleFormFactory` and `SimplePreAuthenticationFactory` classes, use Guard instead. * Added `port` in access_control * Added individual voter decisions to the profiler 4.1.0 ----- * The `switch_user.stateless` firewall option is deprecated, use the `stateless` option instead. * The `logout_on_user_change` firewall option is deprecated. * deprecated `SecurityUserValueResolver`, use `Symfony\Component\Security\Http\Controller\UserValueResolver` instead. 4.0.0 ----- * removed `FirewallContext::getContext()` * made `FirewallMap::$container` and `::$map` private * made the first `UserPasswordEncoderCommand::_construct()` argument mandatory * `UserPasswordEncoderCommand` does not extend `ContainerAwareCommand` anymore * removed support for voters that don't implement the `VoterInterface` * removed HTTP digest authentication * removed command `acl:set` along with `SetAclCommand` class * removed command `init:acl` along with `InitAclCommand` class * removed `acl` configuration key and related services, use symfony/acl-bundle instead * removed auto picking the first registered provider when no configured provider on a firewall and ambiguous * the firewall option `logout_on_user_change` is now always true, which will trigger a logout if the user changes between requests * the `switch_user.stateless` firewall option is `true` for stateless firewalls 3.4.0 ----- * Added new `security.helper` service that is an instance of `Symfony\Component\Security\Core\Security` and provides shortcuts for common security tasks. * Tagging voters with the `security.voter` tag without implementing the `VoterInterface` on the class is now deprecated and will be removed in 4.0. * [BC BREAK] `FirewallContext::getListeners()` now returns `\Traversable|array` * added info about called security listeners in profiler * Added `logout_on_user_change` to the firewall options. This config item will trigger a logout when the user has changed. Should be set to true to avoid deprecations in the configuration. * deprecated HTTP digest authentication * deprecated command `acl:set` along with `SetAclCommand` class * deprecated command `init:acl` along with `InitAclCommand` class * Added support for the new Argon2i password encoder * added `stateless` option to the `switch_user` listener * deprecated auto picking the first registered provider when no configured provider on a firewall and ambiguous 3.3.0 ----- * Deprecated instantiating `UserPasswordEncoderCommand` without its constructor arguments fully provided. * Deprecated `UserPasswordEncoderCommand::getContainer()` and relying on the `ContainerAwareCommand` sub class or `ContainerAwareInterface` implementation for this command. * Deprecated the `FirewallMap::$map` and `$container` properties. * [BC BREAK] Keys of the `users` node for `in_memory` user provider are no longer normalized. * deprecated `FirewallContext::getListeners()` 3.2.0 ----- * Added the `SecurityUserValueResolver` to inject the security users in actions via `Symfony\Component\Security\Core\User\UserInterface` in the method signature. 3.0.0 ----- * Removed the `security.context` service. 2.8.0 ----- * deprecated the `key` setting of `anonymous`, `remember_me` and `http_digest` in favor of the `secret` setting. * deprecated the `intention` firewall listener setting in favor of the `csrf_token_id`. 2.6.0 ----- * Added the possibility to override the default success/failure handler to get the provider key and the options injected * Deprecated the `security.context` service for the `security.token_storage` and `security.authorization_checker` services. 2.4.0 ----- * Added 'host' option to firewall configuration * Added 'csrf_token_generator' and 'csrf_token_id' options to firewall logout listener configuration to supersede/alias 'csrf_provider' and 'intention' respectively * Moved 'security.secure_random' service configuration to FrameworkBundle 2.3.0 ----- * allowed for multiple IP address in security access_control rules 2.2.0 ----- * Added PBKDF2 Password encoder * Added BCrypt password encoder 2.1.0 ----- * [BC BREAK] The custom factories for the firewall configuration are now registered during the build method of bundles instead of being registered by the end-user (you need to remove the 'factories' keys in your security configuration). * [BC BREAK] The Firewall listener is now registered after the Router one. This means that specific Firewall URLs (like /login_check and /logout must now have proper route defined in your routing configuration) * [BC BREAK] refactored the user provider configuration. The configuration changed for the chain provider and the memory provider: Before: ``` yaml security: providers: my_chain_provider: providers: [my_memory_provider, my_doctrine_provider] my_memory_provider: users: toto: { password: foobar, roles: [ROLE_USER] } foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] } ``` After: ``` yaml security: providers: my_chain_provider: chain: providers: [my_memory_provider, my_doctrine_provider] my_memory_provider: memory: users: toto: { password: foobar, roles: [ROLE_USER] } foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] } ``` * [BC BREAK] Method `equals` was removed from `UserInterface` to its own new `EquatableInterface`. The user class can now implement this interface to override the default implementation of users equality test. * added a validator for the user password * added 'erase_credentials' as a configuration key (true by default) * added new events: `security.authentication.success` and `security.authentication.failure` fired on authentication success/failure, regardless of authentication method, events are defined in new event class: `Symfony\Component\Security\Core\AuthenticationEvents`. * Added optional CSRF protection to LogoutListener: ``` yaml security: firewalls: default: logout: path: /logout_path target: / csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token") csrf_provider: security.csrf.token_generator # Required to enable protection intention: logout # Optional (defaults to "logout") ``` If the LogoutListener has CSRF protection enabled but cannot validate a token, then a LogoutException will be thrown. * Added `logout_url` templating helper and Twig extension, which may be used to generate logout URL's within templates. The security firewall's config key must be specified. If a firewall's logout listener has CSRF protection enabled, a token will be automatically added to the generated URL. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; /** * @author Wouter de Jong * * @internal */ class LegacyLogoutHandlerListener implements EventSubscriberInterface { private $logoutHandler; public function __construct(object $logoutHandler) { if (!$logoutHandler instanceof LogoutSuccessHandlerInterface && !$logoutHandler instanceof LogoutHandlerInterface) { throw new \InvalidArgumentException(\sprintf('An instance of "%s" or "%s" must be passed to "%s", "%s" given.', LogoutHandlerInterface::class, LogoutSuccessHandlerInterface::class, __METHOD__, \get_debug_type($logoutHandler))); } $this->logoutHandler = $logoutHandler; } public function onLogout(LogoutEvent $event) : void { if ($this->logoutHandler instanceof LogoutSuccessHandlerInterface) { $event->setResponse($this->logoutHandler->onLogoutSuccess($event->getRequest())); } elseif ($this->logoutHandler instanceof LogoutHandlerInterface) { $this->logoutHandler->logout($event->getRequest(), $event->getResponse(), $event->getToken()); } } public static function getSubscribedEvents() : array { return [LogoutEvent::class => 'onLogout']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use _ContaoManager\Symfony\Component\Security\Http\Event\LazyResponseEvent; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ExceptionListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use _ContaoManager\Symfony\Component\Security\Http\Firewall\LogoutListener; /** * Lazily calls authentication listeners when actually required by the access listener. * * @author Nicolas Grekas */ class LazyFirewallContext extends FirewallContext { private $tokenStorage; public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage) { parent::__construct($listeners, $exceptionListener, $logoutListener, $config); $this->tokenStorage = $tokenStorage; } public function getListeners() : iterable { return [$this]; } public function __invoke(RequestEvent $event) { $listeners = []; $request = $event->getRequest(); $lazy = $request->isMethodCacheable(); foreach (parent::getListeners() as $listener) { if (!$lazy || !$listener instanceof FirewallListenerInterface) { $listeners[] = $listener; $lazy = $lazy && $listener instanceof FirewallListenerInterface; } elseif (\false !== ($supports = $listener->supports($request))) { $listeners[] = [$listener, 'authenticate']; $lazy = null === $supports; } } if (!$lazy) { foreach ($listeners as $listener) { $listener($event); if ($event->hasResponse()) { return; } } return; } $this->tokenStorage->setInitializer(function () use($event, $listeners) { $event = new LazyResponseEvent($event); foreach ($listeners as $listener) { $listener($event); } }); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; /** * A decorator that delegates all method calls to the authenticator * manager of the current firewall. * * @author Wouter de Jong * * @final */ class UserAuthenticator implements UserAuthenticatorInterface { use FirewallAwareTrait; public function __construct(FirewallMap $firewallMap, ContainerInterface $userAuthenticators, RequestStack $requestStack) { $this->firewallMap = $firewallMap; $this->locator = $userAuthenticators; $this->requestStack = $requestStack; } /** * {@inheritdoc} */ public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []) : ?Response { return $this->getForFirewall()->authenticateUser($user, $authenticator, $request, $badges); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ExceptionListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\LogoutListener; /** * This is a wrapper around the actual firewall configuration which allows us * to lazy load the context for one specific firewall only when we need it. * * @author Johannes M. Schmitt */ class FirewallContext { private $listeners; private $exceptionListener; private $logoutListener; private $config; /** * @param iterable $listeners */ public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null, ?FirewallConfig $config = null) { $this->listeners = $listeners; $this->exceptionListener = $exceptionListener; $this->logoutListener = $logoutListener; $this->config = $config; } public function getConfig() { return $this->config; } /** * @return iterable */ public function getListeners() : iterable { return $this->listeners; } public function getExceptionListener() { return $this->exceptionListener; } public function getLogoutListener() { return $this->logoutListener; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Http\FirewallMapInterface; /** * This is a lazy-loading firewall map implementation. * * Listeners will only be initialized if we really need them. * * @author Johannes M. Schmitt */ class FirewallMap implements FirewallMapInterface { private $container; private $map; public function __construct(ContainerInterface $container, iterable $map) { $this->container = $container; $this->map = $map; } public function getListeners(Request $request) { $context = $this->getFirewallContext($request); if (null === $context) { return [[], null, null]; } return [$context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener()]; } /** * @return FirewallConfig|null */ public function getFirewallConfig(Request $request) { $context = $this->getFirewallContext($request); if (null === $context) { return null; } return $context->getConfig(); } private function getFirewallContext(Request $request) : ?FirewallContext { if ($request->attributes->has('_firewall_context')) { $storedContextId = $request->attributes->get('_firewall_context'); foreach ($this->map as $contextId => $requestMatcher) { if ($contextId === $storedContextId) { return $this->container->get($contextId); } } $request->attributes->remove('_firewall_context'); } foreach ($this->map as $contextId => $requestMatcher) { if (null === $requestMatcher || $requestMatcher->matches($request)) { $request->attributes->set('_firewall_context', $contextId); return $this->container->get($contextId); } } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; /** * @author Robin Chalas */ final class FirewallConfig { private $name; private $userChecker; private $requestMatcher; private $securityEnabled; private $stateless; private $provider; private $context; private $entryPoint; private $accessDeniedHandler; private $accessDeniedUrl; private $authenticators; private $switchUser; public function __construct(string $name, string $userChecker, ?string $requestMatcher = null, bool $securityEnabled = \true, bool $stateless = \false, ?string $provider = null, ?string $context = null, ?string $entryPoint = null, ?string $accessDeniedHandler = null, ?string $accessDeniedUrl = null, array $authenticators = [], ?array $switchUser = null) { $this->name = $name; $this->userChecker = $userChecker; $this->requestMatcher = $requestMatcher; $this->securityEnabled = $securityEnabled; $this->stateless = $stateless; $this->provider = $provider; $this->context = $context; $this->entryPoint = $entryPoint; $this->accessDeniedHandler = $accessDeniedHandler; $this->accessDeniedUrl = $accessDeniedUrl; $this->authenticators = $authenticators; $this->switchUser = $switchUser; } public function getName() : string { return $this->name; } /** * @return string|null The request matcher service id or null if neither the request matcher, pattern or host * options were provided */ public function getRequestMatcher() : ?string { return $this->requestMatcher; } public function isSecurityEnabled() : bool { return $this->securityEnabled; } /** * @deprecated since Symfony 5.4 */ public function allowsAnonymous() : bool { \trigger_deprecation('symfony/security-bundle', '5.4', 'The "%s()" method is deprecated.', __METHOD__); return \in_array('anonymous', $this->authenticators, \true); } public function isStateless() : bool { return $this->stateless; } public function getProvider() : ?string { return $this->provider; } /** * @return string|null The context key (will be null if the firewall is stateless) */ public function getContext() : ?string { return $this->context; } public function getEntryPoint() : ?string { return $this->entryPoint; } public function getUserChecker() : string { return $this->userChecker; } public function getAccessDeniedHandler() : ?string { return $this->accessDeniedHandler; } public function getAccessDeniedUrl() : ?string { return $this->accessDeniedUrl; } /** * @deprecated since Symfony 5.4, use {@see getAuthenticators()} instead */ public function getListeners() : array { \trigger_deprecation('symfony/security-bundle', '5.4', 'Method "%s()" is deprecated, use "%s::getAuthenticators()" instead.', __METHOD__, __CLASS__); return $this->getAuthenticators(); } public function getAuthenticators() : array { return $this->authenticators; } public function getSwitchUser() : ?array { return $this->switchUser; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Security; /** * Provides basic functionality for services mapped by the firewall name * in a container locator. * * @author Wouter de Jong * * @internal */ trait FirewallAwareTrait { private $locator; private $requestStack; private $firewallMap; private function getForFirewall() : object { $serviceIdentifier = \str_replace('FirewallAware', '', static::class); if (null === ($request = $this->requestStack->getCurrentRequest())) { throw new \LogicException('Cannot determine the correct ' . $serviceIdentifier . ' to use: there is no active Request and so, the firewall cannot be determined. Try using a specific ' . $serviceIdentifier . ' service.'); } $firewall = $this->firewallMap->getFirewallConfig($request); if (!$firewall) { throw new \LogicException('No ' . $serviceIdentifier . ' found as the current route is not covered by a firewall.'); } $firewallName = $firewall->getName(); if (!$this->locator->has($firewallName)) { $message = 'No ' . $serviceIdentifier . ' found for this firewall.'; if (\defined(static::class . '::FIREWALL_OPTION')) { $message .= \sprintf('Did you forget to add a "' . static::FIREWALL_OPTION . '" key under your "%s" firewall?', $firewallName); } throw new \LogicException($message); } return $this->locator->get($firewallName); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bridge\Twig\Extension\LogoutUrlExtension; use _ContaoManager\Symfony\Bridge\Twig\Extension\SecurityExtension; return static function (ContainerConfigurator $container) { $container->services()->set('twig.extension.logout_url', LogoutUrlExtension::class)->args([service('security.logout_url_generator')])->tag('twig.extension')->set('twig.extension.security', SecurityExtension::class)->args([service('security.authorization_checker')->ignoreOnInvalid(), service('security.impersonate_url_generator')->ignoreOnInvalid()])->tag('twig.extension'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Security\Http\AccessMap; use _ContaoManager\Symfony\Component\Security\Http\Authentication\CustomAuthenticationFailureHandler; use _ContaoManager\Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler; use _ContaoManager\Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; use _ContaoManager\Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\DefaultLogoutListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\SessionLogoutListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AccessListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ChannelListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ContextListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ExceptionListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\LogoutListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\SwitchUserListener; return static function (ContainerConfigurator $container) { $container->services()->set('security.authentication.basic_entry_point', BasicAuthenticationEntryPoint::class)->deprecate('symfony/security-bundle', '5.4', 'The "%service_id%" service is deprecated, the logic is contained in the authenticators.')->set('security.authentication.retry_entry_point', RetryAuthenticationEntryPoint::class)->deprecate('symfony/security-bundle', '5.4', 'The "%service_id%" service is deprecated, the logic is integrated directly in "security.channel_listener".')->args([inline_service('int')->factory([service('router.request_context'), 'getHttpPort']), inline_service('int')->factory([service('router.request_context'), 'getHttpsPort'])])->set('security.channel_listener', ChannelListener::class)->args([service('security.access_map'), service('logger')->nullOnInvalid(), inline_service('int')->factory([service('router.request_context'), 'getHttpPort']), inline_service('int')->factory([service('router.request_context'), 'getHttpsPort'])])->tag('monolog.logger', ['channel' => 'security'])->set('security.access_map', AccessMap::class)->set('security.context_listener', ContextListener::class)->args([service('security.untracked_token_storage'), [], abstract_arg('Provider Key'), service('logger')->nullOnInvalid(), service('event_dispatcher')->nullOnInvalid(), service('security.authentication.trust_resolver')])->tag('monolog.logger', ['channel' => 'security'])->set('security.logout_listener', LogoutListener::class)->abstract()->args([service('security.token_storage'), service('security.http_utils'), abstract_arg('event dispatcher'), []])->set('security.logout.listener.session', SessionLogoutListener::class)->abstract()->set('security.logout.listener.cookie_clearing', CookieClearingLogoutListener::class)->abstract()->set('security.logout.listener.default', DefaultLogoutListener::class)->abstract()->args([service('security.http_utils'), abstract_arg('target url')])->set('security.authentication.form_entry_point', FormAuthenticationEntryPoint::class)->abstract()->args([service('http_kernel')])->set('security.authentication.listener.abstract')->abstract()->args([service('security.token_storage'), service('security.authentication.manager'), service('security.authentication.session_strategy'), service('security.http_utils'), abstract_arg('Provider-shared Key'), service('security.authentication.success_handler'), service('security.authentication.failure_handler'), [], service('logger')->nullOnInvalid(), service('event_dispatcher')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->set('security.authentication.custom_success_handler', CustomAuthenticationSuccessHandler::class)->abstract()->args([ abstract_arg('The custom success handler service'), [], // Options abstract_arg('Provider-shared Key'), ])->set('security.authentication.success_handler', DefaultAuthenticationSuccessHandler::class)->abstract()->args([ service('security.http_utils'), [], // Options service('logger')->nullOnInvalid(), ])->set('security.authentication.custom_failure_handler', CustomAuthenticationFailureHandler::class)->abstract()->args([abstract_arg('The custom failure handler service'), []])->set('security.authentication.failure_handler', DefaultAuthenticationFailureHandler::class)->abstract()->args([ service('http_kernel'), service('security.http_utils'), [], // Options service('logger')->nullOnInvalid(), ])->tag('monolog.logger', ['channel' => 'security'])->set('security.exception_listener', ExceptionListener::class)->abstract()->args([service('security.token_storage'), service('security.authentication.trust_resolver'), service('security.http_utils'), abstract_arg('Provider-shared Key'), service('security.authentication.entry_point')->nullOnInvalid(), param('security.access.denied_url'), service('security.access.denied_handler')->nullOnInvalid(), service('logger')->nullOnInvalid(), \false])->tag('monolog.logger', ['channel' => 'security'])->set('security.authentication.switchuser_listener', SwitchUserListener::class)->abstract()->args([service('security.token_storage'), abstract_arg('User Provider'), abstract_arg('User Checker'), abstract_arg('Provider Key'), service('security.access.decision_manager'), service('logger')->nullOnInvalid(), '_switch_user', 'ROLE_ALLOWED_TO_SWITCH', service('event_dispatcher')->nullOnInvalid(), \false])->tag('monolog.logger', ['channel' => 'security'])->set('security.access_listener', AccessListener::class)->args([service('security.token_storage'), service('security.access.decision_manager'), service('security.access_map'), service('security.authentication.manager')])->tag('monolog.logger', ['channel' => 'security']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\LoginLink\FirewallAwareLoginLinkHandler; use _ContaoManager\Symfony\Component\Security\Core\Signature\ExpiredSignatureStorage; use _ContaoManager\Symfony\Component\Security\Core\Signature\SignatureHasher; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\LoginLinkAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\LoginLinkHandler; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; return static function (ContainerConfigurator $container) { $container->services()->set('security.authenticator.login_link', LoginLinkAuthenticator::class)->abstract()->args([abstract_arg('the login link handler instance'), service('security.http_utils'), abstract_arg('authentication success handler'), abstract_arg('authentication failure handler'), abstract_arg('options')])->set('security.authenticator.abstract_login_link_handler', LoginLinkHandler::class)->abstract()->args([service('router'), abstract_arg('user provider'), abstract_arg('signature hasher'), abstract_arg('options')])->set('security.authenticator.abstract_login_link_signature_hasher', SignatureHasher::class)->args([service('property_accessor'), abstract_arg('signature properties'), '%kernel.secret%', abstract_arg('expired signature storage'), abstract_arg('max signature uses')])->set('security.authenticator.expired_login_link_storage', ExpiredSignatureStorage::class)->abstract()->args([abstract_arg('cache pool service'), abstract_arg('expired login link storage')])->set('security.authenticator.cache.expired_links')->parent('cache.app')->private()->set('security.authenticator.firewall_aware_login_link_handler', FirewallAwareLoginLinkHandler::class)->args([service('security.firewall.map'), tagged_locator('security.authenticator.login_linker', 'firewall'), service('request_stack')])->alias(LoginLinkHandlerInterface::class, 'security.authenticator.firewall_aware_login_link_handler'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\InMemoryTokenProvider; use _ContaoManager\Symfony\Component\Security\Http\Firewall\RememberMeListener; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\ResponseListener; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices; return static function (ContainerConfigurator $container) { $container->services()->set('security.authentication.listener.rememberme', RememberMeListener::class)->abstract()->args([service('security.untracked_token_storage'), service('security.authentication.rememberme'), service('security.authentication.manager'), service('logger')->nullOnInvalid(), service('event_dispatcher')->nullOnInvalid(), abstract_arg('Catch exception flag set in RememberMeFactory'), service('security.authentication.session_strategy')])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.provider.rememberme', RememberMeAuthenticationProvider::class)->abstract()->args([abstract_arg('User Checker')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.rememberme.token.provider.in_memory', InMemoryTokenProvider::class)->set('security.authentication.rememberme.services.abstract')->abstract()->args([ [], // User Providers abstract_arg('Shared Token Key'), abstract_arg('Shared Provider Key'), [], // Options service('logger')->nullOnInvalid(), ])->tag('monolog.logger', ['channel' => 'security'])->set('security.authentication.rememberme.services.persistent', PersistentTokenBasedRememberMeServices::class)->parent('security.authentication.rememberme.services.abstract')->abstract()->set('security.authentication.rememberme.services.simplehash', TokenBasedRememberMeServices::class)->parent('security.authentication.rememberme.services.abstract')->abstract()->set('security.rememberme.response_listener', ResponseListener::class)->tag('kernel.event_subscriber'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener; use _ContaoManager\Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use _ContaoManager\Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; return static function (ContainerConfigurator $container) { $container->services()->set('security.authentication.guard_handler', GuardAuthenticatorHandler::class)->args([service('security.token_storage'), service('event_dispatcher')->nullOnInvalid(), abstract_arg('stateless firewall keys')])->call('setSessionAuthenticationStrategy', [service('security.authentication.session_strategy')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->alias(GuardAuthenticatorHandler::class, 'security.authentication.guard_handler')->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use the new authenticator system instead.')->set('security.authentication.provider.guard', GuardAuthenticationProvider::class)->abstract()->args([abstract_arg('Authenticators'), abstract_arg('User Provider'), abstract_arg('Provider-shared Key'), abstract_arg('User Checker'), service('security.password_hasher')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.guard', GuardAuthenticationListener::class)->abstract()->args([service('security.authentication.guard_handler'), service('security.authentication.manager'), abstract_arg('Provider-shared Key'), abstract_arg('Authenticators'), service('logger')->nullOnInvalid(), param('security.authentication.hide_user_not_found'), service('security.token_storage')])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\PreAuthenticatedAuthenticationProvider; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\RemoteUserAuthenticationListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\X509AuthenticationListener; return static function (ContainerConfigurator $container) { $container->services()->set('security.authentication.manager', AuthenticationProviderManager::class)->args([abstract_arg('providers'), param('security.authentication.manager.erase_credentials')])->call('setEventDispatcher', [service('event_dispatcher')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->alias(AuthenticationManagerInterface::class, 'security.authentication.manager')->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.anonymous', AnonymousAuthenticationListener::class)->args([service('security.untracked_token_storage'), abstract_arg('Key'), service('logger')->nullOnInvalid(), service('security.authentication.manager')])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.provider.anonymous', AnonymousAuthenticationProvider::class)->args([abstract_arg('Key')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.form', UsernamePasswordFormAuthenticationListener::class)->parent('security.authentication.listener.abstract')->abstract()->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.x509', X509AuthenticationListener::class)->abstract()->args([service('security.token_storage'), service('security.authentication.manager'), abstract_arg('Provider-shared Key'), abstract_arg('x509 user'), abstract_arg('x509 credentials'), service('logger')->nullOnInvalid(), service('event_dispatcher')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.json', UsernamePasswordJsonAuthenticationListener::class)->abstract()->args([ service('security.token_storage'), service('security.authentication.manager'), service('security.http_utils'), abstract_arg('Provider-shared Key'), abstract_arg('Failure handler'), abstract_arg('Success Handler'), [], // Options service('logger')->nullOnInvalid(), service('event_dispatcher')->nullOnInvalid(), service('property_accessor')->nullOnInvalid(), ])->call('setTranslator', [service('translator')->ignoreOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.remote_user', RemoteUserAuthenticationListener::class)->abstract()->args([service('security.token_storage'), service('security.authentication.manager'), abstract_arg('Provider-shared Key'), abstract_arg('REMOTE_USER server env var'), service('logger')->nullOnInvalid(), service('event_dispatcher')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.listener.basic', BasicAuthenticationListener::class)->abstract()->args([service('security.token_storage'), service('security.authentication.manager'), abstract_arg('Provider-shared Key'), abstract_arg('Entry Point'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.provider.dao', DaoAuthenticationProvider::class)->abstract()->args([abstract_arg('User Provider'), abstract_arg('User Checker'), abstract_arg('Provider-shared Key'), service('security.password_hasher_factory'), param('security.authentication.hide_user_not_found')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.provider.ldap_bind', LdapBindAuthenticationProvider::class)->abstract()->args([abstract_arg('User Provider'), abstract_arg('UserChecker'), abstract_arg('Provider-shared Key'), abstract_arg('LDAP'), abstract_arg('Base DN'), param('security.authentication.hide_user_not_found'), abstract_arg('search dn'), abstract_arg('search password')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.')->set('security.authentication.provider.pre_authenticated', PreAuthenticatedAuthenticationProvider::class)->abstract()->args([abstract_arg('User Provider'), abstract_arg('UserChecker')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use the new authenticator system instead.'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener; use _ContaoManager\Symfony\Bundle\SecurityBundle\EventListener\VoteListener; use _ContaoManager\Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; return static function (ContainerConfigurator $container) { $container->services()->set('debug.security.access.decision_manager', TraceableAccessDecisionManager::class)->decorate('security.access.decision_manager')->args([service('debug.security.access.decision_manager.inner')])->set('debug.security.voter.vote_listener', VoteListener::class)->args([service('debug.security.access.decision_manager')])->tag('kernel.event_subscriber')->set('debug.security.firewall', TraceableFirewallListener::class)->args([service('security.firewall.map'), service('event_dispatcher'), service('security.logout_url_generator')])->tag('kernel.event_subscriber')->tag('kernel.reset', ['method' => 'reset'])->alias('security.firewall', 'debug.security.firewall'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\Command\DebugFirewallCommand; return static function (ContainerConfigurator $container) { $container->services()->set('security.command.debug_firewall', DebugFirewallCommand::class)->args([param('security.firewalls'), service('security.firewall.context_locator'), tagged_locator('event_dispatcher.dispatcher', 'name'), [], \false])->tag('console.command', ['command' => 'debug:firewall']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand; use _ContaoManager\Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand; return static function (ContainerConfigurator $container) { $container->services()->set('security.command.user_password_encoder', UserPasswordEncoderCommand::class)->args([service('security.encoder_factory'), abstract_arg('encoders user classes')])->tag('console.command', ['command' => 'security:encode-password'])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.command.user_password_hash" instead.'); $container->services()->set('security.command.user_password_hash', UserPasswordHashCommand::class)->args([service('security.password_hasher_factory'), abstract_arg('list of user classes')])->tag('console.command'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; return static function (ContainerConfigurator $container) { $container->services()->set('security.password_hasher_factory', PasswordHasherFactory::class)->args([[]])->alias(PasswordHasherFactoryInterface::class, 'security.password_hasher_factory')->set('security.user_password_hasher', UserPasswordHasher::class)->args([service('security.password_hasher_factory')])->alias('security.password_hasher', 'security.user_password_hasher')->alias(UserPasswordHasherInterface::class, 'security.password_hasher'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector; return static function (ContainerConfigurator $container) { $container->services()->set('data_collector.security', SecurityDataCollector::class)->args([service('security.untracked_token_storage'), service('security.role_hierarchy'), service('security.logout_url_generator'), service('security.access.decision_manager'), service('security.firewall.map'), service('debug.security.firewall')->nullOnInvalid()])->tag('data_collector', ['template' => '@Security/Collector/security.html.twig', 'id' => 'security', 'priority' => 270]); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\UserAuthenticator; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticatorManager; use _ContaoManager\Symfony\Component\Security\Http\Authentication\NoopAuthenticationManager; use _ContaoManager\Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\X509Authenticator; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CheckCredentialsListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\LoginThrottlingListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\PasswordMigratingListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\SessionStrategyListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\UserCheckerListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\UserProviderListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener; return static function (ContainerConfigurator $container) { $container->services()->set('security.authenticator.manager', AuthenticatorManager::class)->abstract()->args([abstract_arg('authenticators'), service('security.token_storage'), service('event_dispatcher'), abstract_arg('provider key'), service('logger')->nullOnInvalid(), param('security.authentication.manager.erase_credentials'), param('security.authentication.hide_user_not_found'), abstract_arg('required badges')])->tag('monolog.logger', ['channel' => 'security'])->set('security.authenticator.managers_locator', ServiceLocator::class)->args([[]])->set('security.user_authenticator', UserAuthenticator::class)->args([service('security.firewall.map'), service('security.authenticator.managers_locator'), service('request_stack')])->alias(UserAuthenticatorInterface::class, 'security.user_authenticator')->set('security.authentication.manager', NoopAuthenticationManager::class)->alias(AuthenticationManagerInterface::class, 'security.authentication.manager')->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use the new authenticator system instead.')->set('security.firewall.authenticator', AuthenticatorManagerListener::class)->abstract()->args([abstract_arg('authenticator manager')])->set('security.listener.check_authenticator_credentials', CheckCredentialsListener::class)->args([service('security.password_hasher_factory')])->tag('kernel.event_subscriber')->set('security.listener.user_provider', UserProviderListener::class)->args([service('security.user_providers')])->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport'])->set('security.listener.user_provider.abstract', UserProviderListener::class)->abstract()->args([abstract_arg('user provider')])->set('security.listener.password_migrating', PasswordMigratingListener::class)->args([service('security.password_hasher_factory')])->tag('kernel.event_subscriber')->set('security.listener.user_checker', UserCheckerListener::class)->abstract()->args([abstract_arg('user checker')])->set('security.listener.session', SessionStrategyListener::class)->abstract()->args([service('security.authentication.session_strategy')])->set('security.listener.login_throttling', LoginThrottlingListener::class)->abstract()->args([service('request_stack'), abstract_arg('request rate limiter')])->set('security.authenticator.http_basic', HttpBasicAuthenticator::class)->abstract()->args([abstract_arg('realm name'), abstract_arg('user provider'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->set('security.authenticator.form_login', FormLoginAuthenticator::class)->abstract()->args([service('security.http_utils'), abstract_arg('user provider'), abstract_arg('authentication success handler'), abstract_arg('authentication failure handler'), abstract_arg('options')])->set('security.authenticator.json_login', JsonLoginAuthenticator::class)->abstract()->args([service('security.http_utils'), abstract_arg('user provider'), abstract_arg('authentication success handler'), abstract_arg('authentication failure handler'), abstract_arg('options'), service('property_accessor')->nullOnInvalid()])->call('setTranslator', [service('translator')->ignoreOnInvalid()])->set('security.authenticator.x509', X509Authenticator::class)->abstract()->args([abstract_arg('user provider'), service('security.token_storage'), abstract_arg('firewall name'), abstract_arg('user key'), abstract_arg('credentials key'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->set('security.authenticator.remote_user', RemoteUserAuthenticator::class)->abstract()->args([abstract_arg('user provider'), service('security.token_storage'), abstract_arg('firewall name'), abstract_arg('user key'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security']); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer; use _ContaoManager\Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallContext; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallMap; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; use _ContaoManager\Symfony\Component\Ldap\Security\LdapUserProvider; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\ExpressionLanguage; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactory; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\UserPasswordEncoder; use _ContaoManager\Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use _ContaoManager\Symfony\Component\Security\Core\Role\RoleHierarchy; use _ContaoManager\Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Core\User\ChainUserProvider; use _ContaoManager\Symfony\Component\Security\Core\User\InMemoryUserChecker; use _ContaoManager\Symfony\Component\Security\Core\User\InMemoryUserProvider; use _ContaoManager\Symfony\Component\Security\Core\User\MissingUserProvider; use _ContaoManager\Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use _ContaoManager\Symfony\Component\Security\Http\Controller\UserValueResolver; use _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\Security\Http\FirewallMapInterface; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; return static function (ContainerConfigurator $container) { $container->parameters()->set('security.role_hierarchy.roles', []); $container->services()->set('security.authorization_checker', AuthorizationChecker::class)->public()->args([service('security.token_storage'), service('security.access.decision_manager'), param('security.access.always_authenticate_before_granting')])->tag('container.private', ['package' => 'symfony/security-bundle', 'version' => '5.3'])->alias(AuthorizationCheckerInterface::class, 'security.authorization_checker')->set('security.token_storage', UsageTrackingTokenStorage::class)->public()->args([service('security.untracked_token_storage'), service_locator(['request_stack' => service('request_stack')])])->tag('kernel.reset', ['method' => 'disableUsageTracking'])->tag('kernel.reset', ['method' => 'setToken'])->tag('container.private', ['package' => 'symfony/security-bundle', 'version' => '5.3'])->alias(TokenStorageInterface::class, 'security.token_storage')->set('security.untracked_token_storage', TokenStorage::class)->set('security.helper', Security::class)->args([service_locator(['security.token_storage' => service('security.token_storage'), 'security.authorization_checker' => service('security.authorization_checker')])])->alias(Security::class, 'security.helper')->set('security.user_value_resolver', UserValueResolver::class)->args([service('security.token_storage')])->tag('controller.argument_value_resolver', ['priority' => 40])->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class)->set('security.authentication.session_strategy', SessionAuthenticationStrategy::class)->args([param('security.authentication.session_strategy.strategy'), service('security.csrf.token_storage')->ignoreOnInvalid()])->alias(SessionAuthenticationStrategyInterface::class, 'security.authentication.session_strategy')->set('security.authentication.session_strategy_noop', SessionAuthenticationStrategy::class)->args(['none'])->set('security.encoder_factory.generic', EncoderFactory::class)->args([[]])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.password_hasher_factory" instead.')->alias('security.encoder_factory', 'security.encoder_factory.generic')->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "security.password_hasher_factory" instead.')->alias(EncoderFactoryInterface::class, 'security.encoder_factory')->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "' . PasswordHasherFactoryInterface::class . '" instead.')->set('security.user_password_encoder.generic', UserPasswordEncoder::class)->args([service('security.encoder_factory')])->deprecate('symfony/security-bundle', '5.3', 'The "%service_id%" service is deprecated, use "security.user_password_hasher" instead.')->alias('security.password_encoder', 'security.user_password_encoder.generic')->public()->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "security.password_hasher"" instead.')->alias(UserPasswordEncoderInterface::class, 'security.password_encoder')->deprecate('symfony/security-bundle', '5.3', 'The "%alias_id%" service is deprecated, use "' . UserPasswordHasherInterface::class . '" instead.')->set('security.user_checker', InMemoryUserChecker::class)->set('security.expression_language', ExpressionLanguage::class)->args([service('cache.security_expression_language')->nullOnInvalid()])->set('security.authentication_utils', AuthenticationUtils::class)->args([service('request_stack')])->alias(AuthenticationUtils::class, 'security.authentication_utils')->set('security.access.decision_manager', AccessDecisionManager::class)->args([[]])->alias(AccessDecisionManagerInterface::class, 'security.access.decision_manager')->set('security.role_hierarchy', RoleHierarchy::class)->args([param('security.role_hierarchy.roles')])->alias(RoleHierarchyInterface::class, 'security.role_hierarchy')->set('security.access.simple_role_voter', RoleVoter::class)->tag('security.voter', ['priority' => 245])->set('security.access.authenticated_voter', AuthenticatedVoter::class)->args([service('security.authentication.trust_resolver')])->tag('security.voter', ['priority' => 250])->set('security.access.role_hierarchy_voter', RoleHierarchyVoter::class)->args([service('security.role_hierarchy')])->tag('security.voter', ['priority' => 245])->set('security.access.expression_voter', ExpressionVoter::class)->args([service('security.expression_language'), service('security.authentication.trust_resolver'), service('security.authorization_checker'), service('security.role_hierarchy')->nullOnInvalid()])->tag('security.voter', ['priority' => 245])->set('security.impersonate_url_generator', ImpersonateUrlGenerator::class)->args([service('request_stack'), service('security.firewall.map'), service('security.token_storage')])->set('security.firewall', FirewallListener::class)->args([service('security.firewall.map'), service('event_dispatcher'), service('security.logout_url_generator')])->tag('kernel.event_subscriber')->alias(Firewall::class, 'security.firewall')->set('security.firewall.map', FirewallMap::class)->args([abstract_arg('Firewall context locator'), abstract_arg('Request matchers')])->alias(FirewallMapInterface::class, 'security.firewall.map')->set('security.firewall.context', FirewallContext::class)->abstract()->args([[], service('security.exception_listener'), abstract_arg('LogoutListener'), abstract_arg('FirewallConfig')])->set('security.firewall.lazy_context', LazyFirewallContext::class)->abstract()->args([[], service('security.exception_listener'), abstract_arg('LogoutListener'), abstract_arg('FirewallConfig'), service('security.untracked_token_storage')])->set('security.firewall.config', FirewallConfig::class)->abstract()->args([ abstract_arg('name'), abstract_arg('user_checker'), abstract_arg('request_matcher'), \false, // security enabled \false, // stateless null, null, null, null, null, [], // listeners null, ])->set('security.logout_url_generator', LogoutUrlGenerator::class)->args([service('request_stack')->nullOnInvalid(), service('router')->nullOnInvalid(), service('security.token_storage')->nullOnInvalid()])->set('security.user.provider.missing', MissingUserProvider::class)->abstract()->args([abstract_arg('firewall')])->set('security.user.provider.in_memory', InMemoryUserProvider::class)->abstract()->set('security.user.provider.ldap', LdapUserProvider::class)->abstract()->args([abstract_arg('security.ldap.ldap'), abstract_arg('base dn'), abstract_arg('search dn'), abstract_arg('search password'), abstract_arg('default_roles'), abstract_arg('uid key'), abstract_arg('filter'), abstract_arg('password_attribute'), abstract_arg('extra_fields (email etc)')])->set('security.user.provider.chain', ChainUserProvider::class)->abstract()->set('security.http_utils', HttpUtils::class)->args([service('router')->nullOnInvalid(), service('router')->nullOnInvalid()])->alias(HttpUtils::class, 'security.http_utils')->set('security.validator.user_password', UserPasswordValidator::class)->args([service('security.token_storage'), service('security.password_hasher_factory')])->tag('validator.constraint_validator', ['alias' => 'security.validator.user_password'])->set('cache.security_expression_language')->parent('cache.system')->private()->tag('cache.pool')->set('security.cache_warmer.expression', ExpressionCacheWarmer::class)->args([[], service('security.expression_language')])->tag('kernel.cache_warmer'); }; * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Bundle\SecurityBundle\RememberMe\FirewallAwareRememberMeHandler; use _ContaoManager\Symfony\Component\Security\Core\Signature\SignatureHasher; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CheckRememberMeConditionsListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\RememberMeListener; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\SignatureRememberMeHandler; return static function (ContainerConfigurator $container) { $container->services()->set('security.authenticator.remember_me_signature_hasher', SignatureHasher::class)->args([service('property_accessor'), abstract_arg('signature properties'), '%kernel.secret%', null, null])->set('security.authenticator.signature_remember_me_handler', SignatureRememberMeHandler::class)->abstract()->args([abstract_arg('signature hasher'), abstract_arg('user provider'), service('request_stack'), abstract_arg('options'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->set('security.authenticator.persistent_remember_me_handler', PersistentRememberMeHandler::class)->abstract()->args([abstract_arg('token provider'), param('kernel.secret'), abstract_arg('user provider'), service('request_stack'), abstract_arg('options'), service('logger')->nullOnInvalid(), abstract_arg('token verifier')])->tag('monolog.logger', ['channel' => 'security'])->set('security.authenticator.firewall_aware_remember_me_handler', FirewallAwareRememberMeHandler::class)->args([service('security.firewall.map'), tagged_locator('security.remember_me_handler', 'firewall'), service('request_stack')])->alias(RememberMeHandlerInterface::class, 'security.authenticator.firewall_aware_remember_me_handler')->set('security.listener.check_remember_me_conditions', CheckRememberMeConditionsListener::class)->abstract()->args([abstract_arg('options'), service('logger')->nullOnInvalid()])->set('security.listener.remember_me', RememberMeListener::class)->abstract()->args([abstract_arg('remember me handler'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->set('security.authenticator.remember_me', RememberMeAuthenticator::class)->abstract()->args([abstract_arg('remember me handler'), param('kernel.secret'), service('security.token_storage'), abstract_arg('options'), service('logger')->nullOnInvalid()])->tag('monolog.logger', ['channel' => 'security'])->set('cache.security_token_verifier')->parent('cache.system')->private()->tag('cache.pool'); }; {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% block page_title 'Security' %} {% block toolbar %} {% if collector.firewall %} {% set color_code = collector.enabled and not collector.authenticatorManagerEnabled ? 'yellow' %} {% set icon %} {{ include('@Security/Collector/icon.svg') }} {{ collector.user|default('n/a') }} {% endset %} {% set text %} {% if collector.impersonated %}
Impersonator {{ collector.impersonatorUser }}
{% endif %}
{% if collector.enabled %} {% if collector.token %}
Logged in as {{ collector.user }}
Authenticated {{ collector.authenticated ? 'Yes' : 'No' }}
Roles {% set remainingRoles = collector.roles|slice(1) %} {{ collector.roles|first }} {% if remainingRoles is not empty %} + {{ remainingRoles|length }} more {% endif %}
Token class {{ collector.tokenClass|abbr_class }}
{% else %}
Authenticated No
{% endif %} {% if collector.firewall %}
Firewall name {{ collector.firewall.name }}
{% endif %} {% if collector.token and collector.logoutUrl %}
Actions Logout {% if collector.impersonated and collector.impersonationExitPath %} | Exit impersonation {% endif %}
{% endif %} {% else %}
The security is disabled.
{% endif %}
{% endset %} {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: color_code }) }} {% endif %} {% endblock %} {% block menu %} {{ include('@Security/Collector/icon.svg') }} Security {% endblock %} {% block panel %}

Security

{% if collector.enabled %}

Token

{% if collector.token %}
{{ collector.user == 'anon.' ? 'Anonymous' : collector.user }} Username
{{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} Authenticated
{% if collector.supportsRoleHierarchy %} {% endif %} {% if collector.token %} {% endif %}
Property Value
Roles {{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }} {% if not collector.authenticated and collector.roles is empty %}

User is not authenticated probably because they have no roles.

{% endif %}
Inherited Roles {{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}
Token {{ profiler_dump(collector.token) }}
{% elseif collector.enabled %}

There is no security token.

{% endif %}

Firewall

{% if collector.firewall %}
{{ collector.firewall.name }} Name
{{ include('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }} Security enabled
{{ include('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }} Stateless
{% if collector.authenticatorManagerEnabled == false %}
{{ include('@WebProfiler/Icon/' ~ (collector.firewall.allows_anonymous ? 'yes' : 'no') ~ '.svg') }} Allows anonymous
{% endif %}
{% if collector.firewall.security_enabled %}

Configuration

{% if collector.authenticatorManagerEnabled %} {% else %} {% endif %}
Key Value
provider {{ collector.firewall.provider ?: '(none)' }}
context {{ collector.firewall.context ?: '(none)' }}
entry_point {{ collector.firewall.entry_point ?: '(none)' }}
user_checker {{ collector.firewall.user_checker ?: '(none)' }}
access_denied_handler {{ collector.firewall.access_denied_handler ?: '(none)' }}
access_denied_url {{ collector.firewall.access_denied_url ?: '(none)' }}
authenticators {{ collector.firewall.authenticators is empty ? '(none)' : profiler_dump(collector.firewall.authenticators, maxDepth=1) }}
listeners {{ collector.firewall.listeners is empty ? '(none)' : profiler_dump(collector.firewall.listeners, maxDepth=1) }}
{% endif %} {% endif %}

Listeners

{% if collector.listeners|default([]) is empty %}

No security listeners have been recorded. Check that debugging is enabled in the kernel.

{% else %} {% set previous_event = (collector.listeners|first) %} {% for listener in collector.listeners %} {% if loop.first or listener != previous_event %} {% if not loop.first %} {% endif %} {% set previous_event = listener %} {% endif %} {% if loop.last %} {% endif %} {% endfor %}
Listener Duration Response
{{ profiler_dump(listener.stub) }} {{ '%0.2f'|format(listener.time * 1000) }} ms {{ listener.response ? profiler_dump(listener.response) : '(none)' }}
{% endif %}

Authenticators

{% if collector.authenticators|default([]) is not empty %} {% set previous_event = (collector.listeners|first) %} {% for authenticator in collector.authenticators %} {% if loop.first or authenticator != previous_event %} {% if not loop.first %} {% endif %} {% set previous_event = authenticator %} {% endif %} {% if loop.last %} {% endif %} {% endfor %}
Authenticator Supports Duration Passport
{{ profiler_dump(authenticator.stub) }} {{ include('@WebProfiler/Icon/' ~ (authenticator.supports ? 'yes' : 'no') ~ '.svg') }} {{ '%0.2f'|format(authenticator.duration * 1000) }} ms {{ authenticator.passport ? profiler_dump(authenticator.passport) : '(none)' }}
{% else %}

No authenticators have been recorded. Check previous profiles on your authentication endpoint.

{% endif %}

Access Decision

{% if collector.voters|default([]) is not empty %}
{{ collector.voterStrategy|default('unknown') }} Strategy
{% for voter in collector.voters %} {% endfor %}
# Voter class
{{ loop.index }} {{ profiler_dump(voter) }}
{% endif %} {% if collector.accessDecisionLog|default([]) is not empty %}

Access decision log

{% for decision in collector.accessDecisionLog %} {% endfor %}
# Result Attributes Object
{{ loop.index }} {{ decision.result ? 'GRANTED' : 'DENIED' }} {% if decision.attributes|length == 1 %} {% set attribute = decision.attributes|first %} {% if attribute.expression is defined %} Expression:
{{ attribute.expression }}
{% elseif attribute.type == 'string' %} {{ attribute }} {% else %} {{ profiler_dump(attribute) }} {% endif %} {% else %} {{ profiler_dump(decision.attributes) }} {% endif %}
{{ profiler_dump(decision.seek('object')) }}
{% if decision.voter_details is not empty %} {% set voter_details_id = 'voter-details-' ~ loop.index %}
{% for voter_detail in decision.voter_details %} {% if collector.voterStrategy == 'unanimous' %} {% endif %} {% endfor %}
{{ profiler_dump(voter_detail['class']) }}attribute {{ voter_detail['attributes'][0] }} {% if voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_GRANTED') %} ACCESS GRANTED {% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_ABSTAIN') %} ACCESS ABSTAIN {% elseif voter_detail['vote'] == constant('Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::ACCESS_DENIED') %} ACCESS DENIED {% else %} unknown ({{ voter_detail['vote'] }}) {% endif %}
Show voter details {% endif %}
{% endif %}
{% endif %} {% endblock %} * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\CacheWarmer; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\ExpressionLanguage; class ExpressionCacheWarmer implements CacheWarmerInterface { private $expressions; private $expressionLanguage; /** * @param iterable $expressions */ public function __construct(iterable $expressions, ExpressionLanguage $expressionLanguage) { $this->expressions = $expressions; $this->expressionLanguage = $expressionLanguage; } public function isOptional() { return \true; } /** * @return string[] */ public function warmUp(string $cacheDir) { foreach ($this->expressions as $expression) { $this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']); } return []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\CleanRememberMeVerifierPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AnonymousFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginLdapFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginLinkFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\LoginThrottlingFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PassConfig; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\Bundle; use _ContaoManager\Symfony\Component\Security\Core\AuthenticationEvents; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; /** * Bundle. * * @author Fabien Potencier */ class SecurityBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); /** @var SecurityExtension $extension */ $extension = $container->getExtension('security'); $extension->addAuthenticatorFactory(new FormLoginFactory()); $extension->addAuthenticatorFactory(new FormLoginLdapFactory()); $extension->addAuthenticatorFactory(new JsonLoginFactory()); $extension->addAuthenticatorFactory(new JsonLoginLdapFactory()); $extension->addAuthenticatorFactory(new HttpBasicFactory()); $extension->addAuthenticatorFactory(new HttpBasicLdapFactory()); $extension->addAuthenticatorFactory(new RememberMeFactory()); $extension->addAuthenticatorFactory(new X509Factory()); $extension->addAuthenticatorFactory(new RemoteUserFactory()); $extension->addAuthenticatorFactory(new GuardAuthenticationFactory()); $extension->addAuthenticatorFactory(new AnonymousFactory()); $extension->addAuthenticatorFactory(new CustomAuthenticatorFactory()); $extension->addAuthenticatorFactory(new LoginThrottlingFactory()); $extension->addAuthenticatorFactory(new LoginLinkFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); $extension->addUserProviderFactory(new LdapFactory()); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new CleanRememberMeVerifierPass()); $container->addCompilerPass(new RegisterCsrfFeaturesPass()); $container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200); $container->addCompilerPass(new RegisterLdapLocatorPass()); $container->addCompilerPass(new RegisterEntryPointPass()); // must be registered after RegisterListenersPass (in the FrameworkBundle) $container->addCompilerPass(new RegisterGlobalSecurityEventListenersPass(), PassConfig::TYPE_BEFORE_REMOVING, -200); // execute after ResolveChildDefinitionsPass optimization pass, to ensure class names are set $container->addCompilerPass(new SortFirewallListenersPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new ReplaceDecoratedRememberMeHandlerPass(), PassConfig::TYPE_OPTIMIZE); $container->addCompilerPass(new AddEventAliasesPass(\array_merge(AuthenticationEvents::ALIASES, SecurityEvents::ALIASES))); } } SecurityBundle ============== SecurityBundle provides a tight integration of the Security component into the Symfony full-stack framework. Resources --------- * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\RememberMe; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeDetails; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; /** * Used as a "workaround" for tagging aliases in the RememberMeFactory. * * @author Wouter de Jong * * @internal */ final class DecoratedRememberMeHandler implements RememberMeHandlerInterface { private $handler; public function __construct(RememberMeHandlerInterface $handler) { $this->handler = $handler; } /** * {@inheritDoc} */ public function createRememberMeCookie(UserInterface $user) : void { $this->handler->createRememberMeCookie($user); } /** * {@inheritDoc} */ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails) : UserInterface { return $this->handler->consumeRememberMeCookie($rememberMeDetails); } /** * {@inheritDoc} */ public function clearRememberMeCookie() : void { $this->handler->clearRememberMeCookie(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\RememberMe; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallMap; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeDetails; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; /** * Decorates {@see RememberMeHandlerInterface} for the current firewall. * * @author Wouter de Jong */ final class FirewallAwareRememberMeHandler implements RememberMeHandlerInterface { use FirewallAwareTrait; private const FIREWALL_OPTION = 'remember_me'; public function __construct(FirewallMap $firewallMap, ContainerInterface $rememberMeHandlerLocator, RequestStack $requestStack) { $this->firewallMap = $firewallMap; $this->locator = $rememberMeHandlerLocator; $this->requestStack = $requestStack; } public function createRememberMeCookie(UserInterface $user) : void { $this->getForFirewall()->createRememberMeCookie($user); } public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails) : UserInterface { return $this->getForFirewall()->consumeRememberMeCookie($rememberMeDetails); } public function clearRememberMeCookie() : void { $this->getForFirewall()->clearRememberMeCookie(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Command; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallContext; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; /** * @author Timo Bakx */ final class DebugFirewallCommand extends Command { protected static $defaultName = 'debug:firewall'; protected static $defaultDescription = 'Display information about your security firewall(s)'; private $firewallNames; private $contexts; private $eventDispatchers; private $authenticators; private $authenticatorManagerEnabled; /** * @param string[] $firewallNames * @param AuthenticatorInterface[][] $authenticators */ public function __construct(array $firewallNames, ContainerInterface $contexts, ContainerInterface $eventDispatchers, array $authenticators, bool $authenticatorManagerEnabled) { if (!$authenticatorManagerEnabled) { \trigger_deprecation('symfony/security-bundle', '5.4', 'Setting the $authenticatorManagerEnabled argument of "%s" to "false" is deprecated, use the new authenticator system instead.', __METHOD__); } $this->firewallNames = $firewallNames; $this->contexts = $contexts; $this->eventDispatchers = $eventDispatchers; $this->authenticators = $authenticators; $this->authenticatorManagerEnabled = $authenticatorManagerEnabled; parent::__construct(); } protected function configure() : void { $exampleName = $this->getExampleName(); $this->setDescription(self::$defaultDescription)->setHelp(<<%command.name%
command displays the firewalls that are configured in your application: php %command.full_name% You can pass a firewall name to display more detailed information about a specific firewall: php %command.full_name% {$exampleName} To include all events and event listeners for a specific firewall, use the events option: php %command.full_name% --events {$exampleName} EOF )->setDefinition([new InputArgument('name', InputArgument::OPTIONAL, \sprintf('A firewall name (for example "%s")', $exampleName)), new InputOption('events', null, InputOption::VALUE_NONE, 'Include a list of event listeners (only available in combination with the "name" argument)')]); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); if (null === $name) { $this->displayFirewallList($io); return 0; } $serviceId = \sprintf('security.firewall.map.context.%s', $name); if (!$this->contexts->has($serviceId)) { $io->error(\sprintf('Firewall %s was not found. Available firewalls are: %s', $name, \implode(', ', $this->firewallNames))); return 1; } /** @var FirewallContext $context */ $context = $this->contexts->get($serviceId); $io->title(\sprintf('Firewall "%s"', $name)); $this->displayFirewallSummary($name, $context, $io); $this->displaySwitchUser($context, $io); if ($input->getOption('events')) { $this->displayEventListeners($name, $context, $io); } if ($this->authenticatorManagerEnabled) { $this->displayAuthenticators($name, $io); } return 0; } protected function displayFirewallList(SymfonyStyle $io) : void { $io->title('Firewalls'); $io->text('The following firewalls are defined:'); $io->listing($this->firewallNames); $io->comment(\sprintf('To view details of a specific firewall, re-run this command with a firewall name. (e.g. debug:firewall %s)', $this->getExampleName())); } protected function displayFirewallSummary(string $name, FirewallContext $context, SymfonyStyle $io) : void { if (null === $context->getConfig()) { return; } $rows = [['Name', $name], ['Context', $context->getConfig()->getContext()], ['Lazy', $context instanceof LazyFirewallContext ? 'Yes' : 'No'], ['Stateless', $context->getConfig()->isStateless() ? 'Yes' : 'No'], ['User Checker', $context->getConfig()->getUserChecker()], ['Provider', $context->getConfig()->getProvider()], ['Entry Point', $context->getConfig()->getEntryPoint()], ['Access Denied URL', $context->getConfig()->getAccessDeniedUrl()], ['Access Denied Handler', $context->getConfig()->getAccessDeniedHandler()]]; $io->table(['Option', 'Value'], $rows); } private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io) { if (null === ($config = $context->getConfig()) || null === ($switchUser = $config->getSwitchUser())) { return; } $io->section('User switching'); $io->table(['Option', 'Value'], [['Parameter', $switchUser['parameter'] ?? ''], ['Provider', $switchUser['provider'] ?? $config->getProvider()], ['User Role', $switchUser['role'] ?? '']]); } protected function displayEventListeners(string $name, FirewallContext $context, SymfonyStyle $io) : void { $io->title(\sprintf('Event listeners for firewall "%s"', $name)); $dispatcherId = \sprintf('security.event_dispatcher.%s', $name); if (!$this->eventDispatchers->has($dispatcherId)) { $io->text('No event dispatcher has been registered for this firewall.'); return; } /** @var EventDispatcherInterface $dispatcher */ $dispatcher = $this->eventDispatchers->get($dispatcherId); foreach ($dispatcher->getListeners() as $event => $listeners) { $io->section(\sprintf('"%s" event', $event)); $rows = []; foreach ($listeners as $order => $listener) { $rows[] = [\sprintf('#%d', $order + 1), $this->formatCallable($listener), $dispatcher->getListenerPriority($event, $listener)]; } $io->table(['Order', 'Callable', 'Priority'], $rows); } } private function displayAuthenticators(string $name, SymfonyStyle $io) : void { $io->title(\sprintf('Authenticators for firewall "%s"', $name)); $authenticators = $this->authenticators[$name] ?? []; if (0 === \count($authenticators)) { $io->text('No authenticators have been registered for this firewall.'); return; } $io->table(['Classname'], \array_map(static function ($authenticator) { return [\get_class($authenticator)]; }, $authenticators)); } private function formatCallable($callable) : string { if (\is_array($callable)) { if (\is_object($callable[0])) { return \sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); } return \sprintf('%s::%s()', $callable[0], $callable[1]); } if (\is_string($callable)) { return \sprintf('%s()', $callable); } if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); if (\false !== \strpos($r->name, '{closure}')) { return 'Closure()'; } if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return \sprintf('%s::%s()', $class->name, $r->name); } return $r->name . '()'; } if (\method_exists($callable, '__invoke')) { return \sprintf('%s::__invoke()', \get_class($callable)); } throw new \InvalidArgumentException('Callable is not describable.'); } private function getExampleName() : string { $name = 'main'; if (!\in_array($name, $this->firewallNames, \true)) { $name = \reset($this->firewallNames); } return $name; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('name')) { $suggestions->suggestValues($this->firewallNames); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Question\Question; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\PasswordHasher\Command\UserPasswordHashCommand; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface; /** * Encode a user's password. * * @author Sarah Khalil * * @final * * @deprecated since Symfony 5.3, use {@link UserPasswordHashCommand} instead */ class UserPasswordEncoderCommand extends Command { protected static $defaultName = 'security:encode-password'; protected static $defaultDescription = 'Encode a password'; private $encoderFactory; private $userClasses; public function __construct(EncoderFactoryInterface $encoderFactory, array $userClasses = []) { $this->encoderFactory = $encoderFactory; $this->userClasses = $userClasses; parent::__construct(); } /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->addArgument('password', InputArgument::OPTIONAL, 'The plain password to encode.')->addArgument('user-class', InputArgument::OPTIONAL, 'The User entity class path associated with the encoder used to encode the password.')->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the encoder generate one.')->setHelp(<<%command.name%
command encodes passwords according to your security configuration. This command is mainly used to generate passwords for the in_memory user provider type and for changing passwords in the database while developing the application. Suppose that you have the following security configuration in your application: # app/config/security.yml security: encoders: Symfony\\Component\\Security\\Core\\User\\InMemoryUser: plaintext App\\Entity\\User: auto If you execute the command non-interactively, the first available configured user class under the security.encoders key is used and a random salt is generated to encode the password: php %command.full_name% --no-interaction [password] Pass the full user class path as the second argument to encode passwords for your own entities: php %command.full_name% --no-interaction [password] 'App\\Entity\\User' Executing the command interactively allows you to generate a random salt for encoding the password: php %command.full_name% [password] 'App\\Entity\\User' In case your encoder doesn't require a salt, add the empty-salt option: php %command.full_name% --empty-salt [password] 'App\\Entity\\User' EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; $errorIo->caution('The use of the "security:encode-password" command is deprecated since version 5.3 and will be removed in 6.0. Use "security:hash-password" instead.'); $input->isInteractive() ? $errorIo->title('Symfony Password Encoder Utility') : $errorIo->newLine(); $password = $input->getArgument('password'); $userClass = $this->getUserClass($input, $io); $emptySalt = $input->getOption('empty-salt'); $encoder = $this->encoderFactory->getEncoder($userClass); $saltlessWithoutEmptySalt = !$emptySalt && $encoder instanceof SelfSaltingEncoderInterface; if ($saltlessWithoutEmptySalt) { $emptySalt = \true; } if (!$password) { if (!$input->isInteractive()) { $errorIo->error('The password must not be empty.'); return 1; } $passwordQuestion = $this->createPasswordQuestion(); $password = $errorIo->askQuestion($passwordQuestion); } $salt = null; if ($input->isInteractive() && !$emptySalt) { $emptySalt = \true; $errorIo->note('The command will take care of generating a salt for you. Be aware that some encoders advise to let them generate their own salt. If you\'re using one of those encoders, please answer \'no\' to the question below. ' . \PHP_EOL . 'Provide the \'empty-salt\' option in order to let the encoder handle the generation itself.'); if ($errorIo->confirm('Confirm salt generation ?')) { $salt = $this->generateSalt(); $emptySalt = \false; } } elseif (!$emptySalt) { $salt = $this->generateSalt(); } $encodedPassword = $encoder->encodePassword($password, $salt); $rows = [['Encoder used', \get_class($encoder)], ['Encoded password', $encodedPassword]]; if (!$emptySalt) { $rows[] = ['Generated salt', $salt]; } $io->table(['Key', 'Value'], $rows); if (!$emptySalt) { $errorIo->note(\sprintf('Make sure that your salt storage field fits the salt length: %s chars', \strlen($salt))); } elseif ($saltlessWithoutEmptySalt) { $errorIo->note('Self-salting encoder used: the encoder generated its own built-in salt.'); } $errorIo->success('Password encoding succeeded'); return 0; } /** * Create the password question to ask the user for the password to be encoded. */ private function createPasswordQuestion() : Question { $passwordQuestion = new Question('Type in your password to be encoded'); return $passwordQuestion->setValidator(function ($value) { if ('' === \trim($value)) { throw new InvalidArgumentException('The password must not be empty.'); } return $value; })->setHidden(\true)->setMaxAttempts(20); } private function generateSalt() : string { return \base64_encode(\random_bytes(30)); } private function getUserClass(InputInterface $input, SymfonyStyle $io) : string { if (null !== ($userClass = $input->getArgument('user-class'))) { return $userClass; } if (empty($this->userClasses)) { throw new RuntimeException('There are no configured encoders for the "security" extension.'); } if (!$input->isInteractive() || 1 === \count($this->userClasses)) { return \reset($this->userClasses); } $userClasses = $this->userClasses; \natcasesort($userClasses); $userClasses = \array_values($userClasses); return $io->choice('For which user class would you like to encode a password?', $userClasses, \reset($userClasses)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\LoginLink; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallAwareTrait; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallMap; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\LoginLinkDetails; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; /** * Decorates the login link handler for the current firewall. * * @author Ryan Weaver */ class FirewallAwareLoginLinkHandler implements LoginLinkHandlerInterface { use FirewallAwareTrait; private const FIREWALL_OPTION = 'login_link'; public function __construct(FirewallMap $firewallMap, ContainerInterface $loginLinkHandlerLocator, RequestStack $requestStack) { $this->firewallMap = $firewallMap; $this->locator = $loginLinkHandlerLocator; $this->requestStack = $requestStack; } public function createLoginLink(UserInterface $user, ?Request $request = null) : LoginLinkDetails { return $this->getForFirewall()->createLoginLink($user, $request); } public function consumeLoginLink(Request $request) : UserInterface { return $this->getForFirewall()->consumeLoginLink($request); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; use _ContaoManager\Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Builder\TreeBuilder; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; /** * SecurityExtension configuration structure. * * @author Johannes M. Schmitt */ class MainConfiguration implements ConfigurationInterface { /** @internal */ public const STRATEGY_AFFIRMATIVE = 'affirmative'; /** @internal */ public const STRATEGY_CONSENSUS = 'consensus'; /** @internal */ public const STRATEGY_UNANIMOUS = 'unanimous'; /** @internal */ public const STRATEGY_PRIORITY = 'priority'; private $factories; private $userProviderFactories; /** * @param array $factories */ public function __construct(array $factories, array $userProviderFactories) { if (\is_array(\current($factories))) { \trigger_deprecation('symfony/security-bundle', '5.4', 'Passing an array of arrays as 1st argument to "%s" is deprecated, pass a sorted array of factories instead.', __METHOD__); $factories = \array_merge(...\array_values($factories)); } $this->factories = $factories; $this->userProviderFactories = $userProviderFactories; } /** * Generates the configuration tree builder. * * @return TreeBuilder */ public function getConfigTreeBuilder() { $tb = new TreeBuilder('security'); $rootNode = $tb->getRootNode(); $rootNode->beforeNormalization()->ifTrue(function ($v) { if ($v['encoders'] ?? \false) { \trigger_deprecation('symfony/security-bundle', '5.3', 'The child node "encoders" at path "security" is deprecated, use "password_hashers" instead.'); return \true; } return $v['password_hashers'] ?? \false; })->then(function ($v) { $v['password_hashers'] = \array_merge($v['password_hashers'] ?? [], $v['encoders'] ?? []); $v['encoders'] = $v['password_hashers']; return $v; })->end()->children()->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()->enumNode('session_fixation_strategy')->values([SessionAuthenticationStrategy::NONE, SessionAuthenticationStrategy::MIGRATE, SessionAuthenticationStrategy::INVALIDATE])->defaultValue(SessionAuthenticationStrategy::MIGRATE)->end()->booleanNode('hide_user_not_found')->defaultTrue()->end()->booleanNode('always_authenticate_before_granting')->defaultFalse()->setDeprecated('symfony/security-bundle', '5.4')->end()->booleanNode('erase_credentials')->defaultTrue()->end()->booleanNode('enable_authenticator_manager')->defaultFalse()->info('Enables the new Symfony Security system based on Authenticators, all used authenticators must support this before enabling this.')->end()->arrayNode('access_decision_manager')->addDefaultsIfNotSet()->children()->enumNode('strategy')->values($this->getAccessDecisionStrategies())->end()->scalarNode('service')->end()->scalarNode('strategy_service')->end()->booleanNode('allow_if_all_abstain')->defaultFalse()->end()->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()->end()->validate()->ifTrue(function ($v) { return isset($v['strategy'], $v['service']); })->thenInvalid('"strategy" and "service" cannot be used together.')->end()->validate()->ifTrue(function ($v) { return isset($v['strategy'], $v['strategy_service']); })->thenInvalid('"strategy" and "strategy_service" cannot be used together.')->end()->validate()->ifTrue(function ($v) { return isset($v['service'], $v['strategy_service']); })->thenInvalid('"service" and "strategy_service" cannot be used together.')->end()->end()->end(); $this->addEncodersSection($rootNode); $this->addPasswordHashersSection($rootNode); $this->addProvidersSection($rootNode); $this->addFirewallsSection($rootNode, $this->factories); $this->addAccessControlSection($rootNode); $this->addRoleHierarchySection($rootNode); return $tb; } private function addRoleHierarchySection(ArrayNodeDefinition $rootNode) { $rootNode->fixXmlConfig('role', 'role_hierarchy')->children()->arrayNode('role_hierarchy')->useAttributeAsKey('id')->prototype('array')->performNoDeepMerging()->beforeNormalization()->ifString()->then(function ($v) { return ['value' => $v]; })->end()->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); })->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v['value']); })->end()->prototype('scalar')->end()->end()->end()->end(); } private function addAccessControlSection(ArrayNodeDefinition $rootNode) { $rootNode->fixXmlConfig('rule', 'access_control')->children()->arrayNode('access_control')->cannotBeOverwritten()->prototype('array')->fixXmlConfig('ip')->fixXmlConfig('method')->children()->scalarNode('requires_channel')->defaultNull()->end()->scalarNode('path')->defaultNull()->info('use the urldecoded format')->example('^/path to resource/')->end()->scalarNode('host')->defaultNull()->end()->integerNode('port')->defaultNull()->end()->arrayNode('ips')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->prototype('scalar')->end()->end()->arrayNode('methods')->beforeNormalization()->ifString()->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v); })->end()->prototype('scalar')->end()->end()->scalarNode('allow_if')->defaultNull()->end()->end()->fixXmlConfig('role')->children()->arrayNode('roles')->beforeNormalization()->ifString()->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v); })->end()->prototype('scalar')->end()->end()->end()->end()->end()->end(); } /** * @param array $factories */ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories) { $firewallNodeBuilder = $rootNode->fixXmlConfig('firewall')->children()->arrayNode('firewalls')->isRequired()->requiresAtLeastOneElement()->disallowNewKeysInSubsequentConfigs()->useAttributeAsKey('name')->prototype('array')->fixXmlConfig('required_badge')->children(); $firewallNodeBuilder->scalarNode('pattern')->end()->scalarNode('host')->end()->arrayNode('methods')->beforeNormalization()->ifString()->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v); })->end()->prototype('scalar')->end()->end()->booleanNode('security')->defaultTrue()->end()->scalarNode('user_checker')->defaultValue('security.user_checker')->treatNullLike('security.user_checker')->info('The UserChecker to use when authenticating users in this firewall.')->end()->scalarNode('request_matcher')->end()->scalarNode('access_denied_url')->end()->scalarNode('access_denied_handler')->end()->scalarNode('entry_point')->info(\sprintf('An enabled authenticator name or a service id that implements "%s"', AuthenticationEntryPointInterface::class))->end()->scalarNode('provider')->end()->booleanNode('stateless')->defaultFalse()->end()->booleanNode('lazy')->defaultFalse()->end()->scalarNode('context')->cannotBeEmpty()->end()->arrayNode('logout')->treatTrueLike([])->canBeUnset()->children()->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end()->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()->scalarNode('csrf_token_id')->defaultValue('logout')->end()->scalarNode('path')->defaultValue('/logout')->end()->scalarNode('target')->defaultValue('/')->end()->scalarNode('success_handler')->setDeprecated('symfony/security-bundle', '5.1', \sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.', LogoutEvent::class))->end()->booleanNode('invalidate_session')->defaultTrue()->end()->end()->fixXmlConfig('delete_cookie')->children()->arrayNode('delete_cookies')->normalizeKeys(\false)->beforeNormalization()->ifTrue(function ($v) { return \is_array($v) && \is_int(\key($v)); })->then(function ($v) { return \array_map(function ($v) { return ['name' => $v]; }, $v); })->end()->useAttributeAsKey('name')->prototype('array')->children()->scalarNode('path')->defaultNull()->end()->scalarNode('domain')->defaultNull()->end()->scalarNode('secure')->defaultFalse()->end()->scalarNode('samesite')->defaultNull()->end()->end()->end()->end()->end()->fixXmlConfig('handler')->children()->arrayNode('handlers')->prototype('scalar')->setDeprecated('symfony/security-bundle', '5.1', \sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.', LogoutEvent::class))->end()->end()->end()->end()->arrayNode('switch_user')->canBeUnset()->children()->scalarNode('provider')->end()->scalarNode('parameter')->defaultValue('_switch_user')->end()->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()->end()->end()->arrayNode('required_badges')->info('A list of badges that must be present on the authenticated passport.')->validate()->always()->then(function ($requiredBadges) { return \array_map(function ($requiredBadge) { if (\class_exists($requiredBadge)) { return $requiredBadge; } if (\false === \strpos($requiredBadge, '\\')) { $fqcn = 'Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\' . $requiredBadge; if (\class_exists($fqcn)) { return $fqcn; } } throw new InvalidConfigurationException(\sprintf('Undefined security Badge class "%s" set in "security.firewall.required_badges".', $requiredBadge)); }, $requiredBadges); })->end()->prototype('scalar')->end()->end(); $abstractFactoryKeys = []; foreach ($factories as $factory) { $name = \str_replace('-', '_', $factory->getKey()); $factoryNode = $firewallNodeBuilder->arrayNode($name)->canBeUnset(); if ($factory instanceof AbstractFactory) { $abstractFactoryKeys[] = $name; } $factory->addConfiguration($factoryNode); } // check for unreachable check paths $firewallNodeBuilder->end()->validate()->ifTrue(function ($v) { return \true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']); })->then(function ($firewall) use($abstractFactoryKeys) { foreach ($abstractFactoryKeys as $k) { if (!isset($firewall[$k]['check_path'])) { continue; } if (\str_contains($firewall[$k]['check_path'], '/') && !\preg_match('#' . $firewall['pattern'] . '#', $firewall[$k]['check_path'])) { throw new \LogicException(\sprintf('The check_path "%s" for login method "%s" is not matched by the firewall pattern "%s".', $firewall[$k]['check_path'], $k, $firewall['pattern'])); } } return $firewall; })->end(); } private function addProvidersSection(ArrayNodeDefinition $rootNode) { $providerNodeBuilder = $rootNode->fixXmlConfig('provider')->children()->arrayNode('providers')->example(['my_memory_provider' => ['memory' => ['users' => ['foo' => ['password' => 'foo', 'roles' => 'ROLE_USER'], 'bar' => ['password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]']]]], 'my_entity_provider' => ['entity' => ['class' => 'SecurityBundle:User', 'property' => 'username']]])->requiresAtLeastOneElement()->useAttributeAsKey('name')->prototype('array'); $providerNodeBuilder->children()->scalarNode('id')->end()->arrayNode('chain')->fixXmlConfig('provider')->children()->arrayNode('providers')->beforeNormalization()->ifString()->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v); })->end()->prototype('scalar')->end()->end()->end()->end()->end(); foreach ($this->userProviderFactories as $factory) { $name = \str_replace('-', '_', $factory->getKey()); $factoryNode = $providerNodeBuilder->children()->arrayNode($name)->canBeUnset(); $factory->addConfiguration($factoryNode); } $providerNodeBuilder->validate()->ifTrue(function ($v) { return \count($v) > 1; })->thenInvalid('You cannot set multiple provider types for the same provider')->end()->validate()->ifTrue(function ($v) { return 0 === \count($v); })->thenInvalid('You must set a provider definition for the provider.')->end(); } private function addEncodersSection(ArrayNodeDefinition $rootNode) { $rootNode->fixXmlConfig('encoder')->children()->arrayNode('encoders')->example(['_ContaoManager\\App\\Entity\\User1' => 'auto', '_ContaoManager\\App\\Entity\\User2' => ['algorithm' => 'auto', 'time_cost' => 8, 'cost' => 13]])->requiresAtLeastOneElement()->useAttributeAsKey('class')->prototype('array')->canBeUnset()->performNoDeepMerging()->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()->children()->scalarNode('algorithm')->cannotBeEmpty()->validate()->ifTrue(function ($v) { return !\is_string($v); })->thenInvalid('You must provide a string value.')->end()->end()->arrayNode('migrate_from')->prototype('scalar')->end()->beforeNormalization()->castToArray()->end()->end()->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()->scalarNode('key_length')->defaultValue(40)->end()->booleanNode('ignore_case')->defaultFalse()->end()->booleanNode('encode_as_base64')->defaultTrue()->end()->scalarNode('iterations')->defaultValue(5000)->end()->integerNode('cost')->min(4)->max(31)->defaultNull()->end()->scalarNode('memory_cost')->defaultNull()->end()->scalarNode('time_cost')->defaultNull()->end()->scalarNode('id')->end()->end()->end()->end()->end(); } private function addPasswordHashersSection(ArrayNodeDefinition $rootNode) { $rootNode->fixXmlConfig('password_hasher')->children()->arrayNode('password_hashers')->example(['_ContaoManager\\App\\Entity\\User1' => 'auto', '_ContaoManager\\App\\Entity\\User2' => ['algorithm' => 'auto', 'time_cost' => 8, 'cost' => 13]])->requiresAtLeastOneElement()->useAttributeAsKey('class')->prototype('array')->canBeUnset()->performNoDeepMerging()->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end()->children()->scalarNode('algorithm')->cannotBeEmpty()->validate()->ifTrue(function ($v) { return !\is_string($v); })->thenInvalid('You must provide a string value.')->end()->end()->arrayNode('migrate_from')->prototype('scalar')->end()->beforeNormalization()->castToArray()->end()->end()->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end()->scalarNode('key_length')->defaultValue(40)->end()->booleanNode('ignore_case')->defaultFalse()->end()->booleanNode('encode_as_base64')->defaultTrue()->end()->scalarNode('iterations')->defaultValue(5000)->end()->integerNode('cost')->min(4)->max(31)->defaultNull()->end()->scalarNode('memory_cost')->defaultNull()->end()->scalarNode('time_cost')->defaultNull()->end()->scalarNode('id')->end()->end()->end()->end()->end(); } private function getAccessDecisionStrategies() : array { return [self::STRATEGY_AFFIRMATIVE, self::STRATEGY_CONSENSUS, self::STRATEGY_UNANIMOUS, self::STRATEGY_PRIORITY]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * LdapFactory creates services for Ldap user provider. * * @author Grégoire Pineau * @author Charles Sarrazin */ class LdapFactory implements UserProviderFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config) { $container->setDefinition($id, new ChildDefinition('security.user.provider.ldap'))->replaceArgument(0, new Reference($config['service']))->replaceArgument(1, $config['base_dn'])->replaceArgument(2, $config['search_dn'])->replaceArgument(3, $config['search_password'])->replaceArgument(4, $config['default_roles'])->replaceArgument(5, $config['uid_key'])->replaceArgument(6, $config['filter'])->replaceArgument(7, $config['password_attribute'])->replaceArgument(8, $config['extra_fields']); } public function getKey() { return 'ldap'; } public function addConfiguration(NodeDefinition $node) { $node->fixXmlConfig('extra_field')->fixXmlConfig('default_role')->children()->scalarNode('service')->isRequired()->cannotBeEmpty()->defaultValue('ldap')->end()->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end()->scalarNode('search_dn')->defaultNull()->end()->scalarNode('search_password')->defaultNull()->end()->arrayNode('extra_fields')->prototype('scalar')->end()->end()->arrayNode('default_roles')->beforeNormalization()->ifString()->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v); })->end()->requiresAtLeastOneElement()->prototype('scalar')->end()->end()->scalarNode('uid_key')->defaultValue('sAMAccountName')->end()->scalarNode('filter')->defaultValue('({uid_key}={username})')->end()->scalarNode('password_attribute')->defaultNull()->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; /** * InMemoryFactory creates services for the memory provider. * * @author Fabien Potencier * @author Christophe Coevoet */ class InMemoryFactory implements UserProviderFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config) { $definition = $container->setDefinition($id, new ChildDefinition('security.user.provider.in_memory')); $defaultPassword = new Parameter('container.build_id'); $users = []; foreach ($config['users'] as $username => $user) { $users[$username] = ['password' => null !== $user['password'] ? (string) $user['password'] : $defaultPassword, 'roles' => $user['roles']]; } $definition->addArgument($users); } public function getKey() { return 'memory'; } public function addConfiguration(NodeDefinition $node) { $node->fixXmlConfig('user')->children()->arrayNode('users')->useAttributeAsKey('identifier')->normalizeKeys(\false)->beforeNormalization()->always()->then(function ($v) { $deprecation = \false; foreach ($v as $i => $child) { if (!isset($child['name'])) { continue; } $deprecation = \true; $v[$i]['identifier'] = $child['name']; unset($v[$i]['name']); } if ($deprecation) { \trigger_deprecation('symfony/security-bundle', '5.3', 'The "in_memory.user.name" option is deprecated, use "identifier" instead.'); } return $v; })->end()->prototype('array')->children()->scalarNode('password')->defaultNull()->end()->arrayNode('roles')->beforeNormalization()->ifString()->then(function ($v) { return \preg_split('/\\s*,\\s*/', $v); })->end()->prototype('scalar')->end()->end()->end()->end()->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * UserProviderFactoryInterface is the interface for all user provider factories. * * @author Fabien Potencier * @author Christophe Coevoet */ interface UserProviderFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config); public function getKey(); public function addConfiguration(NodeDefinition $builder); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator; /** * Configures the "guard" authentication provider key under a firewall. * * @author Ryan Weaver * * @internal */ class GuardAuthenticationFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public function getPosition() : string { return 'pre_auth'; } public function getPriority() : int { return 0; } public function getKey() : string { return 'guard'; } public function addConfiguration(NodeDefinition $node) { $node->fixXmlConfig('authenticator')->children()->scalarNode('provider')->info('A key from the "providers" section of your security config, in case your user provider is different than the firewall')->end()->scalarNode('entry_point')->info('A service id (of one of your authenticators) whose start() method should be called when an anonymous user hits a page that requires authentication')->defaultValue(null)->end()->arrayNode('authenticators')->info('An array of service ids for all of your "authenticators"')->requiresAtLeastOneElement()->prototype('scalar')->end()->end()->end(); } public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { $authenticatorIds = $config['authenticators']; $authenticatorReferences = []; foreach ($authenticatorIds as $authenticatorId) { $authenticatorReferences[] = new Reference($authenticatorId); } $authenticators = new IteratorArgument($authenticatorReferences); // configure the GuardAuthenticationFactory to have the dynamic constructor arguments $providerId = 'security.authentication.provider.guard.' . $id; $container->setDefinition($providerId, new ChildDefinition('security.authentication.provider.guard'))->replaceArgument(0, $authenticators)->replaceArgument(1, new Reference($userProvider))->replaceArgument(2, $id)->replaceArgument(3, new Reference('security.user_checker.' . $id)); // listener $listenerId = 'security.authentication.listener.guard.' . $id; $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.guard')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $authenticators); // determine the entryPointId to use $entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config); // this is always injected - then the listener decides if it should be used $container->getDefinition($listenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => $userProvider]); return [$providerId, $listenerId, $entryPointId]; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) { $userProvider = new Reference($userProviderId); $authenticatorIds = []; if (isset($config['entry_point'])) { throw new InvalidConfigurationException('The "security.firewall.' . $firewallName . '.guard.entry_point" option has no effect in the new authenticator system, configure "security.firewall.' . $firewallName . '.entry_point" instead.'); } $guardAuthenticatorIds = $config['authenticators']; foreach ($guardAuthenticatorIds as $i => $guardAuthenticatorId) { $container->setDefinition($authenticatorIds[] = 'security.authenticator.guard.' . $firewallName . '.' . $i, new Definition(GuardBridgeAuthenticator::class))->setArguments([new Reference($guardAuthenticatorId), $userProvider]); } return $authenticatorIds; } private function determineEntryPoint(?string $defaultEntryPointId, array $config) : string { if ($defaultEntryPointId) { // explode if they've configured the entry_point, but there is already one if ($config['entry_point']) { throw new \LogicException(\sprintf('The guard authentication provider cannot use the "%s" entry_point because another entry point is already configured by another provider! Either remove the other provider or move the entry_point configuration as a root key under your firewall (i.e. at the same level as "guard").', $config['entry_point'])); } return $defaultEntryPointId; } if ($config['entry_point']) { // if it's configured explicitly, use it! return $config['entry_point']; } $authenticatorIds = $config['authenticators']; if (1 == \count($authenticatorIds)) { // if there is only one authenticator, use that as the entry point return \array_shift($authenticatorIds); } // we have multiple entry points - we must ask them to configure one throw new \LogicException(\sprintf('Because you have multiple guard authenticators, you need to set the "guard.entry_point" key to one of your authenticators (%s).', \implode(', ', $authenticatorIds))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeBuilder; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; /** * @internal */ class LoginLinkFactory extends AbstractFactory implements AuthenticatorFactoryInterface { public const PRIORITY = -20; public function addConfiguration(NodeDefinition $node) { /** @var NodeBuilder $builder */ $builder = $node->fixXmlConfig('signature_property', 'signature_properties')->children(); $builder->scalarNode('check_route')->isRequired()->info('Route that will validate the login link - e.g. "app_login_link_verify".')->end()->scalarNode('check_post_only')->defaultFalse()->info('If true, only HTTP POST requests to "check_route" will be handled by the authenticator.')->end()->arrayNode('signature_properties')->isRequired()->prototype('scalar')->end()->requiresAtLeastOneElement()->info('An array of properties on your User that are used to sign the link. If any of these change, all existing links will become invalid.')->example(['email', 'password'])->end()->integerNode('lifetime')->defaultValue(600)->info('The lifetime of the login link in seconds.')->end()->integerNode('max_uses')->defaultNull()->info('Max number of times a login link can be used - null means unlimited within lifetime.')->end()->scalarNode('used_link_cache')->info('Cache service id used to expired links of max_uses is set.')->end()->scalarNode('success_handler')->info(\sprintf('A service id that implements %s.', AuthenticationSuccessHandlerInterface::class))->end()->scalarNode('failure_handler')->info(\sprintf('A service id that implements %s.', AuthenticationFailureHandlerInterface::class))->end()->scalarNode('provider')->info('The user provider to load users from.')->end(); foreach (\array_merge($this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) { if (\is_bool($default)) { $builder->booleanNode($name)->defaultValue($default); } else { $builder->scalarNode($name)->defaultValue($default); } } } public function getKey() : string { return 'login-link'; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : string { if (!$container->hasDefinition('security.authenticator.login_link')) { $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__) . '/../../Resources/config')); $loader->load('security_authenticator_login_link.php'); } if (null !== $config['max_uses'] && !isset($config['used_link_cache'])) { $config['used_link_cache'] = 'security.authenticator.cache.expired_links'; $defaultCacheDefinition = $container->getDefinition($config['used_link_cache']); if (!$defaultCacheDefinition->hasTag('cache.pool')) { $defaultCacheDefinition->addTag('cache.pool'); } } $expiredStorageId = null; if (isset($config['used_link_cache'])) { $expiredStorageId = 'security.authenticator.expired_login_link_storage.' . $firewallName; $container->setDefinition($expiredStorageId, new ChildDefinition('security.authenticator.expired_login_link_storage'))->replaceArgument(0, new Reference($config['used_link_cache']))->replaceArgument(1, $config['lifetime']); } $signatureHasherId = 'security.authenticator.login_link_signature_hasher.' . $firewallName; $container->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.abstract_login_link_signature_hasher'))->replaceArgument(1, $config['signature_properties'])->replaceArgument(3, $expiredStorageId ? new Reference($expiredStorageId) : null)->replaceArgument(4, $config['max_uses'] ?? null); $linkerId = 'security.authenticator.login_link_handler.' . $firewallName; $linkerOptions = ['route_name' => $config['check_route'], 'lifetime' => $config['lifetime']]; $container->setDefinition($linkerId, new ChildDefinition('security.authenticator.abstract_login_link_handler'))->replaceArgument(1, new Reference($userProviderId))->replaceArgument(2, new Reference($signatureHasherId))->replaceArgument(3, $linkerOptions)->addTag('security.authenticator.login_linker', ['firewall' => $firewallName]); $authenticatorId = 'security.authenticator.login_link.' . $firewallName; $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.login_link'))->replaceArgument(0, new Reference($linkerId))->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))->replaceArgument(4, ['check_route' => $config['check_route'], 'check_post_only' => $config['check_post_only']]); return $authenticatorId; } public function getPriority() : int { return self::PRIORITY; } public function getPosition() : string { return 'form'; } protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId) : string { throw new \Exception('The old authentication system is not supported with login_link.'); } protected function getListenerId() : string { throw new \Exception('The old authentication system is not supported with login_link.'); } protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider) { throw new \Exception('The old authentication system is not supported with login_link.'); } protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId) : ?string { throw new \Exception('The old authentication system is not supported with login_link.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; /** * HttpBasicFactory creates services for HTTP basic authentication. * * @author Fabien Potencier * @author Grégoire Pineau * @author Charles Sarrazin * * @internal */ class HttpBasicLdapFactory extends HttpBasicFactory { use LdapFactoryTrait; public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { $provider = 'security.authentication.provider.ldap_bind.' . $id; $definition = $container->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))->replaceArgument(0, new Reference($userProvider))->replaceArgument(1, new Reference('security.user_checker.' . $id))->replaceArgument(2, $id)->replaceArgument(3, new Reference($config['service']))->replaceArgument(4, $config['dn_string'])->replaceArgument(6, $config['search_dn'])->replaceArgument(7, $config['search_password']); // entry point $entryPointId = $defaultEntryPoint; if (null === $entryPointId) { $entryPointId = 'security.authentication.basic_entry_point.' . $id; $container->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point'))->addArgument($config['realm']); } if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } $definition->addMethodCall('setQueryString', [$config['query_string']]); } // listener $listenerId = 'security.authentication.listener.basic.' . $id; $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.basic')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, new Reference($entryPointId)); return [$provider, $listenerId, $entryPointId]; } public function addConfiguration(NodeDefinition $node) { parent::addConfiguration($node); $node->children()->scalarNode('service')->defaultValue('ldap')->end()->scalarNode('dn_string')->defaultValue('{username}')->end()->scalarNode('query_string')->end()->scalarNode('search_dn')->defaultValue('')->end()->scalarNode('search_password')->defaultValue('')->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; /** * JsonLoginLdapFactory creates services for json login ldap authentication. * * @internal */ class JsonLoginLdapFactory extends JsonLoginFactory { use LdapFactoryTrait; protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId) : string { $provider = 'security.authentication.provider.ldap_bind.' . $id; $definition = $container->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))->replaceArgument(0, new Reference($userProviderId))->replaceArgument(1, new Reference('security.user_checker.' . $id))->replaceArgument(2, $id)->replaceArgument(3, new Reference($config['service']))->replaceArgument(4, $config['dn_string'])->replaceArgument(6, $config['search_dn'])->replaceArgument(7, $config['search_password']); if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } $definition->addMethodCall('setQueryString', [$config['query_string']]); } return $provider; } public function addConfiguration(NodeDefinition $node) { parent::addConfiguration($node); $node->children()->scalarNode('service')->defaultValue('ldap')->end()->scalarNode('dn_string')->defaultValue('{username}')->end()->scalarNode('query_string')->end()->scalarNode('search_dn')->defaultValue('')->end()->scalarNode('search_password')->defaultValue('')->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * FormLoginFactory creates services for form login authentication. * * @author Fabien Potencier * @author Johannes M. Schmitt * * @internal */ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryInterface { public const PRIORITY = -30; public function __construct() { $this->addOption('username_parameter', '_username'); $this->addOption('password_parameter', '_password'); $this->addOption('csrf_parameter', '_csrf_token'); $this->addOption('csrf_token_id', 'authenticate'); $this->addOption('enable_csrf', \false); $this->addOption('post_only', \true); $this->addOption('form_only', \false); } public function getPriority() : int { return self::PRIORITY; } public function getPosition() : string { return 'form'; } public function getKey() : string { return 'form-login'; } public function addConfiguration(NodeDefinition $node) { parent::addConfiguration($node); $node->children()->scalarNode('csrf_token_generator')->cannotBeEmpty()->end()->end(); } protected function getListenerId() : string { return 'security.authentication.listener.form'; } protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId) : string { if ($config['enable_csrf'] ?? \false) { throw new InvalidConfigurationException('The "enable_csrf" option of "form_login" is only available when "security.enable_authenticator_manager" is set to "true", use "csrf_token_generator" instead.'); } $provider = 'security.authentication.provider.dao.' . $id; $container->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))->replaceArgument(0, new Reference($userProviderId))->replaceArgument(1, new Reference('security.user_checker.' . $id))->replaceArgument(2, $id); return $provider; } protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider) { $listenerId = parent::createListener($container, $id, $config, $userProvider); $container->getDefinition($listenerId)->addArgument(isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null); return $listenerId; } protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId) : ?string { $entryPointId = 'security.authentication.form_entry_point.' . $id; $container->setDefinition($entryPointId, new ChildDefinition('security.authentication.form_entry_point'))->addArgument(new Reference('security.http_utils'))->addArgument($config['login_path'])->addArgument($config['use_forward']); return $entryPointId; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : string { if (isset($config['csrf_token_generator'])) { throw new InvalidConfigurationException('The "csrf_token_generator" option of "form_login" is only available when "security.enable_authenticator_manager" is set to "false", use "enable_csrf" instead.'); } $authenticatorId = 'security.authenticator.form_login.' . $firewallName; $options = \array_intersect_key($config, $this->options); $authenticator = $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))->replaceArgument(1, new Reference($userProviderId))->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)))->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)))->replaceArgument(4, $options); if ($options['use_forward'] ?? \false) { $authenticator->addMethodCall('setHttpKernel', [new Reference('http_kernel')]); } return $authenticatorId; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; /** * FormLoginLdapFactory creates services for form login ldap authentication. * * @author Grégoire Pineau * @author Charles Sarrazin * * @internal */ class FormLoginLdapFactory extends FormLoginFactory { use LdapFactoryTrait; protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId) : string { $provider = 'security.authentication.provider.ldap_bind.' . $id; $definition = $container->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind'))->replaceArgument(0, new Reference($userProviderId))->replaceArgument(1, new Reference('security.user_checker.' . $id))->replaceArgument(2, $id)->replaceArgument(3, new Reference($config['service']))->replaceArgument(4, $config['dn_string'])->replaceArgument(6, $config['search_dn'])->replaceArgument(7, $config['search_password']); if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } $definition->addMethodCall('setQueryString', [$config['query_string']]); } return $provider; } public function addConfiguration(NodeDefinition $node) { parent::addConfiguration($node); $node->children()->scalarNode('service')->defaultValue('ldap')->end()->scalarNode('dn_string')->defaultValue('{username}')->end()->scalarNode('query_string')->end()->scalarNode('search_dn')->defaultValue('')->end()->scalarNode('search_password')->defaultValue('')->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * @author Wouter de Jong * * @internal */ class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { throw new \LogicException('Custom authenticators are not supported when "security.enable_authenticator_manager" is not set to true.'); } public function getPriority() : int { return 0; } public function getPosition() : string { return 'pre_auth'; } public function getKey() : string { return 'custom_authenticators'; } /** * @param ArrayNodeDefinition $builder */ public function addConfiguration(NodeDefinition $builder) { $builder->info('An array of service ids for all of your "authenticators"')->requiresAtLeastOneElement()->prototype('scalar')->end(); // get the parent array node builder ("firewalls") from inside the children builder $factoryRootNode = $builder->end()->end(); $factoryRootNode->fixXmlConfig('custom_authenticator')->validate()->ifTrue(function ($v) { return isset($v['custom_authenticators']) && empty($v['custom_authenticators']); })->then(function ($v) { unset($v['custom_authenticators']); return $v; })->end(); } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : array { return $config; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * SecurityFactoryInterface is the interface for all security authentication listener. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use AuthenticatorFactoryInterface instead. */ interface SecurityFactoryInterface { /** * Configures the container services required to use the authentication listener. * * @return array containing three values: * - the provider id * - the listener id * - the entry point id */ public function create(ContainerBuilder $container, string $id, array $config, string $userProviderId, ?string $defaultEntryPointId); /** * Defines the position at which the provider is called. * Possible values: pre_auth, form, http, and remember_me. * * @return string */ public function getPosition(); /** * Defines the configuration key used to reference the provider * in the firewall configuration. * * @return string */ public function getKey(); public function addConfiguration(NodeDefinition $builder); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * RemoteUserFactory creates services for REMOTE_USER based authentication. * * @author Fabien Potencier * @author Maxime Douailin * * @internal */ class RemoteUserFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public const PRIORITY = -10; public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { $providerId = 'security.authentication.provider.pre_authenticated.' . $id; $container->setDefinition($providerId, new ChildDefinition('security.authentication.provider.pre_authenticated'))->replaceArgument(0, new Reference($userProvider))->replaceArgument(1, new Reference('security.user_checker.' . $id))->addArgument($id); $listenerId = 'security.authentication.listener.remote_user.' . $id; $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.remote_user')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $config['user']); $listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.' . $id)]); return [$providerId, $listenerId, $defaultEntryPoint]; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) { $authenticatorId = 'security.authenticator.remote_user.' . $firewallName; $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remote_user'))->replaceArgument(0, new Reference($userProviderId))->replaceArgument(2, $firewallName)->replaceArgument(3, $config['user']); return $authenticatorId; } public function getPriority() : int { return self::PRIORITY; } public function getPosition() : string { return 'pre_auth'; } public function getKey() : string { return 'remote-user'; } public function addConfiguration(NodeDefinition $node) { $node->children()->scalarNode('provider')->end()->scalarNode('user')->defaultValue('REMOTE_USER')->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * @method int getPriority() defines the position at which the authenticator is called * * @author Wouter de Jong */ interface AuthenticatorFactoryInterface { /** * Creates the authenticator service(s) for the provided configuration. * * @return string|string[] The authenticator service ID(s) to be used by the firewall */ public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId); /** * Defines the configuration key used to reference the authenticator * in the firewall configuration. * * @return string */ public function getKey(); public function addConfiguration(NodeDefinition $builder); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; use _ContaoManager\Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\CacheTokenVerifier; use _ContaoManager\Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener; /** * @internal */ class RememberMeFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface, PrependExtensionInterface { public const PRIORITY = -50; protected $options = ['name' => 'REMEMBERME', 'lifetime' => 31536000, 'path' => '/', 'domain' => null, 'secure' => \false, 'httponly' => \true, 'samesite' => null, 'always_remember_me' => \false, 'remember_me_parameter' => '_remember_me']; public function create(ContainerBuilder $container, string $id, array $config, ?string $userProvider, ?string $defaultEntryPoint) : array { // authentication provider $authProviderId = 'security.authentication.provider.rememberme.' . $id; $container->setDefinition($authProviderId, new ChildDefinition('security.authentication.provider.rememberme'))->replaceArgument(0, new Reference('security.user_checker.' . $id))->addArgument($config['secret'])->addArgument($id); // remember me services $templateId = $this->generateRememberMeServicesTemplateId($config, $id); $rememberMeServicesId = $templateId . '.' . $id; // attach to remember-me aware listeners $userProviders = []; foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) { foreach ($attributes as $attribute) { if (!isset($attribute['id']) || $attribute['id'] !== $id) { continue; } if (!isset($attribute['provider'])) { throw new \RuntimeException('Each "security.remember_me_aware" tag must have a provider attribute.'); } // context listeners don't need a provider if ('none' !== $attribute['provider']) { $userProviders[] = new Reference($attribute['provider']); } $container->getDefinition($serviceId)->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)]); } } $this->createRememberMeServices($container, $id, $templateId, $userProviders, $config); // remember-me listener $listenerId = 'security.authentication.listener.rememberme.' . $id; $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.rememberme')); $listener->replaceArgument(1, new Reference($rememberMeServicesId)); $listener->replaceArgument(5, $config['catch_exceptions']); // remember-me logout listener $container->setDefinition('security.logout.listener.remember_me.' . $id, new Definition(RememberMeLogoutListener::class))->addArgument(new Reference($rememberMeServicesId))->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.' . $id]); return [$authProviderId, $listenerId, $defaultEntryPoint]; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : string { if (!$container->hasDefinition('security.authenticator.remember_me')) { $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__) . '/../../Resources/config')); $loader->load('security_authenticator_remember_me.php'); } if ('auto' === $config['secure']) { $config['secure'] = null; } // create remember me handler (which manage the remember-me cookies) $rememberMeHandlerId = 'security.authenticator.remember_me_handler.' . $firewallName; if (isset($config['service']) && isset($config['token_provider'])) { throw new InvalidConfigurationException(\sprintf('You cannot use both "service" and "token_provider" in "security.firewalls.%s.remember_me".', $firewallName)); } if (isset($config['service'])) { $container->register($rememberMeHandlerId, DecoratedRememberMeHandler::class)->addArgument(new Reference($config['service']))->addTag('security.remember_me_handler', ['firewall' => $firewallName]); } elseif (isset($config['token_provider'])) { $tokenProviderId = $this->createTokenProvider($container, $firewallName, $config['token_provider']); $tokenVerifier = $this->createTokenVerifier($container, $firewallName, $config['token_verifier'] ?? null); $container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.persistent_remember_me_handler'))->replaceArgument(0, new Reference($tokenProviderId))->replaceArgument(1, $config['secret'])->replaceArgument(2, new Reference($userProviderId))->replaceArgument(4, $config)->replaceArgument(6, $tokenVerifier)->addTag('security.remember_me_handler', ['firewall' => $firewallName]); } else { $signatureHasherId = 'security.authenticator.remember_me_signature_hasher.' . $firewallName; $container->setDefinition($signatureHasherId, new ChildDefinition('security.authenticator.remember_me_signature_hasher'))->replaceArgument(1, $config['signature_properties'])->replaceArgument(2, $config['secret']); $container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.signature_remember_me_handler'))->replaceArgument(0, new Reference($signatureHasherId))->replaceArgument(1, new Reference($userProviderId))->replaceArgument(3, $config)->addTag('security.remember_me_handler', ['firewall' => $firewallName]); } // create check remember me conditions listener (which checks if a remember-me cookie is supported and requested) $rememberMeConditionsListenerId = 'security.listener.check_remember_me_conditions.' . $firewallName; $container->setDefinition($rememberMeConditionsListenerId, new ChildDefinition('security.listener.check_remember_me_conditions'))->replaceArgument(0, \array_intersect_key($config, ['always_remember_me' => \true, 'remember_me_parameter' => \true]))->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.' . $firewallName]); // create remember me listener (which executes the remember me services for other authenticators and logout) $rememberMeListenerId = 'security.listener.remember_me.' . $firewallName; $container->setDefinition($rememberMeListenerId, new ChildDefinition('security.listener.remember_me'))->replaceArgument(0, new Reference($rememberMeHandlerId))->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.' . $firewallName]); // create remember me authenticator (which re-authenticates the user based on the remember-me cookie) $authenticatorId = 'security.authenticator.remember_me.' . $firewallName; $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me'))->replaceArgument(0, new Reference($rememberMeHandlerId))->replaceArgument(3, $config['name'] ?? $this->options['name']); foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) { // register ContextListener if ('security.context_listener' === \substr($serviceId, 0, 25)) { continue; } throw new \LogicException(\sprintf('Symfony Authenticator Security dropped support for the "security.remember_me_aware" tag, service "%s" will no longer work as expected.', $serviceId)); } return $authenticatorId; } public function getPosition() : string { return 'remember_me'; } /** * {@inheritDoc} */ public function getPriority() : int { return self::PRIORITY; } public function getKey() : string { return 'remember-me'; } public function addConfiguration(NodeDefinition $node) { $builder = $node->fixXmlConfig('user_provider')->children(); $builder->scalarNode('secret')->cannotBeEmpty()->defaultValue('%kernel.secret%')->end()->scalarNode('service')->end()->arrayNode('user_providers')->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()->prototype('scalar')->end()->end()->booleanNode('catch_exceptions')->defaultTrue()->end()->arrayNode('signature_properties')->prototype('scalar')->end()->requiresAtLeastOneElement()->info('An array of properties on your User that are used to sign the remember-me cookie. If any of these change, all existing cookies will become invalid.')->example(['email', 'password'])->defaultValue(['password'])->end()->arrayNode('token_provider')->beforeNormalization()->ifString()->then(function ($v) { return ['service' => $v]; })->end()->children()->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end()->arrayNode('doctrine')->canBeEnabled()->children()->scalarNode('connection')->defaultNull()->end()->end()->end()->end()->end()->scalarNode('token_verifier')->info('The service ID of a custom rememberme token verifier.')->end(); foreach ($this->options as $name => $value) { if ('secure' === $name) { $builder->enumNode($name)->values([\true, \false, 'auto'])->defaultValue('auto' === $value ? null : $value); } elseif ('samesite' === $name) { $builder->enumNode($name)->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultValue($value); } elseif (\is_bool($value)) { $builder->booleanNode($name)->defaultValue($value); } elseif (\is_int($value)) { $builder->integerNode($name)->defaultValue($value); } else { $builder->scalarNode($name)->defaultValue($value); } } } private function generateRememberMeServicesTemplateId(array $config, string $id) : string { if (isset($config['service'])) { return $config['service']; } if (isset($config['token_provider'])) { return 'security.authentication.rememberme.services.persistent'; } return 'security.authentication.rememberme.services.simplehash'; } private function createRememberMeServices(ContainerBuilder $container, string $id, string $templateId, array $userProviders, array $config) : void { $rememberMeServicesId = $templateId . '.' . $id; $rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId)); $rememberMeServices->replaceArgument(1, $config['secret']); $rememberMeServices->replaceArgument(2, $id); if (isset($config['token_provider'])) { $tokenProviderId = $this->createTokenProvider($container, $id, $config['token_provider']); $rememberMeServices->addMethodCall('setTokenProvider', [new Reference($tokenProviderId)]); } // remember-me options $mergedOptions = \array_intersect_key($config, $this->options); if ('auto' === $mergedOptions['secure']) { $mergedOptions['secure'] = null; } $rememberMeServices->replaceArgument(3, $mergedOptions); if ($config['user_providers']) { $userProviders = []; foreach ($config['user_providers'] as $providerName) { $userProviders[] = new Reference('security.user.provider.concrete.' . $providerName); } } if (0 === \count($userProviders)) { throw new \RuntimeException('You must configure at least one remember-me aware listener (such as form-login) for each firewall that has remember-me enabled.'); } $rememberMeServices->replaceArgument(0, new IteratorArgument(\array_unique($userProviders))); } private function createTokenProvider(ContainerBuilder $container, string $firewallName, array $config) : string { $tokenProviderId = $config['service'] ?? \false; if ($config['doctrine']['enabled'] ?? \false) { if (!\class_exists(DoctrineTokenProvider::class)) { throw new InvalidConfigurationException('Cannot use the "doctrine" token provider for "remember_me" because the Doctrine Bridge is not installed. Try running "composer require symfony/doctrine-bridge".'); } if (null === $config['doctrine']['connection']) { $connectionId = 'database_connection'; } else { $connectionId = 'doctrine.dbal.' . $config['doctrine']['connection'] . '_connection'; } $tokenProviderId = 'security.remember_me.doctrine_token_provider.' . $firewallName; $container->register($tokenProviderId, DoctrineTokenProvider::class)->addArgument(new Reference($connectionId)); } if (!$tokenProviderId) { throw new InvalidConfigurationException(\sprintf('No token provider was set for firewall "%s". Either configure a service ID or set "remember_me.token_provider.doctrine" to true.', $firewallName)); } return $tokenProviderId; } private function createTokenVerifier(ContainerBuilder $container, string $firewallName, ?string $serviceId) : Reference { if ($serviceId) { return new Reference($serviceId); } $tokenVerifierId = 'security.remember_me.token_verifier.' . $firewallName; $container->register($tokenVerifierId, CacheTokenVerifier::class)->addArgument(new Reference('cache.security_token_verifier', ContainerInterface::NULL_ON_INVALID_REFERENCE))->addArgument(60)->addArgument('rememberme-' . $firewallName . '-stale-'); return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE); } /** * {@inheritdoc} */ public function prepend(ContainerBuilder $container) { $rememberMeSecureDefault = \false; $rememberMeSameSiteDefault = null; if (!isset($container->getExtensions()['framework'])) { return; } foreach ($container->getExtensionConfig('framework') as $config) { if (isset($config['session']) && \is_array($config['session'])) { $rememberMeSecureDefault = $config['session']['cookie_secure'] ?? $rememberMeSecureDefault; $rememberMeSameSiteDefault = \array_key_exists('cookie_samesite', $config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault; } } $this->options['secure'] = $rememberMeSecureDefault; $this->options['samesite'] = $rememberMeSameSiteDefault; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * AbstractFactory is the base class for all classes inheriting from * AbstractAuthenticationListener. * * @author Fabien Potencier * @author Lukas Kahwe Smith * @author Johannes M. Schmitt */ abstract class AbstractFactory implements SecurityFactoryInterface { protected $options = ['check_path' => '/login_check', 'use_forward' => \false, 'require_previous_session' => \false, 'login_path' => '/login']; protected $defaultSuccessHandlerOptions = ['always_use_default_target_path' => \false, 'default_target_path' => '/', 'login_path' => '/login', 'target_path_parameter' => '_target_path', 'use_referer' => \false]; protected $defaultFailureHandlerOptions = ['failure_path' => null, 'failure_forward' => \false, 'login_path' => '/login', 'failure_path_parameter' => '_failure_path']; public function create(ContainerBuilder $container, string $id, array $config, string $userProviderId, ?string $defaultEntryPointId) { // authentication provider $authProviderId = $this->createAuthProvider($container, $id, $config, $userProviderId); // authentication listener $listenerId = $this->createListener($container, $id, $config, $userProviderId); // add remember-me aware tag if requested if ($this->isRememberMeAware($config)) { $container->getDefinition($listenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => $userProviderId]); } // create entry point if applicable (optional) $entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPointId); return [$authProviderId, $listenerId, $entryPointId]; } public function addConfiguration(NodeDefinition $node) { $builder = $node->children(); $builder->scalarNode('provider')->end()->booleanNode('remember_me')->defaultTrue()->end()->scalarNode('success_handler')->end()->scalarNode('failure_handler')->end(); foreach (\array_merge($this->options, $this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) { if (\is_bool($default)) { $builder->booleanNode($name)->defaultValue($default); } else { $builder->scalarNode($name)->defaultValue($default); } } } public final function addOption(string $name, $default = null) { $this->options[$name] = $default; } /** * Subclasses must return the id of a service which implements the * AuthenticationProviderInterface. * * @return string */ protected abstract function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId); /** * Subclasses must return the id of the abstract listener template. * * Listener definitions should inherit from the AbstractAuthenticationListener * like this: * * * * In the above case, this method would return "my.listener.id". * * @return string */ protected abstract function getListenerId(); /** * Subclasses may create an entry point of their as they see fit. The * default implementation does not change the default entry point. * * @return string|null the entry point id */ protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId) { return $defaultEntryPointId; } /** * Subclasses may disable remember-me features for the listener, by * always returning false from this method. * * @return bool Whether a possibly configured RememberMeServices should be set for this listener */ protected function isRememberMeAware(array $config) { return $config['remember_me']; } protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider) { $listenerId = $this->getListenerId(); $listener = new ChildDefinition($listenerId); $listener->replaceArgument(4, $id); $listener->replaceArgument(5, new Reference($this->createAuthenticationSuccessHandler($container, $id, $config))); $listener->replaceArgument(6, new Reference($this->createAuthenticationFailureHandler($container, $id, $config))); $listener->replaceArgument(7, \array_intersect_key($config, $this->options)); $listenerId .= '.' . $id; $container->setDefinition($listenerId, $listener); return $listenerId; } protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config) { $successHandlerId = $this->getSuccessHandlerId($id); $options = \array_intersect_key($config, $this->defaultSuccessHandlerOptions); if (isset($config['success_handler'])) { $successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.custom_success_handler')); $successHandler->replaceArgument(0, new ChildDefinition($config['success_handler'])); $successHandler->replaceArgument(1, $options); $successHandler->replaceArgument(2, $id); } else { $successHandler = $container->setDefinition($successHandlerId, new ChildDefinition('security.authentication.success_handler')); $successHandler->addMethodCall('setOptions', [$options]); $successHandler->addMethodCall('setFirewallName', [$id]); } return $successHandlerId; } protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config) { $id = $this->getFailureHandlerId($id); $options = \array_intersect_key($config, $this->defaultFailureHandlerOptions); if (isset($config['failure_handler'])) { $failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.custom_failure_handler')); $failureHandler->replaceArgument(0, new ChildDefinition($config['failure_handler'])); $failureHandler->replaceArgument(1, $options); } else { $failureHandler = $container->setDefinition($id, new ChildDefinition('security.authentication.failure_handler')); $failureHandler->addMethodCall('setOptions', [$options]); } return $id; } protected function getSuccessHandlerId(string $id) { return 'security.authentication.success_handler.' . $id . '.' . \str_replace('-', '_', $this->getKey()); } protected function getFailureHandlerId(string $id) { return 'security.authentication.failure_handler.' . $id . '.' . \str_replace('-', '_', $this->getKey()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * X509Factory creates services for X509 certificate authentication. * * @author Fabien Potencier * * @internal */ class X509Factory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public const PRIORITY = -10; public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { $providerId = 'security.authentication.provider.pre_authenticated.' . $id; $container->setDefinition($providerId, new ChildDefinition('security.authentication.provider.pre_authenticated'))->replaceArgument(0, new Reference($userProvider))->replaceArgument(1, new Reference('security.user_checker.' . $id))->addArgument($id); // listener $listenerId = 'security.authentication.listener.x509.' . $id; $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.x509')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, $config['user']); $listener->replaceArgument(4, $config['credentials']); $listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.' . $id)]); return [$providerId, $listenerId, $defaultEntryPoint]; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) { $authenticatorId = 'security.authenticator.x509.' . $firewallName; $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.x509'))->replaceArgument(0, new Reference($userProviderId))->replaceArgument(2, $firewallName)->replaceArgument(3, $config['user'])->replaceArgument(4, $config['credentials']); return $authenticatorId; } public function getPriority() : int { return self::PRIORITY; } public function getPosition() : string { return 'pre_auth'; } public function getKey() : string { return 'x509'; } public function addConfiguration(NodeDefinition $node) { $node->children()->scalarNode('provider')->end()->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end()->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Can be implemented by a security factory to add a listener to the firewall. * * @author Christian Scheb */ interface FirewallListenerFactoryInterface { /** * Creates the firewall listener services for the provided configuration. * * @return string[] The listener service IDs to be used by the firewall */ public function createListeners(ContainerBuilder $container, string $firewallName, array $config) : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; /** * @author Wouter de Jong * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class AnonymousFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) { if (null === $config['secret']) { $config['secret'] = new Parameter('container.build_hash'); } $listenerId = 'security.authentication.listener.anonymous.' . $id; $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.anonymous'))->replaceArgument(1, $config['secret']); $providerId = 'security.authentication.provider.anonymous.' . $id; $container->setDefinition($providerId, new ChildDefinition('security.authentication.provider.anonymous'))->replaceArgument(0, $config['secret']); return [$providerId, $listenerId, $defaultEntryPoint]; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : string { throw new InvalidConfigurationException(\sprintf('The authenticator manager no longer has "anonymous" security. Please remove this option under the "%s" firewall' . ($config['lazy'] ? ' and add "lazy: true"' : '') . '.', $firewallName)); } public function getPriority() { return -60; } public function getPosition() { return 'anonymous'; } public function getKey() { return 'anonymous'; } public function addConfiguration(NodeDefinition $builder) { $builder->beforeNormalization()->ifTrue(function ($v) { return 'lazy' === $v; })->then(function ($v) { return ['lazy' => \true]; })->end()->children()->booleanNode('lazy')->defaultFalse()->setDeprecated('symfony/security-bundle', '5.1', 'Using "anonymous: lazy" to make the firewall lazy is deprecated, use "anonymous: true" and "lazy: true" instead.')->end()->scalarNode('secret')->defaultNull()->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * JsonLoginFactory creates services for JSON login authentication. * * @author Kévin Dunglas * * @internal */ class JsonLoginFactory extends AbstractFactory implements AuthenticatorFactoryInterface { public const PRIORITY = -40; public function __construct() { $this->addOption('username_path', 'username'); $this->addOption('password_path', 'password'); $this->defaultFailureHandlerOptions = []; $this->defaultSuccessHandlerOptions = []; } public function getPriority() : int { return self::PRIORITY; } /** * {@inheritdoc} */ public function getPosition() : string { return 'form'; } /** * {@inheritdoc} */ public function getKey() : string { return 'json-login'; } /** * {@inheritdoc} */ protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId) : string { $provider = 'security.authentication.provider.dao.' . $id; $container->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))->replaceArgument(0, new Reference($userProviderId))->replaceArgument(1, new Reference('security.user_checker.' . $id))->replaceArgument(2, $id); return $provider; } /** * {@inheritdoc} */ protected function getListenerId() : string { return 'security.authentication.listener.json'; } /** * {@inheritdoc} */ protected function isRememberMeAware(array $config) : bool { return \false; } /** * {@inheritdoc} */ protected function createListener(ContainerBuilder $container, string $id, array $config, string $userProvider) { $listenerId = $this->getListenerId(); $listener = new ChildDefinition($listenerId); $listener->replaceArgument(3, $id); $listener->replaceArgument(4, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $id, $config)) : null); $listener->replaceArgument(5, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $id, $config)) : null); $listener->replaceArgument(6, \array_intersect_key($config, $this->options)); $listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.' . $id)]); $listenerId .= '.' . $id; $container->setDefinition($listenerId, $listener); return $listenerId; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) { $authenticatorId = 'security.authenticator.json_login.' . $firewallName; $options = \array_intersect_key($config, $this->options); $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.json_login'))->replaceArgument(1, new Reference($userProviderId))->replaceArgument(2, isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null)->replaceArgument(3, isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null)->replaceArgument(4, $options); return $authenticatorId; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * HttpBasicFactory creates services for HTTP basic authentication. * * @author Fabien Potencier * * @internal */ class HttpBasicFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public const PRIORITY = -50; public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { $provider = 'security.authentication.provider.dao.' . $id; $container->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))->replaceArgument(0, new Reference($userProvider))->replaceArgument(1, new Reference('security.user_checker.' . $id))->replaceArgument(2, $id); // entry point $entryPointId = $defaultEntryPoint; if (null === $entryPointId) { $entryPointId = 'security.authentication.basic_entry_point.' . $id; $container->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point'))->addArgument($config['realm']); } // listener $listenerId = 'security.authentication.listener.basic.' . $id; $listener = $container->setDefinition($listenerId, new ChildDefinition('security.authentication.listener.basic')); $listener->replaceArgument(2, $id); $listener->replaceArgument(3, new Reference($entryPointId)); $listener->addMethodCall('setSessionAuthenticationStrategy', [new Reference('security.authentication.session_strategy.' . $id)]); return [$provider, $listenerId, $entryPointId]; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : string { $authenticatorId = 'security.authenticator.http_basic.' . $firewallName; $container->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.http_basic'))->replaceArgument(0, $config['realm'])->replaceArgument(1, new Reference($userProviderId)); return $authenticatorId; } public function getPriority() : int { return self::PRIORITY; } public function getPosition() : string { return 'http'; } public function getKey() : string { return 'http-basic'; } public function addConfiguration(NodeDefinition $node) { $node->children()->scalarNode('provider')->end()->scalarNode('realm')->defaultValue('Secured Area')->end()->end(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Ldap\Security\CheckLdapCredentialsListener; use _ContaoManager\Symfony\Component\Ldap\Security\LdapAuthenticator; /** * A trait decorating the authenticator with LDAP functionality. * * @author Wouter de Jong * * @internal */ trait LdapFactoryTrait { public function getKey() : string { return parent::getKey() . '-ldap'; } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : string { $key = \str_replace('-', '_', $this->getKey()); $authenticatorId = parent::createAuthenticator($container, $firewallName, $config, $userProviderId); $container->setDefinition('security.listener.' . $key . '.' . $firewallName, new Definition(CheckLdapCredentialsListener::class))->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.' . $firewallName])->addArgument(new Reference('security.ldap_locator')); $ldapAuthenticatorId = 'security.authenticator.' . $key . '.' . $firewallName; $definition = $container->setDefinition($ldapAuthenticatorId, new Definition(LdapAuthenticator::class))->setArguments([new Reference($authenticatorId), $config['service'], $config['dn_string'], $config['search_dn'], $config['search_password']]); if (!empty($config['query_string'])) { if ('' === $config['search_dn'] || '' === $config['search_password']) { throw new InvalidConfigurationException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } $definition->addArgument($config['query_string']); } return $ldapAuthenticatorId; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; use _ContaoManager\Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use _ContaoManager\Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use _ContaoManager\Symfony\Component\Config\Definition\Builder\NodeDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface; use _ContaoManager\Symfony\Component\RateLimiter\RateLimiterFactory; use _ContaoManager\Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter; /** * @author Wouter de Jong * * @internal */ class LoginThrottlingFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) : array { throw new \LogicException('Login throttling is not supported when "security.enable_authenticator_manager" is not set to true.'); } public function getPriority() : int { // this factory doesn't register any authenticators, this priority doesn't matter return 0; } public function getPosition() : string { // this factory doesn't register any authenticators, this position doesn't matter return 'pre_auth'; } public function getKey() : string { return 'login_throttling'; } /** * @param ArrayNodeDefinition $builder */ public function addConfiguration(NodeDefinition $builder) { $builder->children()->scalarNode('limiter')->info(\sprintf('A service id implementing "%s".', RequestRateLimiterInterface::class))->end()->integerNode('max_attempts')->defaultValue(5)->end()->scalarNode('interval')->defaultValue('1 minute')->end()->scalarNode('lock_factory')->info('The service ID of the lock factory used by the login rate limiter (or null to disable locking)')->defaultNull()->end()->end(); } public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId) : array { if (!\class_exists(RateLimiterFactory::class)) { throw new \LogicException('Login throttling requires the Rate Limiter component. Try running "composer require symfony/rate-limiter".'); } if (!isset($config['limiter'])) { if (!\class_exists(FrameworkExtension::class) || !\method_exists(FrameworkExtension::class, 'registerRateLimiter')) { throw new \LogicException('You must either configure a rate limiter for "security.firewalls.' . $firewallName . '.login_throttling" or install symfony/framework-bundle:^5.2.'); } $limiterOptions = ['policy' => 'fixed_window', 'limit' => $config['max_attempts'], 'interval' => $config['interval'], 'lock_factory' => $config['lock_factory']]; FrameworkExtension::registerRateLimiter($container, $localId = '_login_local_' . $firewallName, $limiterOptions); $limiterOptions['limit'] = 5 * $config['max_attempts']; FrameworkExtension::registerRateLimiter($container, $globalId = '_login_global_' . $firewallName, $limiterOptions); $container->register($config['limiter'] = 'security.login_throttling.' . $firewallName . '.limiter', DefaultLoginRateLimiter::class)->addArgument(new Reference('limiter.' . $globalId))->addArgument(new Reference('limiter.' . $localId))->addArgument('%kernel.secret%'); } $container->setDefinition('security.listener.login_throttling.' . $firewallName, new ChildDefinition('security.listener.login_throttling'))->replaceArgument(1, new Reference($config['limiter']))->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.' . $firewallName]); return []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection; use Composer\InstalledVersions; use _ContaoManager\Symfony\Bridge\Twig\Extension\LogoutUrlExtension; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\LegacyLogoutHandlerListener; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\FileLocator; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcher; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; use _ContaoManager\Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use _ContaoManager\Symfony\Component\Security\Core\User\ChainUserProvider; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; /** * SecurityExtension. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class SecurityExtension extends Extension implements PrependExtensionInterface { private $requestMatchers = []; private $expressions = []; private $contextListeners = []; /** @var list */ private $factories = []; /** @var list */ private $sortedFactories = []; private $userProviderFactories = []; private $statelessFirewallKeys = []; private $authenticatorManagerEnabled = \false; public function prepend(ContainerBuilder $container) { foreach ($this->getSortedFactories() as $factory) { if ($factory instanceof PrependExtensionInterface) { $factory->prepend($container); } } } public function load(array $configs, ContainerBuilder $container) { if (!\class_exists(InstalledVersions::class)) { \trigger_deprecation('symfony/security-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.'); } if (!\array_filter($configs)) { return; } $mainConfig = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($mainConfig, $configs); // load services $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__) . '/Resources/config')); $loader->load('security.php'); $loader->load('password_hasher.php'); $loader->load('security_listeners.php'); $loader->load('security_rememberme.php'); if ($this->authenticatorManagerEnabled = $config['enable_authenticator_manager']) { if ($config['always_authenticate_before_granting']) { throw new InvalidConfigurationException('The security option "always_authenticate_before_granting" cannot be used when "enable_authenticator_manager" is set to true. If you rely on this behavior, set it to false.'); } $loader->load('security_authenticator.php'); // The authenticator system no longer has anonymous tokens. This makes sure AccessListener // and AuthorizationChecker do not throw AuthenticationCredentialsNotFoundException when no // token is available in the token storage. $container->getDefinition('security.access_listener')->setArgument(3, \false); $container->getDefinition('security.authorization_checker')->setArgument(3, \false); $container->getDefinition('security.authorization_checker')->setArgument(4, \false); } else { \trigger_deprecation('symfony/security-bundle', '5.3', 'Not setting the "security.enable_authenticator_manager" config option to true is deprecated.'); if ($config['always_authenticate_before_granting']) { $authorizationChecker = $container->getDefinition('security.authorization_checker'); $authorizationCheckerArgs = $authorizationChecker->getArguments(); \array_splice($authorizationCheckerArgs, 1, 0, [new Reference('security.authentication.manager')]); $authorizationChecker->setArguments($authorizationCheckerArgs); } $loader->load('security_legacy.php'); } if ($container::willBeAvailable('symfony/twig-bridge', LogoutUrlExtension::class, ['symfony/security-bundle'], \true)) { $loader->load('templating_twig.php'); } $loader->load('collectors.php'); $loader->load('guard.php'); $container->getDefinition('data_collector.security')->addArgument($this->authenticatorManagerEnabled); if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) { $loader->load('security_debug.php'); } if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'], \true)) { $container->removeDefinition('security.expression_language'); $container->removeDefinition('security.access.expression_voter'); } // set some global scalars $container->setParameter('security.access.denied_url', $config['access_denied_url']); $container->setParameter('security.authentication.manager.erase_credentials', $config['erase_credentials']); $container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_strategy']); if (isset($config['access_decision_manager']['service'])) { $container->setAlias('security.access.decision_manager', $config['access_decision_manager']['service']); } elseif (isset($config['access_decision_manager']['strategy_service'])) { $container->getDefinition('security.access.decision_manager')->addArgument(new Reference($config['access_decision_manager']['strategy_service'])); } else { $container->getDefinition('security.access.decision_manager')->addArgument($this->createStrategyDefinition($config['access_decision_manager']['strategy'] ?? MainConfiguration::STRATEGY_AFFIRMATIVE, $config['access_decision_manager']['allow_if_all_abstain'], $config['access_decision_manager']['allow_if_equal_granted_denied'])); } $container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']); $container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']); if (\class_exists(Application::class)) { $loader->load('debug_console.php'); $debugCommand = $container->getDefinition('security.command.debug_firewall'); $debugCommand->replaceArgument(4, $this->authenticatorManagerEnabled); } $this->createFirewalls($config, $container); $this->createAuthorization($config, $container); $this->createRoleHierarchy($config, $container); $container->getDefinition('security.authentication.guard_handler')->replaceArgument(2, $this->statelessFirewallKeys); // @deprecated since Symfony 5.3 if ($config['encoders']) { $this->createEncoders($config['encoders'], $container); } if ($config['password_hashers']) { $this->createHashers($config['password_hashers'], $container); } if (\class_exists(Application::class)) { $loader->load('console.php'); // @deprecated since Symfony 5.3 $container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, \array_keys($config['encoders'])); $container->getDefinition('security.command.user_password_hash')->replaceArgument(1, \array_keys($config['password_hashers'])); } $container->registerForAutoconfiguration(VoterInterface::class)->addTag('security.voter'); } /** * @throws \InvalidArgumentException if the $strategy is invalid */ private function createStrategyDefinition(string $strategy, bool $allowIfAllAbstainDecisions, bool $allowIfEqualGrantedDeniedDecisions) : Definition { switch ($strategy) { case MainConfiguration::STRATEGY_AFFIRMATIVE: return new Definition(AffirmativeStrategy::class, [$allowIfAllAbstainDecisions]); case MainConfiguration::STRATEGY_CONSENSUS: return new Definition(ConsensusStrategy::class, [$allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions]); case MainConfiguration::STRATEGY_UNANIMOUS: return new Definition(UnanimousStrategy::class, [$allowIfAllAbstainDecisions]); case MainConfiguration::STRATEGY_PRIORITY: return new Definition(PriorityStrategy::class, [$allowIfAllAbstainDecisions]); } throw new \InvalidArgumentException(\sprintf('The strategy "%s" is not supported.', $strategy)); } private function createRoleHierarchy(array $config, ContainerBuilder $container) { if (!isset($config['role_hierarchy']) || 0 === \count($config['role_hierarchy'])) { $container->removeDefinition('security.access.role_hierarchy_voter'); return; } $container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']); $container->removeDefinition('security.access.simple_role_voter'); } private function createAuthorization(array $config, ContainerBuilder $container) { foreach ($config['access_control'] as $access) { $matcher = $this->createRequestMatcher($container, $access['path'], $access['host'], $access['port'], $access['methods'], $access['ips']); $attributes = $access['roles']; if ($access['allow_if']) { $attributes[] = $this->createExpression($container, $access['allow_if']); } $emptyAccess = 0 === \count(\array_filter($access)); if ($emptyAccess) { throw new InvalidConfigurationException('One or more access control items are empty. Did you accidentally add lines only containing a "-" under "security.access_control"?'); } $container->getDefinition('security.access_map')->addMethodCall('add', [$matcher, $attributes, $access['requires_channel']]); } // allow cache warm-up for expressions if (\count($this->expressions)) { $container->getDefinition('security.cache_warmer.expression')->replaceArgument(0, new IteratorArgument(\array_values($this->expressions))); } else { $container->removeDefinition('security.cache_warmer.expression'); } } private function createFirewalls(array $config, ContainerBuilder $container) { if (!isset($config['firewalls'])) { return; } $firewalls = $config['firewalls']; $providerIds = $this->createUserProviders($config, $container); $container->setParameter('security.firewalls', \array_keys($firewalls)); // make the ContextListener aware of the configured user providers $contextListenerDefinition = $container->getDefinition('security.context_listener'); $arguments = $contextListenerDefinition->getArguments(); $userProviders = []; foreach ($providerIds as $userProviderId) { $userProviders[] = new Reference($userProviderId); } $arguments[1] = $userProviderIteratorsArgument = new IteratorArgument($userProviders); $contextListenerDefinition->setArguments($arguments); $nbUserProviders = \count($userProviders); if ($nbUserProviders > 1) { $container->setDefinition('security.user_providers', new Definition(ChainUserProvider::class, [$userProviderIteratorsArgument]))->setPublic(\false); } elseif (0 === $nbUserProviders) { $container->removeDefinition('security.listener.user_provider'); } else { $container->setAlias('security.user_providers', new Alias(\current($providerIds)))->setPublic(\false); } if (1 === \count($providerIds)) { $container->setAlias(UserProviderInterface::class, \current($providerIds)); } $customUserChecker = \false; // load firewall map $mapDef = $container->getDefinition('security.firewall.map'); $map = $authenticationProviders = $contextRefs = []; foreach ($firewalls as $name => $firewall) { if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) { $customUserChecker = \true; } $configId = 'security.firewall.map.config.' . $name; [$matcher, $listeners, $exceptionListener, $logoutListener] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); $contextId = 'security.firewall.map.context.' . $name; $isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']); $context = new ChildDefinition($isLazy ? 'security.firewall.lazy_context' : 'security.firewall.context'); $context = $container->setDefinition($contextId, $context); $context->replaceArgument(0, new IteratorArgument($listeners))->replaceArgument(1, $exceptionListener)->replaceArgument(2, $logoutListener)->replaceArgument(3, new Reference($configId)); $contextRefs[$contextId] = new Reference($contextId); $map[$contextId] = $matcher; } $container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs)); $mapDef->replaceArgument(0, new Reference('security.firewall.context_locator')); $mapDef->replaceArgument(1, new IteratorArgument($map)); if (!$this->authenticatorManagerEnabled) { // add authentication providers to authentication manager $authenticationProviders = \array_map(function ($id) { return new Reference($id); }, \array_values(\array_unique($authenticationProviders))); $container->getDefinition('security.authentication.manager')->replaceArgument(0, new IteratorArgument($authenticationProviders)); } // register an autowire alias for the UserCheckerInterface if no custom user checker service is configured if (!$customUserChecker) { $container->setAlias('_ContaoManager\\Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface', new Alias('security.user_checker', \false)); } } private function createFirewall(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, array $providerIds, string $configId) { $config = $container->setDefinition($configId, new ChildDefinition('security.firewall.config')); $config->replaceArgument(0, $id); $config->replaceArgument(1, $firewall['user_checker']); // Matcher $matcher = null; if (isset($firewall['request_matcher'])) { $matcher = new Reference($firewall['request_matcher']); } elseif (isset($firewall['pattern']) || isset($firewall['host'])) { $pattern = $firewall['pattern'] ?? null; $host = $firewall['host'] ?? null; $methods = $firewall['methods'] ?? []; $matcher = $this->createRequestMatcher($container, $pattern, $host, null, $methods); } $config->replaceArgument(2, $matcher ? (string) $matcher : null); $config->replaceArgument(3, $firewall['security']); // Security disabled? if (\false === $firewall['security']) { return [$matcher, [], null, null]; } $config->replaceArgument(4, $firewall['stateless']); $firewallEventDispatcherId = 'security.event_dispatcher.' . $id; // Provider id (must be configured explicitly per firewall/authenticator if more than one provider is set) $defaultProvider = null; if (isset($firewall['provider'])) { if (!isset($providerIds[$normalizedName = \str_replace('-', '_', $firewall['provider'])])) { throw new InvalidConfigurationException(\sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall['provider'])); } $defaultProvider = $providerIds[$normalizedName]; if ($this->authenticatorManagerEnabled) { $container->setDefinition('security.listener.' . $id . '.user_provider', new ChildDefinition('security.listener.user_provider.abstract'))->addTag('kernel.event_listener', ['dispatcher' => $firewallEventDispatcherId, 'event' => CheckPassportEvent::class, 'priority' => 2048, 'method' => 'checkPassport'])->replaceArgument(0, new Reference($defaultProvider)); } } elseif (1 === \count($providerIds)) { $defaultProvider = \reset($providerIds); } $config->replaceArgument(5, $defaultProvider); // Register Firewall-specific event dispatcher $container->register($firewallEventDispatcherId, EventDispatcher::class)->addTag('event_dispatcher.dispatcher', ['name' => $firewallEventDispatcherId]); // Register listeners $listeners = []; $listenerKeys = []; // Channel listener $listeners[] = new Reference('security.channel_listener'); $contextKey = null; $contextListenerId = null; // Context serializer listener if (\false === $firewall['stateless']) { $contextKey = $firewall['context'] ?? $id; $listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey, $this->authenticatorManagerEnabled ? $firewallEventDispatcherId : null)); $sessionStrategyId = 'security.authentication.session_strategy'; if ($this->authenticatorManagerEnabled) { $container->setDefinition('security.listener.session.' . $id, new ChildDefinition('security.listener.session'))->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } } else { $this->statelessFirewallKeys[] = $id; $sessionStrategyId = 'security.authentication.session_strategy_noop'; } $container->setAlias(new Alias('security.authentication.session_strategy.' . $id, \false), $sessionStrategyId); $config->replaceArgument(6, $contextKey); // Logout listener $logoutListenerId = null; if (isset($firewall['logout'])) { $logoutListenerId = 'security.logout_listener.' . $id; $logoutListener = $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener')); $logoutListener->replaceArgument(2, new Reference($firewallEventDispatcherId)); $logoutListener->replaceArgument(3, ['csrf_parameter' => $firewall['logout']['csrf_parameter'], 'csrf_token_id' => $firewall['logout']['csrf_token_id'], 'logout_path' => $firewall['logout']['path']]); // add default logout listener if (isset($firewall['logout']['success_handler'])) { // deprecated, to be removed in Symfony 6.0 $logoutSuccessHandlerId = $firewall['logout']['success_handler']; $container->register('security.logout.listener.legacy_success_listener.' . $id, LegacyLogoutHandlerListener::class)->setArguments([new Reference($logoutSuccessHandlerId)])->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } else { $logoutSuccessListenerId = 'security.logout.listener.default.' . $id; $container->setDefinition($logoutSuccessListenerId, new ChildDefinition('security.logout.listener.default'))->replaceArgument(1, $firewall['logout']['target'])->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } // add CSRF provider if (isset($firewall['logout']['csrf_token_generator'])) { $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); } // add session logout listener if (\true === $firewall['logout']['invalidate_session'] && \false === $firewall['stateless']) { $container->setDefinition('security.logout.listener.session.' . $id, new ChildDefinition('security.logout.listener.session'))->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } // add cookie logout listener if (\count($firewall['logout']['delete_cookies']) > 0) { $container->setDefinition('security.logout.listener.cookie_clearing.' . $id, new ChildDefinition('security.logout.listener.cookie_clearing'))->addArgument($firewall['logout']['delete_cookies'])->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } // add custom listeners (deprecated) foreach ($firewall['logout']['handlers'] as $i => $handlerId) { $container->register('security.logout.listener.legacy_handler.' . $i, LegacyLogoutHandlerListener::class)->addArgument(new Reference($handlerId))->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } // register with LogoutUrlGenerator $container->getDefinition('security.logout_url_generator')->addMethodCall('registerListener', [$id, $firewall['logout']['path'], $firewall['logout']['csrf_token_id'], $firewall['logout']['csrf_parameter'], isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null, \false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null]); } // Determine default entry point $configuredEntryPoint = $firewall['entry_point'] ?? null; // Authentication listeners $firewallAuthenticationProviders = []; [$authListeners, $defaultEntryPoint] = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId); if (!$this->authenticatorManagerEnabled) { $authenticationProviders = \array_merge($authenticationProviders, $firewallAuthenticationProviders); } else { // $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint $configuredEntryPoint = $defaultEntryPoint; // authenticator manager $authenticators = \array_map(function ($id) { return new Reference($id); }, $firewallAuthenticationProviders); $container->setDefinition($managerId = 'security.authenticator.manager.' . $id, new ChildDefinition('security.authenticator.manager'))->replaceArgument(0, $authenticators)->replaceArgument(2, new Reference($firewallEventDispatcherId))->replaceArgument(3, $id)->replaceArgument(7, $firewall['required_badges'] ?? [])->addTag('monolog.logger', ['channel' => 'security']); $managerLocator = $container->getDefinition('security.authenticator.managers_locator'); $managerLocator->replaceArgument(0, \array_merge($managerLocator->getArgument(0), [$id => new ServiceClosureArgument(new Reference($managerId))])); // authenticator manager listener $container->setDefinition('security.firewall.authenticator.' . $id, new ChildDefinition('security.firewall.authenticator'))->replaceArgument(0, new Reference($managerId)); if ($container->hasDefinition('debug.security.firewall') && $this->authenticatorManagerEnabled) { $container->register('debug.security.firewall.authenticator.' . $id, TraceableAuthenticatorManagerListener::class)->setDecoratedService('security.firewall.authenticator.' . $id)->setArguments([new Reference('debug.security.firewall.authenticator.' . $id . '.inner')])->addTag('kernel.reset', ['method' => 'reset']); } // user checker listener $container->setDefinition('security.listener.user_checker.' . $id, new ChildDefinition('security.listener.user_checker'))->replaceArgument(0, new Reference('security.user_checker.' . $id))->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); $listeners[] = new Reference('security.firewall.authenticator.' . $id); // Add authenticators to the debug:firewall command if ($container->hasDefinition('security.command.debug_firewall')) { $debugCommand = $container->getDefinition('security.command.debug_firewall'); $debugCommand->replaceArgument(3, \array_merge($debugCommand->getArgument(3), [$id => $authenticators])); } } $config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint); $listeners = \array_merge($listeners, $authListeners); // Switch user listener if (isset($firewall['switch_user'])) { $listenerKeys[] = 'switch_user'; $listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider, $firewall['stateless'])); } // Access listener $listeners[] = new Reference('security.access_listener'); // Exception listener $exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless'])); $config->replaceArgument(8, $firewall['access_denied_handler'] ?? null); $config->replaceArgument(9, $firewall['access_denied_url'] ?? null); $container->setAlias('security.user_checker.' . $id, new Alias($firewall['user_checker'], \false)); foreach ($this->getSortedFactories() as $factory) { $key = \str_replace('-', '_', $factory->getKey()); if ('custom_authenticators' !== $key && \array_key_exists($key, $firewall)) { $listenerKeys[] = $key; } } if ($firewall['custom_authenticators'] ?? \false) { foreach ($firewall['custom_authenticators'] as $customAuthenticatorId) { $listenerKeys[] = $customAuthenticatorId; } } $config->replaceArgument(10, $listenerKeys); $config->replaceArgument(11, $firewall['switch_user'] ?? null); return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null]; } private function createContextListener(ContainerBuilder $container, string $contextKey, ?string $firewallEventDispatcherId) { if (isset($this->contextListeners[$contextKey])) { return $this->contextListeners[$contextKey]; } $listenerId = 'security.context_listener.' . \count($this->contextListeners); $listener = $container->setDefinition($listenerId, new ChildDefinition('security.context_listener')); $listener->replaceArgument(2, $contextKey); if (null !== $firewallEventDispatcherId) { $listener->replaceArgument(4, new Reference($firewallEventDispatcherId)); $listener->addTag('kernel.event_listener', ['event' => KernelEvents::RESPONSE, 'method' => 'onKernelResponse']); } return $this->contextListeners[$contextKey] = $listenerId; } private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, ?string $contextListenerId = null) { $listeners = []; $hasListeners = \false; $entryPoints = []; foreach ($this->getSortedFactories() as $factory) { $key = \str_replace('-', '_', $factory->getKey()); if (isset($firewall[$key])) { $userProvider = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds, $contextListenerId); if ($this->authenticatorManagerEnabled) { if (!$factory instanceof AuthenticatorFactoryInterface) { throw new InvalidConfigurationException(\sprintf('Cannot configure AuthenticatorManager as "%s" authentication does not support it, set "security.enable_authenticator_manager" to `false`.', $key)); } $authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider); if (\is_array($authenticators)) { foreach ($authenticators as $authenticator) { $authenticationProviders[] = $authenticator; $entryPoints[] = $authenticator; } } else { $authenticationProviders[] = $authenticators; $entryPoints[$key] = $authenticators; } } else { [$provider, $listenerId, $defaultEntryPoint] = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint); $listeners[] = new Reference($listenerId); $authenticationProviders[] = $provider; } if ($factory instanceof FirewallListenerFactoryInterface) { $firewallListenerIds = $factory->createListeners($container, $id, $firewall[$key]); foreach ($firewallListenerIds as $firewallListenerId) { $listeners[] = new Reference($firewallListenerId); } } $hasListeners = \true; } } // the actual entry point is configured by the RegisterEntryPointPass $container->setParameter('security.' . $id . '._indexed_authenticators', $entryPoints); if (\false === $hasListeners && !$this->authenticatorManagerEnabled) { throw new InvalidConfigurationException(\sprintf('No authentication listener registered for firewall "%s".', $id)); } return [$listeners, $defaultEntryPoint]; } private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId) : string { if (isset($firewall[$factoryKey]['provider'])) { if (!isset($providerIds[$normalizedName = \str_replace('-', '_', $firewall[$factoryKey]['provider'])])) { throw new InvalidConfigurationException(\sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$factoryKey]['provider'])); } return $providerIds[$normalizedName]; } if ('remember_me' === $factoryKey && $contextListenerId) { $container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']); } if ($defaultProvider) { return $defaultProvider; } if (!$providerIds) { $userProvider = \sprintf('security.user.provider.missing.%s', $factoryKey); $container->setDefinition($userProvider, (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id)); return $userProvider; } if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) { if ('custom_authenticators' === $factoryKey) { \trigger_deprecation('symfony/security-bundle', '5.4', 'Not configuring explicitly the provider for the "%s" firewall is deprecated because it\'s ambiguous as there is more than one registered provider. Set the "provider" key to one of the configured providers, even if your custom authenticators don\'t use it.', $id); } return 'security.user_providers'; } throw new InvalidConfigurationException(\sprintf('Not configuring explicitly the provider for the "%s" %s on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $this->authenticatorManagerEnabled ? 'authenticator' : 'listener', $id)); } private function createEncoders(array $encoders, ContainerBuilder $container) { $encoderMap = []; foreach ($encoders as $class => $encoder) { if (\class_exists($class) && !\is_a($class, PasswordAuthenticatedUserInterface::class, \true)) { \trigger_deprecation('symfony/security-bundle', '5.3', 'Configuring an encoder for a user class that does not implement "%s" is deprecated, class "%s" should implement it.', PasswordAuthenticatedUserInterface::class, $class); } $encoderMap[$class] = $this->createEncoder($encoder); } $container->getDefinition('security.encoder_factory.generic')->setArguments([$encoderMap]); } private function createEncoder(array $config) { // a custom encoder service if (isset($config['id'])) { return new Reference($config['id']); } if ($config['migrate_from'] ?? \false) { return $config; } // plaintext encoder if ('plaintext' === $config['algorithm']) { $arguments = [$config['ignore_case']]; return ['class' => '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\PlaintextPasswordEncoder', 'arguments' => $arguments]; } // pbkdf2 encoder if ('pbkdf2' === $config['algorithm']) { return ['class' => '_ContaoManager\\Symfony\\Component\\Security\\Core\\Encoder\\Pbkdf2PasswordEncoder', 'arguments' => [$config['hash_algorithm'], $config['encode_as_base64'], $config['iterations'], $config['key_length']]]; } // bcrypt encoder if ('bcrypt' === $config['algorithm']) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_BCRYPT; return $this->createEncoder($config); } // Argon2i encoder if ('argon2i' === $config['algorithm']) { if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2I')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { throw new InvalidConfigurationException(\sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto')); } return $this->createEncoder($config); } if ('argon2id' === $config['algorithm']) { if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2ID')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { throw new InvalidConfigurationException(\sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->createEncoder($config); } if ('native' === $config['algorithm']) { return ['class' => NativePasswordEncoder::class, 'arguments' => [$config['time_cost'], ($config['memory_cost'] ?? 0) << 10 ?: null, $config['cost']] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : [])]; } if ('sodium' === $config['algorithm']) { if (!SodiumPasswordHasher::isSupported()) { throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.'); } return ['class' => SodiumPasswordEncoder::class, 'arguments' => [$config['time_cost'], ($config['memory_cost'] ?? 0) << 10 ?: null]]; } // run-time configured encoder return $config; } private function createHashers(array $hashers, ContainerBuilder $container) { $hasherMap = []; foreach ($hashers as $class => $hasher) { // @deprecated since Symfony 5.3, remove the check in 6.0 if (\class_exists($class) && !\is_a($class, PasswordAuthenticatedUserInterface::class, \true)) { \trigger_deprecation('symfony/security-bundle', '5.3', 'Configuring a password hasher for a user class that does not implement "%s" is deprecated, class "%s" should implement it.', PasswordAuthenticatedUserInterface::class, $class); } $hasherMap[$class] = $this->createHasher($hasher); } $container->getDefinition('security.password_hasher_factory')->setArguments([$hasherMap]); } private function createHasher(array $config) { // a custom hasher service if (isset($config['id'])) { return $config['migrate_from'] ?? \false ? ['instance' => new Reference($config['id']), 'migrate_from' => $config['migrate_from']] : new Reference($config['id']); } if ($config['migrate_from'] ?? \false) { return $config; } // plaintext hasher if ('plaintext' === $config['algorithm']) { $arguments = [$config['ignore_case']]; return ['class' => PlaintextPasswordHasher::class, 'arguments' => $arguments]; } // pbkdf2 hasher if ('pbkdf2' === $config['algorithm']) { return ['class' => Pbkdf2PasswordHasher::class, 'arguments' => [$config['hash_algorithm'], $config['encode_as_base64'], $config['iterations'], $config['key_length']]]; } // bcrypt hasher if ('bcrypt' === $config['algorithm']) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_BCRYPT; return $this->createHasher($config); } // Argon2i hasher if ('argon2i' === $config['algorithm']) { if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2I')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { throw new InvalidConfigurationException(\sprintf('Algorithm "argon2i" is not available. Either use "%s" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto')); } return $this->createHasher($config); } if ('argon2id' === $config['algorithm']) { if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2ID')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { throw new InvalidConfigurationException(\sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->createHasher($config); } if ('native' === $config['algorithm']) { return ['class' => NativePasswordHasher::class, 'arguments' => [$config['time_cost'], ($config['memory_cost'] ?? 0) << 10 ?: null, $config['cost']] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : [])]; } if ('sodium' === $config['algorithm']) { if (!SodiumPasswordHasher::isSupported()) { throw new InvalidConfigurationException('Libsodium is not available. Install the sodium extension or use "auto" instead.'); } return ['class' => SodiumPasswordHasher::class, 'arguments' => [$config['time_cost'], ($config['memory_cost'] ?? 0) << 10 ?: null]]; } // run-time configured hasher return $config; } // Parses user providers and returns an array of their ids private function createUserProviders(array $config, ContainerBuilder $container) : array { $providerIds = []; foreach ($config['providers'] as $name => $provider) { $id = $this->createUserDaoProvider($name, $provider, $container); $providerIds[\str_replace('-', '_', $name)] = $id; } return $providerIds; } // Parses a tag and returns the id for the related user provider service private function createUserDaoProvider(string $name, array $provider, ContainerBuilder $container) : string { $name = $this->getUserProviderId($name); // Doctrine Entity and In-memory DAO provider are managed by factories foreach ($this->userProviderFactories as $factory) { $key = \str_replace('-', '_', $factory->getKey()); if (!empty($provider[$key])) { $factory->create($container, $name, $provider[$key]); return $name; } } // Existing DAO service provider if (isset($provider['id'])) { $container->setAlias($name, new Alias($provider['id'], \false)); return $provider['id']; } // Chain provider if (isset($provider['chain'])) { $providers = []; foreach ($provider['chain']['providers'] as $providerName) { $providers[] = new Reference($this->getUserProviderId($providerName)); } $container->setDefinition($name, new ChildDefinition('security.user.provider.chain'))->addArgument(new IteratorArgument($providers)); return $name; } throw new InvalidConfigurationException(\sprintf('Unable to create definition for "%s" user provider.', $name)); } private function getUserProviderId(string $name) : string { return 'security.user.provider.concrete.' . \strtolower($name); } private function createExceptionListener(ContainerBuilder $container, array $config, string $id, ?string $defaultEntryPoint, bool $stateless) : string { $exceptionListenerId = 'security.exception_listener.' . $id; $listener = $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener')); $listener->replaceArgument(3, $id); $listener->replaceArgument(4, null === $defaultEntryPoint ? null : new Reference($defaultEntryPoint)); $listener->replaceArgument(8, $stateless); // access denied handler setup if (isset($config['access_denied_handler'])) { $listener->replaceArgument(6, new Reference($config['access_denied_handler'])); } elseif (isset($config['access_denied_url'])) { $listener->replaceArgument(5, $config['access_denied_url']); } return $exceptionListenerId; } private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, ?string $defaultProvider, bool $stateless) : string { $userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider; if (!$userProvider) { throw new InvalidConfigurationException(\sprintf('Not configuring explicitly the provider for the "switch_user" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $id)); } $switchUserListenerId = 'security.authentication.switchuser_listener.' . $id; $listener = $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener')); $listener->replaceArgument(1, new Reference($userProvider)); $listener->replaceArgument(2, new Reference('security.user_checker.' . $id)); $listener->replaceArgument(3, $id); $listener->replaceArgument(6, $config['parameter']); $listener->replaceArgument(7, $config['role']); $listener->replaceArgument(9, $stateless); return $switchUserListenerId; } private function createExpression(ContainerBuilder $container, string $expression) : Reference { if (isset($this->expressions[$id = '.security.expression.' . ContainerBuilder::hash($expression)])) { return $this->expressions[$id]; } if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'], \true)) { throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } $container->register($id, '_ContaoManager\\Symfony\\Component\\ExpressionLanguage\\Expression')->setPublic(\false)->addArgument($expression); return $this->expressions[$id] = new Reference($id); } private function createRequestMatcher(ContainerBuilder $container, ?string $path = null, ?string $host = null, ?int $port = null, array $methods = [], ?array $ips = null, array $attributes = []) : Reference { if ($methods) { $methods = \array_map('strtoupper', $methods); } if (null !== $ips) { foreach ($ips as $ip) { $container->resolveEnvPlaceholders($ip, null, $usedEnvs); if (!$usedEnvs && !$this->isValidIps($ip)) { throw new \LogicException(\sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.', $ip)); } $usedEnvs = null; } } $id = '.security.request_matcher.' . ContainerBuilder::hash([$path, $host, $port, $methods, $ips, $attributes]); if (isset($this->requestMatchers[$id])) { return $this->requestMatchers[$id]; } // only add arguments that are necessary $arguments = [$path, $host, $methods, $ips, $attributes, null, $port]; while (\count($arguments) > 0 && !\end($arguments)) { \array_pop($arguments); } $container->register($id, '_ContaoManager\\Symfony\\Component\\HttpFoundation\\RequestMatcher')->setPublic(\false)->setArguments($arguments); return $this->requestMatchers[$id] = new Reference($id); } /** * @deprecated since Symfony 5.4, use "addAuthenticatorFactory()" instead */ public function addSecurityListenerFactory(SecurityFactoryInterface $factory) { \trigger_deprecation('symfony/security-bundle', '5.4', 'Method "%s()" is deprecated, use "addAuthenticatorFactory()" instead.', __METHOD__); $this->factories[] = [['pre_auth' => -10, 'form' => -30, 'http' => -40, 'remember_me' => -50, 'anonymous' => -60][$factory->getPosition()], $factory]; $this->sortedFactories = []; } public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory) { $this->factories[] = [\method_exists($factory, 'getPriority') ? $factory->getPriority() : 0, $factory]; $this->sortedFactories = []; } public function addUserProviderFactory(UserProviderFactoryInterface $factory) { $this->userProviderFactories[] = $factory; } /** * {@inheritdoc} */ public function getXsdValidationBasePath() { return __DIR__ . '/../Resources/config/schema'; } public function getNamespace() { return 'http://symfony.com/schema/dic/security'; } public function getConfiguration(array $config, ContainerBuilder $container) { // first assemble the factories return new MainConfiguration($this->getSortedFactories(), $this->userProviderFactories); } private function isValidIps($ips) : bool { $ipsList = \array_reduce((array) $ips, static function (array $ips, string $ip) { return \array_merge($ips, \preg_split('/\\s*,\\s*/', $ip)); }, []); if (!$ipsList) { return \false; } foreach ($ipsList as $cidr) { if (!$this->isValidIp($cidr)) { return \false; } } return \true; } private function isValidIp(string $cidr) : bool { $cidrParts = \explode('/', $cidr); if (1 === \count($cidrParts)) { return \false !== \filter_var($cidrParts[0], \FILTER_VALIDATE_IP); } $ip = $cidrParts[0]; $netmask = $cidrParts[1]; if (!\ctype_digit($netmask)) { return \false; } if (\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { return $netmask <= 32; } if (\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { return $netmask <= 128; } return \false; } /** * @return array */ private function getSortedFactories() : array { if (!$this->sortedFactories) { $factories = []; foreach ($this->factories as $i => $factory) { $factories[] = \array_merge($factory, [$i]); } \usort($factories, function ($a, $b) { return $b[0] <=> $a[0] ?: $a[2] <=> $b[2]; }); $this->sortedFactories = \array_column($factories, 1); } return $this->sortedFactories; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Registers the expression language providers. * * @author Fabien Potencier */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if ($container->has('security.expression_language')) { $definition = $container->findDefinition('security.expression_language'); foreach ($container->findTaggedServiceIds('security.expression_language_provider', \true) as $id => $attributes) { $definition->addMethodCall('registerProvider', [new Reference($id)]); } } if (!$container->hasDefinition('cache.system')) { $container->removeDefinition('cache.security_expression_language'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Monolog\Processor\ProcessorInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; /** * Injects the session tracker enabler in "security.context_listener" + binds "security.untracked_token_storage" to ProcessorInterface instances. * * @author Nicolas Grekas * * @internal */ class RegisterTokenUsageTrackingPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->has('security.untracked_token_storage')) { return; } $processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class); $processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + [TokenStorageInterface::class => new BoundArgument(new Reference('security.untracked_token_storage'), \false)]); if (!$container->has('session.factory') && !$container->has('session.storage')) { $container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(\true); $container->getDefinition('security.untracked_token_storage')->addTag('kernel.reset', ['method' => 'reset']); } elseif ($container->hasDefinition('security.context_listener')) { $tokenStorageClass = $container->getParameterBag()->resolveValue($container->findDefinition('security.token_storage')->getClass()); if (\method_exists($tokenStorageClass, 'enableUsageTracking')) { $container->getDefinition('security.context_listener')->setArgument(6, [new Reference('security.token_storage'), 'enableUsageTracking']); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; \trigger_deprecation('symfony/security-bundle', '5.1', 'The "%s" class is deprecated.', RegisterCsrfTokenClearingLogoutHandlerPass::class); /** * @deprecated since symfony/security-bundle 5.1 */ class RegisterCsrfTokenClearingLogoutHandlerPass extends RegisterCsrfFeaturesPass { public function process(ContainerBuilder $container) { if (!$container->has('security.csrf.token_storage')) { return; } $this->registerLogoutHandler($container); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; /** * @author Wouter de Jong * * @internal */ class RegisterLdapLocatorPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $definition = $container->setDefinition('security.ldap_locator', new Definition(ServiceLocator::class)); $locators = []; foreach ($container->findTaggedServiceIds('ldap') as $serviceId => $tags) { $locators[$serviceId] = new ServiceClosureArgument(new Reference($serviceId)); } $definition->addArgument($locators); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\Security\Core\AuthenticationEvents; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginFailureEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; /** * Makes sure all event listeners on the global dispatcher are also listening * to events on the firewall-specific dispatchers. * * This compiler pass must be run after RegisterListenersPass of the * EventDispatcher component. * * @author Wouter de Jong * * @internal */ class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface { private const EVENT_BUBBLING_EVENTS = [ CheckPassportEvent::class, LoginFailureEvent::class, LoginSuccessEvent::class, LogoutEvent::class, AuthenticationTokenCreatedEvent::class, AuthenticationSuccessEvent::class, InteractiveLoginEvent::class, TokenDeauthenticatedEvent::class, // When events are registered by their name AuthenticationEvents::AUTHENTICATION_SUCCESS, SecurityEvents::INTERACTIVE_LOGIN, ]; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { return; } $firewallDispatchers = []; foreach ($container->getParameter('security.firewalls') as $firewallName) { if (!$container->has('security.event_dispatcher.' . $firewallName)) { continue; } $firewallDispatchers[] = $container->findDefinition('security.event_dispatcher.' . $firewallName); } $globalDispatcher = $container->findDefinition('event_dispatcher'); foreach ($globalDispatcher->getMethodCalls() as $methodCall) { if ('addListener' !== $methodCall[0]) { continue; } $methodCallArguments = $methodCall[1]; if (!\in_array($methodCallArguments[0], self::EVENT_BUBBLING_EVENTS, \true)) { continue; } foreach ($firewallDispatchers as $firewallDispatcher) { $firewallDispatcher->addMethodCall('addListener', $methodCallArguments); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; /** * Sorts firewall listeners based on the execution order provided by FirewallListenerInterface::getPriority(). * * @author Christian Scheb */ class SortFirewallListenersPass implements CompilerPassInterface { public function process(ContainerBuilder $container) : void { if (!$container->hasParameter('security.firewalls')) { return; } foreach ($container->getParameter('security.firewalls') as $firewallName) { $firewallContextDefinition = $container->getDefinition('security.firewall.map.context.' . $firewallName); $this->sortFirewallContextListeners($firewallContextDefinition, $container); } } private function sortFirewallContextListeners(Definition $definition, ContainerBuilder $container) : void { /** @var IteratorArgument $listenerIteratorArgument */ $listenerIteratorArgument = $definition->getArgument(0); $prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container); $listeners = $listenerIteratorArgument->getValues(); \usort($listeners, function (Reference $a, Reference $b) use($prioritiesByServiceId) { return $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]; }); $listenerIteratorArgument->setValues(\array_values($listeners)); } private function getListenerPriorities(IteratorArgument $listeners, ContainerBuilder $container) : array { $priorities = []; foreach ($listeners->getValues() as $reference) { $id = (string) $reference; $def = $container->getDefinition($id); // We must assume that the class value has been correctly filled, even if the service is created by a factory $class = $def->getClass(); if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } $priority = 0; if ($r->isSubclassOf(FirewallListenerInterface::class)) { $priority = $r->getMethod('getPriority')->invoke(null); } $priorities[$id] = $priority; } return $priorities; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Bundle\SecurityBundle\RememberMe\DecoratedRememberMeHandler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Replaces the DecoratedRememberMeHandler services with the real definition. * * @author Wouter de Jong * * @internal */ final class ReplaceDecoratedRememberMeHandlerPass implements CompilerPassInterface { private const HANDLER_TAG = 'security.remember_me_handler'; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) : void { $handledFirewalls = []; foreach ($container->findTaggedServiceIds(self::HANDLER_TAG) as $definitionId => $rememberMeHandlerTags) { $definition = $container->findDefinition($definitionId); if (DecoratedRememberMeHandler::class !== $definition->getClass()) { continue; } // get the actual custom remember me handler definition (passed to the decorator) $realRememberMeHandler = $container->findDefinition((string) $definition->getArgument(0)); if (null === $realRememberMeHandler) { throw new \LogicException(\sprintf('Invalid service definition for custom remember me handler; no service found with ID "%s".', (string) $definition->getArgument(0))); } foreach ($rememberMeHandlerTags as $rememberMeHandlerTag) { // some custom handlers may be used on multiple firewalls in the same application if (\in_array($rememberMeHandlerTag['firewall'], $handledFirewalls, \true)) { continue; } $rememberMeHandler = clone $realRememberMeHandler; $rememberMeHandler->addTag(self::HANDLER_TAG, $rememberMeHandlerTag); $container->setDefinition('security.authenticator.remember_me_handler.' . $rememberMeHandlerTag['firewall'], $rememberMeHandler); $handledFirewalls[] = $rememberMeHandlerTag['firewall']; } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Uses the session domain to restrict allowed redirection targets. * * @author Nicolas Grekas */ class AddSessionDomainConstraintPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) { return; } $sessionOptions = $container->getParameter('session.storage.options'); $domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : \sprintf('(?:%%%%s|(?:.+\\.)?%s)', \preg_quote(\trim($sessionOptions['cookie_domain'], '.'))); if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) { $secureDomainRegexp = \sprintf('{^https://%s$}i', $domainRegexp); $domainRegexp = 'https?://' . $domainRegexp; } else { $secureDomainRegexp = null; $domainRegexp = (empty($sessionOptions['cookie_secure']) ? 'https?://' : 'https://') . $domainRegexp; } $container->findDefinition('security.http_utils')->addArgument(\sprintf('{^%s$}i', $domainRegexp))->addArgument($secureDomainRegexp); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Adds all configured security voters to the access decision manager. * * @author Johannes M. Schmitt */ class AddSecurityVotersPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('security.access.decision_manager')) { return; } $voters = $this->findAndSortTaggedServices('security.voter', $container); if (!$voters) { throw new LogicException('No security voters found. You need to tag at least one with "security.voter".'); } $debug = $container->getParameter('kernel.debug'); $voterServices = []; foreach ($voters as $voter) { $voterServiceId = (string) $voter; $definition = $container->getDefinition($voterServiceId); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (!\is_a($class, VoterInterface::class, \true)) { throw new LogicException(\sprintf('"%s" must implement the "%s" when used as a voter.', $class, VoterInterface::class)); } if ($debug) { $voterServices[] = new Reference($debugVoterServiceId = 'debug.security.voter.' . $voterServiceId); $container->register($debugVoterServiceId, TraceableVoter::class)->addArgument($voter)->addArgument(new Reference('event_dispatcher')); } else { $voterServices[] = $voter; } } $container->getDefinition('security.access.decision_manager')->replaceArgument(0, new IteratorArgument($voterServices)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * @author Wouter de Jong */ class RegisterEntryPointPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasParameter('security.firewalls')) { return; } $firewalls = $container->getParameter('security.firewalls'); foreach ($firewalls as $firewallName) { if (!$container->hasDefinition('security.authenticator.manager.' . $firewallName) || !$container->hasParameter('security.' . $firewallName . '._indexed_authenticators')) { continue; } $entryPoints = []; $indexedAuthenticators = $container->getParameter('security.' . $firewallName . '._indexed_authenticators'); // this is a compile-only parameter, removing it cleans up space and avoids unintended usage $container->getParameterBag()->remove('security.' . $firewallName . '._indexed_authenticators'); foreach ($indexedAuthenticators as $key => $authenticatorId) { if (!$container->has($authenticatorId)) { continue; } // because this pass runs before ResolveChildDefinitionPass, child definitions didn't inherit the parent class yet $definition = $container->findDefinition($authenticatorId); while (!($authenticatorClass = $definition->getClass()) && $definition instanceof ChildDefinition) { $definition = $container->findDefinition($definition->getParent()); } if (\is_a($authenticatorClass, AuthenticationEntryPointInterface::class, \true)) { $entryPoints[$key] = $authenticatorId; } } if (!$entryPoints) { continue; } $config = $container->getDefinition('security.firewall.map.config.' . $firewallName); $configuredEntryPoint = $config->getArgument(7); if (null !== $configuredEntryPoint) { // allow entry points to be configured by authenticator key (e.g. "http_basic") $entryPoint = $entryPoints[$configuredEntryPoint] ?? $configuredEntryPoint; } elseif (1 === \count($entryPoints)) { $entryPoint = \array_shift($entryPoints); } else { $entryPointNames = []; foreach ($entryPoints as $key => $serviceId) { $entryPointNames[] = \is_numeric($key) ? $serviceId : $key; } throw new InvalidConfigurationException(\sprintf('Because you have multiple authenticators in firewall "%s", you need to set the "entry_point" key to one of your authenticators ("%s") or a service ID implementing "%s". The "entry_point" determines what should happen (e.g. redirect to "/login") when an anonymous user tries to access a protected page.', $firewallName, \implode('", "', $entryPointNames), AuthenticationEntryPointInterface::class)); } $config->replaceArgument(7, $entryPoint); $container->getDefinition('security.exception_listener.' . $firewallName)->replaceArgument(4, new Reference($entryPoint)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Cleans up the remember me verifier cache if cache is missing. * * @author Jordi Boggiano */ class CleanRememberMeVerifierPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('cache.system')) { $container->removeDefinition('cache.security_token_verifier'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CsrfProtectionListener; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; /** * @author Christian Flothmann * @author Wouter de Jong * * @internal */ class RegisterCsrfFeaturesPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $this->registerCsrfProtectionListener($container); $this->registerLogoutHandler($container); } private function registerCsrfProtectionListener(ContainerBuilder $container) { if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) { return; } $container->register('security.listener.csrf_protection', CsrfProtectionListener::class)->addArgument(new Reference('security.csrf.token_manager'))->addTag('kernel.event_subscriber')->setPublic(\false); } protected function registerLogoutHandler(ContainerBuilder $container) { if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { return; } $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); if (!\is_subclass_of($csrfTokenStorageClass, '_ContaoManager\\Symfony\\Component\\Security\\Csrf\\TokenStorage\\ClearableTokenStorageInterface')) { return; } $container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class)->addArgument(new Reference('security.csrf.token_storage'))->addTag('kernel.event_subscriber')->setPublic(\false); } } { "name": "symfony\/security-bundle", "type": "symfony-bundle", "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "ext-xml": "*", "symfony\/config": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^5.3|^6.0", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/event-dispatcher": "^5.1|^6.0", "symfony\/http-kernel": "^5.3|^6.0", "symfony\/http-foundation": "^5.3|^6.0", "symfony\/password-hasher": "^5.3|^6.0", "symfony\/polyfill-php80": "^1.16", "symfony\/security-core": "^5.4|^6.0", "symfony\/security-csrf": "^4.4|^5.0|^6.0", "symfony\/security-guard": "^5.3", "symfony\/security-http": "^5.4.30|^6.3.6", "symfony\/service-contracts": "^1.10|^2|^3" }, "require-dev": { "doctrine\/annotations": "^1.10.4|^2", "symfony\/asset": "^4.4|^5.0|^6.0", "symfony\/browser-kit": "^4.4|^5.0|^6.0", "symfony\/console": "^4.4|^5.0|^6.0", "symfony\/css-selector": "^4.4|^5.0|^6.0", "symfony\/dom-crawler": "^4.4|^5.0|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/form": "^4.4|^5.0|^6.0", "symfony\/framework-bundle": "^5.3|^6.0", "symfony\/ldap": "^5.3|^6.0", "symfony\/process": "^4.4|^5.0|^6.0", "symfony\/rate-limiter": "^5.2|^6.0", "symfony\/serializer": "^4.4|^5.0|^6.0", "symfony\/translation": "^4.4|^5.0|^6.0", "symfony\/twig-bundle": "^4.4|^5.0|^6.0", "symfony\/twig-bridge": "^4.4|^5.0|^6.0", "symfony\/validator": "^4.4|^5.0|^6.0", "symfony\/yaml": "^4.4|^5.0|^6.0", "twig\/twig": "^2.13|^3.0.4" }, "conflict": { "symfony\/browser-kit": "<4.4", "symfony\/console": "<4.4", "symfony\/framework-bundle": "<4.4", "symfony\/ldap": "<5.1", "symfony\/twig-bundle": "<4.4" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Bundle\\SecurityBundle\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Debug; use _ContaoManager\Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallContext; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Firewall collecting called security listeners and authenticators. * * @author Robin Chalas */ final class TraceableFirewallListener extends FirewallListener implements ResetInterface { private $wrappedListeners = []; private $authenticatorsInfo = []; public function getWrappedListeners() { return $this->wrappedListeners; } public function getAuthenticatorsInfo() : array { return $this->authenticatorsInfo; } public function reset() : void { $this->wrappedListeners = []; $this->authenticatorsInfo = []; } protected function callListeners(RequestEvent $event, iterable $listeners) { $wrappedListeners = []; $wrappedLazyListeners = []; $authenticatorManagerListener = null; foreach ($listeners as $listener) { if ($listener instanceof LazyFirewallContext) { \Closure::bind(function () use(&$wrappedLazyListeners, &$wrappedListeners, &$authenticatorManagerListener) { $listeners = []; foreach ($this->listeners as $listener) { if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) { $authenticatorManagerListener = $listener; } if ($listener instanceof FirewallListenerInterface) { $listener = new WrappedLazyListener($listener); $listeners[] = $listener; $wrappedLazyListeners[] = $listener; } else { $listeners[] = function (RequestEvent $event) use($listener, &$wrappedListeners) { $wrappedListener = new WrappedListener($listener); $wrappedListener($event); $wrappedListeners[] = $wrappedListener->getInfo(); }; } } $this->listeners = $listeners; }, $listener, FirewallContext::class)(); $listener($event); } else { $wrappedListener = $listener instanceof FirewallListenerInterface ? new WrappedLazyListener($listener) : new WrappedListener($listener); $wrappedListener($event); $wrappedListeners[] = $wrappedListener->getInfo(); if (!$authenticatorManagerListener && $listener instanceof TraceableAuthenticatorManagerListener) { $authenticatorManagerListener = $listener; } } if ($event->hasResponse()) { break; } } if ($wrappedLazyListeners) { foreach ($wrappedLazyListeners as $lazyListener) { $this->wrappedListeners[] = $lazyListener->getInfo(); } } $this->wrappedListeners = \array_merge($this->wrappedListeners, $wrappedListeners); if ($authenticatorManagerListener) { $this->authenticatorsInfo = $authenticatorManagerListener->getAuthenticatorsInfo(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Debug; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; /** * Wraps a security listener for calls record. * * @author Robin Chalas * * @internal */ final class WrappedListener { use TraceableListenerTrait; public function __construct(callable $listener) { $this->listener = $listener; } public function __invoke(RequestEvent $event) { $startTime = \microtime(\true); ($this->listener)($event); $this->time = \microtime(\true) - $startTime; $this->response = $event->getResponse(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Debug; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use _ContaoManager\Symfony\Component\VarDumper\Caster\ClassStub; /** * @author Robin Chalas * * @internal */ trait TraceableListenerTrait { private $response; private $listener; private $time; private $stub; /** * Proxies all method calls to the original listener. */ public function __call(string $method, array $arguments) { return $this->listener->{$method}(...$arguments); } public function getWrappedListener() { return $this->listener; } public function getInfo() : array { return ['response' => $this->response, 'time' => $this->time, 'stub' => $this->stub ?? ($this->stub = ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener))]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\Debug; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Exception\LazyResponseException; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AbstractListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; /** * Wraps a lazy security listener. * * @author Robin Chalas * * @internal */ final class WrappedLazyListener extends AbstractListener { use TraceableListenerTrait; public function __construct(FirewallListenerInterface $listener) { $this->listener = $listener; } public function supports(Request $request) : ?bool { return $this->listener->supports($request); } /** * {@inheritdoc} */ public function authenticate(RequestEvent $event) { $startTime = \microtime(\true); try { $ret = $this->listener->authenticate($event); } catch (LazyResponseException $e) { $this->response = $e->getResponse(); throw $e; } finally { $this->time = \microtime(\true) - $startTime; } $this->response = $event->getResponse(); return $ret; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\EventListener; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallMap; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\Security\Http\FirewallMapInterface; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * @author Maxime Steinhausser */ class FirewallListener extends Firewall { private $map; private $logoutUrlGenerator; public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator) { $this->map = $map; $this->logoutUrlGenerator = $logoutUrlGenerator; parent::__construct($map, $dispatcher); } public function configureLogoutUrlGenerator(RequestEvent $event) { if (!$event->isMainRequest()) { return; } if ($this->map instanceof FirewallMap && ($config = $this->map->getFirewallConfig($event->getRequest()))) { $this->logoutUrlGenerator->setCurrentFirewall($config->getName(), $config->getContext()); } } public function onKernelFinishRequest(FinishRequestEvent $event) { if ($event->isMainRequest()) { $this->logoutUrlGenerator->setCurrentFirewall(null); } parent::onKernelFinishRequest($event); } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [KernelEvents::REQUEST => [['configureLogoutUrlGenerator', 8], ['onKernelRequest', 8]], KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Bundle\SecurityBundle\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use _ContaoManager\Symfony\Component\Security\Core\Event\VoteEvent; /** * Listen to vote events from traceable voters. * * @author Laurent VOULLEMIER * * @internal */ class VoteListener implements EventSubscriberInterface { private $traceableAccessDecisionManager; public function __construct(TraceableAccessDecisionManager $traceableAccessDecisionManager) { $this->traceableAccessDecisionManager = $traceableAccessDecisionManager; } /** * Event dispatched by a voter during access manager decision. */ public function onVoterVote(VoteEvent $event) { $this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote()); } public static function getSubscribedEvents() : array { return ['debug.security.authorization.vote' => 'onVoterVote']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; use _ContaoManager\Symfony\Component\Filesystem\Exception\IOException; use _ContaoManager\Symfony\Component\Filesystem\Filesystem; /** * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface * to check whether cached data is still fresh. * * @author Matthias Pigulla */ class ResourceCheckerConfigCache implements ConfigCacheInterface { /** * @var string */ private $file; /** * @var iterable */ private $resourceCheckers; /** * @param string $file The absolute cache path * @param iterable $resourceCheckers The ResourceCheckers to use for the freshness check */ public function __construct(string $file, iterable $resourceCheckers = []) { $this->file = $file; $this->resourceCheckers = $resourceCheckers; } /** * {@inheritdoc} */ public function getPath() { return $this->file; } /** * Checks if the cache is still fresh. * * This implementation will make a decision solely based on the ResourceCheckers * passed in the constructor. * * The first ResourceChecker that supports a given resource is considered authoritative. * Resources with no matching ResourceChecker will silently be ignored and considered fresh. * * @return bool */ public function isFresh() { if (!\is_file($this->file)) { return \false; } if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) { $this->resourceCheckers = \iterator_to_array($this->resourceCheckers); } if (!\count($this->resourceCheckers)) { return \true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all } $metadata = $this->getMetaFile(); if (!\is_file($metadata)) { return \false; } $meta = $this->safelyUnserialize($metadata); if (\false === $meta) { return \false; } $time = \filemtime($this->file); foreach ($meta as $resource) { foreach ($this->resourceCheckers as $checker) { if (!$checker->supports($resource)) { continue; // next checker } if ($checker->isFresh($resource, $time)) { break; // no need to further check this resource } return \false; // cache is stale } // no suitable checker found, ignore this resource } return \true; } /** * Writes cache. * * @param string $content The content to write in the cache * @param ResourceInterface[] $metadata An array of metadata * * @throws \RuntimeException When cache file can't be written */ public function write(string $content, ?array $metadata = null) { $mode = 0666; $umask = \umask(); $filesystem = new Filesystem(); $filesystem->dumpFile($this->file, $content); try { $filesystem->chmod($this->file, $mode, $umask); } catch (IOException $e) { // discard chmod failure (some filesystem may not support it) } if (null !== $metadata) { $filesystem->dumpFile($this->getMetaFile(), \serialize($metadata)); try { $filesystem->chmod($this->getMetaFile(), $mode, $umask); } catch (IOException $e) { // discard chmod failure (some filesystem may not support it) } } if (\function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { @\opcache_invalidate($this->file, \true); } } /** * Gets the meta file path. */ private function getMetaFile() : string { return $this->file . '.meta'; } private function safelyUnserialize(string $file) { $meta = \false; $content = \file_get_contents($file); $signalingException = new \UnexpectedValueException(); $prevUnserializeHandler = \ini_set('unserialize_callback_func', self::class . '::handleUnserializeCallback'); $prevErrorHandler = \set_error_handler(function ($type, $msg, $file, $line, $context = []) use(&$prevErrorHandler, $signalingException) { if (__FILE__ === $file) { throw $signalingException; } return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : \false; }); try { $meta = \unserialize($content); } catch (\Throwable $e) { if ($e !== $signalingException) { throw $e; } } finally { \restore_error_handler(); \ini_set('unserialize_callback_func', $prevUnserializeHandler); } return $meta; } /** * @internal */ public static function handleUnserializeCallback(string $class) { \trigger_error('Class not found: ' . $class); } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Util; use _ContaoManager\Symfony\Component\Config\Util\Exception\InvalidXmlException; use _ContaoManager\Symfony\Component\Config\Util\Exception\XmlParsingException; /** * XMLUtils is a bunch of utility methods to XML operations. * * This class contains static methods only and is not meant to be instantiated. * * @author Fabien Potencier * @author Martin Hasoň * @author Ole Rößner */ class XmlUtils { /** * This class should not be instantiated. */ private function __construct() { } /** * Parses an XML string. * * @param string $content An XML string * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation * * @return \DOMDocument * * @throws XmlParsingException When parsing of XML file returns error * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself * @throws \RuntimeException When DOM extension is missing */ public static function parse(string $content, $schemaOrCallable = null) { if (!\extension_loaded('dom')) { throw new \LogicException('Extension DOM is required.'); } $internalErrors = \libxml_use_internal_errors(\true); if (\LIBXML_VERSION < 20900) { $disableEntities = \libxml_disable_entity_loader(\true); } \libxml_clear_errors(); $dom = new \DOMDocument(); $dom->validateOnParse = \true; if (!$dom->loadXML($content, \LIBXML_NONET | (\defined('LIBXML_COMPACT') ? \LIBXML_COMPACT : 0))) { if (\LIBXML_VERSION < 20900) { \libxml_disable_entity_loader($disableEntities); } throw new XmlParsingException(\implode("\n", static::getXmlErrors($internalErrors))); } $dom->normalizeDocument(); \libxml_use_internal_errors($internalErrors); if (\LIBXML_VERSION < 20900) { \libxml_disable_entity_loader($disableEntities); } foreach ($dom->childNodes as $child) { if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) { throw new XmlParsingException('Document types are not allowed.'); } } if (null !== $schemaOrCallable) { $internalErrors = \libxml_use_internal_errors(\true); \libxml_clear_errors(); $e = null; if (\is_callable($schemaOrCallable)) { try { $valid = $schemaOrCallable($dom, $internalErrors); } catch (\Exception $e) { $valid = \false; } } elseif (!\is_array($schemaOrCallable) && \is_file((string) $schemaOrCallable)) { $schemaSource = \file_get_contents((string) $schemaOrCallable); $valid = @$dom->schemaValidateSource($schemaSource); } else { \libxml_use_internal_errors($internalErrors); throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.'); } if (!$valid) { $messages = static::getXmlErrors($internalErrors); if (empty($messages)) { throw new InvalidXmlException('The XML is not valid.', 0, $e); } throw new XmlParsingException(\implode("\n", $messages), 0, $e); } } \libxml_clear_errors(); \libxml_use_internal_errors($internalErrors); return $dom; } /** * Loads an XML file. * * @param string $file An XML file path * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation * * @return \DOMDocument * * @throws \InvalidArgumentException When loading of XML file returns error * @throws XmlParsingException When XML parsing returns any errors * @throws \RuntimeException When DOM extension is missing */ public static function loadFile(string $file, $schemaOrCallable = null) { if (!\is_file($file)) { throw new \InvalidArgumentException(\sprintf('Resource "%s" is not a file.', $file)); } if (!\is_readable($file)) { throw new \InvalidArgumentException(\sprintf('File "%s" is not readable.', $file)); } $content = @\file_get_contents($file); if ('' === \trim($content)) { throw new \InvalidArgumentException(\sprintf('File "%s" does not contain valid XML, it is empty.', $file)); } try { return static::parse($content, $schemaOrCallable); } catch (InvalidXmlException $e) { throw new XmlParsingException(\sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious()); } } /** * Converts a \DOMElement object to a PHP array. * * The following rules applies during the conversion: * * * Each tag is converted to a key value or an array * if there is more than one "value" * * * The content of a tag is set under a "value" key (bar) * if the tag also has some nested tags * * * The attributes are converted to keys () * * * The nested-tags are converted to keys (bar) * * @param \DOMElement $element A \DOMElement instance * @param bool $checkPrefix Check prefix in an element or an attribute name * * @return mixed */ public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = \true) { $prefix = (string) $element->prefix; $empty = \true; $config = []; foreach ($element->attributes as $name => $node) { if ($checkPrefix && !\in_array((string) $node->prefix, ['', $prefix], \true)) { continue; } $config[$name] = static::phpize($node->value); $empty = \false; } $nodeValue = \false; foreach ($element->childNodes as $node) { if ($node instanceof \DOMText) { if ('' !== \trim($node->nodeValue)) { $nodeValue = \trim($node->nodeValue); $empty = \false; } } elseif ($checkPrefix && $prefix != (string) $node->prefix) { continue; } elseif (!$node instanceof \DOMComment) { $value = static::convertDomElementToArray($node, $checkPrefix); $key = $node->localName; if (isset($config[$key])) { if (!\is_array($config[$key]) || !\is_int(\key($config[$key]))) { $config[$key] = [$config[$key]]; } $config[$key][] = $value; } else { $config[$key] = $value; } $empty = \false; } } if (\false !== $nodeValue) { $value = static::phpize($nodeValue); if (\count($config)) { $config['value'] = $value; } else { $config = $value; } } return !$empty ? $config : null; } /** * Converts an xml value to a PHP type. * * @param mixed $value * * @return mixed */ public static function phpize($value) { $value = (string) $value; $lowercaseValue = \strtolower($value); switch (\true) { case 'null' === $lowercaseValue: return null; case \ctype_digit($value): case isset($value[1]) && '-' === $value[0] && \ctype_digit(\substr($value, 1)): $raw = $value; $cast = (int) $value; return self::isOctal($value) ? \intval($value, 8) : ($raw === (string) $cast ? $cast : $raw); case 'true' === $lowercaseValue: return \true; case 'false' === $lowercaseValue: return \false; case isset($value[1]) && '0b' == $value[0] . $value[1] && \preg_match('/^0b[01]*$/', $value): return \bindec($value); case \is_numeric($value): return '0x' === $value[0] . $value[1] ? \hexdec($value) : (float) $value; case \preg_match('/^0x[0-9a-f]++$/i', $value): return \hexdec($value); case \preg_match('/^[+-]?[0-9]+(\\.[0-9]+)?$/', $value): return (float) $value; default: return $value; } } protected static function getXmlErrors(bool $internalErrors) { $errors = []; foreach (\libxml_get_errors() as $error) { $errors[] = \sprintf('[%s %s] %s (in %s - line %d, column %d)', \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', $error->code, \trim($error->message), $error->file ?: 'n/a', $error->line, $error->column); } \libxml_clear_errors(); \libxml_use_internal_errors($internalErrors); return $errors; } private static function isOctal(string $str) : bool { if ('-' === $str[0]) { $str = \substr($str, 1); } return $str === '0' . \decoct(\intval($str, 8)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Util\Exception; /** * Exception class for when XML cannot be parsed properly. * * @author Ole Rößner */ class XmlParsingException extends \InvalidArgumentException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Util\Exception; /** * Exception class for when XML parsing with an XSD schema file path or a callable validator produces errors unrelated * to the actual XML parsing. * * @author Ole Rößner */ class InvalidXmlException extends XmlParsingException { } CHANGELOG ========= 5.3.0 ----- * Add support for generating `ConfigBuilder` for extensions 5.1.0 ----- * updated the signature of method `NodeDefinition::setDeprecated()` to `NodeDefinition::setDeprecation(string $package, string $version, string $message)` * updated the signature of method `BaseNode::setDeprecated()` to `BaseNode::setDeprecation(string $package, string $version, string $message)` * deprecated passing a null message to `BaseNode::setDeprecated()` to un-deprecate a node * deprecated `BaseNode::getDeprecationMessage()`, use `BaseNode::getDeprecation()` instead 5.0.0 ----- * Dropped support for constructing a `TreeBuilder` without passing root node information. * Removed the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead * Added method `getChildNodeDefinitions()` to ParentNodeDefinitionInterface * Removed `FileLoaderLoadException`, use `LoaderLoadException` instead 4.4.0 ----- * added a way to exclude patterns of resources from being imported by the `import()` method 4.3.0 ----- * deprecated using environment variables with `cannotBeEmpty()` if the value is validated with `validate()` * made `Resource\*` classes final and not implement `Serializable` anymore * deprecated the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead 4.2.0 ----- * deprecated constructing a `TreeBuilder` without passing root node information * renamed `FileLoaderLoadException` to `LoaderLoadException` 4.1.0 ----- * added `setPathSeparator` method to `NodeBuilder` class * added third `$pathSeparator` constructor argument to `BaseNode` * the `Processor` class has been made final 4.0.0 ----- * removed `ConfigCachePass` 3.4.0 ----- * added `setDeprecated()` method to indicate a deprecated node * added `XmlUtils::parse()` method to parse an XML string * deprecated `ConfigCachePass` 3.3.0 ----- * added `ReflectionClassResource` class * added second `$exists` constructor argument to `ClassExistenceResource` * made `ClassExistenceResource` work with interfaces and traits * added `ConfigCachePass` (originally in FrameworkBundle) * added `castToArray()` helper to turn any config value into an array 3.0.0 ----- * removed `ReferenceDumper` class * removed the `ResourceInterface::isFresh()` method * removed `BCResourceInterfaceChecker` class * removed `ResourceInterface::getResource()` method 2.8.0 ----- The edge case of defining just one value for nodes of type Enum is now allowed: ```php $rootNode ->children() ->enumNode('variable') ->values(['value']) ->end() ->end() ; ``` Before: `InvalidArgumentException` (variable must contain at least two distinct elements). After: the code will work as expected and it will restrict the values of the `variable` option to just `value`. * deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they can be validated that way, make them implement the new `SelfCheckingResourceInterface`. * deprecated the getResource() method in ResourceInterface. You can still call this method on concrete classes implementing the interface, but it does not make sense at the interface level as you need to know about the particular type of resource at hand to understand the semantics of the returned value. 2.7.0 ----- * added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory` implementation to delegate creation of ConfigCache instances 2.2.0 ----- * added `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()` to ease configuration when some sections are respectively disabled / enabled by default. * added a `normalizeKeys()` method for array nodes (to avoid key normalization) * added numerical type handling for config definitions * added convenience methods for optional configuration sections to `ArrayNodeDefinition` * added a utils class for XML manipulations 2.1.0 ----- * added a way to add documentation on configuration * implemented `Serializable` on resources * `LoaderResolverInterface` is now used instead of `LoaderResolver` for type hinting * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; /** * Interface for ConfigCache. * * @author Matthias Pigulla */ interface ConfigCacheInterface { /** * Gets the cache file path. * * @return string */ public function getPath(); /** * Checks if the cache is still fresh. * * This check should take the metadata passed to the write() method into consideration. * * @return bool */ public function isFresh(); /** * Writes the given content into the cache file. Metadata will be stored * independently and can be used to check cache freshness at a later time. * * @param string $content The content to write into the cache * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances * * @throws \RuntimeException When the cache file cannot be written */ public function write(string $content, ?array $metadata = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; use _ContaoManager\Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; /** * @author Fabien Potencier */ interface FileLocatorInterface { /** * Returns a full path for a given file name. * * @param string $name The file name to locate * @param string|null $currentPath The current path * @param bool $first Whether to return the first occurrence or an array of filenames * * @return string|array The full path to the file or an array of file paths * * @throws \InvalidArgumentException If $name is empty * @throws FileLocatorFileNotFoundException If a file is not found */ public function locate(string $name, ?string $currentPath = null, bool $first = \true); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; use _ContaoManager\Symfony\Component\Config\Resource\SelfCheckingResourceChecker; /** * ConfigCache caches arbitrary content in files on disk. * * When in debug mode, those metadata resources that implement * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will * be used to check cache freshness. * * @author Fabien Potencier * @author Matthias Pigulla */ class ConfigCache extends ResourceCheckerConfigCache { private $debug; /** * @param string $file The absolute cache path * @param bool $debug Whether debugging is enabled or not */ public function __construct(string $file, bool $debug) { $this->debug = $debug; $checkers = []; if (\true === $this->debug) { $checkers = [new SelfCheckingResourceChecker()]; } parent::__construct($file, $checkers); } /** * Checks if the cache is still fresh. * * This implementation always returns true when debug is off and the * cache file exists. * * @return bool */ public function isFresh() { if (!$this->debug && \is_file($this->getPath())) { return \true; } return parent::isFresh(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; /** * Interface for a ConfigCache factory. This factory creates * an instance of ConfigCacheInterface and initializes the * cache if necessary. * * @author Matthias Pigulla */ interface ConfigCacheFactoryInterface { /** * Creates a cache instance and (re-)initializes it if necessary. * * @param string $file The absolute cache file path * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback * * @return ConfigCacheInterface */ public function cache(string $file, callable $callable); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; use _ContaoManager\Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; /** * FileLocator uses an array of pre-defined paths to find files. * * @author Fabien Potencier */ class FileLocator implements FileLocatorInterface { protected $paths; /** * @param string|string[] $paths A path or an array of paths where to look for resources */ public function __construct($paths = []) { $this->paths = (array) $paths; } /** * {@inheritdoc} */ public function locate(string $name, ?string $currentPath = null, bool $first = \true) { if ('' === $name) { throw new \InvalidArgumentException('An empty file name is not valid to be located.'); } if ($this->isAbsolutePath($name)) { if (!\file_exists($name)) { throw new FileLocatorFileNotFoundException(\sprintf('The file "%s" does not exist.', $name), 0, null, [$name]); } return $name; } $paths = $this->paths; if (null !== $currentPath) { \array_unshift($paths, $currentPath); } $paths = \array_unique($paths); $filepaths = $notfound = []; foreach ($paths as $path) { if (@\file_exists($file = $path . \DIRECTORY_SEPARATOR . $name)) { if (\true === $first) { return $file; } $filepaths[] = $file; } else { $notfound[] = $file; } } if (!$filepaths) { throw new FileLocatorFileNotFoundException(\sprintf('The file "%s" does not exist (in: "%s").', $name, \implode('", "', $paths)), 0, null, $notfound); } return $filepaths; } /** * Returns whether the file path is an absolute path. */ private function isAbsolutePath(string $file) : bool { if ('/' === $file[0] || '\\' === $file[0] || \strlen($file) > 3 && \ctype_alpha($file[0]) && ':' === $file[1] && ('\\' === $file[2] || '/' === $file[2]) || null !== \parse_url($file, \PHP_URL_SCHEME)) { return \true; } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; /** * Interface for ResourceCheckers. * * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated * metadata resources are passed to ResourceCheckers. The ResourceCheckers * can then inspect the resources and decide whether the cache can be considered * fresh or not. * * @author Matthias Pigulla * @author Benjamin Klotz */ interface ResourceCheckerInterface { /** * Queries the ResourceChecker whether it can validate a given * resource or not. * * @return bool */ public function supports(ResourceInterface $metadata); /** * Validates the resource. * * @param int $timestamp The timestamp at which the cache associated with this resource was created * * @return bool */ public function isFresh(ResourceInterface $resource, int $timestamp); } Config Component ================ The Config component helps find, load, combine, autofill and validate configuration values of any kind, whatever their source may be (YAML, XML, INI files, or for instance a database). Resources --------- * [Documentation](https://symfony.com/doc/current/components/config.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; use _ContaoManager\Symfony\Component\Config\Exception\LoaderLoadException; /** * Loader is the abstract class used by all built-in loaders. * * @author Fabien Potencier */ abstract class Loader implements LoaderInterface { protected $resolver; protected $env; public function __construct(?string $env = null) { $this->env = $env; } /** * {@inheritdoc} */ public function getResolver() { return $this->resolver; } /** * {@inheritdoc} */ public function setResolver(LoaderResolverInterface $resolver) { $this->resolver = $resolver; } /** * Imports a resource. * * @param mixed $resource A resource * @param string|null $type The resource type or null if unknown * * @return mixed */ public function import($resource, ?string $type = null) { return $this->resolve($resource, $type)->load($resource, $type); } /** * Finds a loader able to load an imported resource. * * @param mixed $resource A resource * @param string|null $type The resource type or null if unknown * * @return LoaderInterface * * @throws LoaderLoadException If no loader is found */ public function resolve($resource, ?string $type = null) { if ($this->supports($resource, $type)) { return $this; } $loader = null === $this->resolver ? \false : $this->resolver->resolve($resource, $type); if (\false === $loader) { throw new LoaderLoadException($resource, null, 0, null, $type); } return $loader; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; use _ContaoManager\Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException; use _ContaoManager\Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; use _ContaoManager\Symfony\Component\Config\Exception\LoaderLoadException; use _ContaoManager\Symfony\Component\Config\FileLocatorInterface; use _ContaoManager\Symfony\Component\Config\Resource\FileExistenceResource; use _ContaoManager\Symfony\Component\Config\Resource\GlobResource; /** * FileLoader is the abstract class used by all built-in loaders that are file based. * * @author Fabien Potencier */ abstract class FileLoader extends Loader { protected static $loading = []; protected $locator; private $currentDir; public function __construct(FileLocatorInterface $locator, ?string $env = null) { $this->locator = $locator; parent::__construct($env); } /** * Sets the current directory. */ public function setCurrentDir(string $dir) { $this->currentDir = $dir; } /** * Returns the file locator used by this loader. * * @return FileLocatorInterface */ public function getLocator() { return $this->locator; } /** * Imports a resource. * * @param mixed $resource A Resource * @param string|null $type The resource type or null if unknown * @param bool $ignoreErrors Whether to ignore import errors or not * @param string|null $sourceResource The original resource importing the new resource * @param string|string[]|null $exclude Glob patterns to exclude from the import * * @return mixed * * @throws LoaderLoadException * @throws FileLoaderImportCircularReferenceException * @throws FileLocatorFileNotFoundException */ public function import($resource, ?string $type = null, bool $ignoreErrors = \false, ?string $sourceResource = null, $exclude = null) { if (\is_string($resource) && \strlen($resource) !== ($i = \strcspn($resource, '*?{[')) && !\str_contains($resource, "\n")) { $excluded = []; foreach ((array) $exclude as $pattern) { foreach ($this->glob($pattern, \true, $_, \false, \true) as $path => $info) { // normalize Windows slashes and remove trailing slashes $excluded[\rtrim(\str_replace('\\', '/', $path), '/')] = \true; } } $ret = []; $isSubpath = 0 !== $i && \str_contains(\substr($resource, 0, $i), '/'); foreach ($this->glob($resource, \false, $_, $ignoreErrors || !$isSubpath, \false, $excluded) as $path => $info) { if (null !== ($res = $this->doImport($path, 'glob' === $type ? null : $type, $ignoreErrors, $sourceResource))) { $ret[] = $res; } $isSubpath = \true; } if ($isSubpath) { return isset($ret[1]) ? $ret : $ret[0] ?? null; } } return $this->doImport($resource, $type, $ignoreErrors, $sourceResource); } /** * @internal */ protected function glob(string $pattern, bool $recursive, &$resource = null, bool $ignoreErrors = \false, bool $forExclusion = \false, array $excluded = []) { if (\strlen($pattern) === ($i = \strcspn($pattern, '*?{['))) { $prefix = $pattern; $pattern = ''; } elseif (0 === $i || !\str_contains(\substr($pattern, 0, $i), '/')) { $prefix = '.'; $pattern = '/' . $pattern; } else { $prefix = \dirname(\substr($pattern, 0, 1 + $i)); $pattern = \substr($pattern, \strlen($prefix)); } try { $prefix = $this->locator->locate($prefix, $this->currentDir, \true); } catch (FileLocatorFileNotFoundException $e) { if (!$ignoreErrors) { throw $e; } $resource = []; foreach ($e->getPaths() as $path) { $resource[] = new FileExistenceResource($path); } return; } $resource = new GlobResource($prefix, $pattern, $recursive, $forExclusion, $excluded); yield from $resource; } private function doImport($resource, ?string $type = null, bool $ignoreErrors = \false, ?string $sourceResource = null) { try { $loader = $this->resolve($resource, $type); if ($loader instanceof self && null !== $this->currentDir) { $resource = $loader->getLocator()->locate($resource, $this->currentDir, \false); } $resources = \is_array($resource) ? $resource : [$resource]; for ($i = 0; $i < ($resourcesCount = \count($resources)); ++$i) { if (isset(self::$loading[$resources[$i]])) { if ($i == $resourcesCount - 1) { throw new FileLoaderImportCircularReferenceException(\array_keys(self::$loading)); } } else { $resource = $resources[$i]; break; } } self::$loading[$resource] = \true; try { $ret = $loader->load($resource, $type); } finally { unset(self::$loading[$resource]); } return $ret; } catch (FileLoaderImportCircularReferenceException $e) { throw $e; } catch (\Exception $e) { if (!$ignoreErrors) { // prevent embedded imports from nesting multiple exceptions if ($e instanceof LoaderLoadException) { throw $e; } throw new LoaderLoadException($resource, $sourceResource, 0, $e, $type); } } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; /** * Placeholder for a parameter. * * @author Tobias Nyholm */ class ParamConfigurator { private $name; public function __construct(string $name) { $this->name = $name; } public function __toString() : string { return '%' . $this->name . '%'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; /** * LoaderInterface is the interface implemented by all loader classes. * * @author Fabien Potencier */ interface LoaderInterface { /** * Loads a resource. * * @param mixed $resource The resource * * @return mixed * * @throws \Exception If something went wrong */ public function load($resource, ?string $type = null); /** * Returns whether this class supports the given resource. * * @param mixed $resource A resource * * @return bool */ public function supports($resource, ?string $type = null); /** * Gets the loader resolver. * * @return LoaderResolverInterface */ public function getResolver(); /** * Sets the loader resolver. */ public function setResolver(LoaderResolverInterface $resolver); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; /** * LoaderResolverInterface selects a loader for a given resource. * * @author Fabien Potencier */ interface LoaderResolverInterface { /** * Returns a loader able to load the resource. * * @param mixed $resource A resource * @param string|null $type The resource type or null if unknown * * @return LoaderInterface|false */ public function resolve($resource, ?string $type = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; /** * LoaderResolver selects a loader for a given resource. * * A resource can be anything (e.g. a full path to a config file or a Closure). * Each loader determines whether it can load a resource and how. * * @author Fabien Potencier */ class LoaderResolver implements LoaderResolverInterface { /** * @var LoaderInterface[] An array of LoaderInterface objects */ private $loaders = []; /** * @param LoaderInterface[] $loaders An array of loaders */ public function __construct(array $loaders = []) { foreach ($loaders as $loader) { $this->addLoader($loader); } } /** * {@inheritdoc} */ public function resolve($resource, ?string $type = null) { foreach ($this->loaders as $loader) { if ($loader->supports($resource, $type)) { return $loader; } } return \false; } public function addLoader(LoaderInterface $loader) { $this->loaders[] = $loader; $loader->setResolver($this); } /** * Returns the registered loaders. * * @return LoaderInterface[] */ public function getLoaders() { return $this->loaders; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; use _ContaoManager\Symfony\Component\Config\Exception\LoaderLoadException; /** * DelegatingLoader delegates loading to other loaders using a loader resolver. * * This loader acts as an array of LoaderInterface objects - each having * a chance to load a given resource (handled by the resolver) * * @author Fabien Potencier */ class DelegatingLoader extends Loader { public function __construct(LoaderResolverInterface $resolver) { $this->resolver = $resolver; } /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { if (\false === ($loader = $this->resolver->resolve($resource, $type))) { throw new LoaderLoadException($resource, null, 0, null, $type); } return $loader->load($resource, $type); } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return \false !== $this->resolver->resolve($resource, $type); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Loader; /** * GlobFileLoader loads files from a glob pattern. * * @author Fabien Potencier */ class GlobFileLoader extends FileLoader { /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { return $this->import($resource); } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return 'glob' === $type; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; /** * This node represents a float value in the config tree. * * @author Jeanmonod David */ class FloatNode extends NumericNode { /** * {@inheritdoc} */ protected function validateType($value) { // Integers are also accepted, we just cast them if (\is_int($value)) { $value = (float) $value; } if (!\is_float($value)) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "float", but got "%s".', $this->getPath(), \get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } $ex->setPath($this->getPath()); throw $ex; } } /** * {@inheritdoc} */ protected function getValidPlaceholderTypes() : array { return ['float']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\Exception; use _ContaoManager\Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\UnsetKeyException; /** * The base node class. * * @author Johannes M. Schmitt */ abstract class BaseNode implements NodeInterface { public const DEFAULT_PATH_SEPARATOR = '.'; private static $placeholderUniquePrefixes = []; private static $placeholders = []; protected $name; protected $parent; protected $normalizationClosures = []; protected $finalValidationClosures = []; protected $allowOverwrite = \true; protected $required = \false; protected $deprecation = []; protected $equivalentValues = []; protected $attributes = []; protected $pathSeparator; private $handlingPlaceholder; /** * @throws \InvalidArgumentException if the name contains a period */ public function __construct(?string $name, ?NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR) { if (\str_contains($name = (string) $name, $pathSeparator)) { throw new \InvalidArgumentException('The name must not contain ".' . $pathSeparator . '".'); } $this->name = $name; $this->parent = $parent; $this->pathSeparator = $pathSeparator; } /** * Register possible (dummy) values for a dynamic placeholder value. * * Matching configuration values will be processed with a provided value, one by one. After a provided value is * successfully processed the configuration value is returned as is, thus preserving the placeholder. * * @internal */ public static function setPlaceholder(string $placeholder, array $values) : void { if (!$values) { throw new \InvalidArgumentException('At least one value must be provided.'); } self::$placeholders[$placeholder] = $values; } /** * Adds a common prefix for dynamic placeholder values. * * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence. * * @internal */ public static function setPlaceholderUniquePrefix(string $prefix) : void { self::$placeholderUniquePrefixes[] = $prefix; } /** * Resets all current placeholders available. * * @internal */ public static function resetPlaceholders() : void { self::$placeholderUniquePrefixes = []; self::$placeholders = []; } public function setAttribute(string $key, $value) { $this->attributes[$key] = $value; } /** * @return mixed */ public function getAttribute(string $key, $default = null) { return $this->attributes[$key] ?? $default; } /** * @return bool */ public function hasAttribute(string $key) { return isset($this->attributes[$key]); } /** * @return array */ public function getAttributes() { return $this->attributes; } public function setAttributes(array $attributes) { $this->attributes = $attributes; } public function removeAttribute(string $key) { unset($this->attributes[$key]); } /** * Sets an info message. */ public function setInfo(string $info) { $this->setAttribute('info', $info); } /** * Returns info message. * * @return string|null */ public function getInfo() { return $this->getAttribute('info'); } /** * Sets the example configuration for this node. * * @param string|array $example */ public function setExample($example) { $this->setAttribute('example', $example); } /** * Retrieves the example configuration for this node. * * @return string|array|null */ public function getExample() { return $this->getAttribute('example'); } /** * Adds an equivalent value. * * @param mixed $originalValue * @param mixed $equivalentValue */ public function addEquivalentValue($originalValue, $equivalentValue) { $this->equivalentValues[] = [$originalValue, $equivalentValue]; } /** * Set this node as required. */ public function setRequired(bool $boolean) { $this->required = $boolean; } /** * Sets this node as deprecated. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message the deprecation message to use * * You can use %node% and %path% placeholders in your message to display, * respectively, the node name and its complete path */ public function setDeprecated(?string $package) { $args = \func_get_args(); if (\func_num_args() < 2) { \trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); if (!isset($args[0])) { \trigger_deprecation('symfony/config', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); $this->deprecation = []; return; } $message = (string) $args[0]; $package = $version = ''; } else { $package = (string) $args[0]; $version = (string) $args[1]; $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.'); } $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message]; } /** * Sets if this node can be overridden. */ public function setAllowOverwrite(bool $allow) { $this->allowOverwrite = $allow; } /** * Sets the closures used for normalization. * * @param \Closure[] $closures An array of Closures used for normalization */ public function setNormalizationClosures(array $closures) { $this->normalizationClosures = $closures; } /** * Sets the closures used for final validation. * * @param \Closure[] $closures An array of Closures used for final validation */ public function setFinalValidationClosures(array $closures) { $this->finalValidationClosures = $closures; } /** * {@inheritdoc} */ public function isRequired() { return $this->required; } /** * Checks if this node is deprecated. * * @return bool */ public function isDeprecated() { return (bool) $this->deprecation; } /** * Returns the deprecated message. * * @param string $node the configuration node name * @param string $path the path of the node * * @return string * * @deprecated since Symfony 5.1, use "getDeprecation()" instead. */ public function getDeprecationMessage(string $node, string $path) { \trigger_deprecation('symfony/config', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); return $this->getDeprecation($node, $path)['message']; } /** * @param string $node The configuration node name * @param string $path The path of the node */ public function getDeprecation(string $node, string $path) : array { return ['package' => $this->deprecation['package'] ?? '', 'version' => $this->deprecation['version'] ?? '', 'message' => \strtr($this->deprecation['message'] ?? '', ['%node%' => $node, '%path%' => $path])]; } /** * {@inheritdoc} */ public function getName() { return $this->name; } /** * {@inheritdoc} */ public function getPath() { if (null !== $this->parent) { return $this->parent->getPath() . $this->pathSeparator . $this->name; } return $this->name; } /** * {@inheritdoc} */ public final function merge($leftSide, $rightSide) { if (!$this->allowOverwrite) { throw new ForbiddenOverwriteException(\sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath())); } if ($leftSide !== ($leftPlaceholders = self::resolvePlaceholderValue($leftSide))) { foreach ($leftPlaceholders as $leftPlaceholder) { $this->handlingPlaceholder = $leftSide; try { $this->merge($leftPlaceholder, $rightSide); } finally { $this->handlingPlaceholder = null; } } return $rightSide; } if ($rightSide !== ($rightPlaceholders = self::resolvePlaceholderValue($rightSide))) { foreach ($rightPlaceholders as $rightPlaceholder) { $this->handlingPlaceholder = $rightSide; try { $this->merge($leftSide, $rightPlaceholder); } finally { $this->handlingPlaceholder = null; } } return $rightSide; } $this->doValidateType($leftSide); $this->doValidateType($rightSide); return $this->mergeValues($leftSide, $rightSide); } /** * {@inheritdoc} */ public final function normalize($value) { $value = $this->preNormalize($value); // run custom normalization closures foreach ($this->normalizationClosures as $closure) { $value = $closure($value); } // resolve placeholder value if ($value !== ($placeholders = self::resolvePlaceholderValue($value))) { foreach ($placeholders as $placeholder) { $this->handlingPlaceholder = $value; try { $this->normalize($placeholder); } finally { $this->handlingPlaceholder = null; } } return $value; } // replace value with their equivalent foreach ($this->equivalentValues as $data) { if ($data[0] === $value) { $value = $data[1]; } } // validate type $this->doValidateType($value); // normalize value return $this->normalizeValue($value); } /** * Normalizes the value before any other normalization is applied. * * @param mixed $value * * @return mixed */ protected function preNormalize($value) { return $value; } /** * Returns parent node for this node. * * @return NodeInterface|null */ public function getParent() { return $this->parent; } /** * {@inheritdoc} */ public final function finalize($value) { if ($value !== ($placeholders = self::resolvePlaceholderValue($value))) { foreach ($placeholders as $placeholder) { $this->handlingPlaceholder = $value; try { $this->finalize($placeholder); } finally { $this->handlingPlaceholder = null; } } return $value; } $this->doValidateType($value); $value = $this->finalizeValue($value); // Perform validation on the final value if a closure has been set. // The closure is also allowed to return another value. foreach ($this->finalValidationClosures as $closure) { try { $value = $closure($value); } catch (Exception $e) { if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) { continue; } throw $e; } catch (\Exception $e) { throw new InvalidConfigurationException(\sprintf('Invalid configuration for path "%s": ', $this->getPath()) . $e->getMessage(), $e->getCode(), $e); } } return $value; } /** * Validates the type of a Node. * * @param mixed $value The value to validate * * @throws InvalidTypeException when the value is invalid */ protected abstract function validateType($value); /** * Normalizes the value. * * @param mixed $value The value to normalize * * @return mixed */ protected abstract function normalizeValue($value); /** * Merges two values together. * * @param mixed $leftSide * @param mixed $rightSide * * @return mixed */ protected abstract function mergeValues($leftSide, $rightSide); /** * Finalizes a value. * * @param mixed $value The value to finalize * * @return mixed */ protected abstract function finalizeValue($value); /** * Tests if placeholder values are allowed for this node. */ protected function allowPlaceholders() : bool { return \true; } /** * Tests if a placeholder is being handled currently. */ protected function isHandlingPlaceholder() : bool { return null !== $this->handlingPlaceholder; } /** * Gets allowed dynamic types for this node. */ protected function getValidPlaceholderTypes() : array { return []; } private static function resolvePlaceholderValue($value) { if (\is_string($value)) { if (isset(self::$placeholders[$value])) { return self::$placeholders[$value]; } foreach (self::$placeholderUniquePrefixes as $placeholderUniquePrefix) { if (\str_starts_with($value, $placeholderUniquePrefix)) { return []; } } } return $value; } private function doValidateType($value) : void { if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) { $e = new InvalidTypeException(\sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath())); $e->setPath($this->getPath()); throw $e; } if (null === $this->handlingPlaceholder || null === $value) { $this->validateType($value); return; } $knownTypes = \array_keys(self::$placeholders[$this->handlingPlaceholder]); $validTypes = $this->getValidPlaceholderTypes(); if ($validTypes && \array_diff($knownTypes, $validTypes)) { $e = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected %s, but got %s.', $this->getPath(), 1 === \count($validTypes) ? '"' . \reset($validTypes) . '"' : 'one of "' . \implode('", "', $validTypes) . '"', 1 === \count($knownTypes) ? '"' . \reset($knownTypes) . '"' : 'one of "' . \implode('", "', $knownTypes) . '"')); if ($hint = $this->getInfo()) { $e->addHint($hint); } $e->setPath($this->getPath()); throw $e; } $this->validateType($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; /** * This node represents a numeric value in the config tree. * * @author David Jeanmonod */ class NumericNode extends ScalarNode { protected $min; protected $max; /** * @param int|float|null $min * @param int|float|null $max */ public function __construct(?string $name, ?NodeInterface $parent = null, $min = null, $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) { parent::__construct($name, $parent, $pathSeparator); $this->min = $min; $this->max = $max; } /** * {@inheritdoc} */ protected function finalizeValue($value) { $value = parent::finalizeValue($value); $errorMsg = null; if (isset($this->min) && $value < $this->min) { $errorMsg = \sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min); } if (isset($this->max) && $value > $this->max) { $errorMsg = \sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max); } if (isset($errorMsg)) { $ex = new InvalidConfigurationException($errorMsg); $ex->setPath($this->getPath()); throw $ex; } return $value; } /** * {@inheritdoc} */ protected function isValueEmpty($value) { // a numeric value cannot be empty return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; /** * This node represents an integer value in the config tree. * * @author Jeanmonod David */ class IntegerNode extends NumericNode { /** * {@inheritdoc} */ protected function validateType($value) { if (!\is_int($value)) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), \get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } $ex->setPath($this->getPath()); throw $ex; } } /** * {@inheritdoc} */ protected function getValidPlaceholderTypes() : array { return ['int']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; /** * This interface must be implemented by nodes which can be used as prototypes. * * @author Johannes M. Schmitt */ interface PrototypeNodeInterface extends NodeInterface { /** * Sets the name of the node. */ public function setName(string $name); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\DuplicateKeyException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\Exception; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\UnsetKeyException; /** * Represents a prototyped Array node in the config tree. * * @author Johannes M. Schmitt */ class PrototypedArrayNode extends ArrayNode { protected $prototype; protected $keyAttribute; protected $removeKeyAttribute = \false; protected $minNumberOfElements = 0; protected $defaultValue = []; protected $defaultChildren; /** * @var NodeInterface[] An array of the prototypes of the simplified value children */ private $valuePrototypes = []; /** * Sets the minimum number of elements that a prototype based node must * contain. By default this is zero, meaning no elements. */ public function setMinNumberOfElements(int $number) { $this->minNumberOfElements = $number; } /** * Sets the attribute which value is to be used as key. * * This is useful when you have an indexed array that should be an * associative array. You can select an item from within the array * to be the key of the particular item. For example, if "id" is the * "key", then: * * [ * ['id' => 'my_name', 'foo' => 'bar'], * ]; * * becomes * * [ * 'my_name' => ['foo' => 'bar'], * ]; * * If you'd like "'id' => 'my_name'" to still be present in the resulting * array, then you can set the second argument of this method to false. * * @param string $attribute The name of the attribute which value is to be used as a key * @param bool $remove Whether or not to remove the key */ public function setKeyAttribute(string $attribute, bool $remove = \true) { $this->keyAttribute = $attribute; $this->removeKeyAttribute = $remove; } /** * Retrieves the name of the attribute which value should be used as key. * * @return string|null */ public function getKeyAttribute() { return $this->keyAttribute; } /** * Sets the default value of this node. */ public function setDefaultValue(array $value) { $this->defaultValue = $value; } /** * {@inheritdoc} */ public function hasDefaultValue() { return \true; } /** * Adds default children when none are set. * * @param int|string|array|null $children The number of children|The child name|The children names to be added */ public function setAddChildrenIfNoneSet($children = ['defaults']) { if (null === $children) { $this->defaultChildren = ['defaults']; } else { $this->defaultChildren = \is_int($children) && $children > 0 ? \range(1, $children) : (array) $children; } } /** * {@inheritdoc} * * The default value could be either explicited or derived from the prototype * default value. */ public function getDefaultValue() { if (null !== $this->defaultChildren) { $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : []; $defaults = []; foreach (\array_values($this->defaultChildren) as $i => $name) { $defaults[null === $this->keyAttribute ? $i : $name] = $default; } return $defaults; } return $this->defaultValue; } /** * Sets the node prototype. */ public function setPrototype(PrototypeNodeInterface $node) { $this->prototype = $node; } /** * Retrieves the prototype. * * @return PrototypeNodeInterface */ public function getPrototype() { return $this->prototype; } /** * Disable adding concrete children for prototyped nodes. * * @throws Exception */ public function addChild(NodeInterface $node) { throw new Exception('A prototyped array node cannot have concrete children.'); } /** * {@inheritdoc} */ protected function finalizeValue($value) { if (\false === $value) { throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), \json_encode($value))); } foreach ($value as $k => $v) { $prototype = $this->getPrototypeForChild($k); try { $value[$k] = $prototype->finalize($v); } catch (UnsetKeyException $e) { unset($value[$k]); } } if (\count($value) < $this->minNumberOfElements) { $ex = new InvalidConfigurationException(\sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements)); $ex->setPath($this->getPath()); throw $ex; } return $value; } /** * {@inheritdoc} * * @throws DuplicateKeyException */ protected function normalizeValue($value) { if (\false === $value) { return $value; } $value = $this->remapXml($value); $isList = \array_is_list($value); $normalized = []; foreach ($value as $k => $v) { if (null !== $this->keyAttribute && \is_array($v)) { if (!isset($v[$this->keyAttribute]) && \is_int($k) && $isList) { $ex = new InvalidConfigurationException(\sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath())); $ex->setPath($this->getPath()); throw $ex; } elseif (isset($v[$this->keyAttribute])) { $k = $v[$this->keyAttribute]; if (\is_float($k)) { $k = \var_export($k, \true); } // remove the key attribute when required if ($this->removeKeyAttribute) { unset($v[$this->keyAttribute]); } // if only "value" is left if (\array_keys($v) === ['value']) { $v = $v['value']; if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && \array_key_exists('value', $children)) { $valuePrototype = \current($this->valuePrototypes) ?: clone $children['value']; $valuePrototype->parent = $this; $originalClosures = $this->prototype->normalizationClosures; if (\is_array($originalClosures)) { $valuePrototypeClosures = $valuePrototype->normalizationClosures; $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? \array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; } $this->valuePrototypes[$k] = $valuePrototype; } } } if (\array_key_exists($k, $normalized)) { $ex = new DuplicateKeyException(\sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath())); $ex->setPath($this->getPath()); throw $ex; } } $prototype = $this->getPrototypeForChild($k); if (null !== $this->keyAttribute || !$isList) { $normalized[$k] = $prototype->normalize($v); } else { $normalized[] = $prototype->normalize($v); } } return $normalized; } /** * {@inheritdoc} */ protected function mergeValues($leftSide, $rightSide) { if (\false === $rightSide) { // if this is still false after the last config has been merged the // finalization pass will take care of removing this key entirely return \false; } if (\false === $leftSide || !$this->performDeepMerging) { return $rightSide; } $isList = \array_is_list($rightSide); foreach ($rightSide as $k => $v) { // prototype, and key is irrelevant there are no named keys, append the element if (null === $this->keyAttribute && $isList) { $leftSide[] = $v; continue; } // no conflict if (!\array_key_exists($k, $leftSide)) { if (!$this->allowNewKeys) { $ex = new InvalidConfigurationException(\sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath())); $ex->setPath($this->getPath()); throw $ex; } $leftSide[$k] = $v; continue; } $prototype = $this->getPrototypeForChild($k); $leftSide[$k] = $prototype->merge($leftSide[$k], $v); } return $leftSide; } /** * Returns a prototype for the child node that is associated to $key in the value array. * For general child nodes, this will be $this->prototype. * But if $this->removeKeyAttribute is true and there are only two keys in the child node: * one is same as this->keyAttribute and the other is 'value', then the prototype will be different. * * For example, assume $this->keyAttribute is 'name' and the value array is as follows: * * [ * [ * 'name' => 'name001', * 'value' => 'value001' * ] * ] * * Now, the key is 0 and the child node is: * * [ * 'name' => 'name001', * 'value' => 'value001' * ] * * When normalizing the value array, the 'name' element will removed from the child node * and its value becomes the new key of the child node: * * [ * 'name001' => ['value' => 'value001'] * ] * * Now only 'value' element is left in the child node which can be further simplified into a string: * * ['name001' => 'value001'] * * Now, the key becomes 'name001' and the child node becomes 'value001' and * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance. * * @return mixed */ private function getPrototypeForChild(string $key) { $prototype = $this->valuePrototypes[$key] ?? $this->prototype; $prototype->setName($key); return $prototype; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; /** * This class is the entry point for config normalization/merging/finalization. * * @author Johannes M. Schmitt * * @final */ class Processor { /** * Processes an array of configurations. * * @param array $configs An array of configuration items to process */ public function process(NodeInterface $configTree, array $configs) : array { $currentConfig = []; foreach ($configs as $config) { $config = $configTree->normalize($config); $currentConfig = $configTree->merge($currentConfig, $config); } return $configTree->finalize($currentConfig); } /** * Processes an array of configurations. * * @param array $configs An array of configuration items to process */ public function processConfiguration(ConfigurationInterface $configuration, array $configs) : array { return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs); } /** * Normalizes a configuration entry. * * This method returns a normalize configuration array for a given key * to remove the differences due to the original format (YAML and XML mainly). * * Here is an example. * * The configuration in XML: * * twig.extension.foo * twig.extension.bar * * And the same configuration in YAML: * * extensions: ['twig.extension.foo', 'twig.extension.bar'] * * @param array $config A config array * @param string $key The key to normalize * @param string|null $plural The plural form of the key if it is irregular */ public static function normalizeConfig(array $config, string $key, ?string $plural = null) : array { if (null === $plural) { $plural = $key . 's'; } if (isset($config[$plural])) { return $config[$plural]; } if (isset($config[$key])) { if (\is_string($config[$key]) || !\is_int(\key($config[$key]))) { // only one return [$config[$key]]; } return $config[$key]; } return []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; /** * Common Interface among all nodes. * * In most cases, it is better to inherit from BaseNode instead of implementing * this interface yourself. * * @author Johannes M. Schmitt */ interface NodeInterface { /** * Returns the name of the node. * * @return string */ public function getName(); /** * Returns the path of the node. * * @return string */ public function getPath(); /** * Returns true when the node is required. * * @return bool */ public function isRequired(); /** * Returns true when the node has a default value. * * @return bool */ public function hasDefaultValue(); /** * Returns the default value of the node. * * @return mixed * * @throws \RuntimeException if the node has no default value */ public function getDefaultValue(); /** * Normalizes a value. * * @param mixed $value The value to normalize * * @return mixed * * @throws InvalidTypeException if the value type is invalid */ public function normalize($value); /** * Merges two values together. * * @param mixed $leftSide * @param mixed $rightSide * * @return mixed * * @throws ForbiddenOverwriteException if the configuration path cannot be overwritten * @throws InvalidTypeException if the value type is invalid */ public function merge($leftSide, $rightSide); /** * Finalizes a value. * * @param mixed $value The value to finalize * * @return mixed * * @throws InvalidTypeException if the value type is invalid * @throws InvalidConfigurationException if the value is invalid configuration */ public function finalize($value); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; /** * Node which only allows a finite set of values. * * @author Johannes M. Schmitt */ class EnumNode extends ScalarNode { private $values; public function __construct(?string $name, ?NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) { $values = \array_unique($values); if (empty($values)) { throw new \InvalidArgumentException('$values must contain at least one element.'); } parent::__construct($name, $parent, $pathSeparator); $this->values = $values; } public function getValues() { return $this->values; } /** * {@inheritdoc} */ protected function finalizeValue($value) { $value = parent::finalizeValue($value); if (!\in_array($value, $this->values, \true)) { $ex = new InvalidConfigurationException(\sprintf('The value %s is not allowed for path "%s". Permissible values: %s', \json_encode($value), $this->getPath(), \implode(', ', \array_map('json_encode', $this->values)))); $ex->setPath($this->getPath()); throw $ex; } return $value; } /** * {@inheritdoc} */ protected function allowPlaceholders() : bool { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; /** * This node represents a Boolean value in the config tree. * * @author Johannes M. Schmitt */ class BooleanNode extends ScalarNode { /** * {@inheritdoc} */ protected function validateType($value) { if (!\is_bool($value)) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), \get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } $ex->setPath($this->getPath()); throw $ex; } } /** * {@inheritdoc} */ protected function isValueEmpty($value) { // a boolean value cannot be empty return \false; } /** * {@inheritdoc} */ protected function getValidPlaceholderTypes() : array { return ['bool']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; use _ContaoManager\Symfony\Component\Config\Definition\Exception\UnsetKeyException; /** * Represents an Array node in the config tree. * * @author Johannes M. Schmitt */ class ArrayNode extends BaseNode implements PrototypeNodeInterface { protected $xmlRemappings = []; protected $children = []; protected $allowFalse = \false; protected $allowNewKeys = \true; protected $addIfNotSet = \false; protected $performDeepMerging = \true; protected $ignoreExtraKeys = \false; protected $removeExtraKeys = \true; protected $normalizeKeys = \true; public function setNormalizeKeys(bool $normalizeKeys) { $this->normalizeKeys = $normalizeKeys; } /** * {@inheritdoc} * * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML. * After running this method, all keys are normalized to foo_bar. * * If you have a mixed key like foo-bar_moo, it will not be altered. * The key will also not be altered if the target key already exists. */ protected function preNormalize($value) { if (!$this->normalizeKeys || !\is_array($value)) { return $value; } $normalized = []; foreach ($value as $k => $v) { if (\str_contains($k, '-') && !\str_contains($k, '_') && !\array_key_exists($normalizedKey = \str_replace('-', '_', $k), $value)) { $normalized[$normalizedKey] = $v; } else { $normalized[$k] = $v; } } return $normalized; } /** * Retrieves the children of this node. * * @return array */ public function getChildren() { return $this->children; } /** * Sets the xml remappings that should be performed. * * @param array $remappings An array of the form [[string, string]] */ public function setXmlRemappings(array $remappings) { $this->xmlRemappings = $remappings; } /** * Gets the xml remappings that should be performed. * * @return array an array of the form [[string, string]] */ public function getXmlRemappings() { return $this->xmlRemappings; } /** * Sets whether to add default values for this array if it has not been * defined in any of the configuration files. */ public function setAddIfNotSet(bool $boolean) { $this->addIfNotSet = $boolean; } /** * Sets whether false is allowed as value indicating that the array should be unset. */ public function setAllowFalse(bool $allow) { $this->allowFalse = $allow; } /** * Sets whether new keys can be defined in subsequent configurations. */ public function setAllowNewKeys(bool $allow) { $this->allowNewKeys = $allow; } /** * Sets if deep merging should occur. */ public function setPerformDeepMerging(bool $boolean) { $this->performDeepMerging = $boolean; } /** * Whether extra keys should just be ignored without an exception. * * @param bool $boolean To allow extra keys * @param bool $remove To remove extra keys */ public function setIgnoreExtraKeys(bool $boolean, bool $remove = \true) { $this->ignoreExtraKeys = $boolean; $this->removeExtraKeys = $this->ignoreExtraKeys && $remove; } /** * Returns true when extra keys should be ignored without an exception. */ public function shouldIgnoreExtraKeys() : bool { return $this->ignoreExtraKeys; } /** * {@inheritdoc} */ public function setName(string $name) { $this->name = $name; } /** * {@inheritdoc} */ public function hasDefaultValue() { return $this->addIfNotSet; } /** * {@inheritdoc} */ public function getDefaultValue() { if (!$this->hasDefaultValue()) { throw new \RuntimeException(\sprintf('The node at path "%s" has no default value.', $this->getPath())); } $defaults = []; foreach ($this->children as $name => $child) { if ($child->hasDefaultValue()) { $defaults[$name] = $child->getDefaultValue(); } } return $defaults; } /** * Adds a child node. * * @throws \InvalidArgumentException when the child node has no name * @throws \InvalidArgumentException when the child node's name is not unique */ public function addChild(NodeInterface $node) { $name = $node->getName(); if ('' === $name) { throw new \InvalidArgumentException('Child nodes must be named.'); } if (isset($this->children[$name])) { throw new \InvalidArgumentException(\sprintf('A child node named "%s" already exists.', $name)); } $this->children[$name] = $node; } /** * {@inheritdoc} * * @throws UnsetKeyException * @throws InvalidConfigurationException if the node doesn't have enough children */ protected function finalizeValue($value) { if (\false === $value) { throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), \json_encode($value))); } foreach ($this->children as $name => $child) { if (!\array_key_exists($name, $value)) { if ($child->isRequired()) { $message = \sprintf('The child config "%s" under "%s" must be configured', $name, $this->getPath()); if ($child->getInfo()) { $message .= \sprintf(': %s', $child->getInfo()); } else { $message .= '.'; } $ex = new InvalidConfigurationException($message); $ex->setPath($this->getPath()); throw $ex; } if ($child->hasDefaultValue()) { $value[$name] = $child->getDefaultValue(); } continue; } if ($child->isDeprecated()) { $deprecation = $child->getDeprecation($name, $this->getPath()); \trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } try { $value[$name] = $child->finalize($value[$name]); } catch (UnsetKeyException $e) { unset($value[$name]); } } return $value; } /** * {@inheritdoc} */ protected function validateType($value) { if (!\is_array($value) && (!$this->allowFalse || \false !== $value)) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), \get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } $ex->setPath($this->getPath()); throw $ex; } } /** * {@inheritdoc} * * @throws InvalidConfigurationException */ protected function normalizeValue($value) { if (\false === $value) { return $value; } $value = $this->remapXml($value); $normalized = []; foreach ($value as $name => $val) { if (isset($this->children[$name])) { try { $normalized[$name] = $this->children[$name]->normalize($val); } catch (UnsetKeyException $e) { } unset($value[$name]); } elseif (!$this->removeExtraKeys) { $normalized[$name] = $val; } } // if extra fields are present, throw exception if (\count($value) && !$this->ignoreExtraKeys) { $proposals = \array_keys($this->children); \sort($proposals); $guesses = []; foreach (\array_keys($value) as $subject) { $minScore = \INF; foreach ($proposals as $proposal) { $distance = \levenshtein($subject, $proposal); if ($distance <= $minScore && $distance < 3) { $guesses[$proposal] = $distance; $minScore = $distance; } } } $msg = \sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', \implode(', ', \array_keys($value)), $this->getPath()); if (\count($guesses)) { \asort($guesses); $msg .= \sprintf('. Did you mean "%s"?', \implode('", "', \array_keys($guesses))); } else { $msg .= \sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', \implode('", "', $proposals)); } $ex = new InvalidConfigurationException($msg); $ex->setPath($this->getPath()); throw $ex; } return $normalized; } /** * Remaps multiple singular values to a single plural value. * * @return array */ protected function remapXml(array $value) { foreach ($this->xmlRemappings as [$singular, $plural]) { if (!isset($value[$singular])) { continue; } $value[$plural] = Processor::normalizeConfig($value, $singular, $plural); unset($value[$singular]); } return $value; } /** * {@inheritdoc} * * @throws InvalidConfigurationException * @throws \RuntimeException */ protected function mergeValues($leftSide, $rightSide) { if (\false === $rightSide) { // if this is still false after the last config has been merged the // finalization pass will take care of removing this key entirely return \false; } if (\false === $leftSide || !$this->performDeepMerging) { return $rightSide; } foreach ($rightSide as $k => $v) { // no conflict if (!\array_key_exists($k, $leftSide)) { if (!$this->allowNewKeys) { $ex = new InvalidConfigurationException(\sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath())); $ex->setPath($this->getPath()); throw $ex; } $leftSide[$k] = $v; continue; } if (!isset($this->children[$k])) { if (!$this->ignoreExtraKeys || $this->removeExtraKeys) { throw new \RuntimeException('merge() expects a normalized config array.'); } $leftSide[$k] = $v; continue; } $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v); } return $leftSide; } /** * {@inheritdoc} */ protected function allowPlaceholders() : bool { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidTypeException; /** * This node represents a scalar value in the config tree. * * The following values are considered scalars: * * booleans * * strings * * null * * integers * * floats * * @author Johannes M. Schmitt */ class ScalarNode extends VariableNode { /** * {@inheritdoc} */ protected function validateType($value) { if (!\is_scalar($value) && null !== $value) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), \get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } $ex->setPath($this->getPath()); throw $ex; } } /** * {@inheritdoc} */ protected function isValueEmpty($value) { // assume environment variables are never empty (which in practice is likely to be true during runtime) // not doing so breaks many configs that are valid today if ($this->isHandlingPlaceholder()) { return \false; } return null === $value || '' === $value; } /** * {@inheritdoc} */ protected function getValidPlaceholderTypes() : array { return ['bool', 'int', 'float', 'string']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Builder\TreeBuilder; /** * Configuration interface. * * @author Victor Berchet */ interface ConfigurationInterface { /** * Generates the configuration tree builder. * * @return TreeBuilder */ public function getConfigTreeBuilder(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\Exception\UnsetKeyException; /** * This class builds an if expression. * * @author Johannes M. Schmitt * @author Christophe Coevoet */ class ExprBuilder { protected $node; public $ifPart; public $thenPart; public function __construct(NodeDefinition $node) { $this->node = $node; } /** * Marks the expression as being always used. * * @return $this */ public function always(?\Closure $then = null) { $this->ifPart = function () { return \true; }; if (null !== $then) { $this->thenPart = $then; } return $this; } /** * Sets a closure to use as tests. * * The default one tests if the value is true. * * @return $this */ public function ifTrue(?\Closure $closure = null) { if (null === $closure) { $closure = function ($v) { return \true === $v; }; } $this->ifPart = $closure; return $this; } /** * Tests if the value is a string. * * @return $this */ public function ifString() { $this->ifPart = function ($v) { return \is_string($v); }; return $this; } /** * Tests if the value is null. * * @return $this */ public function ifNull() { $this->ifPart = function ($v) { return null === $v; }; return $this; } /** * Tests if the value is empty. * * @return $this */ public function ifEmpty() { $this->ifPart = function ($v) { return empty($v); }; return $this; } /** * Tests if the value is an array. * * @return $this */ public function ifArray() { $this->ifPart = function ($v) { return \is_array($v); }; return $this; } /** * Tests if the value is in an array. * * @return $this */ public function ifInArray(array $array) { $this->ifPart = function ($v) use($array) { return \in_array($v, $array, \true); }; return $this; } /** * Tests if the value is not in an array. * * @return $this */ public function ifNotInArray(array $array) { $this->ifPart = function ($v) use($array) { return !\in_array($v, $array, \true); }; return $this; } /** * Transforms variables of any type into an array. * * @return $this */ public function castToArray() { $this->ifPart = function ($v) { return !\is_array($v); }; $this->thenPart = function ($v) { return [$v]; }; return $this; } /** * Sets the closure to run if the test pass. * * @return $this */ public function then(\Closure $closure) { $this->thenPart = $closure; return $this; } /** * Sets a closure returning an empty array. * * @return $this */ public function thenEmptyArray() { $this->thenPart = function () { return []; }; return $this; } /** * Sets a closure marking the value as invalid at processing time. * * if you want to add the value of the node in your message just use a %s placeholder. * * @return $this * * @throws \InvalidArgumentException */ public function thenInvalid(string $message) { $this->thenPart = function ($v) use($message) { throw new \InvalidArgumentException(\sprintf($message, \json_encode($v))); }; return $this; } /** * Sets a closure unsetting this key of the array at processing time. * * @return $this * * @throws UnsetKeyException */ public function thenUnset() { $this->thenPart = function () { throw new UnsetKeyException('Unsetting key.'); }; return $this; } /** * Returns the related node. * * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition * * @throws \RuntimeException */ public function end() { if (null === $this->ifPart) { throw new \RuntimeException('You must specify an if part.'); } if (null === $this->thenPart) { throw new \RuntimeException('You must specify a then part.'); } return $this->node; } /** * Builds the expressions. * * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build * * @return array */ public static function buildExpressions(array $expressions) { foreach ($expressions as $k => $expr) { if ($expr instanceof self) { $if = $expr->ifPart; $then = $expr->thenPart; $expressions[$k] = function ($v) use($if, $then) { return $if($v) ? $then($v) : $v; }; } } return $expressions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\EnumNode; /** * Enum Node Definition. * * @author Johannes M. Schmitt */ class EnumNodeDefinition extends ScalarNodeDefinition { private $values; /** * @return $this */ public function values(array $values) { $values = \array_unique($values); if (empty($values)) { throw new \InvalidArgumentException('->values() must be called with at least one value.'); } $this->values = $values; return $this; } /** * Instantiate a Node. * * @return EnumNode * * @throws \RuntimeException */ protected function instantiateNode() { if (null === $this->values) { throw new \RuntimeException('You must call ->values() on enum nodes.'); } return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * This class builds merge conditions. * * @author Johannes M. Schmitt */ class MergeBuilder { protected $node; public $allowFalse = \false; public $allowOverwrite = \true; public function __construct(NodeDefinition $node) { $this->node = $node; } /** * Sets whether the node can be unset. * * @return $this */ public function allowUnset(bool $allow = \true) { $this->allowFalse = $allow; return $this; } /** * Sets whether the node can be overwritten. * * @return $this */ public function denyOverwrite(bool $deny = \true) { $this->allowOverwrite = !$deny; return $this; } /** * Returns the related node. * * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition */ public function end() { return $this->node; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; /** * Abstract class that contains common code of integer and float node definitions. * * @author David Jeanmonod */ abstract class NumericNodeDefinition extends ScalarNodeDefinition { protected $min; protected $max; /** * Ensures that the value is smaller than the given reference. * * @param int|float $max * * @return $this * * @throws \InvalidArgumentException when the constraint is inconsistent */ public function max($max) { if (isset($this->min) && $this->min > $max) { throw new \InvalidArgumentException(\sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min)); } $this->max = $max; return $this; } /** * Ensures that the value is bigger than the given reference. * * @param int|float $min * * @return $this * * @throws \InvalidArgumentException when the constraint is inconsistent */ public function min($min) { if (isset($this->max) && $this->max < $min) { throw new \InvalidArgumentException(\sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max)); } $this->min = $min; return $this; } /** * {@inheritdoc} * * @throws InvalidDefinitionException */ public function cannotBeEmpty() { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * This class provides a fluent interface for building a node. * * @author Johannes M. Schmitt */ class NodeBuilder implements NodeParentInterface { protected $parent; protected $nodeMapping; public function __construct() { $this->nodeMapping = ['variable' => VariableNodeDefinition::class, 'scalar' => ScalarNodeDefinition::class, 'boolean' => BooleanNodeDefinition::class, 'integer' => IntegerNodeDefinition::class, 'float' => FloatNodeDefinition::class, 'array' => ArrayNodeDefinition::class, 'enum' => EnumNodeDefinition::class]; } /** * Set the parent node. * * @return $this */ public function setParent(?ParentNodeDefinitionInterface $parent = null) { $this->parent = $parent; return $this; } /** * Creates a child array node. * * @return ArrayNodeDefinition */ public function arrayNode(string $name) { return $this->node($name, 'array'); } /** * Creates a child scalar node. * * @return ScalarNodeDefinition */ public function scalarNode(string $name) { return $this->node($name, 'scalar'); } /** * Creates a child Boolean node. * * @return BooleanNodeDefinition */ public function booleanNode(string $name) { return $this->node($name, 'boolean'); } /** * Creates a child integer node. * * @return IntegerNodeDefinition */ public function integerNode(string $name) { return $this->node($name, 'integer'); } /** * Creates a child float node. * * @return FloatNodeDefinition */ public function floatNode(string $name) { return $this->node($name, 'float'); } /** * Creates a child EnumNode. * * @return EnumNodeDefinition */ public function enumNode(string $name) { return $this->node($name, 'enum'); } /** * Creates a child variable node. * * @return VariableNodeDefinition */ public function variableNode(string $name) { return $this->node($name, 'variable'); } /** * Returns the parent node. * * @return NodeDefinition&ParentNodeDefinitionInterface */ public function end() { return $this->parent; } /** * Creates a child node. * * @return NodeDefinition * * @throws \RuntimeException When the node type is not registered * @throws \RuntimeException When the node class is not found */ public function node(?string $name, string $type) { $class = $this->getNodeClass($type); $node = new $class($name); $this->append($node); return $node; } /** * Appends a node definition. * * Usage: * * $node = new ArrayNodeDefinition('name') * ->children() * ->scalarNode('foo')->end() * ->scalarNode('baz')->end() * ->append($this->getBarNodeDefinition()) * ->end() * ; * * @return $this */ public function append(NodeDefinition $node) { if ($node instanceof BuilderAwareInterface) { $builder = clone $this; $builder->setParent(null); $node->setBuilder($builder); } if (null !== $this->parent) { $this->parent->append($node); // Make this builder the node parent to allow for a fluid interface $node->setParent($this); } return $this; } /** * Adds or overrides a node Type. * * @param string $type The name of the type * @param string $class The fully qualified name the node definition class * * @return $this */ public function setNodeClass(string $type, string $class) { $this->nodeMapping[\strtolower($type)] = $class; return $this; } /** * Returns the class name of the node definition. * * @return string * * @throws \RuntimeException When the node type is not registered * @throws \RuntimeException When the node class is not found */ protected function getNodeClass(string $type) { $type = \strtolower($type); if (!isset($this->nodeMapping[$type])) { throw new \RuntimeException(\sprintf('The node type "%s" is not registered.', $type)); } $class = $this->nodeMapping[$type]; if (!\class_exists($class)) { throw new \RuntimeException(\sprintf('The node class "%s" does not exist.', $class)); } return $class; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\VariableNode; /** * This class provides a fluent interface for defining a node. * * @author Johannes M. Schmitt */ class VariableNodeDefinition extends NodeDefinition { /** * Instantiate a Node. * * @return VariableNode */ protected function instantiateNode() { return new VariableNode($this->name, $this->parent, $this->pathSeparator); } /** * {@inheritdoc} */ protected function createNode() { $node = $this->instantiateNode(); if (null !== $this->normalization) { $node->setNormalizationClosures($this->normalization->before); } if (null !== $this->merge) { $node->setAllowOverwrite($this->merge->allowOverwrite); } if (\true === $this->default) { $node->setDefaultValue($this->defaultValue); } $node->setAllowEmptyValue($this->allowEmptyValue); $node->addEquivalentValue(null, $this->nullEquivalent); $node->addEquivalentValue(\true, $this->trueEquivalent); $node->addEquivalentValue(\false, $this->falseEquivalent); $node->setRequired($this->required); if ($this->deprecation) { $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']); } if (null !== $this->validation) { $node->setFinalValidationClosures($this->validation->rules); } return $node; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\ArrayNode; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; use _ContaoManager\Symfony\Component\Config\Definition\PrototypedArrayNode; /** * This class provides a fluent interface for defining an array node. * * @author Johannes M. Schmitt */ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface { protected $performDeepMerging = \true; protected $ignoreExtraKeys = \false; protected $removeExtraKeys = \true; protected $children = []; protected $prototype; protected $atLeastOne = \false; protected $allowNewKeys = \true; protected $key; protected $removeKeyItem; protected $addDefaults = \false; protected $addDefaultChildren = \false; protected $nodeBuilder; protected $normalizeKeys = \true; /** * {@inheritdoc} */ public function __construct(?string $name, ?NodeParentInterface $parent = null) { parent::__construct($name, $parent); $this->nullEquivalent = []; $this->trueEquivalent = []; } /** * {@inheritdoc} */ public function setBuilder(NodeBuilder $builder) { $this->nodeBuilder = $builder; } /** * {@inheritdoc} */ public function children() { return $this->getNodeBuilder(); } /** * Sets a prototype for child nodes. * * @return NodeDefinition */ public function prototype(string $type) { return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this); } /** * @return VariableNodeDefinition */ public function variablePrototype() { return $this->prototype('variable'); } /** * @return ScalarNodeDefinition */ public function scalarPrototype() { return $this->prototype('scalar'); } /** * @return BooleanNodeDefinition */ public function booleanPrototype() { return $this->prototype('boolean'); } /** * @return IntegerNodeDefinition */ public function integerPrototype() { return $this->prototype('integer'); } /** * @return FloatNodeDefinition */ public function floatPrototype() { return $this->prototype('float'); } /** * @return ArrayNodeDefinition */ public function arrayPrototype() { return $this->prototype('array'); } /** * @return EnumNodeDefinition */ public function enumPrototype() { return $this->prototype('enum'); } /** * Adds the default value if the node is not set in the configuration. * * This method is applicable to concrete nodes only (not to prototype nodes). * If this function has been called and the node is not set during the finalization * phase, it's default value will be derived from its children default values. * * @return $this */ public function addDefaultsIfNotSet() { $this->addDefaults = \true; return $this; } /** * Adds children with a default value when none are defined. * * This method is applicable to prototype nodes only. * * @param int|string|array|null $children The number of children|The child name|The children names to be added * * @return $this */ public function addDefaultChildrenIfNoneSet($children = null) { $this->addDefaultChildren = $children; return $this; } /** * Requires the node to have at least one element. * * This method is applicable to prototype nodes only. * * @return $this */ public function requiresAtLeastOneElement() { $this->atLeastOne = \true; return $this; } /** * Disallows adding news keys in a subsequent configuration. * * If used all keys have to be defined in the same configuration file. * * @return $this */ public function disallowNewKeysInSubsequentConfigs() { $this->allowNewKeys = \false; return $this; } /** * Sets a normalization rule for XML configurations. * * @param string $singular The key to remap * @param string|null $plural The plural of the key for irregular plurals * * @return $this */ public function fixXmlConfig(string $singular, ?string $plural = null) { $this->normalization()->remap($singular, $plural); return $this; } /** * Sets the attribute which value is to be used as key. * * This is useful when you have an indexed array that should be an * associative array. You can select an item from within the array * to be the key of the particular item. For example, if "id" is the * "key", then: * * [ * ['id' => 'my_name', 'foo' => 'bar'], * ]; * * becomes * * [ * 'my_name' => ['foo' => 'bar'], * ]; * * If you'd like "'id' => 'my_name'" to still be present in the resulting * array, then you can set the second argument of this method to false. * * This method is applicable to prototype nodes only. * * @param string $name The name of the key * @param bool $removeKeyItem Whether or not the key item should be removed * * @return $this */ public function useAttributeAsKey(string $name, bool $removeKeyItem = \true) { $this->key = $name; $this->removeKeyItem = $removeKeyItem; return $this; } /** * Sets whether the node can be unset. * * @return $this */ public function canBeUnset(bool $allow = \true) { $this->merge()->allowUnset($allow); return $this; } /** * Adds an "enabled" boolean to enable the current section. * * By default, the section is disabled. If any configuration is specified then * the node will be automatically enabled: * * enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden * enableableArrayNode: ~ # The config is enabled & use the default values * enableableArrayNode: true # The config is enabled & use the default values * enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden * enableableArrayNode: {enabled: false, ...} # The config is disabled * enableableArrayNode: false # The config is disabled * * @return $this */ public function canBeEnabled() { $this->addDefaultsIfNotSet()->treatFalseLike(['enabled' => \false])->treatTrueLike(['enabled' => \true])->treatNullLike(['enabled' => \true])->beforeNormalization()->ifArray()->then(function (array $v) { $v['enabled'] = $v['enabled'] ?? \true; return $v; })->end()->children()->booleanNode('enabled')->defaultFalse(); return $this; } /** * Adds an "enabled" boolean to enable the current section. * * By default, the section is enabled. * * @return $this */ public function canBeDisabled() { $this->addDefaultsIfNotSet()->treatFalseLike(['enabled' => \false])->treatTrueLike(['enabled' => \true])->treatNullLike(['enabled' => \true])->children()->booleanNode('enabled')->defaultTrue(); return $this; } /** * Disables the deep merging of the node. * * @return $this */ public function performNoDeepMerging() { $this->performDeepMerging = \false; return $this; } /** * Allows extra config keys to be specified under an array without * throwing an exception. * * Those config values are ignored and removed from the resulting * array. This should be used only in special cases where you want * to send an entire configuration array through a special tree that * processes only part of the array. * * @param bool $remove Whether to remove the extra keys * * @return $this */ public function ignoreExtraKeys(bool $remove = \true) { $this->ignoreExtraKeys = \true; $this->removeExtraKeys = $remove; return $this; } /** * Sets whether to enable key normalization. * * @return $this */ public function normalizeKeys(bool $bool) { $this->normalizeKeys = $bool; return $this; } /** * {@inheritdoc} */ public function append(NodeDefinition $node) { $this->children[$node->name] = $node->setParent($this); return $this; } /** * Returns a node builder to be used to add children and prototype. * * @return NodeBuilder */ protected function getNodeBuilder() { if (null === $this->nodeBuilder) { $this->nodeBuilder = new NodeBuilder(); } return $this->nodeBuilder->setParent($this); } /** * {@inheritdoc} */ protected function createNode() { if (null === $this->prototype) { $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator); $this->validateConcreteNode($node); $node->setAddIfNotSet($this->addDefaults); foreach ($this->children as $child) { $child->parent = $node; $node->addChild($child->getNode()); } } else { $node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator); $this->validatePrototypeNode($node); if (null !== $this->key) { $node->setKeyAttribute($this->key, $this->removeKeyItem); } if (\true === $this->atLeastOne || \false === $this->allowEmptyValue) { $node->setMinNumberOfElements(1); } if ($this->default) { if (!\is_array($this->defaultValue)) { throw new \InvalidArgumentException(\sprintf('%s: the default value of an array node has to be an array.', $node->getPath())); } $node->setDefaultValue($this->defaultValue); } if (\false !== $this->addDefaultChildren) { $node->setAddChildrenIfNoneSet($this->addDefaultChildren); if ($this->prototype instanceof static && null === $this->prototype->prototype) { $this->prototype->addDefaultsIfNotSet(); } } $this->prototype->parent = $node; $node->setPrototype($this->prototype->getNode()); } $node->setAllowNewKeys($this->allowNewKeys); $node->addEquivalentValue(null, $this->nullEquivalent); $node->addEquivalentValue(\true, $this->trueEquivalent); $node->addEquivalentValue(\false, $this->falseEquivalent); $node->setPerformDeepMerging($this->performDeepMerging); $node->setRequired($this->required); $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys); $node->setNormalizeKeys($this->normalizeKeys); if ($this->deprecation) { $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']); } if (null !== $this->normalization) { $node->setNormalizationClosures($this->normalization->before); $node->setXmlRemappings($this->normalization->remappings); } if (null !== $this->merge) { $node->setAllowOverwrite($this->merge->allowOverwrite); $node->setAllowFalse($this->merge->allowFalse); } if (null !== $this->validation) { $node->setFinalValidationClosures($this->validation->rules); } return $node; } /** * Validate the configuration of a concrete node. * * @throws InvalidDefinitionException */ protected function validateConcreteNode(ArrayNode $node) { $path = $node->getPath(); if (null !== $this->key) { throw new InvalidDefinitionException(\sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path)); } if (\false === $this->allowEmptyValue) { throw new InvalidDefinitionException(\sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path)); } if (\true === $this->atLeastOne) { throw new InvalidDefinitionException(\sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path)); } if ($this->default) { throw new InvalidDefinitionException(\sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path)); } if (\false !== $this->addDefaultChildren) { throw new InvalidDefinitionException(\sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path)); } } /** * Validate the configuration of a prototype node. * * @throws InvalidDefinitionException */ protected function validatePrototypeNode(PrototypedArrayNode $node) { $path = $node->getPath(); if ($this->addDefaults) { throw new InvalidDefinitionException(\sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path)); } if (\false !== $this->addDefaultChildren) { if ($this->default) { throw new InvalidDefinitionException(\sprintf('A default value and default children might not be used together at path "%s".', $path)); } if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) { throw new InvalidDefinitionException(\sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path)); } if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) { throw new InvalidDefinitionException(\sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path)); } } } /** * @return NodeDefinition[] */ public function getChildNodeDefinitions() { return $this->children; } /** * Finds a node defined by the given $nodePath. * * @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings" */ public function find(string $nodePath) : NodeDefinition { $firstPathSegment = \false === ($pathSeparatorPos = \strpos($nodePath, $this->pathSeparator)) ? $nodePath : \substr($nodePath, 0, $pathSeparatorPos); if (null === ($node = $this->children[$firstPathSegment] ?? null)) { throw new \RuntimeException(\sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name)); } if (\false === $pathSeparatorPos) { return $node; } return $node->find(\substr($nodePath, $pathSeparatorPos + \strlen($this->pathSeparator))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * An interface that can be implemented by nodes which build other nodes. * * @author Roland Franssen */ interface BuilderAwareInterface { /** * Sets a custom children builder. */ public function setBuilder(NodeBuilder $builder); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * This class builds validation conditions. * * @author Christophe Coevoet */ class ValidationBuilder { protected $node; public $rules = []; public function __construct(NodeDefinition $node) { $this->node = $node; } /** * Registers a closure to run as normalization or an expression builder to build it if null is provided. * * @return ExprBuilder|$this */ public function rule(?\Closure $closure = null) { if (null !== $closure) { $this->rules[] = $closure; return $this; } return $this->rules[] = new ExprBuilder($this->node); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\NodeInterface; /** * This is the entry class for building a config tree. * * @author Johannes M. Schmitt */ class TreeBuilder implements NodeParentInterface { protected $tree; protected $root; public function __construct(string $name, string $type = 'array', ?NodeBuilder $builder = null) { $builder = $builder ?? new NodeBuilder(); $this->root = $builder->node($name, $type)->setParent($this); } /** * @return NodeDefinition|ArrayNodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array') */ public function getRootNode() : NodeDefinition { return $this->root; } /** * Builds the tree. * * @return NodeInterface * * @throws \RuntimeException */ public function buildTree() { if (null !== $this->tree) { return $this->tree; } return $this->tree = $this->root->getNode(\true); } public function setPathSeparator(string $separator) { // unset last built as changing path separator changes all nodes $this->tree = null; $this->root->setPathSeparator($separator); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\IntegerNode; /** * This class provides a fluent interface for defining an integer node. * * @author Jeanmonod David */ class IntegerNodeDefinition extends NumericNodeDefinition { /** * Instantiates a Node. * * @return IntegerNode */ protected function instantiateNode() { return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * An interface that must be implemented by all node parents. * * @author Victor Berchet */ interface NodeParentInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * This class builds normalization conditions. * * @author Johannes M. Schmitt */ class NormalizationBuilder { protected $node; public $before = []; public $remappings = []; public function __construct(NodeDefinition $node) { $this->node = $node; } /** * Registers a key to remap to its plural form. * * @param string $key The key to remap * @param string|null $plural The plural of the key in case of irregular plural * * @return $this */ public function remap(string $key, ?string $plural = null) { $this->remappings[] = [$key, null === $plural ? $key . 's' : $plural]; return $this; } /** * Registers a closure to run before the normalization or an expression builder to build it if null is provided. * * @return ExprBuilder|$this */ public function before(?\Closure $closure = null) { if (null !== $closure) { $this->before[] = $closure; return $this; } return $this->before[] = new ExprBuilder($this->node); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\BooleanNode; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; /** * This class provides a fluent interface for defining a node. * * @author Johannes M. Schmitt */ class BooleanNodeDefinition extends ScalarNodeDefinition { /** * {@inheritdoc} */ public function __construct(?string $name, ?NodeParentInterface $parent = null) { parent::__construct($name, $parent); $this->nullEquivalent = \true; } /** * Instantiate a Node. * * @return BooleanNode */ protected function instantiateNode() { return new BooleanNode($this->name, $this->parent, $this->pathSeparator); } /** * {@inheritdoc} * * @throws InvalidDefinitionException */ public function cannotBeEmpty() { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; /** * An interface that must be implemented by nodes which can have children. * * @author Victor Berchet */ interface ParentNodeDefinitionInterface extends BuilderAwareInterface { /** * Returns a builder to add children nodes. * * @return NodeBuilder */ public function children(); /** * Appends a node definition. * * Usage: * * $node = $parentNode * ->children() * ->scalarNode('foo')->end() * ->scalarNode('baz')->end() * ->append($this->getBarNodeDefinition()) * ->end() * ; * * @return $this */ public function append(NodeDefinition $node); /** * Gets the child node definitions. * * @return NodeDefinition[] */ public function getChildNodeDefinitions(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\ScalarNode; /** * This class provides a fluent interface for defining a node. * * @author Johannes M. Schmitt */ class ScalarNodeDefinition extends VariableNodeDefinition { /** * Instantiate a Node. * * @return ScalarNode */ protected function instantiateNode() { return new ScalarNode($this->name, $this->parent, $this->pathSeparator); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\FloatNode; /** * This class provides a fluent interface for defining a float node. * * @author Jeanmonod David */ class FloatNodeDefinition extends NumericNodeDefinition { /** * Instantiates a Node. * * @return FloatNode */ protected function instantiateNode() { return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Builder; use _ContaoManager\Symfony\Component\Config\Definition\BaseNode; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; use _ContaoManager\Symfony\Component\Config\Definition\NodeInterface; /** * This class provides a fluent interface for defining a node. * * @author Johannes M. Schmitt */ abstract class NodeDefinition implements NodeParentInterface { protected $name; protected $normalization; protected $validation; protected $defaultValue; protected $default = \false; protected $required = \false; protected $deprecation = []; protected $merge; protected $allowEmptyValue = \true; protected $nullEquivalent; protected $trueEquivalent = \true; protected $falseEquivalent = \false; protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR; protected $parent; protected $attributes = []; public function __construct(?string $name, ?NodeParentInterface $parent = null) { $this->parent = $parent; $this->name = $name; } /** * Sets the parent node. * * @return $this */ public function setParent(NodeParentInterface $parent) { $this->parent = $parent; return $this; } /** * Sets info message. * * @return $this */ public function info(string $info) { return $this->attribute('info', $info); } /** * Sets example configuration. * * @param string|array $example * * @return $this */ public function example($example) { return $this->attribute('example', $example); } /** * Sets an attribute on the node. * * @param mixed $value * * @return $this */ public function attribute(string $key, $value) { $this->attributes[$key] = $value; return $this; } /** * Returns the parent node. * * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null */ public function end() { return $this->parent; } /** * Creates the node. * * @return NodeInterface */ public function getNode(bool $forceRootNode = \false) { if ($forceRootNode) { $this->parent = null; } if (null !== $this->normalization) { $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before); } if (null !== $this->validation) { $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules); } $node = $this->createNode(); if ($node instanceof BaseNode) { $node->setAttributes($this->attributes); } return $node; } /** * Sets the default value. * * @param mixed $value The default value * * @return $this */ public function defaultValue($value) { $this->default = \true; $this->defaultValue = $value; return $this; } /** * Sets the node as required. * * @return $this */ public function isRequired() { $this->required = \true; return $this; } /** * Sets the node as deprecated. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message the deprecation message to use * * You can use %node% and %path% placeholders in your message to display, * respectively, the node name and its complete path * * @return $this */ public function setDeprecated() { $args = \func_get_args(); if (\func_num_args() < 2) { \trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); $message = $args[0] ?? 'The child node "%node%" at path "%path%" is deprecated.'; $package = $version = ''; } else { $package = (string) $args[0]; $version = (string) $args[1]; $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.'); } $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message]; return $this; } /** * Sets the equivalent value used when the node contains null. * * @param mixed $value * * @return $this */ public function treatNullLike($value) { $this->nullEquivalent = $value; return $this; } /** * Sets the equivalent value used when the node contains true. * * @param mixed $value * * @return $this */ public function treatTrueLike($value) { $this->trueEquivalent = $value; return $this; } /** * Sets the equivalent value used when the node contains false. * * @param mixed $value * * @return $this */ public function treatFalseLike($value) { $this->falseEquivalent = $value; return $this; } /** * Sets null as the default value. * * @return $this */ public function defaultNull() { return $this->defaultValue(null); } /** * Sets true as the default value. * * @return $this */ public function defaultTrue() { return $this->defaultValue(\true); } /** * Sets false as the default value. * * @return $this */ public function defaultFalse() { return $this->defaultValue(\false); } /** * Sets an expression to run before the normalization. * * @return ExprBuilder */ public function beforeNormalization() { return $this->normalization()->before(); } /** * Denies the node value being empty. * * @return $this */ public function cannotBeEmpty() { $this->allowEmptyValue = \false; return $this; } /** * Sets an expression to run for the validation. * * The expression receives the value of the node and must return it. It can * modify it. * An exception should be thrown when the node is not valid. * * @return ExprBuilder */ public function validate() { return $this->validation()->rule(); } /** * Sets whether the node can be overwritten. * * @return $this */ public function cannotBeOverwritten(bool $deny = \true) { $this->merge()->denyOverwrite($deny); return $this; } /** * Gets the builder for validation rules. * * @return ValidationBuilder */ protected function validation() { if (null === $this->validation) { $this->validation = new ValidationBuilder($this); } return $this->validation; } /** * Gets the builder for merging rules. * * @return MergeBuilder */ protected function merge() { if (null === $this->merge) { $this->merge = new MergeBuilder($this); } return $this->merge; } /** * Gets the builder for normalization rules. * * @return NormalizationBuilder */ protected function normalization() { if (null === $this->normalization) { $this->normalization = new NormalizationBuilder($this); } return $this->normalization; } /** * Instantiate and configure the node according to this definition. * * @return NodeInterface * * @throws InvalidDefinitionException When the definition is invalid */ protected abstract function createNode(); /** * Set PathSeparator to use. * * @return $this */ public function setPathSeparator(string $separator) { if ($this instanceof ParentNodeDefinitionInterface) { foreach ($this->getChildNodeDefinitions() as $child) { $child->setPathSeparator($separator); } } $this->pathSeparator = $separator; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * This exception is thrown whenever the key of an array is not unique. This can * only be the case if the configuration is coming from an XML file. * * @author Johannes M. Schmitt */ class DuplicateKeyException extends InvalidConfigurationException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * Thrown when an error is detected in a node Definition. * * @author Victor Berchet */ class InvalidDefinitionException extends Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * This exception is thrown if an invalid type is encountered. * * @author Johannes M. Schmitt */ class InvalidTypeException extends InvalidConfigurationException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * This exception is usually not encountered by the end-user, but only used * internally to signal the parent scope to unset a key. * * @author Johannes M. Schmitt */ class UnsetKeyException extends Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * A very general exception which can be thrown whenever non of the more specific * exceptions is suitable. * * @author Johannes M. Schmitt */ class InvalidConfigurationException extends Exception { private $path; private $containsHints = \false; public function setPath(string $path) { $this->path = $path; } public function getPath() { return $this->path; } /** * Adds extra information that is suffixed to the original exception message. */ public function addHint(string $hint) { if (!$this->containsHints) { $this->message .= "\nHint: " . $hint; $this->containsHints = \true; } else { $this->message .= ', ' . $hint; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * This exception is thrown when a configuration path is overwritten from a * subsequent configuration file, but the entry node specifically forbids this. * * @author Johannes M. Schmitt */ class ForbiddenOverwriteException extends InvalidConfigurationException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Exception; /** * Base exception for all configuration exceptions. * * @author Johannes M. Schmitt */ class Exception extends \RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; /** * This node represents a value of variable type in the config tree. * * This node is intended for values of arbitrary type. * Any PHP type is accepted as a value. * * @author Jeremy Mikola */ class VariableNode extends BaseNode implements PrototypeNodeInterface { protected $defaultValueSet = \false; protected $defaultValue; protected $allowEmptyValue = \true; public function setDefaultValue($value) { $this->defaultValueSet = \true; $this->defaultValue = $value; } /** * {@inheritdoc} */ public function hasDefaultValue() { return $this->defaultValueSet; } /** * {@inheritdoc} */ public function getDefaultValue() { $v = $this->defaultValue; return $v instanceof \Closure ? $v() : $v; } /** * Sets if this node is allowed to have an empty value. * * @param bool $boolean True if this entity will accept empty values */ public function setAllowEmptyValue(bool $boolean) { $this->allowEmptyValue = $boolean; } /** * {@inheritdoc} */ public function setName(string $name) { $this->name = $name; } /** * {@inheritdoc} */ protected function validateType($value) { } /** * {@inheritdoc} */ protected function finalizeValue($value) { // deny environment variables only when using custom validators // this avoids ever passing an empty value to final validation closures if (!$this->allowEmptyValue && $this->isHandlingPlaceholder() && $this->finalValidationClosures) { $e = new InvalidConfigurationException(\sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath())); if ($hint = $this->getInfo()) { $e->addHint($hint); } $e->setPath($this->getPath()); throw $e; } if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { $ex = new InvalidConfigurationException(\sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), \json_encode($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } $ex->setPath($this->getPath()); throw $ex; } return $value; } /** * {@inheritdoc} */ protected function normalizeValue($value) { return $value; } /** * {@inheritdoc} */ protected function mergeValues($leftSide, $rightSide) { return $rightSide; } /** * Evaluates if the given value is to be treated as empty. * * By default, PHP's empty() function is used to test for emptiness. This * method may be overridden by subtypes to better match their understanding * of empty data. * * @param mixed $value * * @return bool * * @see finalizeValue() */ protected function isValueEmpty($value) { return empty($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Dumper; use _ContaoManager\Symfony\Component\Config\Definition\ArrayNode; use _ContaoManager\Symfony\Component\Config\Definition\BaseNode; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\EnumNode; use _ContaoManager\Symfony\Component\Config\Definition\NodeInterface; use _ContaoManager\Symfony\Component\Config\Definition\PrototypedArrayNode; use _ContaoManager\Symfony\Component\Config\Definition\ScalarNode; use _ContaoManager\Symfony\Component\Config\Definition\VariableNode; use _ContaoManager\Symfony\Component\Yaml\Inline; /** * Dumps a Yaml reference configuration for the given configuration/node instance. * * @author Kevin Bond */ class YamlReferenceDumper { private $reference; public function dump(ConfigurationInterface $configuration) { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); } public function dumpAtPath(ConfigurationInterface $configuration, string $path) { $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree(); foreach (\explode('.', $path) as $step) { if (!$node instanceof ArrayNode) { throw new \UnexpectedValueException(\sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); } /** @var NodeInterface[] $children */ $children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren(); foreach ($children as $child) { if ($child->getName() === $step) { $node = $child; continue 2; } } throw new \UnexpectedValueException(\sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); } return $this->dumpNode($node); } public function dumpNode(NodeInterface $node) { $this->reference = ''; $this->writeNode($node); $ref = $this->reference; $this->reference = null; return $ref; } private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = \false) { $comments = []; $default = ''; $defaultArray = null; $children = null; $example = null; if ($node instanceof BaseNode) { $example = $node->getExample(); } // defaults if ($node instanceof ArrayNode) { $children = $node->getChildren(); if ($node instanceof PrototypedArrayNode) { $children = $this->getPrototypeChildren($node); } if (!$children) { if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) { $default = ''; } elseif (!\is_array($example)) { $default = '[]'; } } } elseif ($node instanceof EnumNode) { $comments[] = 'One of ' . \implode('; ', \array_map('json_encode', $node->getValues())); $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~'; } elseif (VariableNode::class === \get_class($node) && \is_array($example)) { // If there is an array example, we are sure we dont need to print a default value $default = ''; } else { $default = '~'; if ($node->hasDefaultValue()) { $default = $node->getDefaultValue(); if (\is_array($default)) { if (\count($defaultArray = $node->getDefaultValue())) { $default = ''; } elseif (!\is_array($example)) { $default = '[]'; } } else { $default = Inline::dump($default); } } } // required? if ($node->isRequired()) { $comments[] = 'Required'; } // deprecated? if ($node instanceof BaseNode && $node->isDeprecated()) { $deprecation = $node->getDeprecation($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath()); $comments[] = \sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '') . $deprecation['message']); } // example if ($example && !\is_array($example)) { $comments[] = 'Example: ' . Inline::dump($example); } $default = '' != (string) $default ? ' ' . $default : ''; $comments = \count($comments) ? '# ' . \implode(', ', $comments) : ''; $key = $prototypedArray ? '-' : $node->getName() . ':'; $text = \rtrim(\sprintf('%-21s%s %s', $key, $default, $comments), ' '); if ($node instanceof BaseNode && ($info = $node->getInfo())) { $this->writeLine(''); // indenting multi-line info $info = \str_replace("\n", \sprintf("\n%" . $depth * 4 . 's# ', ' '), $info); $this->writeLine('# ' . $info, $depth * 4); } $this->writeLine($text, $depth * 4); // output defaults if ($defaultArray) { $this->writeLine(''); $message = \count($defaultArray) > 1 ? 'Defaults' : 'Default'; $this->writeLine('# ' . $message . ':', $depth * 4 + 4); $this->writeArray($defaultArray, $depth + 1); } if (\is_array($example)) { $this->writeLine(''); $message = \count($example) > 1 ? 'Examples' : 'Example'; $this->writeLine('# ' . $message . ':', $depth * 4 + 4); $this->writeArray(\array_map([Inline::class, 'dump'], $example), $depth + 1); } if ($children) { foreach ($children as $childNode) { $this->writeNode($childNode, $node, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute()); } } } /** * Outputs a single config reference line. */ private function writeLine(string $text, int $indent = 0) { $indent = \strlen($text) + $indent; $format = '%' . $indent . 's'; $this->reference .= \sprintf($format, $text) . "\n"; } private function writeArray(array $array, int $depth) { $isIndexed = \array_values($array) === $array; foreach ($array as $key => $value) { if (\is_array($value)) { $val = ''; } else { $val = $value; } if ($isIndexed) { $this->writeLine('- ' . $val, $depth * 4); } else { $this->writeLine(\sprintf('%-20s %s', $key . ':', $val), $depth * 4); } if (\is_array($value)) { $this->writeArray($value, $depth + 1); } } } private function getPrototypeChildren(PrototypedArrayNode $node) : array { $prototype = $node->getPrototype(); $key = $node->getKeyAttribute(); // Do not expand prototype if it isn't an array node nor uses attribute as key if (!$key && !$prototype instanceof ArrayNode) { return $node->getChildren(); } if ($prototype instanceof ArrayNode) { $keyNode = new ArrayNode($key, $node); $children = $prototype->getChildren(); if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) { $children = $this->getPrototypeChildren($prototype); } // add children foreach ($children as $childNode) { $keyNode->addChild($childNode); } } else { $keyNode = new ScalarNode($key, $node); } $info = 'Prototype'; if (null !== $prototype->getInfo()) { $info .= ': ' . $prototype->getInfo(); } $keyNode->setInfo($info); return [$key => $keyNode]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Definition\Dumper; use _ContaoManager\Symfony\Component\Config\Definition\ArrayNode; use _ContaoManager\Symfony\Component\Config\Definition\BaseNode; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\EnumNode; use _ContaoManager\Symfony\Component\Config\Definition\NodeInterface; use _ContaoManager\Symfony\Component\Config\Definition\PrototypedArrayNode; /** * Dumps an XML reference configuration for the given configuration/node instance. * * @author Wouter J */ class XmlReferenceDumper { private $reference; public function dump(ConfigurationInterface $configuration, ?string $namespace = null) { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); } public function dumpNode(NodeInterface $node, ?string $namespace = null) { $this->reference = ''; $this->writeNode($node, 0, \true, $namespace); $ref = $this->reference; $this->reference = null; return $ref; } private function writeNode(NodeInterface $node, int $depth = 0, bool $root = \false, ?string $namespace = null) { $rootName = $root ? 'config' : $node->getName(); $rootNamespace = $namespace ?: ($root ? 'http://example.org/schema/dic/' . $node->getName() : null); // xml remapping if ($node->getParent()) { $remapping = \array_filter($node->getParent()->getXmlRemappings(), function (array $mapping) use($rootName) { return $rootName === $mapping[1]; }); if (\count($remapping)) { [$singular] = \current($remapping); $rootName = $singular; } } $rootName = \str_replace('_', '-', $rootName); $rootAttributes = []; $rootAttributeComments = []; $rootChildren = []; $rootComments = []; if ($node instanceof ArrayNode) { $children = $node->getChildren(); // comments about the root node if ($rootInfo = $node->getInfo()) { $rootComments[] = $rootInfo; } if ($rootNamespace) { $rootComments[] = 'Namespace: ' . $rootNamespace; } // render prototyped nodes if ($node instanceof PrototypedArrayNode) { $prototype = $node->getPrototype(); $info = 'prototype'; if (null !== $prototype->getInfo()) { $info .= ': ' . $prototype->getInfo(); } \array_unshift($rootComments, $info); if ($key = $node->getKeyAttribute()) { $rootAttributes[$key] = \str_replace('-', ' ', $rootName) . ' ' . $key; } if ($prototype instanceof PrototypedArrayNode) { $prototype->setName($key ?? ''); $children = [$key => $prototype]; } elseif ($prototype instanceof ArrayNode) { $children = $prototype->getChildren(); } else { if ($prototype->hasDefaultValue()) { $prototypeValue = $prototype->getDefaultValue(); } else { switch (\get_class($prototype)) { case 'Symfony\\Component\\Config\\Definition\\ScalarNode': $prototypeValue = 'scalar value'; break; case 'Symfony\\Component\\Config\\Definition\\FloatNode': case 'Symfony\\Component\\Config\\Definition\\IntegerNode': $prototypeValue = 'numeric value'; break; case 'Symfony\\Component\\Config\\Definition\\BooleanNode': $prototypeValue = 'true|false'; break; case 'Symfony\\Component\\Config\\Definition\\EnumNode': $prototypeValue = \implode('|', \array_map('json_encode', $prototype->getValues())); break; default: $prototypeValue = 'value'; } } } } // get attributes and elements foreach ($children as $child) { if ($child instanceof ArrayNode) { // get elements $rootChildren[] = $child; continue; } // get attributes // metadata $name = \str_replace('_', '-', $child->getName()); $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world // comments $comments = []; if ($child instanceof BaseNode && ($info = $child->getInfo())) { $comments[] = $info; } if ($child instanceof BaseNode && ($example = $child->getExample())) { $comments[] = 'Example: ' . (\is_array($example) ? \implode(', ', $example) : $example); } if ($child->isRequired()) { $comments[] = 'Required'; } if ($child instanceof BaseNode && $child->isDeprecated()) { $deprecation = $child->getDeprecation($child->getName(), $node->getPath()); $comments[] = \sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '') . $deprecation['message']); } if ($child instanceof EnumNode) { $comments[] = 'One of ' . \implode('; ', \array_map('json_encode', $child->getValues())); } if (\count($comments)) { $rootAttributeComments[$name] = \implode(";\n", $comments); } // default values if ($child->hasDefaultValue()) { $value = $child->getDefaultValue(); } // append attribute $rootAttributes[$name] = $value; } } // render comments // root node comment if (\count($rootComments)) { foreach ($rootComments as $comment) { $this->writeLine('', $depth); } } // attribute comments if (\count($rootAttributeComments)) { foreach ($rootAttributeComments as $attrName => $comment) { $commentDepth = $depth + 4 + \strlen($attrName) + 2; $commentLines = \explode("\n", $comment); $multiline = \count($commentLines) > 1; $comment = \implode(\PHP_EOL . \str_repeat(' ', $commentDepth), $commentLines); if ($multiline) { $this->writeLine('', $depth); } else { $this->writeLine('', $depth); } } } // render start tag + attributes $rootIsVariablePrototype = isset($prototypeValue); $rootIsEmptyTag = 0 === \count($rootChildren) && !$rootIsVariablePrototype; $rootOpenTag = '<' . $rootName; if (1 >= ($attributesCount = \count($rootAttributes))) { if (1 === $attributesCount) { $rootOpenTag .= \sprintf(' %s="%s"', \current(\array_keys($rootAttributes)), $this->writeValue(\current($rootAttributes))); } $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>'; if ($rootIsVariablePrototype) { $rootOpenTag .= $prototypeValue . ''; } $this->writeLine($rootOpenTag, $depth); } else { $this->writeLine($rootOpenTag, $depth); $i = 1; foreach ($rootAttributes as $attrName => $attrValue) { $attr = \sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); $this->writeLine($attr, $depth + 4); if ($attributesCount === $i++) { $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth); if ($rootIsVariablePrototype) { $rootOpenTag .= $prototypeValue . ''; } } } } // render children tags foreach ($rootChildren as $child) { $this->writeLine(''); $this->writeNode($child, $depth + 4); } // render end tag if (!$rootIsEmptyTag && !$rootIsVariablePrototype) { $this->writeLine(''); $rootEndTag = ''; $this->writeLine($rootEndTag, $depth); } } /** * Outputs a single config reference line. */ private function writeLine(string $text, int $indent = 0) { $indent = \strlen($text) + $indent; $format = '%' . $indent . 's'; $this->reference .= \sprintf($format, $text) . \PHP_EOL; } /** * Renders the string conversion of the value. * * @param mixed $value */ private function writeValue($value) : string { if ('%%%%not_defined%%%%' === $value) { return ''; } if (\is_string($value) || \is_numeric($value)) { return $value; } if (\false === $value) { return 'false'; } if (\true === $value) { return 'true'; } if (null === $value) { return 'null'; } if (empty($value)) { return ''; } if (\is_array($value)) { return \implode(',', $value); } return ''; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * Interface for Resources that can check for freshness autonomously, * without special support from external services. * * @author Matthias Pigulla */ interface SelfCheckingResourceInterface extends ResourceInterface { /** * Returns true if the resource has not been updated since the given timestamp. * * @param int $timestamp The last time the resource was loaded * * @return bool */ public function isFresh(int $timestamp); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * FileResource represents a resource stored on the filesystem. * * The resource can be a file or a directory. * * @author Fabien Potencier * * @final */ class FileResource implements SelfCheckingResourceInterface { /** * @var string|false */ private $resource; /** * @param string $resource The file path to the resource * * @throws \InvalidArgumentException */ public function __construct(string $resource) { $this->resource = \realpath($resource) ?: (\file_exists($resource) ? $resource : \false); if (\false === $this->resource) { throw new \InvalidArgumentException(\sprintf('The file "%s" does not exist.', $resource)); } } public function __toString() : string { return $this->resource; } /** * Returns the canonicalized, absolute path to the resource. */ public function getResource() : string { return $this->resource; } /** * {@inheritdoc} */ public function isFresh(int $timestamp) : bool { return \false !== ($filemtime = @\filemtime($this->resource)) && $filemtime <= $timestamp; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; /** * @author Nicolas Grekas * * @final */ class ReflectionClassResource implements SelfCheckingResourceInterface { private $files = []; private $className; private $classReflector; private $excludedVendors = []; private $hash; public function __construct(\ReflectionClass $classReflector, array $excludedVendors = []) { $this->className = $classReflector->name; $this->classReflector = $classReflector; $this->excludedVendors = $excludedVendors; } /** * {@inheritdoc} */ public function isFresh(int $timestamp) : bool { if (null === $this->hash) { $this->hash = $this->computeHash(); $this->loadFiles($this->classReflector); } foreach ($this->files as $file => $v) { if (\false === ($filemtime = @\filemtime($file))) { return \false; } if ($filemtime > $timestamp) { return $this->hash === $this->computeHash(); } } return \true; } public function __toString() : string { return 'reflection.' . $this->className; } /** * @internal */ public function __sleep() : array { if (null === $this->hash) { $this->hash = $this->computeHash(); $this->loadFiles($this->classReflector); } return ['files', 'className', 'hash']; } private function loadFiles(\ReflectionClass $class) { foreach ($class->getInterfaces() as $v) { $this->loadFiles($v); } do { $file = $class->getFileName(); if (\false !== $file && \is_file($file)) { foreach ($this->excludedVendors as $vendor) { if (\str_starts_with($file, $vendor) && \false !== \strpbrk(\substr($file, \strlen($vendor), 1), '/' . \DIRECTORY_SEPARATOR)) { $file = \false; break; } } if ($file) { $this->files[$file] = null; } } foreach ($class->getTraits() as $v) { $this->loadFiles($v); } } while ($class = $class->getParentClass()); } private function computeHash() : string { if (null === $this->classReflector) { try { $this->classReflector = new \ReflectionClass($this->className); } catch (\ReflectionException $e) { // the class does not exist anymore return \false; } } $hash = \hash_init('md5'); foreach ($this->generateSignature($this->classReflector) as $info) { \hash_update($hash, $info); } return \hash_final($hash); } private function generateSignature(\ReflectionClass $class) : iterable { if (\PHP_VERSION_ID >= 80000) { $attributes = []; foreach ($class->getAttributes() as $a) { $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; } (yield \print_r($attributes, \true)); $attributes = []; } (yield $class->getDocComment()); (yield (int) $class->isFinal()); (yield (int) $class->isAbstract()); if ($class->isTrait()) { (yield \print_r(\class_uses($class->name), \true)); } else { (yield \print_r(\class_parents($class->name), \true)); (yield \print_r(\class_implements($class->name), \true)); (yield \print_r($class->getConstants(), \true)); } if (!$class->isInterface()) { $defaults = $class->getDefaultProperties(); foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { if (\PHP_VERSION_ID >= 80000) { foreach ($p->getAttributes() as $a) { $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; } (yield \print_r($attributes, \true)); $attributes = []; } (yield $p->getDocComment()); (yield $p->isDefault() ? '' : ''); (yield $p->isPublic() ? 'public' : 'protected'); (yield $p->isStatic() ? 'static' : ''); (yield '$' . $p->name); (yield \print_r(isset($defaults[$p->name]) && !\is_object($defaults[$p->name]) ? $defaults[$p->name] : null, \true)); } } $defined = \Closure::bind(static function ($c) { return \defined($c); }, null, $class->name); foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { if (\PHP_VERSION_ID >= 80000) { foreach ($m->getAttributes() as $a) { $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; } (yield \print_r($attributes, \true)); $attributes = []; } $defaults = []; $parametersWithUndefinedConstants = []; foreach ($m->getParameters() as $p) { if (\PHP_VERSION_ID >= 80000) { foreach ($p->getAttributes() as $a) { $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()]; } (yield \print_r($attributes, \true)); $attributes = []; } if (!$p->isDefaultValueAvailable()) { $defaults[$p->name] = null; continue; } if (\PHP_VERSION_ID >= 80100) { $defaults[$p->name] = (string) $p; continue; } if (!$p->isDefaultValueConstant() || $defined($p->getDefaultValueConstantName())) { $defaults[$p->name] = $p->getDefaultValue(); continue; } $defaults[$p->name] = $p->getDefaultValueConstantName(); $parametersWithUndefinedConstants[$p->name] = \true; } if (!$parametersWithUndefinedConstants) { (yield \preg_replace('/^ @@.*/m', '', $m)); } else { $t = $m->getReturnType(); $stack = [$m->getDocComment(), $m->getName(), $m->isAbstract(), $m->isFinal(), $m->isStatic(), $m->isPublic(), $m->isPrivate(), $m->isProtected(), $m->returnsReference(), $t instanceof \ReflectionNamedType ? (string) $t->allowsNull() . $t->getName() : (string) $t]; foreach ($m->getParameters() as $p) { if (!isset($parametersWithUndefinedConstants[$p->name])) { $stack[] = (string) $p; } else { $t = $p->getType(); $stack[] = $p->isOptional(); $stack[] = $t instanceof \ReflectionNamedType ? (string) $t->allowsNull() . $t->getName() : (string) $t; $stack[] = $p->isPassedByReference(); $stack[] = $p->isVariadic(); $stack[] = $p->getName(); } } (yield \implode(',', $stack)); } (yield \print_r($defaults, \true)); } if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) { return; } if (\interface_exists(EventSubscriberInterface::class, \false) && $class->isSubclassOf(EventSubscriberInterface::class)) { (yield EventSubscriberInterface::class); (yield \print_r($class->name::getSubscribedEvents(), \true)); } if (\interface_exists(MessageSubscriberInterface::class, \false) && $class->isSubclassOf(MessageSubscriberInterface::class)) { (yield MessageSubscriberInterface::class); foreach ($class->name::getHandledMessages() as $key => $value) { (yield $key . \print_r($value, \true)); } } if (\interface_exists(ServiceSubscriberInterface::class, \false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) { (yield ServiceSubscriberInterface::class); (yield \print_r($class->name::getSubscribedServices(), \true)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; use _ContaoManager\Symfony\Component\Config\ResourceCheckerInterface; /** * Resource checker for instances of SelfCheckingResourceInterface. * * As these resources perform the actual check themselves, we can provide * this class as a standard way of validating them. * * @author Matthias Pigulla */ class SelfCheckingResourceChecker implements ResourceCheckerInterface { // Common shared cache, because this checker can be used in different // situations. For example, when using the full stack framework, the router // and the container have their own cache. But they may check the very same // resources private static $cache = []; public function supports(ResourceInterface $metadata) { return $metadata instanceof SelfCheckingResourceInterface; } /** * @param SelfCheckingResourceInterface $resource */ public function isFresh(ResourceInterface $resource, int $timestamp) { $key = "{$resource}:{$timestamp}"; return self::$cache[$key] ?? (self::$cache[$key] = $resource->isFresh($timestamp)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * ComposerResource tracks the PHP version and Composer dependencies. * * @author Nicolas Grekas * * @final */ class ComposerResource implements SelfCheckingResourceInterface { private $vendors; private static $runtimeVendors; public function __construct() { self::refresh(); $this->vendors = self::$runtimeVendors; } public function getVendors() : array { return \array_keys($this->vendors); } public function __toString() : string { return __CLASS__; } /** * {@inheritdoc} */ public function isFresh(int $timestamp) : bool { self::refresh(); return \array_values(self::$runtimeVendors) === \array_values($this->vendors); } private static function refresh() { self::$runtimeVendors = []; foreach (\get_declared_classes() as $class) { if ('C' === $class[0] && \str_starts_with($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); $v = \dirname($r->getFileName(), 2); if (\is_file($v . '/composer/installed.json')) { self::$runtimeVendors[$v] = @\filemtime($v . '/composer/installed.json'); } } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * FileExistenceResource represents a resource stored on the filesystem. * Freshness is only evaluated against resource creation or deletion. * * The resource can be a file or a directory. * * @author Charles-Henri Bruyand * * @final */ class FileExistenceResource implements SelfCheckingResourceInterface { private $resource; private $exists; /** * @param string $resource The file path to the resource */ public function __construct(string $resource) { $this->resource = $resource; $this->exists = \file_exists($resource); } public function __toString() : string { return 'existence.' . $this->resource; } public function getResource() : string { return $this->resource; } /** * {@inheritdoc} */ public function isFresh(int $timestamp) : bool { return \file_exists($this->resource) === $this->exists; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * ResourceInterface is the interface that must be implemented by all Resource classes. * * @author Fabien Potencier */ interface ResourceInterface { /** * Returns a string representation of the Resource. * * This method is necessary to allow for resource de-duplication, for example by means * of array_unique(). The string returned need not have a particular meaning, but has * to be identical for different ResourceInterface instances referring to the same * resource; and it should be unlikely to collide with that of other, unrelated * resource instances. */ public function __toString(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * ClassExistenceResource represents a class existence. * Freshness is only evaluated against resource existence. * * The resource must be a fully-qualified class name. * * @author Fabien Potencier * * @final */ class ClassExistenceResource implements SelfCheckingResourceInterface { private $resource; private $exists; private static $autoloadLevel = 0; private static $autoloadedClass; private static $existsCache = []; /** * @param string $resource The fully-qualified class name * @param bool|null $exists Boolean when the existence check has already been done */ public function __construct(string $resource, ?bool $exists = null) { $this->resource = $resource; if (null !== $exists) { $this->exists = [$exists, null]; } } public function __toString() : string { return $this->resource; } public function getResource() : string { return $this->resource; } /** * {@inheritdoc} * * @throws \ReflectionException when a parent class/interface/trait is not found */ public function isFresh(int $timestamp) : bool { $loaded = \class_exists($this->resource, \false) || \interface_exists($this->resource, \false) || \trait_exists($this->resource, \false); if (null !== ($exists =& self::$existsCache[$this->resource])) { if ($loaded) { $exists = [\true, null]; } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) { throw new \ReflectionException($exists[1]); } } elseif ([\false, null] === ($exists = [$loaded, null])) { if (!self::$autoloadLevel++) { \spl_autoload_register(__CLASS__ . '::throwOnRequiredClass'); } $autoloadedClass = self::$autoloadedClass; self::$autoloadedClass = \ltrim($this->resource, '\\'); try { $exists[0] = \class_exists($this->resource) || \interface_exists($this->resource, \false) || \trait_exists($this->resource, \false); } catch (\Exception $e) { $exists[1] = $e->getMessage(); try { self::throwOnRequiredClass($this->resource, $e); } catch (\ReflectionException $e) { if (0 >= $timestamp) { throw $e; } } } catch (\Throwable $e) { $exists[1] = $e->getMessage(); throw $e; } finally { self::$autoloadedClass = $autoloadedClass; if (!--self::$autoloadLevel) { \spl_autoload_unregister(__CLASS__ . '::throwOnRequiredClass'); } } } if (null === $this->exists) { $this->exists = $exists; } return $this->exists[0] xor !$exists[0]; } /** * @internal */ public function __sleep() : array { if (null === $this->exists) { $this->isFresh(0); } return ['resource', 'exists']; } /** * @internal */ public function __wakeup() { if (\is_bool($this->exists)) { $this->exists = [$this->exists, null]; } } /** * Throws a reflection exception when the passed class does not exist but is required. * * A class is considered "not required" when it's loaded as part of a "class_exists" or similar check. * * This function can be used as an autoload function to throw a reflection * exception if the class was not found by previous autoload functions. * * A previous exception can be passed. In this case, the class is considered as being * required totally, so if it doesn't exist, a reflection exception is always thrown. * If it exists, the previous exception is rethrown. * * @throws \ReflectionException * * @internal */ public static function throwOnRequiredClass(string $class, ?\Exception $previous = null) { // If the passed class is the resource being checked, we shouldn't throw. if (null === $previous && self::$autoloadedClass === $class) { return; } if (\class_exists($class, \false) || \interface_exists($class, \false) || \trait_exists($class, \false)) { if (null !== $previous) { throw $previous; } return; } if ($previous instanceof \ReflectionException) { throw $previous; } $message = \sprintf('Class "%s" not found.', $class); if (self::$autoloadedClass !== $class) { $message = \substr_replace($message, \sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0); } if (null !== $previous) { $message = $previous->getMessage(); } $e = new \ReflectionException($message, 0, $previous); if (null !== $previous) { throw $e; } $trace = \debug_backtrace(); $autoloadFrame = ['function' => 'spl_autoload_call', 'args' => [$class]]; if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) { $callerFrame = $trace[1]; $i = 2; } elseif (\false !== ($i = \array_search($autoloadFrame, $trace, \true))) { $callerFrame = $trace[++$i]; } else { throw $e; } if (isset($callerFrame['function']) && !isset($callerFrame['class'])) { switch ($callerFrame['function']) { case 'get_class_methods': case 'get_class_vars': case 'get_parent_class': case 'is_a': case 'is_subclass_of': case 'class_exists': case 'class_implements': case 'class_parents': case 'trait_exists': case 'defined': case 'interface_exists': case 'method_exists': case 'property_exists': case 'is_callable': return; } $props = ['file' => $callerFrame['file'] ?? null, 'line' => $callerFrame['line'] ?? null, 'trace' => \array_slice($trace, 1 + $i)]; foreach ($props as $p => $v) { if (null !== $v) { $r = new \ReflectionProperty(\Exception::class, $p); $r->setAccessible(\true); $r->setValue($e, $v); } } } throw $e; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; /** * DirectoryResource represents a resources stored in a subdirectory tree. * * @author Fabien Potencier * * @final */ class DirectoryResource implements SelfCheckingResourceInterface { private $resource; private $pattern; /** * @param string $resource The file path to the resource * @param string|null $pattern A pattern to restrict monitored files * * @throws \InvalidArgumentException */ public function __construct(string $resource, ?string $pattern = null) { $this->resource = \realpath($resource) ?: (\file_exists($resource) ? $resource : \false); $this->pattern = $pattern; if (\false === $this->resource || !\is_dir($this->resource)) { throw new \InvalidArgumentException(\sprintf('The directory "%s" does not exist.', $resource)); } } public function __toString() : string { return \md5(\serialize([$this->resource, $this->pattern])); } public function getResource() : string { return $this->resource; } public function getPattern() : ?string { return $this->pattern; } /** * {@inheritdoc} */ public function isFresh(int $timestamp) : bool { if (!\is_dir($this->resource)) { return \false; } if ($timestamp < \filemtime($this->resource)) { return \false; } foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { // if regex filtering is enabled only check matching files if ($this->pattern && $file->isFile() && !\preg_match($this->pattern, $file->getBasename())) { continue; } // always monitor directories for changes, except the .. entries // (otherwise deleted files wouldn't get detected) if ($file->isDir() && \str_ends_with($file, '/..')) { continue; } // for broken links try { $fileMTime = $file->getMTime(); } catch (\RuntimeException $e) { continue; } // early return if a file's mtime exceeds the passed timestamp if ($timestamp < $fileMTime) { return \false; } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Resource; use _ContaoManager\Symfony\Component\Finder\Finder; use _ContaoManager\Symfony\Component\Finder\Glob; /** * GlobResource represents a set of resources stored on the filesystem. * * Only existence/removal is tracked (not mtimes.) * * @author Nicolas Grekas * * @final * * @implements \IteratorAggregate */ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface { private $prefix; private $pattern; private $recursive; private $hash; private $forExclusion; private $excludedPrefixes; private $globBrace; /** * @param string $prefix A directory prefix * @param string $pattern A glob pattern * @param bool $recursive Whether directories should be scanned recursively or not * * @throws \InvalidArgumentException */ public function __construct(string $prefix, string $pattern, bool $recursive, bool $forExclusion = \false, array $excludedPrefixes = []) { \ksort($excludedPrefixes); $this->prefix = \realpath($prefix) ?: (\file_exists($prefix) ? $prefix : \false); $this->pattern = $pattern; $this->recursive = $recursive; $this->forExclusion = $forExclusion; $this->excludedPrefixes = $excludedPrefixes; $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; if (\false === $this->prefix) { throw new \InvalidArgumentException(\sprintf('The path "%s" does not exist.', $prefix)); } } public function getPrefix() : string { return $this->prefix; } public function __toString() : string { return 'glob.' . $this->prefix . (int) $this->recursive . $this->pattern . (int) $this->forExclusion . \implode("\x00", $this->excludedPrefixes); } /** * {@inheritdoc} */ public function isFresh(int $timestamp) : bool { $hash = $this->computeHash(); if (null === $this->hash) { $this->hash = $hash; } return $this->hash === $hash; } /** * @internal */ public function __sleep() : array { if (null === $this->hash) { $this->hash = $this->computeHash(); } return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes']; } /** * @internal */ public function __wakeup() : void { $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; } public function getIterator() : \Traversable { if (!\file_exists($this->prefix) || !$this->recursive && '' === $this->pattern) { return; } $prefix = \str_replace('\\', '/', $this->prefix); $paths = null; if ('' === $this->pattern && \is_file($prefix)) { $paths = [$this->prefix]; } elseif (!\str_starts_with($this->prefix, 'phar://') && !\str_contains($this->pattern, '/**/')) { if ($this->globBrace || !\str_contains($this->pattern, '{')) { $paths = \glob($this->prefix . $this->pattern, \GLOB_NOSORT | $this->globBrace); } elseif (!\str_contains($this->pattern, '\\') || !\preg_match('/\\\\[,{}]/', $this->pattern)) { foreach ($this->expandGlob($this->pattern) as $p) { $paths[] = \glob($this->prefix . $p, \GLOB_NOSORT); } $paths = \array_merge(...$paths); } } if (null !== $paths) { \natsort($paths); foreach ($paths as $path) { if ($this->excludedPrefixes) { $normalizedPath = \str_replace('\\', '/', $path); do { if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { continue 2; } } while ($prefix !== $dirPath && $dirPath !== ($normalizedPath = \dirname($dirPath))); } if (\is_file($path)) { (yield $path => new \SplFileInfo($path)); } if (!\is_dir($path)) { continue; } if ($this->forExclusion) { (yield $path => new \SplFileInfo($path)); continue; } if (!$this->recursive || isset($this->excludedPrefixes[\str_replace('\\', '/', $path)])) { continue; } $files = \iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveCallbackFilterIterator(new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), function (\SplFileInfo $file, $path) { return !isset($this->excludedPrefixes[\str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0]; }), \RecursiveIteratorIterator::LEAVES_ONLY)); \uksort($files, 'strnatcmp'); foreach ($files as $path => $info) { if ($info->isFile()) { (yield $path => $info); } } } return; } if (!\class_exists(Finder::class)) { throw new \LogicException(\sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern)); } if (\is_file($prefix = $this->prefix)) { $prefix = \dirname($prefix); $pattern = \basename($prefix) . $this->pattern; } else { $pattern = $this->pattern; } $finder = new Finder(); $regex = Glob::toRegex($pattern); if ($this->recursive) { $regex = \substr_replace($regex, '(/|$)', -2, 1); } $prefixLen = \strlen($prefix); foreach ($finder->followLinks()->sortByName()->in($prefix) as $path => $info) { $normalizedPath = \str_replace('\\', '/', $path); if (!\preg_match($regex, \substr($normalizedPath, $prefixLen)) || !$info->isFile()) { continue; } if ($this->excludedPrefixes) { do { if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { continue 2; } } while ($prefix !== $dirPath && $dirPath !== ($normalizedPath = \dirname($dirPath))); } (yield $path => $info); } } private function computeHash() : string { $hash = \hash_init('md5'); foreach ($this->getIterator() as $path => $info) { \hash_update($hash, $path . "\n"); } return \hash_final($hash); } private function expandGlob(string $pattern) : array { $segments = \preg_split('/\\{([^{}]*+)\\}/', $pattern, -1, \PREG_SPLIT_DELIM_CAPTURE); $paths = [$segments[0]]; $patterns = []; for ($i = 1; $i < \count($segments); $i += 2) { $patterns = []; foreach (\explode(',', $segments[$i]) as $s) { foreach ($paths as $p) { $patterns[] = $p . $s . $segments[1 + $i]; } } $paths = $patterns; } $j = 0; foreach ($patterns as $i => $p) { if (\str_contains($p, '{')) { $p = $this->expandGlob($p); \array_splice($paths, $i + $j, 1, $p); $j += \count($p) - 1; } } return $paths; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Builder; /** * A ConfigBuilder provides helper methods to build a large complex array. * * @author Tobias Nyholm */ interface ConfigBuilderInterface { /** * Gets all configuration represented as an array. */ public function toArray() : array; /** * Gets the alias for the extension which config we are building. */ public function getExtensionAlias() : string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Builder; /** * Represents a method when building classes. * * @internal * * @author Tobias Nyholm */ class Method { private $content; public function __construct(string $content) { $this->content = $content; } public function getContent() : string { return $this->content; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Builder; /** * Build PHP classes to generate config. * * @internal * * @author Tobias Nyholm */ class ClassBuilder { /** @var string */ private $namespace; /** @var string */ private $name; /** @var Property[] */ private $properties = []; /** @var Method[] */ private $methods = []; private $require = []; private $use = []; private $implements = []; private $allowExtraKeys = \false; public function __construct(string $namespace, string $name) { $this->namespace = $namespace; $this->name = \ucfirst($this->camelCase($name)) . 'Config'; } public function getDirectory() : string { return \str_replace('\\', \DIRECTORY_SEPARATOR, $this->namespace); } public function getFilename() : string { return $this->name . '.php'; } public function build() : string { $rootPath = \explode(\DIRECTORY_SEPARATOR, $this->getDirectory()); $require = ''; foreach ($this->require as $class) { // figure out relative path. $path = \explode(\DIRECTORY_SEPARATOR, $class->getDirectory()); $path[] = $class->getFilename(); foreach ($rootPath as $key => $value) { if ($path[$key] !== $value) { break; } unset($path[$key]); } $require .= \sprintf('require_once __DIR__.\\DIRECTORY_SEPARATOR.\'%s\';', \implode('\'.\\DIRECTORY_SEPARATOR.\'', $path)) . "\n"; } $use = $require ? "\n" : ''; foreach (\array_keys($this->use) as $statement) { $use .= \sprintf('use %s;', $statement) . "\n"; } $implements = [] === $this->implements ? '' : 'implements ' . \implode(', ', $this->implements); $body = ''; foreach ($this->properties as $property) { $body .= ' ' . $property->getContent() . "\n"; } foreach ($this->methods as $method) { $lines = \explode("\n", $method->getContent()); foreach ($lines as $line) { $body .= ($line ? ' ' . $line : '') . "\n"; } } $content = \strtr(' $this->namespace, 'REQUIRE' => $require, 'USE' => $use, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]); return $content; } public function addRequire(self $class) : void { $this->require[] = $class; } public function addUse(string $class) : void { $this->use[$class] = \true; } public function addImplements(string $interface) : void { $this->implements[] = '\\' . \ltrim($interface, '\\'); } public function addMethod(string $name, string $body, array $params = []) : void { $this->methods[] = new Method(\strtr($body, ['NAME' => $this->camelCase($name)] + $params)); } public function addProperty(string $name, ?string $classType = null, ?string $defaultValue = null) : Property { $property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name); if (null !== $classType) { $property->setType($classType); } $this->properties[] = $property; $defaultValue = null !== $defaultValue ? \sprintf(' = %s', $defaultValue) : ''; $property->setContent(\sprintf('private $%s%s;', $property->getName(), $defaultValue)); return $property; } public function getProperties() : array { return $this->properties; } private function camelCase(string $input) : string { $output = \lcfirst(\str_replace(' ', '', \ucwords(\str_replace('_', ' ', $input)))); return \preg_replace('#\\W#', '', $output); } public function getName() : string { return $this->name; } public function getNamespace() : string { return $this->namespace; } public function getFqcn() : string { return '\\' . $this->namespace . '\\' . $this->name; } public function setAllowExtraKeys(bool $allowExtraKeys) : void { $this->allowExtraKeys = $allowExtraKeys; } public function shouldAllowExtraKeys() : bool { return $this->allowExtraKeys; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Builder; /** * Represents a property when building classes. * * @internal * * @author Tobias Nyholm */ class Property { private $name; private $originalName; private $array = \false; private $scalarsAllowed = \false; private $type = null; private $content; public function __construct(string $originalName, string $name) { $this->name = $name; $this->originalName = $originalName; } public function getName() : string { return $this->name; } public function getOriginalName() : string { return $this->originalName; } public function setType(string $type) : void { $this->array = \false; $this->type = $type; if ('|scalar' === \substr($type, -7)) { $this->scalarsAllowed = \true; $this->type = $type = \substr($type, 0, -7); } if ('[]' === \substr($type, -2)) { $this->array = \true; $this->type = \substr($type, 0, -2); } } public function getType() : ?string { return $this->type; } public function getContent() : ?string { return $this->content; } public function setContent(string $content) : void { $this->content = $content; } public function isArray() : bool { return $this->array; } public function areScalarsAllowed() : bool { return $this->scalarsAllowed; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Builder; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; /** * Generates ConfigBuilders to help create valid config. * * @author Tobias Nyholm */ interface ConfigBuilderGeneratorInterface { /** * @return \Closure that will return the root config class */ public function build(ConfigurationInterface $configuration) : \Closure; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Builder; use _ContaoManager\Symfony\Component\Config\Definition\ArrayNode; use _ContaoManager\Symfony\Component\Config\Definition\BooleanNode; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\EnumNode; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use _ContaoManager\Symfony\Component\Config\Definition\FloatNode; use _ContaoManager\Symfony\Component\Config\Definition\IntegerNode; use _ContaoManager\Symfony\Component\Config\Definition\NodeInterface; use _ContaoManager\Symfony\Component\Config\Definition\PrototypedArrayNode; use _ContaoManager\Symfony\Component\Config\Definition\ScalarNode; use _ContaoManager\Symfony\Component\Config\Definition\VariableNode; use _ContaoManager\Symfony\Component\Config\Loader\ParamConfigurator; /** * Generate ConfigBuilders to help create valid config. * * @author Tobias Nyholm */ class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface { /** * @var ClassBuilder[] */ private $classes; private $outputDir; public function __construct(string $outputDir) { $this->outputDir = $outputDir; } /** * @return \Closure that will return the root config class */ public function build(ConfigurationInterface $configuration) : \Closure { $this->classes = []; $rootNode = $configuration->getConfigTreeBuilder()->buildTree(); $rootClass = new ClassBuilder('_ContaoManager\\Symfony\\Config', $rootNode->getName()); $path = $this->getFullPath($rootClass); if (!\is_file($path)) { // Generate the class if the file not exists $this->classes[] = $rootClass; $this->buildNode($rootNode, $rootClass, $this->getSubNamespace($rootClass)); $rootClass->addImplements(ConfigBuilderInterface::class); $rootClass->addMethod('getExtensionAlias', ' public function NAME(): string { return \'ALIAS\'; }', ['ALIAS' => $rootNode->getPath()]); $this->writeClasses(); } $loader = \Closure::fromCallable(function () use($path, $rootClass) { require_once $path; $className = $rootClass->getFqcn(); return new $className(); }); return $loader; } private function getFullPath(ClassBuilder $class) : string { $directory = $this->outputDir . \DIRECTORY_SEPARATOR . $class->getDirectory(); if (!\is_dir($directory)) { @\mkdir($directory, 0777, \true); } return $directory . \DIRECTORY_SEPARATOR . $class->getFilename(); } private function writeClasses() : void { foreach ($this->classes as $class) { $this->buildConstructor($class); $this->buildToArray($class); if ($class->getProperties()) { $class->addProperty('_usedProperties', null, '[]'); } $this->buildSetExtraKey($class); \file_put_contents($this->getFullPath($class), $class->build()); } $this->classes = []; } private function buildNode(NodeInterface $node, ClassBuilder $class, string $namespace) : void { if (!$node instanceof ArrayNode) { throw new \LogicException('The node was expected to be an ArrayNode. This Configuration includes an edge case not supported yet.'); } foreach ($node->getChildren() as $child) { switch (\true) { case $child instanceof ScalarNode: $this->handleScalarNode($child, $class); break; case $child instanceof PrototypedArrayNode: $this->handlePrototypedArrayNode($child, $class, $namespace); break; case $child instanceof VariableNode: $this->handleVariableNode($child, $class); break; case $child instanceof ArrayNode: $this->handleArrayNode($child, $class, $namespace); break; default: throw new \RuntimeException(\sprintf('Unknown node "%s".', \get_class($child))); } } } private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace) : void { $childClass = new ClassBuilder($namespace, $node->getName()); $childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys()); $class->addRequire($childClass); $this->classes[] = $childClass; $hasNormalizationClosures = $this->hasNormalizationClosures($node); $property = $class->addProperty($node->getName(), $this->getType($childClass->getFqcn(), $hasNormalizationClosures)); $body = $hasNormalizationClosures ? ' /** * @return CLASS|$this */ public function NAME($value = []) { if (!\\is_array($value)) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; } if (!$this->PROPERTY instanceof CLASS) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = new CLASS($value); } elseif (0 < \\func_num_args()) { throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); } return $this->PROPERTY; }' : ' public function NAME(array $value = []): CLASS { if (null === $this->PROPERTY) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = new CLASS($value); } elseif (0 < \\func_num_args()) { throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); } return $this->PROPERTY; }'; $class->addUse(InvalidConfigurationException::class); $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]); $this->buildNode($node, $childClass, $this->getSubNamespace($childClass)); } private function handleVariableNode(VariableNode $node, ClassBuilder $class) : void { $comment = $this->getComment($node); $property = $class->addProperty($node->getName()); $class->addUse(ParamConfigurator::class); $body = ' /** COMMENT * @return $this */ public function NAME($valueDEFAULT): self { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; }'; $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = ' . \var_export($node->getDefaultValue(), \true) : '']); } private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace) : void { $name = $this->getSingularName($node); $prototype = $node->getPrototype(); $methodName = $name; $parameterType = $this->getParameterType($prototype); if (null !== $parameterType || $prototype instanceof ScalarNode) { $class->addUse(ParamConfigurator::class); $property = $class->addProperty($node->getName()); if (null === ($key = $node->getKeyAttribute())) { // This is an array of values; don't use singular name $body = ' /** * @param ParamConfigurator|list $value * @return $this */ public function NAME($value): self { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; }'; $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType]); } else { $body = ' /** * @param ParamConfigurator|TYPE $value * @return $this */ public function NAME(string $VAR, $VALUE): self { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY[$VAR] = $VALUE; return $this; }'; $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType, 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']); } return; } $childClass = new ClassBuilder($namespace, $name); if ($prototype instanceof ArrayNode) { $childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys()); } $class->addRequire($childClass); $this->classes[] = $childClass; $hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype); $property = $class->addProperty($node->getName(), $this->getType($childClass->getFqcn() . '[]', $hasNormalizationClosures)); if (null === ($key = $node->getKeyAttribute())) { $body = $hasNormalizationClosures ? ' /** * @return CLASS|$this */ public function NAME($value = []) { $this->_usedProperties[\'PROPERTY\'] = true; if (!\\is_array($value)) { $this->PROPERTY[] = $value; return $this; } return $this->PROPERTY[] = new CLASS($value); }' : ' public function NAME(array $value = []): CLASS { $this->_usedProperties[\'PROPERTY\'] = true; return $this->PROPERTY[] = new CLASS($value); }'; $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]); } else { $body = $hasNormalizationClosures ? ' /** * @return CLASS|$this */ public function NAME(string $VAR, $VALUE = []) { if (!\\is_array($VALUE)) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY[$VAR] = $VALUE; return $this; } if (!isset($this->PROPERTY[$VAR]) || !$this->PROPERTY[$VAR] instanceof CLASS) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY[$VAR] = new CLASS($VALUE); } elseif (1 < \\func_num_args()) { throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); } return $this->PROPERTY[$VAR]; }' : ' public function NAME(string $VAR, array $VALUE = []): CLASS { if (!isset($this->PROPERTY[$VAR])) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY[$VAR] = new CLASS($VALUE); } elseif (1 < \\func_num_args()) { throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'); } return $this->PROPERTY[$VAR]; }'; $class->addUse(InvalidConfigurationException::class); $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']); } $this->buildNode($prototype, $childClass, $namespace . '\\' . $childClass->getName()); } private function handleScalarNode(ScalarNode $node, ClassBuilder $class) : void { $comment = $this->getComment($node); $property = $class->addProperty($node->getName()); $class->addUse(ParamConfigurator::class); $body = ' /** COMMENT * @return $this */ public function NAME($value): self { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = $value; return $this; }'; $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]); } private function getParameterType(NodeInterface $node) : ?string { if ($node instanceof BooleanNode) { return 'bool'; } if ($node instanceof IntegerNode) { return 'int'; } if ($node instanceof FloatNode) { return 'float'; } if ($node instanceof EnumNode) { return ''; } if ($node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode) { // This is just an array of variables return 'array'; } if ($node instanceof VariableNode) { // mixed return ''; } return null; } private function getComment(VariableNode $node) : string { $comment = ''; if ('' !== ($info = (string) $node->getInfo())) { $comment .= ' * ' . $info . "\n"; } foreach ((array) ($node->getExample() ?? []) as $example) { $comment .= ' * @example ' . $example . "\n"; } if ('' !== ($default = $node->getDefaultValue())) { $comment .= ' * @default ' . (null === $default ? 'null' : \var_export($default, \true)) . "\n"; } if ($node instanceof EnumNode) { $comment .= \sprintf(' * @param ParamConfigurator|%s $value', \implode('|', \array_map(function ($a) { return \var_export($a, \true); }, $node->getValues()))) . "\n"; } else { $parameterType = $this->getParameterType($node); if (null === $parameterType || '' === $parameterType) { $parameterType = 'mixed'; } $comment .= ' * @param ParamConfigurator|' . $parameterType . ' $value' . "\n"; } if ($node->isDeprecated()) { $comment .= ' * @deprecated ' . $node->getDeprecation($node->getName(), $node->getParent()->getName())['message'] . "\n"; } return $comment; } /** * Pick a good singular name. */ private function getSingularName(PrototypedArrayNode $node) : string { $name = $node->getName(); if ('s' !== \substr($name, -1)) { return $name; } $parent = $node->getParent(); $mappings = $parent instanceof ArrayNode ? $parent->getXmlRemappings() : []; foreach ($mappings as $map) { if ($map[1] === $name) { $name = $map[0]; break; } } return $name; } private function buildToArray(ClassBuilder $class) : void { $body = '$output = [];'; foreach ($class->getProperties() as $p) { $code = '$this->PROPERTY'; if (null !== $p->getType()) { if ($p->isArray()) { $code = $p->areScalarsAllowed() ? 'array_map(function ($v) { return $v instanceof CLASS ? $v->toArray() : $v; }, $this->PROPERTY)' : 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)'; } else { $code = $p->areScalarsAllowed() ? '$this->PROPERTY instanceof CLASS ? $this->PROPERTY->toArray() : $this->PROPERTY' : '$this->PROPERTY->toArray()'; } } $body .= \strtr(' if (isset($this->_usedProperties[\'PROPERTY\'])) { $output[\'ORG_NAME\'] = ' . $code . '; }', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName(), 'CLASS' => $p->getType()]); } $extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : ''; $class->addMethod('toArray', ' public function NAME(): array { ' . $body . ' return $output' . $extraKeys . '; }'); } private function buildConstructor(ClassBuilder $class) : void { $body = ''; foreach ($class->getProperties() as $p) { $code = '$value[\'ORG_NAME\']'; if (null !== $p->getType()) { if ($p->isArray()) { $code = $p->areScalarsAllowed() ? 'array_map(function ($v) { return \\is_array($v) ? new ' . $p->getType() . '($v) : $v; }, $value[\'ORG_NAME\'])' : 'array_map(function ($v) { return new ' . $p->getType() . '($v); }, $value[\'ORG_NAME\'])'; } else { $code = $p->areScalarsAllowed() ? '\\is_array($value[\'ORG_NAME\']) ? new ' . $p->getType() . '($value[\'ORG_NAME\']) : $value[\'ORG_NAME\']' : 'new ' . $p->getType() . '($value[\'ORG_NAME\'])'; } } $body .= \strtr(' if (array_key_exists(\'ORG_NAME\', $value)) { $this->_usedProperties[\'PROPERTY\'] = true; $this->PROPERTY = ' . $code . '; unset($value[\'ORG_NAME\']); } ', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]); } if ($class->shouldAllowExtraKeys()) { $body .= ' $this->_extraKeys = $value; '; } else { $body .= ' if ([] !== $value) { throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__).implode(\', \', array_keys($value))); }'; $class->addUse(InvalidConfigurationException::class); } $class->addMethod('__construct', ' public function __construct(array $value = []) {' . $body . ' }'); } private function buildSetExtraKey(ClassBuilder $class) : void { if (!$class->shouldAllowExtraKeys()) { return; } $class->addUse(ParamConfigurator::class); $class->addProperty('_extraKeys'); $class->addMethod('set', ' /** * @param ParamConfigurator|mixed $value * @return $this */ public function NAME(string $key, $value): self { $this->_extraKeys[$key] = $value; return $this; }'); } private function getSubNamespace(ClassBuilder $rootClass) : string { return \sprintf('%s\\%s', $rootClass->getNamespace(), \substr($rootClass->getName(), 0, -6)); } private function hasNormalizationClosures(NodeInterface $node) : bool { try { $r = new \ReflectionProperty($node, 'normalizationClosures'); } catch (\ReflectionException $e) { return \false; } $r->setAccessible(\true); return [] !== $r->getValue($node); } private function getType(string $classType, bool $hasNormalizationClosures) : string { return $classType . ($hasNormalizationClosures ? '|scalar' : ''); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Exception; /** * Exception class for when a resource cannot be loaded or imported. * * @author Ryan Weaver */ class LoaderLoadException extends \Exception { /** * @param string $resource The resource that could not be imported * @param string|null $sourceResource The original resource importing the new resource * @param int|null $code The error code * @param \Throwable|null $previous A previous exception * @param string|null $type The type of resource */ public function __construct(string $resource, ?string $sourceResource = null, ?int $code = 0, ?\Throwable $previous = null, ?string $type = null) { if (null === $code) { \trigger_deprecation('symfony/config', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } $message = ''; if ($previous) { // Include the previous exception, to help the user see what might be the underlying cause // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim... if ('.' === \substr($previous->getMessage(), -1)) { $trimmedMessage = \substr($previous->getMessage(), 0, -1); $message .= \sprintf('%s', $trimmedMessage) . ' in '; } else { $message .= \sprintf('%s', $previous->getMessage()) . ' in '; } $message .= $resource . ' '; // show tweaked trace to complete the human readable sentence if (null === $sourceResource) { $message .= \sprintf('(which is loaded in resource "%s")', $resource); } else { $message .= \sprintf('(which is being imported from "%s")', $sourceResource); } $message .= '.'; // if there's no previous message, present it the default way } elseif (null === $sourceResource) { $message .= \sprintf('Cannot load resource "%s".', $resource); } else { $message .= \sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource); } // Is the resource located inside a bundle? if ('@' === $resource[0]) { $parts = \explode(\DIRECTORY_SEPARATOR, $resource); $bundle = \substr($parts[0], 1); $message .= \sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); $message .= \sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); } elseif (null !== $type) { // maybe there is no loader for this specific type if ('annotation' === $type) { $message .= ' Make sure to use PHP 8+ or that annotations are installed and enabled.'; } else { $message .= \sprintf(' Make sure there is a loader supporting the "%s" type.', $type); } } parent::__construct($message, $code, $previous); } protected function varToString($var) { if (\is_object($var)) { return \sprintf('Object(%s)', \get_class($var)); } if (\is_array($var)) { $a = []; foreach ($var as $k => $v) { $a[] = \sprintf('%s => %s', $k, $this->varToString($v)); } return \sprintf('Array(%s)', \implode(', ', $a)); } if (\is_resource($var)) { return \sprintf('Resource(%s)', \get_resource_type($var)); } if (null === $var) { return 'null'; } if (\false === $var) { return 'false'; } if (\true === $var) { return 'true'; } return (string) $var; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Exception; /** * Exception class for when a circular reference is detected when importing resources. * * @author Fabien Potencier */ class FileLoaderImportCircularReferenceException extends LoaderLoadException { public function __construct(array $resources, ?int $code = 0, ?\Throwable $previous = null) { if (null === $code) { \trigger_deprecation('symfony/config', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } $message = \sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), \implode('" > "', $resources), $resources[0]); \Exception::__construct($message, $code, $previous); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config\Exception; /** * File locator exception if a file does not exist. * * @author Leo Feyer */ class FileLocatorFileNotFoundException extends \InvalidArgumentException { private $paths; public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $paths = []) { parent::__construct($message, $code, $previous); $this->paths = $paths; } public function getPaths() { return $this->paths; } } { "name": "symfony\/config", "type": "library", "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/filesystem": "^4.4|^5.0|^6.0", "symfony\/polyfill-ctype": "~1.8", "symfony\/polyfill-php80": "^1.16", "symfony\/polyfill-php81": "^1.22" }, "require-dev": { "symfony\/event-dispatcher": "^4.4|^5.0|^6.0", "symfony\/finder": "^4.4|^5.0|^6.0", "symfony\/messenger": "^4.4|^5.0|^6.0", "symfony\/service-contracts": "^1.1|^2|^3", "symfony\/yaml": "^4.4|^5.0|^6.0" }, "conflict": { "symfony\/finder": "<4.4" }, "suggest": { "symfony\/yaml": "To use the yaml reference dumper" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Config\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; /** * Basic implementation of ConfigCacheFactoryInterface that * creates an instance of the default ConfigCache. * * This factory and/or cache do not support cache validation * by means of ResourceChecker instances (that is, service-based). * * @author Matthias Pigulla */ class ConfigCacheFactory implements ConfigCacheFactoryInterface { private $debug; /** * @param bool $debug The debug flag to pass to ConfigCache */ public function __construct(bool $debug) { $this->debug = $debug; } /** * {@inheritdoc} */ public function cache(string $file, callable $callback) { $cache = new ConfigCache($file, $this->debug); if (!$cache->isFresh()) { $callback($cache); } return $cache; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Config; /** * A ConfigCacheFactory implementation that validates the * cache with an arbitrary set of ResourceCheckers. * * @author Matthias Pigulla */ class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface { private $resourceCheckers = []; /** * @param iterable $resourceCheckers */ public function __construct(iterable $resourceCheckers = []) { $this->resourceCheckers = $resourceCheckers; } /** * {@inheritdoc} */ public function cache(string $file, callable $callable) { $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers); if (!$cache->isFresh()) { $callable($cache); } return $cache; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RateLimiter; use _ContaoManager\Symfony\Component\HttpFoundation\RateLimiter\AbstractRequestRateLimiter; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\RateLimiter\RateLimiterFactory; use _ContaoManager\Symfony\Component\Security\Core\Security; /** * A default login throttling limiter. * * This limiter prevents breadth-first attacks by enforcing * a limit on username+IP and a (higher) limit on IP. * * @author Wouter de Jong */ final class DefaultLoginRateLimiter extends AbstractRequestRateLimiter { private $globalFactory; private $localFactory; public function __construct(RateLimiterFactory $globalFactory, RateLimiterFactory $localFactory) { $this->globalFactory = $globalFactory; $this->localFactory = $localFactory; } protected function getLimiters(Request $request) : array { $username = $request->attributes->get(Security::LAST_USERNAME, ''); $username = \preg_match('//u', $username) ? \mb_strtolower($username, 'UTF-8') : \strtolower($username); return [$this->globalFactory->create($request->getClientIp()), $this->localFactory->create($username . '-' . $request->getClientIp())]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Attribute; /** * Indicates that a controller argument should receive the current logged user. */ #[\Attribute(\Attribute::TARGET_PARAMETER)] class CurrentUser { } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Util; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Trait to get (and set) the URL the user last visited before being forced to authenticate. */ trait TargetPathTrait { /** * Sets the target path the user should be redirected to after authentication. * * Usually, you do not need to set this directly. */ private function saveTargetPath(SessionInterface $session, string $firewallName, string $uri) { $session->set('_security.' . $firewallName . '.target_path', $uri); } /** * Returns the URL (if any) the user visited that forced them to login. */ private function getTargetPath(SessionInterface $session, string $firewallName) : ?string { return $session->get('_security.' . $firewallName . '.target_path'); } /** * Removes the target path from the session. */ private function removeTargetPath(SessionInterface $session, string $firewallName) { $session->remove('_security.' . $firewallName . '.target_path'); } } CHANGELOG ========= 5.4 --- * Deprecate the `$authenticationEntryPoint` argument of `ChannelListener`, and add `$httpPort` and `$httpsPort` arguments * Deprecate `RetryAuthenticationEntryPoint`, this code is now inlined in the `ChannelListener` * Deprecate `FormAuthenticationEntryPoint` and `BasicAuthenticationEntryPoint`, in the new system the `FormLoginAuthenticator` and `HttpBasicAuthenticator` should be used instead * Deprecate `AbstractRememberMeServices`, `PersistentTokenBasedRememberMeServices`, `RememberMeServicesInterface`, `TokenBasedRememberMeServices`, use the remember me handler alternatives instead * Deprecate the `$authManager` argument of `AccessListener` * Deprecate not setting the `$exceptionOnNoToken` argument of `AccessListener` to `false` * Deprecate `DeauthenticatedEvent`, use `TokenDeauthenticatedEvent` instead * Deprecate `CookieClearingLogoutHandler`, `SessionLogoutHandler` and `CsrfTokenClearingLogoutHandler`. Use `CookieClearingLogoutListener`, `SessionLogoutListener` and `CsrfTokenClearingLogoutListener` instead * Deprecate `PassportInterface`, `UserPassportInterface` and `PassportTrait`, use `Passport` instead 5.3 --- The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestMatcherInterface; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ExceptionListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\LogoutListener; /** * FirewallMap allows configuration of different firewalls for specific parts * of the website. * * @author Fabien Potencier */ class FirewallMap implements FirewallMapInterface { /** * @var list, ExceptionListener|null, LogoutListener|null}> */ private $map = []; /** * @param list $listeners */ public function add(?RequestMatcherInterface $requestMatcher = null, array $listeners = [], ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null) { $this->map[] = [$requestMatcher, $listeners, $exceptionListener, $logoutListener]; } /** * {@inheritdoc} */ public function getListeners(Request $request) { foreach ($this->map as $elements) { if (null === $elements[0] || $elements[0]->matches($request)) { return [$elements[1], $elements[2], $elements[3]]; } } return [[], null, null]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ExceptionListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Firewall uses a FirewallMap to register security listeners for the given * request. * * It allows for different security strategies within the same application * (a Basic authentication for the /api, and a web based authentication for * everything else for instance). * * @author Fabien Potencier */ class Firewall implements EventSubscriberInterface { private $map; private $dispatcher; /** * @var \SplObjectStorage */ private $exceptionListeners; public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher) { $this->map = $map; $this->dispatcher = $dispatcher; $this->exceptionListeners = new \SplObjectStorage(); } public function onKernelRequest(RequestEvent $event) { if (!$event->isMainRequest()) { return; } // register listeners for this firewall $listeners = $this->map->getListeners($event->getRequest()); $authenticationListeners = $listeners[0]; $exceptionListener = $listeners[1]; $logoutListener = $listeners[2]; if (null !== $exceptionListener) { $this->exceptionListeners[$event->getRequest()] = $exceptionListener; $exceptionListener->register($this->dispatcher); } // Authentication listeners are pre-sorted by SortFirewallListenersPass $authenticationListeners = function () use($authenticationListeners, $logoutListener) { if (null !== $logoutListener) { $logoutListenerPriority = $this->getListenerPriority($logoutListener); } foreach ($authenticationListeners as $listener) { $listenerPriority = $this->getListenerPriority($listener); // Yielding the LogoutListener at the correct position if (null !== $logoutListener && $listenerPriority < $logoutListenerPriority) { (yield $logoutListener); $logoutListener = null; } (yield $listener); } // When LogoutListener has the lowest priority of all listeners if (null !== $logoutListener) { (yield $logoutListener); } }; $this->callListeners($event, $authenticationListeners()); } public function onKernelFinishRequest(FinishRequestEvent $event) { $request = $event->getRequest(); if (isset($this->exceptionListeners[$request])) { $this->exceptionListeners[$request]->unregister($this->dispatcher); unset($this->exceptionListeners[$request]); } } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [KernelEvents::REQUEST => ['onKernelRequest', 8], KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest']; } protected function callListeners(RequestEvent $event, iterable $listeners) { foreach ($listeners as $listener) { $listener($event); if ($event->hasResponse()) { break; } } } private function getListenerPriority(object $logoutListener) : int { return $logoutListener instanceof FirewallListenerInterface ? $logoutListener->getPriority() : 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * Interface that needs to be implemented by LogoutHandlers. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.1 */ interface LogoutHandlerInterface { /** * This method is called by the LogoutListener when a user has requested * to be logged out. Usually, you would unset session variables, or remove * cookies, etc. */ public function logout(Request $request, Response $response, TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Http\EventListener\SessionLogoutListener; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', SessionLogoutHandler::class, SessionLogoutListener::class); /** * Handler for clearing invalidating the current session. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.4, use {@link SessionLogoutListener} instead */ class SessionLogoutHandler implements LogoutHandlerInterface { /** * Invalidate the current session. */ public function logout(Request $request, Response $response, TokenInterface $token) { $request->getSession()->invalidate(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; \trigger_deprecation('symfony/security-http', '5.1', 'The "%s" interface is deprecated, create a listener for the "%s" event instead.', LogoutSuccessHandlerInterface::class, LogoutEvent::class); /** * LogoutSuccesshandlerInterface. * * In contrast to the LogoutHandlerInterface, this interface can return a response * which is then used instead of the default behavior. * * If you want to only perform some logout related clean-up task, use the * LogoutHandlerInterface instead. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.1 */ interface LogoutSuccessHandlerInterface { /** * Creates a Response object to send upon a successful logout. * * @return Response */ public function onLogoutSuccess(Request $request); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', CookieClearingLogoutHandler::class, CookieClearingLogoutListener::class); /** * This handler clears the passed cookies when a user logs out. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.4, use {@link CookieClearingLogoutListener} instead */ class CookieClearingLogoutHandler implements LogoutHandlerInterface { private $cookies; /** * @param array $cookies An array of cookie names to unset */ public function __construct(array $cookies) { $this->cookies = $cookies; } /** * Implementation for the LogoutHandlerInterface. Deletes all requested cookies. */ public function logout(Request $request, Response $response, TokenInterface $token) { foreach ($this->cookies as $cookieName => $cookieData) { $response->headers->clearCookie($cookieName, $cookieData['path'], $cookieData['domain'], $cookieData['secure'] ?? \false, \true, $cookieData['samesite'] ?? null); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', CsrfTokenClearingLogoutHandler::class, CsrfTokenClearingLogoutListener::class); /** * @author Christian Flothmann * * @deprecated since Symfony 5.4, use {@link CsrfTokenClearingLogoutListener} instead */ class CsrfTokenClearingLogoutHandler implements LogoutHandlerInterface { private $csrfTokenStorage; public function __construct(ClearableTokenStorageInterface $csrfTokenStorage) { $this->csrfTokenStorage = $csrfTokenStorage; } public function logout(Request $request, Response $response, TokenInterface $token) { $this->csrfTokenStorage->clear(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** * Provides generator functions for the logout URL. * * @author Fabien Potencier * @author Jeremy Mikola */ class LogoutUrlGenerator { private $requestStack; private $router; private $tokenStorage; private $listeners = []; /** @var string|null */ private $currentFirewallName; /** @var string|null */ private $currentFirewallContext; public function __construct(?RequestStack $requestStack = null, ?UrlGeneratorInterface $router = null, ?TokenStorageInterface $tokenStorage = null) { $this->requestStack = $requestStack; $this->router = $router; $this->tokenStorage = $tokenStorage; } /** * Registers a firewall's LogoutListener, allowing its URL to be generated. * * @param string $key The firewall key * @param string $logoutPath The path that starts the logout process * @param string|null $csrfTokenId The ID of the CSRF token * @param string|null $csrfParameter The CSRF token parameter name * @param string|null $context The listener context */ public function registerListener(string $key, string $logoutPath, ?string $csrfTokenId, ?string $csrfParameter, ?CsrfTokenManagerInterface $csrfTokenManager = null, ?string $context = null) { $this->listeners[$key] = [$logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager, $context]; } /** * Generates the absolute logout path for the firewall. * * @return string */ public function getLogoutPath(?string $key = null) { return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_PATH); } /** * Generates the absolute logout URL for the firewall. * * @return string */ public function getLogoutUrl(?string $key = null) { return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL); } public function setCurrentFirewall(?string $key, ?string $context = null) { $this->currentFirewallName = $key; $this->currentFirewallContext = $context; } /** * Generates the logout URL for the firewall. */ private function generateLogoutUrl(?string $key, int $referenceType) : string { [$logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager] = $this->getListener($key); if (null === $logoutPath) { throw new \LogicException('Unable to generate the logout URL without a path.'); } $parameters = null !== $csrfTokenManager ? [$csrfParameter => (string) $csrfTokenManager->getToken($csrfTokenId)] : []; if ('/' === $logoutPath[0]) { if (!$this->requestStack) { throw new \LogicException('Unable to generate the logout URL without a RequestStack.'); } $request = $this->requestStack->getCurrentRequest(); if (!$request) { throw new \LogicException('Unable to generate the logout URL without a Request.'); } $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBaseUrl() . $logoutPath; if (!empty($parameters)) { $url .= '?' . \http_build_query($parameters, '', '&'); } } else { if (!$this->router) { throw new \LogicException('Unable to generate the logout URL without a Router.'); } $url = $this->router->generate($logoutPath, $parameters, $referenceType); } return $url; } /** * @throws \InvalidArgumentException if no LogoutListener is registered for the key or could not be found automatically */ private function getListener(?string $key) : array { if (null !== $key) { if (isset($this->listeners[$key])) { return $this->listeners[$key]; } throw new \InvalidArgumentException(\sprintf('No LogoutListener found for firewall key "%s".', $key)); } // Fetch the current provider key from token, if possible if (null !== $this->tokenStorage) { $token = $this->tokenStorage->getToken(); // @deprecated since Symfony 5.4 if ($token instanceof AnonymousToken) { throw new \InvalidArgumentException('Unable to generate a logout url for an anonymous token.'); } if (null !== $token) { if (\method_exists($token, 'getFirewallName')) { $key = $token->getFirewallName(); } elseif (\method_exists($token, 'getProviderKey')) { \trigger_deprecation('symfony/security-http', '5.2', 'Method "%s::getProviderKey()" has been deprecated, rename it to "getFirewallName()" instead.', \get_class($token)); $key = $token->getProviderKey(); } if (isset($this->listeners[$key])) { return $this->listeners[$key]; } } } // Fetch from injected current firewall information, if possible if (isset($this->listeners[$this->currentFirewallName])) { return $this->listeners[$this->currentFirewallName]; } foreach ($this->listeners as $listener) { if (isset($listener[4]) && $this->currentFirewallContext === $listener[4]) { return $listener; } } throw new \InvalidArgumentException('Unable to find the current firewall LogoutListener, please provide the provider key manually.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Logout; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Http\EventListener\DefaultLogoutListener; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; \trigger_deprecation('symfony/security-http', '5.1', 'The "%s" class is deprecated, use "%s" instead.', DefaultLogoutSuccessHandler::class, DefaultLogoutListener::class); /** * Default logout success handler will redirect users to a configured path. * * @author Fabien Potencier * @author Alexander * * @deprecated since Symfony 5.1 */ class DefaultLogoutSuccessHandler implements LogoutSuccessHandlerInterface { protected $httpUtils; protected $targetUrl; public function __construct(HttpUtils $httpUtils, string $targetUrl = '/') { $this->httpUtils = $httpUtils; $this->targetUrl = $targetUrl; } /** * {@inheritdoc} */ public function onLogoutSuccess(Request $request) { return $this->httpUtils->createRedirectResponse($request, $this->targetUrl); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\SwitchUserEvent; final class SecurityEvents { /** * The INTERACTIVE_LOGIN event occurs after a user has actively logged * into your website. It is important to distinguish this action from * non-interactive authentication methods, such as: * - authentication based on your session. * - authentication using an HTTP basic or HTTP digest header. * * @Event("Symfony\Component\Security\Http\Event\InteractiveLoginEvent") */ public const INTERACTIVE_LOGIN = 'security.interactive_login'; /** * The SWITCH_USER event occurs before switch to another user and * before exit from an already switched user. * * @Event("Symfony\Component\Security\Http\Event\SwitchUserEvent") */ public const SWITCH_USER = 'security.switch_user'; /** * Event aliases. * * These aliases can be consumed by RegisterListenersPass. */ public const ALIASES = [InteractiveLoginEvent::class => self::INTERACTIVE_LOGIN, SwitchUserEvent::class => self::SWITCH_USER]; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\NullToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccessDeniedException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use _ContaoManager\Symfony\Component\Security\Http\AccessMapInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\NoopAuthenticationManager; use _ContaoManager\Symfony\Component\Security\Http\Event\LazyResponseEvent; /** * AccessListener enforces access control rules. * * @author Fabien Potencier * * @final */ class AccessListener extends AbstractListener { private $tokenStorage; private $accessDecisionManager; private $map; private $authManager; private $exceptionOnNoToken; public function __construct( TokenStorageInterface $tokenStorage, AccessDecisionManagerInterface $accessDecisionManager, AccessMapInterface $map, /* bool */ $exceptionOnNoToken = \true ) { if ($exceptionOnNoToken instanceof AuthenticationManagerInterface) { \trigger_deprecation('symfony/security-http', '5.4', 'The $authManager argument of "%s" is deprecated.', __METHOD__); $authManager = $exceptionOnNoToken; $exceptionOnNoToken = \func_num_args() > 4 ? \func_get_arg(4) : \true; } if (\false !== $exceptionOnNoToken) { \trigger_deprecation('symfony/security-http', '5.4', 'Not setting the $exceptionOnNoToken argument of "%s" to "false" is deprecated.', __METHOD__); } $this->tokenStorage = $tokenStorage; $this->accessDecisionManager = $accessDecisionManager; $this->map = $map; $this->authManager = $authManager ?? (\class_exists(AuthenticationManagerInterface::class) ? new NoopAuthenticationManager() : null); $this->exceptionOnNoToken = $exceptionOnNoToken; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { [$attributes] = $this->map->getPatterns($request); $request->attributes->set('_access_control_attributes', $attributes); if ($attributes && ((\defined(AuthenticatedVoter::class . '::IS_AUTHENTICATED_ANONYMOUSLY') ? [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes : \true) && [AuthenticatedVoter::PUBLIC_ACCESS] !== $attributes)) { return \true; } return null; } /** * Handles access authorization. * * @throws AccessDeniedException * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token and $exceptionOnNoToken is set to true */ public function authenticate(RequestEvent $event) { if (!$event instanceof LazyResponseEvent && null === ($token = $this->tokenStorage->getToken()) && $this->exceptionOnNoToken) { throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); } $request = $event->getRequest(); $attributes = $request->attributes->get('_access_control_attributes'); $request->attributes->remove('_access_control_attributes'); if (!$attributes || ((\defined(AuthenticatedVoter::class . '::IS_AUTHENTICATED_ANONYMOUSLY') ? [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes : \false) || [AuthenticatedVoter::PUBLIC_ACCESS] === $attributes) && $event instanceof LazyResponseEvent) { return; } if ($event instanceof LazyResponseEvent) { $token = $this->tokenStorage->getToken(); } if (null === $token) { if ($this->exceptionOnNoToken) { throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); } $token = new NullToken(); } // @deprecated since Symfony 5.4 if (\method_exists($token, 'isAuthenticated') && !$token->isAuthenticated(\false)) { \trigger_deprecation('symfony/core', '5.4', 'Returning false from "%s::isAuthenticated()" is deprecated, return null from "getUser()" instead.', \get_debug_type($token)); if ($this->authManager) { $token = $this->authManager->authenticate($token); $this->tokenStorage->setToken($token); } } if (!$this->accessDecisionManager->decide($token, $attributes, $request, \true)) { throw $this->createAccessDeniedException($request, $attributes); } } private function createAccessDeniedException(Request $request, array $attributes) { $exception = new AccessDeniedException(); $exception->setAttributes($attributes); $exception->setSubject($request); return $exception; } public static function getPriority() : int { return -255; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\Event; use _ContaoManager\Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Session; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\UnsupportedUserException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\EquatableInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\DeauthenticatedEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * ContextListener manages the SecurityContext persistence through a session. * * @author Fabien Potencier * @author Johannes M. Schmitt * * @final */ class ContextListener extends AbstractListener { private $tokenStorage; private $sessionKey; private $logger; private $userProviders; private $dispatcher; private $registered; private $trustResolver; private $rememberMeServices; private $sessionTrackerEnabler; /** * @param iterable $userProviders */ public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, ?AuthenticationTrustResolverInterface $trustResolver = null, ?callable $sessionTrackerEnabler = null) { if (empty($contextKey)) { throw new \InvalidArgumentException('$contextKey must not be empty.'); } $this->tokenStorage = $tokenStorage; $this->userProviders = $userProviders; $this->sessionKey = '_security_' . $contextKey; $this->logger = $logger; $this->dispatcher = \class_exists(Event::class) ? LegacyEventDispatcherProxy::decorate($dispatcher) : $dispatcher; $this->trustResolver = $trustResolver ?? new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); $this->sessionTrackerEnabler = $sessionTrackerEnabler; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { return null; // always run authenticate() lazily with lazy firewalls } /** * Reads the Security Token from the session. */ public function authenticate(RequestEvent $event) { if (!$this->registered && null !== $this->dispatcher && $event->isMainRequest()) { $this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']); $this->registered = \true; } $request = $event->getRequest(); $session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null; $request->attributes->set('_security_firewall_run', $this->sessionKey); if (null !== $session) { $usageIndexValue = $session instanceof Session ? $usageIndexReference =& $session->getUsageIndex() : 0; $usageIndexReference = \PHP_INT_MIN; $sessionId = $request->cookies->all()[$session->getName()] ?? null; $token = $session->get($this->sessionKey); // sessionId = true is used in the tests if ($this->sessionTrackerEnabler && \in_array($sessionId, [\true, $session->getId()], \true)) { $usageIndexReference = $usageIndexValue; } else { $usageIndexReference = $usageIndexReference - \PHP_INT_MIN + $usageIndexValue; } } if (null === $session || null === $token) { if ($this->sessionTrackerEnabler) { ($this->sessionTrackerEnabler)(); } $this->tokenStorage->setToken(null); return; } $token = $this->safelyUnserialize($token); if (null !== $this->logger) { $this->logger->debug('Read existing security token from the session.', ['key' => $this->sessionKey, 'token_class' => \is_object($token) ? \get_class($token) : null]); } if ($token instanceof TokenInterface) { $originalToken = $token; $token = $this->refreshUser($token); if (!$token) { if ($this->logger) { $this->logger->debug('Token was deauthenticated after trying to refresh it.'); } if ($this->dispatcher) { $this->dispatcher->dispatch(new TokenDeauthenticatedEvent($originalToken, $request)); } if ($this->rememberMeServices) { $this->rememberMeServices->loginFail($request); } } } elseif (null !== $token) { if (null !== $this->logger) { $this->logger->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]); } $token = null; } if ($this->sessionTrackerEnabler) { ($this->sessionTrackerEnabler)(); } $this->tokenStorage->setToken($token); } /** * Writes the security token into the session. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); if (!$request->hasSession() || $request->attributes->get('_security_firewall_run') !== $this->sessionKey) { return; } if ($this->dispatcher) { $this->dispatcher->removeListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']); } $this->registered = \false; $session = $request->getSession(); $sessionId = $session->getId(); $usageIndexValue = $session instanceof Session ? $usageIndexReference =& $session->getUsageIndex() : null; $token = $this->tokenStorage->getToken(); // @deprecated always use isAuthenticated() in 6.0 $notAuthenticated = \method_exists($this->trustResolver, 'isAuthenticated') ? !$this->trustResolver->isAuthenticated($token) : null === $token || $this->trustResolver->isAnonymous($token); if ($notAuthenticated) { if ($request->hasPreviousSession()) { $session->remove($this->sessionKey); } } else { $session->set($this->sessionKey, \serialize($token)); if (null !== $this->logger) { $this->logger->debug('Stored the security token in the session.', ['key' => $this->sessionKey]); } } if ($this->sessionTrackerEnabler && $session->getId() === $sessionId) { $usageIndexReference = $usageIndexValue; } } /** * Refreshes the user by reloading it from the user provider. * * @throws \RuntimeException */ protected function refreshUser(TokenInterface $token) : ?TokenInterface { $user = $token->getUser(); if (!$user instanceof UserInterface) { return $token; } $userNotFoundByProvider = \false; $userDeauthenticated = \false; $userClass = \get_class($user); foreach ($this->userProviders as $provider) { if (!$provider instanceof UserProviderInterface) { throw new \InvalidArgumentException(\sprintf('User provider "%s" must implement "%s".', \get_debug_type($provider), UserProviderInterface::class)); } if (!$provider->supportsClass($userClass)) { continue; } try { $refreshedUser = $provider->refreshUser($user); $newToken = clone $token; $newToken->setUser($refreshedUser, \false); // tokens can be deauthenticated if the user has been changed. if ($token instanceof AbstractToken && $this->hasUserChanged($user, $newToken)) { $userDeauthenticated = \true; // @deprecated since Symfony 5.4 if (\method_exists($newToken, 'setAuthenticated')) { $newToken->setAuthenticated(\false, \false); } if (null !== $this->logger) { // @deprecated since Symfony 5.3, change to $refreshedUser->getUserIdentifier() in 6.0 $this->logger->debug('Cannot refresh token because user has changed.', ['username' => \method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername(), 'provider' => \get_class($provider)]); } continue; } $token->setUser($refreshedUser); if (null !== $this->logger) { // @deprecated since Symfony 5.3, change to $refreshedUser->getUserIdentifier() in 6.0 $context = ['provider' => \get_class($provider), 'username' => \method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername()]; if ($token instanceof SwitchUserToken) { $originalToken = $token->getOriginalToken(); // @deprecated since Symfony 5.3, change to $originalToken->getUserIdentifier() in 6.0 $context['impersonator_username'] = \method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername(); } $this->logger->debug('User was reloaded from a user provider.', $context); } return $token; } catch (UnsupportedUserException $e) { // let's try the next user provider } catch (UserNotFoundException $e) { if (null !== $this->logger) { $this->logger->warning('Username could not be found in the selected user provider.', ['username' => \method_exists($e, 'getUserIdentifier') ? $e->getUserIdentifier() : $e->getUsername(), 'provider' => \get_class($provider)]); } $userNotFoundByProvider = \true; } } if ($userDeauthenticated) { // @deprecated since Symfony 5.4 if ($this->dispatcher) { $this->dispatcher->dispatch(new DeauthenticatedEvent($token, $newToken, \false), DeauthenticatedEvent::class); } return null; } if ($userNotFoundByProvider) { return null; } throw new \RuntimeException(\sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', $userClass)); } private function safelyUnserialize(string $serializedToken) { $token = null; $prevUnserializeHandler = \ini_set('unserialize_callback_func', __CLASS__ . '::handleUnserializeCallback'); $prevErrorHandler = \set_error_handler(function ($type, $msg, $file, $line, $context = []) use(&$prevErrorHandler) { if (__FILE__ === $file) { throw new \ErrorException($msg, 0x37313bc, $type, $file, $line); } return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : \false; }); try { $token = \unserialize($serializedToken); } catch (\ErrorException $e) { if (0x37313bc !== $e->getCode()) { throw $e; } if ($this->logger) { $this->logger->warning('Failed to unserialize the security token from the session.', ['key' => $this->sessionKey, 'received' => $serializedToken, 'exception' => $e]); } } finally { \restore_error_handler(); \ini_set('unserialize_callback_func', $prevUnserializeHandler); } return $token; } /** * @param string|\Stringable|UserInterface $originalUser */ private static function hasUserChanged($originalUser, TokenInterface $refreshedToken) : bool { $refreshedUser = $refreshedToken->getUser(); if ($originalUser instanceof UserInterface) { if (!$refreshedUser instanceof UserInterface) { return \true; } else { // noop } } elseif ($refreshedUser instanceof UserInterface) { return \true; } else { return (string) $originalUser !== (string) $refreshedUser; } if ($originalUser instanceof EquatableInterface) { return !(bool) $originalUser->isEqualTo($refreshedUser); } // @deprecated since Symfony 5.3, check for PasswordAuthenticatedUserInterface on both user objects before comparing passwords if ($originalUser->getPassword() !== $refreshedUser->getPassword()) { return \true; } // @deprecated since Symfony 5.3, check for LegacyPasswordAuthenticatedUserInterface on both user objects before comparing salts if ($originalUser->getSalt() !== $refreshedUser->getSalt()) { return \true; } $userRoles = \array_map('strval', (array) $refreshedUser->getRoles()); if ($refreshedToken instanceof SwitchUserToken) { $userRoles[] = 'ROLE_PREVIOUS_ADMIN'; } if (\count($userRoles) !== \count($refreshedToken->getRoleNames()) || \count($userRoles) !== \count(\array_intersect($userRoles, $refreshedToken->getRoleNames()))) { return \true; } // @deprecated since Symfony 5.3, drop getUsername() in 6.0 $userIdentifier = function ($refreshedUser) { return \method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername(); }; if ($userIdentifier($originalUser) !== $userIdentifier($refreshedUser)) { return \true; } return \false; } /** * @internal */ public static function handleUnserializeCallback(string $class) { throw new \ErrorException('Class not found: ' . $class, 0x37313bc); } /** * @deprecated since Symfony 5.4 */ public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices) { \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use the new remember me handlers instead.', __METHOD__); $this->rememberMeServices = $rememberMeServices; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AnonymousAuthenticationListener::class); // Help opcache.preload discover always-needed symbols \class_exists(AnonymousToken::class); /** * AnonymousAuthenticationListener automatically adds a Token if none is * already present. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class AnonymousAuthenticationListener extends AbstractListener { private $tokenStorage; private $secret; private $authenticationManager; private $logger; public function __construct(TokenStorageInterface $tokenStorage, string $secret, ?LoggerInterface $logger = null, ?AuthenticationManagerInterface $authenticationManager = null) { $this->tokenStorage = $tokenStorage; $this->secret = $secret; $this->authenticationManager = $authenticationManager; $this->logger = $logger; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { return null; // always run authenticate() lazily with lazy firewalls } /** * Handles anonymous authentication. */ public function authenticate(RequestEvent $event) { if (null !== $this->tokenStorage->getToken()) { return; } try { $token = new AnonymousToken($this->secret, 'anon.', []); if (null !== $this->authenticationManager) { $token = $this->authenticationManager->authenticate($token); } $this->tokenStorage->setToken($token); if (null !== $this->logger) { $this->logger->info('Populated the TokenStorage with an anonymous Token.'); } } catch (AuthenticationException $failed) { if (null !== $this->logger) { $this->logger->info('Anonymous authentication failed.', ['exception' => $failed]); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; /** * A base class for listeners that can tell whether they should authenticate incoming requests. * * @author Nicolas Grekas */ abstract class AbstractListener implements FirewallListenerInterface { public final function __invoke(RequestEvent $event) { if (\false !== $this->supports($event->getRequest())) { $this->authenticate($event); } } public static function getPriority() : int { return 0; // Default } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', RememberMeListener::class); /** * RememberMeListener implements authentication capabilities via a cookie. * * @author Johannes M. Schmitt * * @final * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class RememberMeListener extends AbstractListener { private $tokenStorage; private $rememberMeServices; private $authenticationManager; private $logger; private $dispatcher; private $catchExceptions = \true; private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, bool $catchExceptions = \true, ?SessionAuthenticationStrategyInterface $sessionStrategy = null) { $this->tokenStorage = $tokenStorage; $this->rememberMeServices = $rememberMeServices; $this->authenticationManager = $authenticationManager; $this->logger = $logger; $this->dispatcher = $dispatcher; $this->catchExceptions = $catchExceptions; $this->sessionStrategy = $sessionStrategy ?? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { return null; // always run authenticate() lazily with lazy firewalls } /** * Handles remember-me cookie based authentication. */ public function authenticate(RequestEvent $event) { if (null !== $this->tokenStorage->getToken()) { return; } $request = $event->getRequest(); try { if (null === ($token = $this->rememberMeServices->autoLogin($request))) { return; } } catch (AuthenticationException $e) { if (null !== $this->logger) { $this->logger->warning('The token storage was not populated with remember-me token as the' . ' RememberMeServices was not able to create a token from the remember' . ' me information.', ['exception' => $e]); } $this->rememberMeServices->loginFail($request); if (!$this->catchExceptions) { throw $e; } return; } try { $token = $this->authenticationManager->authenticate($token); if ($request->hasSession() && $request->getSession()->isStarted()) { $this->sessionStrategy->onAuthentication($request, $token); } $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { $loginEvent = new InteractiveLoginEvent($request, $token); $this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } if (null !== $this->logger) { $this->logger->debug('Populated the token storage with a remember-me token.'); } } catch (AuthenticationException $e) { if (null !== $this->logger) { $this->logger->warning('The token storage was not populated with remember-me token as the' . ' AuthenticationManager rejected the AuthenticationToken returned' . ' by the RememberMeServices.', ['exception' => $e]); } $this->rememberMeServices->loginFail($request, $e); if (!$this->catchExceptions) { throw $e; } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticatorManagerInterface; /** * Firewall authentication listener that delegates to the authenticator system. * * @author Wouter de Jong */ class AuthenticatorManagerListener extends AbstractListener { private $authenticatorManager; public function __construct(AuthenticatorManagerInterface $authenticationManager) { $this->authenticatorManager = $authenticationManager; } public function supports(Request $request) : ?bool { return $this->authenticatorManager->supports($request); } public function authenticate(RequestEvent $event) : void { $request = $event->getRequest(); $response = $this->authenticatorManager->authenticateRequest($request); if (null === $response) { return; } $event->setResponse($response); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AnonymousAuthenticationListener::class); /** * BasicAuthenticationListener implements Basic HTTP authentication. * * @author Fabien Potencier * * @final * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class BasicAuthenticationListener extends AbstractListener { private $tokenStorage; private $authenticationManager; private $providerKey; private $authenticationEntryPoint; private $logger; private $ignoreFailure; private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, AuthenticationEntryPointInterface $authenticationEntryPoint, ?LoggerInterface $logger = null) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); } $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; $this->providerKey = $providerKey; $this->authenticationEntryPoint = $authenticationEntryPoint; $this->logger = $logger; $this->ignoreFailure = \false; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { return null !== $request->headers->get('PHP_AUTH_USER'); } /** * Handles basic authentication. */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); if (null === ($username = $request->headers->get('PHP_AUTH_USER'))) { return; } if (null !== ($token = $this->tokenStorage->getToken())) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 if ($token instanceof UsernamePasswordToken && $token->isAuthenticated(\false) && (\method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $username) { return; } } if (null !== $this->logger) { $this->logger->info('Basic authentication Authorization header found for user.', ['username' => $username]); } try { $previousToken = $token; $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey)); $this->migrateSession($request, $token, $previousToken); $this->tokenStorage->setToken($token); } catch (AuthenticationException $e) { $token = $this->tokenStorage->getToken(); if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) { $this->tokenStorage->setToken(null); } if (null !== $this->logger) { $this->logger->info('Basic authentication failed for user.', ['username' => $username, 'exception' => $e]); } if ($this->ignoreFailure) { return; } $event->setResponse($this->authenticationEntryPoint->start($request, $e)); } } /** * Call this method if your authentication token is stored to a session. * * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { $this->sessionStrategy = $sessionStrategy; } private function migrateSession(Request $request, TokenInterface $token, ?TokenInterface $previousToken) { if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } if ($previousToken) { $user = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = \method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); if ('' !== ($user ?? '') && $user === $previousUser) { return; } } $this->sessionStrategy->onAuthentication($request, $token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AbstractPreAuthenticatedListener::class); /** * AbstractPreAuthenticatedListener is the base class for all listener that * authenticates users based on a pre-authenticated request (like a certificate * for instance). * * @author Fabien Potencier * * @internal * * @deprecated since Symfony 5.3, use the new authenticator system instead */ abstract class AbstractPreAuthenticatedListener extends AbstractListener { protected $logger; private $tokenStorage; private $authenticationManager; private $providerKey; private $dispatcher; private $sessionStrategy; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null) { $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; $this->providerKey = $providerKey; $this->logger = $logger; $this->dispatcher = $dispatcher; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { try { $request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request)); } catch (BadCredentialsException $e) { $this->clearToken($e); return \false; } return \true; } /** * Handles pre-authentication. */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); [$user, $credentials] = $request->attributes->get('_pre_authenticated_data'); $request->attributes->remove('_pre_authenticated_data'); if (null !== $this->logger) { $this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]); } if (null !== ($token = $this->tokenStorage->getToken())) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 if ($token instanceof PreAuthenticatedToken && $this->providerKey == $token->getFirewallName() && $token->isAuthenticated() && (\method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $user) { return; } } if (null !== $this->logger) { $this->logger->debug('Trying to pre-authenticate user.', ['username' => (string) $user]); } try { $previousToken = $token; $token = $this->authenticationManager->authenticate(new PreAuthenticatedToken($user, $credentials, $this->providerKey)); if (null !== $this->logger) { $this->logger->info('Pre-authentication successful.', ['token' => (string) $token]); } $this->migrateSession($request, $token, $previousToken); $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { $loginEvent = new InteractiveLoginEvent($request, $token); $this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } } catch (AuthenticationException $e) { $this->clearToken($e); } } /** * Call this method if your authentication token is stored to a session. * * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { $this->sessionStrategy = $sessionStrategy; } /** * Clears a PreAuthenticatedToken for this provider (if present). */ private function clearToken(AuthenticationException $exception) { $token = $this->tokenStorage->getToken(); if ($token instanceof PreAuthenticatedToken && $this->providerKey === $token->getFirewallName()) { $this->tokenStorage->setToken(null); if (null !== $this->logger) { $this->logger->info('Cleared security token due to an exception.', ['exception' => $exception]); } } } /** * Gets the user and credentials from the Request. * * @return array An array composed of the user and the credentials */ protected abstract function getPreAuthenticatedData(Request $request); private function migrateSession(Request $request, TokenInterface $token, ?TokenInterface $previousToken) { if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } if ($previousToken) { $user = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = \method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); if ('' !== ($user ?? '') && $user === $previousUser) { return; } } $this->sessionStrategy->onAuthentication($request, $token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccessDeniedException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\LazyResponseException; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogoutException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\Util\TargetPathTrait; /** * ExceptionListener catches authentication exception and converts them to * Response instances. * * @author Fabien Potencier * * @final */ class ExceptionListener { use TargetPathTrait; private $tokenStorage; private $firewallName; private $accessDeniedHandler; private $authenticationEntryPoint; private $authenticationTrustResolver; private $errorPage; private $logger; private $httpUtils; private $stateless; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $trustResolver, HttpUtils $httpUtils, string $firewallName, ?AuthenticationEntryPointInterface $authenticationEntryPoint = null, ?string $errorPage = null, ?AccessDeniedHandlerInterface $accessDeniedHandler = null, ?LoggerInterface $logger = null, bool $stateless = \false) { $this->tokenStorage = $tokenStorage; $this->accessDeniedHandler = $accessDeniedHandler; $this->httpUtils = $httpUtils; $this->firewallName = $firewallName; $this->authenticationEntryPoint = $authenticationEntryPoint; $this->authenticationTrustResolver = $trustResolver; $this->errorPage = $errorPage; $this->logger = $logger; $this->stateless = $stateless; } /** * Registers a onKernelException listener to take care of security exceptions. */ public function register(EventDispatcherInterface $dispatcher) { $dispatcher->addListener(KernelEvents::EXCEPTION, [$this, 'onKernelException'], 1); } /** * Unregisters the dispatcher. */ public function unregister(EventDispatcherInterface $dispatcher) { $dispatcher->removeListener(KernelEvents::EXCEPTION, [$this, 'onKernelException']); } /** * Handles security related exceptions. */ public function onKernelException(ExceptionEvent $event) { $exception = $event->getThrowable(); do { if ($exception instanceof AuthenticationException) { $this->handleAuthenticationException($event, $exception); return; } if ($exception instanceof AccessDeniedException) { $this->handleAccessDeniedException($event, $exception); return; } if ($exception instanceof LazyResponseException) { $event->setResponse($exception->getResponse()); return; } if ($exception instanceof LogoutException) { $this->handleLogoutException($event, $exception); return; } } while (null !== ($exception = $exception->getPrevious())); } private function handleAuthenticationException(ExceptionEvent $event, AuthenticationException $exception) : void { if (null !== $this->logger) { $this->logger->info('An AuthenticationException was thrown; redirecting to authentication entry point.', ['exception' => $exception]); } try { $event->setResponse($this->startAuthentication($event->getRequest(), $exception)); $event->allowCustomResponseCode(); } catch (\Exception $e) { $event->setThrowable($e); } } private function handleAccessDeniedException(ExceptionEvent $event, AccessDeniedException $exception) { $event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception)); $token = $this->tokenStorage->getToken(); if (!$this->authenticationTrustResolver->isFullFledged($token)) { if (null !== $this->logger) { $this->logger->debug('Access denied, the user is not fully authenticated; redirecting to authentication entry point.', ['exception' => $exception]); } try { $insufficientAuthenticationException = new InsufficientAuthenticationException('Full authentication is required to access this resource.', 0, $exception); if (null !== $token) { $insufficientAuthenticationException->setToken($token); } $event->setResponse($this->startAuthentication($event->getRequest(), $insufficientAuthenticationException)); } catch (\Exception $e) { $event->setThrowable($e); } return; } if (null !== $this->logger) { $this->logger->debug('Access denied, the user is neither anonymous, nor remember-me.', ['exception' => $exception]); } try { if (null !== $this->accessDeniedHandler) { $response = $this->accessDeniedHandler->handle($event->getRequest(), $exception); if ($response instanceof Response) { $event->setResponse($response); } } elseif (null !== $this->errorPage) { $subRequest = $this->httpUtils->createRequest($event->getRequest(), $this->errorPage); $subRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $exception); $event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST, \true)); $event->allowCustomResponseCode(); } } catch (\Exception $e) { if (null !== $this->logger) { $this->logger->error('An exception was thrown when handling an AccessDeniedException.', ['exception' => $e]); } $event->setThrowable(new \RuntimeException('Exception thrown when handling an exception.', 0, $e)); } } private function handleLogoutException(ExceptionEvent $event, LogoutException $exception) : void { $event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception)); if (null !== $this->logger) { $this->logger->info('A LogoutException was thrown; wrapping with AccessDeniedHttpException', ['exception' => $exception]); } } private function startAuthentication(Request $request, AuthenticationException $authException) : Response { if (null === $this->authenticationEntryPoint) { $this->throwUnauthorizedException($authException); } if (null !== $this->logger) { $this->logger->debug('Calling Authentication entry point.'); } if (!$this->stateless) { $this->setTargetPath($request); } if ($authException instanceof AccountStatusException) { // remove the security token to prevent infinite redirect loops $this->tokenStorage->setToken(null); if (null !== $this->logger) { $this->logger->info('The security token was removed due to an AccountStatusException.', ['exception' => $authException]); } } try { $response = $this->authenticationEntryPoint->start($request, $authException); } catch (NotAnEntryPointException $e) { $this->throwUnauthorizedException($authException); } if (!$response instanceof Response) { $given = \get_debug_type($response); throw new \LogicException(\sprintf('The "%s::start()" method must return a Response object ("%s" returned).', \get_debug_type($this->authenticationEntryPoint), $given)); } return $response; } protected function setTargetPath(Request $request) { // session isn't required when using HTTP basic authentication mechanism for example if ($request->hasSession() && $request->isMethodSafe() && !$request->isXmlHttpRequest()) { $this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri()); } } private function throwUnauthorizedException(AuthenticationException $authException) { if (null !== $this->logger) { $this->logger->notice(\sprintf('No Authentication entry point configured, returning a %s HTTP response. Configure "entry_point" on the firewall "%s" if you want to modify the response.', Response::HTTP_UNAUTHORIZED, $this->firewallName)); } throw new HttpException(Response::HTTP_UNAUTHORIZED, $authException->getMessage(), $authException, [], $authException->getCode()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; /** * Can be implemented by firewall listeners. * * @author Christian Scheb * @author Nicolas Grekas * @author Robin Chalas */ interface FirewallListenerInterface { /** * Tells whether the authenticate() method should be called or not depending on the incoming request. * * Returning null means authenticate() can be called lazily when accessing the token storage. */ public function supports(Request $request) : ?bool; /** * Does whatever is required to authenticate the request, typically calling $event->setResponse() internally. */ public function authenticate(RequestEvent $event); /** * Defines the priority of the listener. * The higher the number, the earlier a listener is executed. */ public static function getPriority() : int; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Http\AccessMapInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * ChannelListener switches the HTTP protocol based on the access control * configuration. * * @author Fabien Potencier * * @final */ class ChannelListener extends AbstractListener { private $map; private $authenticationEntryPoint = null; private $logger; private $httpPort; private $httpsPort; public function __construct( AccessMapInterface $map, /* LoggerInterface */ $logger = null, /* int */ $httpPort = 80, /* int */ $httpsPort = 443 ) { if ($logger instanceof AuthenticationEntryPointInterface) { \trigger_deprecation('symfony/security-http', '5.4', 'The "$authenticationEntryPoint" argument of "%s()" is deprecated.', __METHOD__); $this->authenticationEntryPoint = $logger; $nrOfArgs = \func_num_args(); $logger = $nrOfArgs > 2 ? \func_get_arg(2) : null; $httpPort = $nrOfArgs > 3 ? \func_get_arg(3) : 80; $httpsPort = $nrOfArgs > 4 ? \func_get_arg(4) : 443; } if (null !== $logger && !$logger instanceof LoggerInterface) { throw new \TypeError(\sprintf('Argument "$logger" of "%s()" must be instance of "%s", "%s" given.', __METHOD__, LoggerInterface::class, \get_debug_type($logger))); } $this->map = $map; $this->logger = $logger; $this->httpPort = $httpPort; $this->httpsPort = $httpsPort; } /** * Handles channel management. */ public function supports(Request $request) : ?bool { [, $channel] = $this->map->getPatterns($request); if ('https' === $channel && !$request->isSecure()) { if (null !== $this->logger) { if ('https' === $request->headers->get('X-Forwarded-Proto')) { $this->logger->info('Redirecting to HTTPS. ("X-Forwarded-Proto" header is set to "https" - did you set "trusted_proxies" correctly?)'); } elseif (\str_contains($request->headers->get('Forwarded', ''), 'proto=https')) { $this->logger->info('Redirecting to HTTPS. ("Forwarded" header is set to "proto=https" - did you set "trusted_proxies" correctly?)'); } else { $this->logger->info('Redirecting to HTTPS.'); } } return \true; } if ('http' === $channel && $request->isSecure()) { if (null !== $this->logger) { $this->logger->info('Redirecting to HTTP.'); } return \true; } return \false; } public function authenticate(RequestEvent $event) { $request = $event->getRequest(); $event->setResponse($this->createRedirectResponse($request)); } private function createRedirectResponse(Request $request) : RedirectResponse { if (null !== $this->authenticationEntryPoint) { return $this->authenticationEntryPoint->start($request); } $scheme = $request->isSecure() ? 'http' : 'https'; if ('http' === $scheme && 80 != $this->httpPort) { $port = ':' . $this->httpPort; } elseif ('https' === $scheme && 443 != $this->httpsPort) { $port = ':' . $this->httpsPort; } else { $port = ''; } $qs = $request->getQueryString(); if (null !== $qs) { $qs = '?' . $qs; } $url = $scheme . '://' . $request->getHost() . $port . $request->getBaseUrl() . $request->getPathInfo() . $qs; return new RedirectResponse($url, 301); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\JsonResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\AccessException; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccess; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessorInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', UsernamePasswordJsonAuthenticationListener::class); /** * UsernamePasswordJsonAuthenticationListener is a stateless implementation of * an authentication via a JSON document composed of a username and a password. * * @author Kévin Dunglas * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class UsernamePasswordJsonAuthenticationListener extends AbstractListener { private $tokenStorage; private $authenticationManager; private $httpUtils; private $providerKey; private $successHandler; private $failureHandler; private $options; private $logger; private $eventDispatcher; private $propertyAccessor; private $sessionStrategy; /** * @var TranslatorInterface|null */ private $translator; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpUtils $httpUtils, string $providerKey, ?AuthenticationSuccessHandlerInterface $successHandler = null, ?AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], ?LoggerInterface $logger = null, ?EventDispatcherInterface $eventDispatcher = null, ?PropertyAccessorInterface $propertyAccessor = null) { $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; $this->httpUtils = $httpUtils; $this->providerKey = $providerKey; $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; $this->logger = $logger; $this->eventDispatcher = $eventDispatcher; $this->options = \array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } public function supports(Request $request) : ?bool { if (!\str_contains($request->getRequestFormat() ?? '', 'json') && !\str_contains($request->getContentType() ?? '', 'json')) { return \false; } if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { return \false; } return \true; } /** * {@inheritdoc} */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); $data = \json_decode($request->getContent()); $previousToken = $this->tokenStorage->getToken(); try { if (!$data instanceof \stdClass) { throw new BadRequestHttpException('Invalid JSON.'); } try { $username = $this->propertyAccessor->getValue($data, $this->options['username_path']); } catch (AccessException $e) { throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', $this->options['username_path']), $e); } try { $password = $this->propertyAccessor->getValue($data, $this->options['password_path']); } catch (AccessException $e) { throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', $this->options['password_path']), $e); } if (!\is_string($username)) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', $this->options['username_path'])); } if (\strlen($username) > Security::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Invalid username.'); } if (!\is_string($password)) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', $this->options['password_path'])); } $token = new UsernamePasswordToken($username, $password, $this->providerKey); $authenticatedToken = $this->authenticationManager->authenticate($token); $response = $this->onSuccess($request, $authenticatedToken, $previousToken); } catch (AuthenticationException $e) { $response = $this->onFailure($request, $e); } catch (BadRequestHttpException $e) { $request->setRequestFormat('json'); throw $e; } if (null === $response) { return; } $event->setResponse($response); } private function onSuccess(Request $request, TokenInterface $token, ?TokenInterface $previousToken) : ?Response { if (null !== $this->logger) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 $this->logger->info('User has been authenticated successfully.', ['username' => \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]); } $this->migrateSession($request, $token, $previousToken); $this->tokenStorage->setToken($token); if (null !== $this->eventDispatcher) { $loginEvent = new InteractiveLoginEvent($request, $token); $this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } if (!$this->successHandler) { return null; // let the original request succeeds } $response = $this->successHandler->onAuthenticationSuccess($request, $token); if (!$response instanceof Response) { throw new \RuntimeException('Authentication Success Handler did not return a Response.'); } return $response; } private function onFailure(Request $request, AuthenticationException $failed) : Response { if (null !== $this->logger) { $this->logger->info('Authentication request failed.', ['exception' => $failed]); } $token = $this->tokenStorage->getToken(); if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) { $this->tokenStorage->setToken(null); } if (!$this->failureHandler) { if (null !== $this->translator) { $errorMessage = $this->translator->trans($failed->getMessageKey(), $failed->getMessageData(), 'security'); } else { $errorMessage = \strtr($failed->getMessageKey(), $failed->getMessageData()); } return new JsonResponse(['error' => $errorMessage], 401); } $response = $this->failureHandler->onAuthenticationFailure($request, $failed); if (!$response instanceof Response) { throw new \RuntimeException('Authentication Failure Handler did not return a Response.'); } return $response; } /** * Call this method if your authentication token is stored to a session. * * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { $this->sessionStrategy = $sessionStrategy; } public function setTranslator(TranslatorInterface $translator) { $this->translator = $translator; } private function migrateSession(Request $request, TokenInterface $token, ?TokenInterface $previousToken) { if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } if ($previousToken) { $user = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = \method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); if ('' !== ($user ?? '') && $user === $previousUser) { return; } } $this->sessionStrategy->onAuthentication($request, $token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccessDeniedException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\SwitchUserEvent; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * SwitchUserListener allows a user to impersonate another one temporarily * (like the Unix su command). * * @author Fabien Potencier * * @final */ class SwitchUserListener extends AbstractListener { public const EXIT_VALUE = '_exit'; private $tokenStorage; private $provider; private $userChecker; private $firewallName; private $accessDecisionManager; private $usernameParameter; private $role; private $logger; private $dispatcher; private $stateless; public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, ?LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', ?EventDispatcherInterface $dispatcher = null, bool $stateless = \false) { if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); } $this->tokenStorage = $tokenStorage; $this->provider = $provider; $this->userChecker = $userChecker; $this->firewallName = $firewallName; $this->accessDecisionManager = $accessDecisionManager; $this->usernameParameter = $usernameParameter; $this->role = $role; $this->logger = $logger; $this->dispatcher = $dispatcher; $this->stateless = $stateless; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { // usernames can be falsy $username = $request->get($this->usernameParameter); if (null === $username || '' === $username) { $username = $request->headers->get($this->usernameParameter); } // if it's still "empty", nothing to do. if (null === $username || '' === $username) { return \false; } $request->attributes->set('_switch_user_username', $username); return \true; } /** * Handles the switch to another user. * * @throws \LogicException if switching to a user failed */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); $username = $request->attributes->get('_switch_user_username'); $request->attributes->remove('_switch_user_username'); if (null === $this->tokenStorage->getToken()) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } if (self::EXIT_VALUE === $username) { $this->tokenStorage->setToken($this->attemptExitUser($request)); } else { try { $this->tokenStorage->setToken($this->attemptSwitchUser($request, $username)); } catch (AuthenticationException $e) { // Generate 403 in any conditions to prevent user enumeration vulnerabilities throw new AccessDeniedException('Switch User failed: ' . $e->getMessage(), $e); } } if (!$this->stateless) { $request->query->remove($this->usernameParameter); $request->server->set('QUERY_STRING', \http_build_query($request->query->all(), '', '&')); $response = new RedirectResponse($request->getUri(), 302); $event->setResponse($response); } } /** * Attempts to switch to another user and returns the new token if successfully switched. * * @throws \LogicException * @throws AccessDeniedException */ private function attemptSwitchUser(Request $request, string $username) : ?TokenInterface { $token = $this->tokenStorage->getToken(); $originalToken = $this->getOriginalToken($token); if (null !== $originalToken) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 if ((\method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $username) { return $token; } // User already switched, exit before seamlessly switching to another user $token = $this->attemptExitUser($request); } // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 $currentUsername = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $nonExistentUsername = '_' . \md5(\random_bytes(8) . $username); // To protect against user enumeration via timing measurements // we always load both successfully and unsuccessfully $methodName = 'loadUserByIdentifier'; if (!\method_exists($this->provider, $methodName)) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->provider)); $methodName = 'loadUserByUsername'; } try { $user = $this->provider->{$methodName}($username); try { $this->provider->{$methodName}($nonExistentUsername); } catch (\Exception $e) { } } catch (AuthenticationException $e) { $this->provider->{$methodName}($currentUsername); throw $e; } if (\false === $this->accessDecisionManager->decide($token, [$this->role], $user)) { $exception = new AccessDeniedException(); $exception->setAttributes($this->role); throw $exception; } if (null !== $this->logger) { $this->logger->info('Attempting to switch to user.', ['username' => $username]); } $this->userChecker->checkPostAuth($user); $roles = $user->getRoles(); $roles[] = 'ROLE_PREVIOUS_ADMIN'; $originatedFromUri = \str_replace('/&', '/?', \preg_replace('#[&?]' . $this->usernameParameter . '=[^&]*#', '', $request->getRequestUri())); $token = new SwitchUserToken($user, $this->firewallName, $roles, $token, $originatedFromUri); if (null !== $this->dispatcher) { $switchEvent = new SwitchUserEvent($request, $token->getUser(), $token); $this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER); // use the token from the event in case any listeners have replaced it. $token = $switchEvent->getToken(); } return $token; } /** * Attempts to exit from an already switched user and returns the original token. * * @throws AuthenticationCredentialsNotFoundException */ private function attemptExitUser(Request $request) : TokenInterface { if (null === ($currentToken = $this->tokenStorage->getToken()) || null === ($original = $this->getOriginalToken($currentToken))) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } if (null !== $this->dispatcher && $original->getUser() instanceof UserInterface) { $user = $this->provider->refreshUser($original->getUser()); $original->setUser($user); $switchEvent = new SwitchUserEvent($request, $user, $original); $this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER); $original = $switchEvent->getToken(); } return $original; } private function getOriginalToken(TokenInterface $token) : ?TokenInterface { if ($token instanceof SwitchUserToken) { return $token->getOriginalToken(); } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcher; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogoutException; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfToken; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * LogoutListener logout users. * * @author Fabien Potencier * * @final */ class LogoutListener extends AbstractListener { private $tokenStorage; private $options; private $httpUtils; private $csrfTokenManager; private $eventDispatcher; /** * @param EventDispatcherInterface $eventDispatcher * @param array $options An array of options to process a logout attempt */ public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, $eventDispatcher, array $options = [], ?CsrfTokenManagerInterface $csrfTokenManager = null) { if (!$eventDispatcher instanceof EventDispatcherInterface) { \trigger_deprecation('symfony/security-http', '5.1', 'Passing a logout success handler to "%s" is deprecated, pass an instance of "%s" instead.', __METHOD__, EventDispatcherInterface::class); if (!$eventDispatcher instanceof LogoutSuccessHandlerInterface) { throw new \TypeError(\sprintf('Argument 3 of "%s" must be instance of "%s" or "%s", "%s" given.', __METHOD__, EventDispatcherInterface::class, LogoutSuccessHandlerInterface::class, \get_debug_type($eventDispatcher))); } $successHandler = $eventDispatcher; $eventDispatcher = new EventDispatcher(); $eventDispatcher->addListener(LogoutEvent::class, function (LogoutEvent $event) use($successHandler) { $event->setResponse($r = $successHandler->onLogoutSuccess($event->getRequest())); }); } $this->tokenStorage = $tokenStorage; $this->httpUtils = $httpUtils; $this->options = \array_merge(['csrf_parameter' => '_csrf_token', 'csrf_token_id' => 'logout', 'logout_path' => '/logout'], $options); $this->csrfTokenManager = $csrfTokenManager; $this->eventDispatcher = $eventDispatcher; } /** * @deprecated since Symfony 5.1 */ public function addHandler(LogoutHandlerInterface $handler) { \trigger_deprecation('symfony/security-http', '5.1', 'Calling "%s" is deprecated, register a listener on the "%s" event instead.', __METHOD__, LogoutEvent::class); $this->eventDispatcher->addListener(LogoutEvent::class, function (LogoutEvent $event) use($handler) { if (null === $event->getResponse()) { throw new LogicException(\sprintf('No response was set for this logout action. Make sure the DefaultLogoutListener or another listener has set the response before "%s" is called.', __CLASS__)); } $handler->logout($event->getRequest(), $event->getResponse(), $event->getToken()); }); } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { return $this->requiresLogout($request); } /** * Performs the logout if requested. * * If a CsrfTokenManagerInterface instance is available, it will be used to * validate the request. * * @throws LogoutException if the CSRF token is invalid * @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); if (!\is_string($csrfToken) || \false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new LogoutException('Invalid CSRF token.'); } } $logoutEvent = new LogoutEvent($request, $this->tokenStorage->getToken()); $this->eventDispatcher->dispatch($logoutEvent); if (!($response = $logoutEvent->getResponse())) { throw new \RuntimeException('No logout listener set the Response, make sure at least the DefaultLogoutListener is registered.'); } $this->tokenStorage->setToken(null); $event->setResponse($response); } /** * Whether this request is asking for logout. * * The default implementation only processed requests to a specific path, * but a subclass could change this to logout requests where * certain parameters is present. */ protected function requiresLogout(Request $request) : bool { return isset($this->options['logout_path']) && $this->httpUtils->checkRequestPath($request, $this->options['logout_path']); } public static function getPriority() : int { return -127; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfToken; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', UsernamePasswordFormAuthenticationListener::class); /** * UsernamePasswordFormAuthenticationListener is the default implementation of * an authentication via a simple form composed of a username and a password. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener { private $csrfTokenManager; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = [], ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, ?CsrfTokenManagerInterface $csrfTokenManager = null) { parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, \array_merge(['username_parameter' => '_username', 'password_parameter' => '_password', 'csrf_parameter' => '_csrf_token', 'csrf_token_id' => 'authenticate', 'post_only' => \true], $options), $logger, $dispatcher); $this->csrfTokenManager = $csrfTokenManager; } /** * {@inheritdoc} */ protected function requiresAuthentication(Request $request) { if ($this->options['post_only'] && !$request->isMethod('POST')) { return \false; } return parent::requiresAuthentication($request); } /** * {@inheritdoc} */ protected function attemptAuthentication(Request $request) { if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); if (!\is_string($csrfToken) || \false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } if ($this->options['post_only']) { $username = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']); $password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']); } else { $username = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']); $password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']); } if (!\is_string($username) && (!\is_object($username) || !\method_exists($username, '__toString'))) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \get_debug_type($username))); } $username = \trim($username); if (\strlen($username) > Security::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Invalid username.'); } if (null === $password) { throw new \LogicException(\sprintf('The key "%s" cannot be null; check that the password field name of the form matches.', $this->options['password_parameter'])); } $request->getSession()->set(Security::LAST_USERNAME, $username); return $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\SessionUnavailableException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AbstractAuthenticationListener::class); /** * The AbstractAuthenticationListener is the preferred base class for all * browser-/HTTP-based authentication requests. * * Subclasses likely have to implement the following: * - an TokenInterface to hold authentication related data * - an AuthenticationProvider to perform the actual authentication of the * token, retrieve the UserInterface implementation from a database, and * perform the specific account checks using the UserChecker * * By default, this listener only is active for a specific path, e.g. * /login_check. If you want to change this behavior, you can overwrite the * requiresAuthentication() method. * * @author Fabien Potencier * @author Johannes M. Schmitt * * @deprecated since Symfony 5.3, use the new authenticator system instead */ abstract class AbstractAuthenticationListener extends AbstractListener { protected $options; protected $logger; protected $authenticationManager; protected $providerKey; protected $httpUtils; private $tokenStorage; private $sessionStrategy; private $dispatcher; private $successHandler; private $failureHandler; private $rememberMeServices; /** * @throws \InvalidArgumentException */ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = [], ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); } $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; $this->sessionStrategy = $sessionStrategy; $this->providerKey = $providerKey; $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; $this->options = \array_merge(['check_path' => '/login_check', 'login_path' => '/login', 'always_use_default_target_path' => \false, 'default_target_path' => '/', 'target_path_parameter' => '_target_path', 'use_referer' => \false, 'failure_path' => null, 'failure_forward' => \false, 'require_previous_session' => \true], $options); $this->logger = $logger; $this->dispatcher = $dispatcher; $this->httpUtils = $httpUtils; } /** * Sets the RememberMeServices implementation to use. */ public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices) { $this->rememberMeServices = $rememberMeServices; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { return $this->requiresAuthentication($request); } /** * Handles form based authentication. * * @throws \RuntimeException * @throws SessionUnavailableException */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); if (!$request->hasSession()) { throw new \RuntimeException('This authentication method requires a session.'); } try { if ($this->options['require_previous_session'] && !$request->hasPreviousSession()) { throw new SessionUnavailableException('Your session has timed out, or you have disabled cookies.'); } $previousToken = $this->tokenStorage->getToken(); if (null === ($returnValue = $this->attemptAuthentication($request))) { return; } if ($returnValue instanceof TokenInterface) { $this->migrateSession($request, $returnValue, $previousToken); $response = $this->onSuccess($request, $returnValue); } elseif ($returnValue instanceof Response) { $response = $returnValue; } else { throw new \RuntimeException('attemptAuthentication() must either return a Response, an implementation of TokenInterface, or null.'); } } catch (AuthenticationException $e) { $response = $this->onFailure($request, $e); } $event->setResponse($response); } /** * Whether this request requires authentication. * * The default implementation only processes requests to a specific path, * but a subclass could change this to only authenticate requests where a * certain parameters is present. * * @return bool */ protected function requiresAuthentication(Request $request) { return $this->httpUtils->checkRequestPath($request, $this->options['check_path']); } /** * Performs authentication. * * @return TokenInterface|Response|null The authenticated token, null if full authentication is not possible, or a Response * * @throws AuthenticationException if the authentication fails */ protected abstract function attemptAuthentication(Request $request); private function onFailure(Request $request, AuthenticationException $failed) : Response { if (null !== $this->logger) { $this->logger->info('Authentication request failed.', ['exception' => $failed]); } $token = $this->tokenStorage->getToken(); if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) { $this->tokenStorage->setToken(null); } $response = $this->failureHandler->onAuthenticationFailure($request, $failed); if (!$response instanceof Response) { throw new \RuntimeException('Authentication Failure Handler did not return a Response.'); } return $response; } private function onSuccess(Request $request, TokenInterface $token) : Response { if (null !== $this->logger) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 $this->logger->info('User has been authenticated successfully.', ['username' => \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]); } $this->tokenStorage->setToken($token); $session = $request->getSession(); $session->remove(Security::AUTHENTICATION_ERROR); $session->remove(Security::LAST_USERNAME); if (null !== $this->dispatcher) { $loginEvent = new InteractiveLoginEvent($request, $token); $this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } $response = $this->successHandler->onAuthenticationSuccess($request, $token); if (!$response instanceof Response) { throw new \RuntimeException('Authentication Success Handler did not return a Response.'); } if (null !== $this->rememberMeServices) { $this->rememberMeServices->loginSuccess($request, $response, $token); } return $response; } private function migrateSession(Request $request, TokenInterface $token, ?TokenInterface $previousToken) { if ($previousToken) { $user = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = \method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); if ('' !== ($user ?? '') && $user === $previousUser) { return; } } $this->sessionStrategy->onAuthentication($request, $token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', X509AuthenticationListener::class); /** * X509 authentication listener. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class X509AuthenticationListener extends AbstractPreAuthenticatedListener { private $userKey; private $credentialKey; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialKey = 'SSL_CLIENT_S_DN', ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null) { parent::__construct($tokenStorage, $authenticationManager, $providerKey, $logger, $dispatcher); $this->userKey = $userKey; $this->credentialKey = $credentialKey; } /** * {@inheritdoc} */ protected function getPreAuthenticatedData(Request $request) { $user = null; if ($request->server->has($this->userKey)) { $user = $request->server->get($this->userKey); } elseif ($request->server->has($this->credentialKey) && \preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $request->server->get($this->credentialKey), $matches)) { $user = $matches[1]; } if (null === $user) { throw new BadCredentialsException(\sprintf('SSL credentials not found: "%s", "%s".', $this->userKey, $this->credentialKey)); } return [$user, $request->server->get($this->credentialKey, '')]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', RemoteUserAuthenticationListener::class); /** * REMOTE_USER authentication listener. * * @author Fabien Potencier * @author Maxime Douailin * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class RemoteUserAuthenticationListener extends AbstractPreAuthenticatedListener { private $userKey; public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, string $userKey = 'REMOTE_USER', ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null) { parent::__construct($tokenStorage, $authenticationManager, $providerKey, $logger, $dispatcher); $this->userKey = $userKey; } /** * {@inheritdoc} */ protected function getPreAuthenticatedData(Request $request) { if (!$request->server->has($this->userKey)) { throw new BadCredentialsException(\sprintf('User key was not found: "%s".', $this->userKey)); } return [$request->server->get($this->userKey), null]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Attribute\CurrentUser; /** * Supports the argument type of {@see UserInterface}. * * @author Iltar van der Berg */ final class UserValueResolver implements ArgumentValueResolverInterface { private $tokenStorage; public function __construct(TokenStorageInterface $tokenStorage) { $this->tokenStorage = $tokenStorage; } public function supports(Request $request, ArgumentMetadata $argument) : bool { // with the attribute, the type can be any UserInterface implementation // otherwise, the type must be UserInterface if (UserInterface::class !== $argument->getType() && !$argument->getAttributes(CurrentUser::class, ArgumentMetadata::IS_INSTANCEOF)) { return \false; } $token = $this->tokenStorage->getToken(); if (!$token instanceof TokenInterface) { return \false; } $user = $token->getUser(); // in case it's not an object we cannot do anything with it; E.g. "anon." // @deprecated since 5.4 return $user instanceof UserInterface; } public function resolve(Request $request, ArgumentMetadata $argument) : iterable { (yield $this->tokenStorage->getToken()->getUser()); } } Security Component - HTTP Integration ===================================== The Security HTTP component provides an HTTP integration of the Security Core component. It allows securing (parts of) your application using firewalls and provides authenticators to authenticate visitors. Getting Started --------------- ``` $ composer require symfony/security-http ``` Sponsor ------- The Security component for Symfony 5.4/6.0 is [backed][1] by [SymfonyCasts][2]. Learn Symfony faster by watching real projects being built and actively coding along with them. SymfonyCasts bridges that learning gap, bringing you video tutorials and coding challenges. Code on! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/security.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://symfonycasts.com [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" interface is deprecated, use "%s" instead.', RememberMeServicesInterface::class, RememberMeHandlerInterface::class); /** * Interface that needs to be implemented by classes which provide remember-me * capabilities. * * We provide two implementations out-of-the-box: * - TokenBasedRememberMeServices (does not require a TokenProvider) * - PersistentTokenBasedRememberMeServices (requires a TokenProvider) * * @author Johannes M. Schmitt * * @method logout(Request $request, Response $response, TokenInterface $token) * * @deprecated since Symfony 5.4, use {@see RememberMeHandlerInterface} instead */ interface RememberMeServicesInterface { /** * This attribute name can be used by the implementation if it needs to set * a cookie on the Request when there is no actual Response, yet. */ public const COOKIE_ATTR_NAME = '_security_remember_me_cookie'; /** * This method will be called whenever the TokenStorage does not contain * a TokenInterface object and the framework wishes to provide an implementation * with an opportunity to authenticate the request using remember-me capabilities. * * No attempt whatsoever is made to determine whether the browser has requested * remember-me services or presented a valid cookie. Any and all such determinations * are left to the implementation of this method. * * If a browser has presented an unauthorised cookie for whatever reason, * make sure to throw an AuthenticationException as this will consequentially * result in a call to loginFail() and therefore an invalidation of the cookie. * * @return TokenInterface|null */ public function autoLogin(Request $request); /** * Called whenever an interactive authentication attempt was made, but the * credentials supplied by the user were missing or otherwise invalid. * * This method needs to take care of invalidating the cookie. */ public function loginFail(Request $request, ?\Exception $exception = null); /** * Called whenever an interactive authentication attempt is successful * (e.g. a form login). * * An implementation may always set a remember-me cookie in the Response, * although this is not recommended. * * Instead, implementations should typically look for a request parameter * (such as an HTTP POST parameter) that indicates the browser has explicitly * requested for the authentication to be remembered. */ public function loginSuccess(Request $request, Response $response, TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CookieTheftException; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', PersistentTokenBasedRememberMeServices::class, PersistentRememberMeHandler::class); /** * Concrete implementation of the RememberMeServicesInterface which needs * an implementation of TokenProviderInterface for providing remember-me * capabilities. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.4, use {@see PersistentRememberMeHandler} instead */ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices { private const HASHED_TOKEN_PREFIX = 'sha256_'; /** @var TokenProviderInterface */ private $tokenProvider; public function setTokenProvider(TokenProviderInterface $tokenProvider) { $this->tokenProvider = $tokenProvider; } /** * {@inheritdoc} */ protected function cancelCookie(Request $request) { // Delete cookie on the client parent::cancelCookie($request); // Delete cookie from the tokenProvider if (null !== ($cookie = $request->cookies->get($this->options['name'])) && 2 === \count($parts = $this->decodeCookie($cookie))) { [$series] = $parts; $this->tokenProvider->deleteTokenBySeries($series); } } /** * {@inheritdoc} */ protected function processAutoLoginCookie(array $cookieParts, Request $request) { if (2 !== \count($cookieParts)) { throw new AuthenticationException('The cookie is invalid.'); } [$series, $tokenValue] = $cookieParts; $persistentToken = $this->tokenProvider->loadTokenBySeries($series); if (!$this->isTokenValueValid($persistentToken, $tokenValue)) { throw new CookieTheftException('This token was already used. The account is possibly compromised.'); } if ($persistentToken->getLastUsed()->getTimestamp() + $this->options['lifetime'] < \time()) { throw new AuthenticationException('The cookie has expired.'); } $tokenValue = \base64_encode(\random_bytes(64)); $this->tokenProvider->updateToken($series, $this->generateHash($tokenValue), new \DateTime()); $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], $this->encodeCookie([$series, $tokenValue]), \time() + $this->options['lifetime'], $this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], \false, $this->options['samesite'])); $userProvider = $this->getUserProvider($persistentToken->getClass()); // @deprecated since Symfony 5.3, change to $persistentToken->getUserIdentifier() in 6.0 if (\method_exists($persistentToken, 'getUserIdentifier')) { $userIdentifier = $persistentToken->getUserIdentifier(); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier()" in persistent token "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($persistentToken)); $userIdentifier = $persistentToken->getUsername(); } // @deprecated since Symfony 5.3, change to $userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($userProvider, 'loadUserByIdentifier')) { return $userProvider->loadUserByIdentifier($userIdentifier); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($userProvider)); return $userProvider->loadUserByUsername($userIdentifier); } } /** * {@inheritdoc} */ protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token) { $series = \base64_encode(\random_bytes(64)); $tokenValue = \base64_encode(\random_bytes(64)); $this->tokenProvider->createNewToken(new PersistentToken( \get_class($user = $token->getUser()), // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $series, $this->generateHash($tokenValue), new \DateTime() )); $response->headers->setCookie(new Cookie($this->options['name'], $this->encodeCookie([$series, $tokenValue]), \time() + $this->options['lifetime'], $this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], \false, $this->options['samesite'])); } private function generateHash(string $tokenValue) : string { return self::HASHED_TOKEN_PREFIX . \hash_hmac('sha256', $tokenValue, $this->getSecret()); } private function isTokenValueValid(PersistentTokenInterface $persistentToken, string $tokenValue) : bool { if (0 === \strpos($persistentToken->getTokenValue(), self::HASHED_TOKEN_PREFIX)) { return \hash_equals($persistentToken->getTokenValue(), $this->generateHash($tokenValue)); } return \hash_equals($persistentToken->getTokenValue(), $tokenValue); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * Adds remember-me cookies to the Response. * * @author Johannes M. Schmitt * * @final */ class ResponseListener implements EventSubscriberInterface { /** * This attribute name can be used by the implementation if it needs to set * a cookie on the Request when there is no actual Response, yet. */ public const COOKIE_ATTR_NAME = '_security_remember_me_cookie'; public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); $response = $event->getResponse(); if ($request->attributes->has(self::COOKIE_ATTR_NAME)) { $response->headers->setCookie($request->attributes->get(self::COOKIE_ATTR_NAME)); } } /** * {@inheritdoc} */ public static function getSubscribedEvents() : array { return [KernelEvents::RESPONSE => 'onKernelResponse']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', TokenBasedRememberMeServices::class, SignatureRememberMeHandler::class); /** * Concrete implementation of the RememberMeServicesInterface providing * remember-me capabilities without requiring a TokenProvider. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.4, use {@see SignatureRememberMeHandler} instead */ class TokenBasedRememberMeServices extends AbstractRememberMeServices { /** * {@inheritdoc} */ protected function processAutoLoginCookie(array $cookieParts, Request $request) { if (4 !== \count($cookieParts)) { throw new AuthenticationException('The cookie is invalid.'); } [$class, $userIdentifier, $expires, $hash] = $cookieParts; if (\false === ($userIdentifier = \base64_decode($userIdentifier, \true))) { throw new AuthenticationException('$userIdentifier contains a character from outside the base64 alphabet.'); } try { $userProvider = $this->getUserProvider($class); // @deprecated since Symfony 5.3, change to $userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($userProvider, 'loadUserByIdentifier')) { $user = $userProvider->loadUserByIdentifier($userIdentifier); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($userProvider)); $user = $userProvider->loadUserByUsername($userIdentifier); } } catch (\Exception $e) { if (!$e instanceof AuthenticationException) { $e = new AuthenticationException($e->getMessage(), $e->getCode(), $e); } throw $e; } if (!$user instanceof UserInterface) { throw new \RuntimeException(\sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', \get_debug_type($user))); } if (\true !== \hash_equals($this->generateCookieHash($class, $userIdentifier, $expires, $user->getPassword()), $hash)) { throw new AuthenticationException('The cookie\'s hash is invalid.'); } if ($expires < \time()) { throw new AuthenticationException('The cookie has expired.'); } return $user; } /** * {@inheritdoc} */ protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token) { $user = $token->getUser(); $expires = \time() + $this->options['lifetime']; // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 $value = $this->generateCookieValue(\get_class($user), \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $expires, $user->getPassword()); $response->headers->setCookie(new Cookie($this->options['name'], $value, $expires, $this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], \false, $this->options['samesite'])); } /** * Generates the cookie value. * * @param int $expires The Unix timestamp when the cookie expires * @param string|null $password The encoded password * * @return string */ protected function generateCookieValue(string $class, string $userIdentifier, int $expires, ?string $password) { // $userIdentifier is encoded because it might contain COOKIE_DELIMITER, // we assume other values don't return $this->encodeCookie([$class, \base64_encode($userIdentifier), $expires, $this->generateCookieHash($class, $userIdentifier, $expires, $password)]); } /** * Generates a hash for the cookie to ensure it is not being tampered with. * * @param int $expires The Unix timestamp when the cookie expires * @param string|null $password The encoded password * * @return string */ protected function generateCookieHash(string $class, string $userIdentifier, int $expires, ?string $password) { return \hash_hmac('sha256', $class . self::COOKIE_DELIMITER . $userIdentifier . self::COOKIE_DELIMITER . $expires . self::COOKIE_DELIMITER . $password, $this->getSecret()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\TokenVerifierInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CookieTheftException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; /** * Implements remember-me tokens using a {@see TokenProviderInterface}. * * This requires storing remember-me tokens in a database. This allows * more control over the invalidation of remember-me tokens. See * {@see SignatureRememberMeHandler} if you don't want to use a database. * * @author Wouter de Jong */ final class PersistentRememberMeHandler extends AbstractRememberMeHandler { private $tokenProvider; private $tokenVerifier; public function __construct(TokenProviderInterface $tokenProvider, string $secret, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, ?LoggerInterface $logger = null, ?TokenVerifierInterface $tokenVerifier = null) { parent::__construct($userProvider, $requestStack, $options, $logger); if (!$tokenVerifier && $tokenProvider instanceof TokenVerifierInterface) { $tokenVerifier = $tokenProvider; } $this->tokenProvider = $tokenProvider; $this->tokenVerifier = $tokenVerifier; } /** * {@inheritdoc} */ public function createRememberMeCookie(UserInterface $user) : void { $series = \random_bytes(66); $tokenValue = \strtr(\base64_encode(\substr($series, 33)), '+/=', '-_~'); $series = \strtr(\base64_encode(\substr($series, 0, 33)), '+/=', '-_~'); $token = new PersistentToken(\get_class($user), \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $series, $tokenValue, new \DateTime()); $this->tokenProvider->createNewToken($token); $this->createCookie(RememberMeDetails::fromPersistentToken($token, \time() + $this->options['lifetime'])); } public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails) : UserInterface { if (!\str_contains($rememberMeDetails->getValue(), ':')) { throw new AuthenticationException('The cookie is incorrectly formatted.'); } [$series, $tokenValue] = \explode(':', $rememberMeDetails->getValue()); $persistentToken = $this->tokenProvider->loadTokenBySeries($series); if ($this->tokenVerifier) { $isTokenValid = $this->tokenVerifier->verifyToken($persistentToken, $tokenValue); } else { $isTokenValid = \hash_equals($persistentToken->getTokenValue(), $tokenValue); } if (!$isTokenValid) { throw new CookieTheftException('This token was already used. The account is possibly compromised.'); } if ($persistentToken->getLastUsed()->getTimestamp() + $this->options['lifetime'] < \time()) { throw new AuthenticationException('The cookie has expired.'); } return parent::consumeRememberMeCookie($rememberMeDetails->withValue($persistentToken->getLastUsed()->getTimestamp() . ':' . $rememberMeDetails->getValue() . ':' . $persistentToken->getClass())); } public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user) : void { [$lastUsed, $series, $tokenValue, $class] = \explode(':', $rememberMeDetails->getValue(), 4); $persistentToken = new PersistentToken($class, $rememberMeDetails->getUserIdentifier(), $series, $tokenValue, new \DateTime('@' . $lastUsed)); // if a token was regenerated less than a minute ago, there is no need to regenerate it // if multiple concurrent requests reauthenticate a user we do not want to update the token several times if ($persistentToken->getLastUsed()->getTimestamp() + 60 >= \time()) { return; } $tokenValue = \strtr(\base64_encode(\random_bytes(33)), '+/=', '-_~'); $tokenLastUsed = new \DateTime(); if ($this->tokenVerifier) { $this->tokenVerifier->updateExistingToken($persistentToken, $tokenValue, $tokenLastUsed); } $this->tokenProvider->updateToken($series, $tokenValue, $tokenLastUsed); $this->createCookie($rememberMeDetails->withValue($series . ':' . $tokenValue)); } /** * {@inheritdoc} */ public function clearRememberMeCookie() : void { parent::clearRememberMeCookie(); $cookie = $this->requestStack->getMainRequest()->cookies->get($this->options['name']); if (null === $cookie) { return; } $rememberMeDetails = RememberMeDetails::fromRawCookie($cookie); [$series] = \explode(':', $rememberMeDetails->getValue()); $this->tokenProvider->deleteTokenBySeries($series); } /** * @internal */ public function getTokenProvider() : TokenProviderInterface { return $this->tokenProvider; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Handles creating and validating remember-me cookies. * * If you want to add a custom implementation, you want to extend from * {@see AbstractRememberMeHandler} instead. * * @author Wouter de Jong */ interface RememberMeHandlerInterface { /** * Creates a remember-me cookie. * * The actual cookie should be set as an attribute on the main request, * which is transformed into a response cookie by {@see ResponseListener}. */ public function createRememberMeCookie(UserInterface $user) : void; /** * Validates the remember-me cookie and returns the associated User. * * Every cookie should only be used once. This means that this method should also: * - Create a new remember-me cookie to be sent with the response (using the * {@see ResponseListener::COOKIE_ATTR_NAME} request attribute); * - If you store the token somewhere else (e.g. in a database), invalidate the * stored token. * * @throws AuthenticationException */ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails) : UserInterface; /** * Clears the remember-me cookie. * * This should set a cookie with a `null` value on the request attribute. */ public function clearRememberMeCookie() : void; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; /** * @author Wouter de Jong */ class RememberMeDetails { public const COOKIE_DELIMITER = ':'; private $userFqcn; private $userIdentifier; private $expires; private $value; public function __construct(string $userFqcn, string $userIdentifier, int $expires, string $value) { $this->userFqcn = $userFqcn; $this->userIdentifier = $userIdentifier; $this->expires = $expires; $this->value = $value; } public static function fromRawCookie(string $rawCookie) : self { if (!\str_contains($rawCookie, self::COOKIE_DELIMITER)) { $rawCookie = \base64_decode($rawCookie); } $cookieParts = \explode(self::COOKIE_DELIMITER, $rawCookie, 4); if (4 !== \count($cookieParts)) { throw new AuthenticationException('The cookie contains invalid data.'); } if (\false === ($cookieParts[1] = \base64_decode(\strtr($cookieParts[1], '-_~', '+/='), \true))) { throw new AuthenticationException('The user identifier contains a character from outside the base64 alphabet.'); } $cookieParts[0] = \strtr($cookieParts[0], '.', '\\'); return new static(...$cookieParts); } public static function fromPersistentToken(PersistentToken $persistentToken, int $expires) : self { return new static($persistentToken->getClass(), $persistentToken->getUserIdentifier(), $expires, $persistentToken->getSeries() . ':' . $persistentToken->getTokenValue()); } public function withValue(string $value) : self { $details = clone $this; $details->value = $value; return $details; } public function getUserFqcn() : string { return $this->userFqcn; } public function getUserIdentifier() : string { return $this->userIdentifier; } public function getExpires() : int { return $this->expires; } public function getValue() : string { return $this->value; } public function toString() : string { // $userIdentifier is encoded because it might contain COOKIE_DELIMITER, we assume other values don't return \implode(self::COOKIE_DELIMITER, [\strtr($this->userFqcn, '\\', '.'), \strtr(\base64_encode($this->userIdentifier), '+/=', '-_~'), $this->expires, $this->value]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; /** * @author Wouter de Jong */ abstract class AbstractRememberMeHandler implements RememberMeHandlerInterface { private $userProvider; protected $requestStack; protected $options; protected $logger; public function __construct(UserProviderInterface $userProvider, RequestStack $requestStack, array $options = [], ?LoggerInterface $logger = null) { $this->userProvider = $userProvider; $this->requestStack = $requestStack; $this->options = $options + ['name' => 'REMEMBERME', 'lifetime' => 31536000, 'path' => '/', 'domain' => null, 'secure' => \false, 'httponly' => \true, 'samesite' => null, 'always_remember_me' => \false, 'remember_me_parameter' => '_remember_me']; $this->logger = $logger; } /** * Checks if the RememberMeDetails is a valid cookie to login the given User. * * This method should also: * - Create a new remember-me cookie to be sent with the response (using {@see createCookie()}); * - If you store the token somewhere else (e.g. in a database), invalidate the stored token. * * @throws AuthenticationException If the remember-me details are not accepted */ protected abstract function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user) : void; /** * {@inheritdoc} */ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails) : UserInterface { try { // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 $method = 'loadUserByIdentifier'; if (!\method_exists($this->userProvider, 'loadUserByIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $method = 'loadUserByUsername'; } $user = $this->userProvider->{$method}($rememberMeDetails->getUserIdentifier()); } catch (AuthenticationException $e) { throw $e; } if (!$user instanceof UserInterface) { throw new \LogicException(\sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', \get_debug_type($user))); } $this->processRememberMe($rememberMeDetails, $user); if (null !== $this->logger) { $this->logger->info('Remember-me cookie accepted.'); } return $user; } /** * {@inheritdoc} */ public function clearRememberMeCookie() : void { if (null !== $this->logger) { $this->logger->debug('Clearing remember-me cookie.', ['name' => $this->options['name']]); } $this->createCookie(null); } /** * Creates the remember-me cookie using the correct configuration. * * @param RememberMeDetails|null $rememberMeDetails The details for the cookie, or null to clear the remember-me cookie */ protected function createCookie(?RememberMeDetails $rememberMeDetails) { $request = $this->requestStack->getMainRequest(); if (!$request) { throw new \LogicException('Cannot create the remember-me cookie; no master request available.'); } // the ResponseListener configures the cookie saved in this attribute on the final response object $request->attributes->set(ResponseListener::COOKIE_ATTR_NAME, new Cookie($this->options['name'], $rememberMeDetails ? $rememberMeDetails->toString() : null, $rememberMeDetails ? $rememberMeDetails->getExpires() : 1, $this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], \false, $this->options['samesite'])); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CookieTheftException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UnsupportedUserException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" instead.', AbstractRememberMeServices::class, AbstractRememberMeHandler::class); /** * Base class implementing the RememberMeServicesInterface. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.4, use {@see AbstractRememberMeHandler} instead */ abstract class AbstractRememberMeServices implements RememberMeServicesInterface, LogoutHandlerInterface { public const COOKIE_DELIMITER = ':'; protected $logger; protected $options = ['secure' => \false, 'httponly' => \true, 'samesite' => null, 'path' => null, 'domain' => null]; private $firewallName; private $secret; private $userProviders; /** * @throws \InvalidArgumentException */ public function __construct(iterable $userProviders, string $secret, string $firewallName, array $options = [], ?LoggerInterface $logger = null) { if (empty($secret)) { throw new \InvalidArgumentException('$secret must not be empty.'); } if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); } if (!\is_array($userProviders) && !$userProviders instanceof \Countable) { $userProviders = \iterator_to_array($userProviders, \false); } if (0 === \count($userProviders)) { throw new \InvalidArgumentException('You must provide at least one user provider.'); } $this->userProviders = $userProviders; $this->secret = $secret; $this->firewallName = $firewallName; $this->options = \array_merge($this->options, $options); $this->logger = $logger; } /** * Returns the parameter that is used for checking whether remember-me * services have been requested. * * @return string */ public function getRememberMeParameter() { return $this->options['remember_me_parameter']; } /** * @return string */ public function getSecret() { return $this->secret; } /** * Implementation of RememberMeServicesInterface. Detects whether a remember-me * cookie was set, decodes it, and hands it to subclasses for further processing. * * @throws CookieTheftException * @throws \RuntimeException */ public final function autoLogin(Request $request) : ?TokenInterface { if (($cookie = $request->attributes->get(self::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) { return null; } if (null === ($cookie = $request->cookies->get($this->options['name']))) { return null; } if (null !== $this->logger) { $this->logger->debug('Remember-me cookie detected.'); } $cookieParts = $this->decodeCookie($cookie); try { $user = $this->processAutoLoginCookie($cookieParts, $request); if (!$user instanceof UserInterface) { throw new \RuntimeException('processAutoLoginCookie() must return a UserInterface implementation.'); } if (null !== $this->logger) { $this->logger->info('Remember-me cookie accepted.'); } return new RememberMeToken($user, $this->firewallName, $this->secret); } catch (CookieTheftException $e) { $this->loginFail($request, $e); throw $e; } catch (UserNotFoundException $e) { if (null !== $this->logger) { $this->logger->info('User for remember-me cookie not found.', ['exception' => $e]); } $this->loginFail($request, $e); } catch (UnsupportedUserException $e) { if (null !== $this->logger) { $this->logger->warning('User class for remember-me cookie not supported.', ['exception' => $e]); } $this->loginFail($request, $e); } catch (AuthenticationException $e) { if (null !== $this->logger) { $this->logger->debug('Remember-Me authentication failed.', ['exception' => $e]); } $this->loginFail($request, $e); } catch (\Exception $e) { $this->loginFail($request, $e); throw $e; } return null; } /** * Implementation for LogoutHandlerInterface. Deletes the cookie. */ public function logout(Request $request, Response $response, TokenInterface $token) { $this->cancelCookie($request); } /** * Implementation for RememberMeServicesInterface. Deletes the cookie when * an attempted authentication fails. */ public final function loginFail(Request $request, ?\Exception $exception = null) { $this->cancelCookie($request); $this->onLoginFail($request, $exception); } /** * Implementation for RememberMeServicesInterface. This is called when an * authentication is successful. */ public final function loginSuccess(Request $request, Response $response, TokenInterface $token) { // Make sure any old remember-me cookies are cancelled $this->cancelCookie($request); if (!$token->getUser() instanceof UserInterface) { if (null !== $this->logger) { $this->logger->debug('Remember-me ignores token since it does not contain a UserInterface implementation.'); } return; } if (!$this->isRememberMeRequested($request)) { if (null !== $this->logger) { $this->logger->debug('Remember-me was not requested.'); } return; } if (null !== $this->logger) { $this->logger->debug('Remember-me was requested; setting cookie.'); } // Remove attribute from request that sets a NULL cookie. // It was set by $this->cancelCookie() // (cancelCookie does other things too for some RememberMeServices // so we should still call it at the start of this method) $request->attributes->remove(self::COOKIE_ATTR_NAME); $this->onLoginSuccess($request, $response, $token); } /** * Subclasses should validate the cookie and do any additional processing * that is required. This is called from autoLogin(). * * @return UserInterface */ protected abstract function processAutoLoginCookie(array $cookieParts, Request $request); protected function onLoginFail(Request $request, ?\Exception $exception = null) { } /** * This is called after a user has been logged in successfully, and has * requested remember-me capabilities. The implementation usually sets a * cookie and possibly stores a persistent record of it. */ protected abstract function onLoginSuccess(Request $request, Response $response, TokenInterface $token); protected final function getUserProvider(string $class) : UserProviderInterface { foreach ($this->userProviders as $provider) { if ($provider->supportsClass($class)) { return $provider; } } throw new UnsupportedUserException(\sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', $class)); } /** * Decodes the raw cookie value. * * @return array */ protected function decodeCookie(string $rawCookie) { return \explode(self::COOKIE_DELIMITER, \base64_decode($rawCookie)); } /** * Encodes the cookie parts. * * @return string * * @throws \InvalidArgumentException When $cookieParts contain the cookie delimiter. Extending class should either remove or escape it. */ protected function encodeCookie(array $cookieParts) { foreach ($cookieParts as $cookiePart) { if (\str_contains($cookiePart, self::COOKIE_DELIMITER)) { throw new \InvalidArgumentException(\sprintf('$cookieParts should not contain the cookie delimiter "%s".', self::COOKIE_DELIMITER)); } } return \base64_encode(\implode(self::COOKIE_DELIMITER, $cookieParts)); } /** * Deletes the remember-me cookie. */ protected function cancelCookie(Request $request) { if (null !== $this->logger) { $this->logger->debug('Clearing remember-me cookie.', ['name' => $this->options['name']]); } $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], \false, $this->options['samesite'])); } /** * Checks whether remember-me capabilities were requested. * * @return bool */ protected function isRememberMeRequested(Request $request) { if (\true === $this->options['always_remember_me']) { return \true; } $parameter = ParameterBagUtils::getRequestParameterValue($request, $this->options['remember_me_parameter']); if (null === $parameter && null !== $this->logger) { $this->logger->debug('Did not send remember-me cookie.', ['parameter' => $this->options['remember_me_parameter']]); } return 'true' === $parameter || 'on' === $parameter || '1' === $parameter || 'yes' === $parameter || \true === $parameter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\RememberMe; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; use _ContaoManager\Symfony\Component\Security\Core\Signature\SignatureHasher; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; /** * Implements safe remember-me cookies using the {@see SignatureHasher}. * * This handler doesn't require a database for the remember-me tokens. * However, it cannot invalidate a specific user session, all sessions for * that user will be invalidated instead. Use {@see PersistentRememberMeHandler} * if you need this. * * @author Wouter de Jong */ final class SignatureRememberMeHandler extends AbstractRememberMeHandler { private $signatureHasher; public function __construct(SignatureHasher $signatureHasher, UserProviderInterface $userProvider, RequestStack $requestStack, array $options, ?LoggerInterface $logger = null) { parent::__construct($userProvider, $requestStack, $options, $logger); $this->signatureHasher = $signatureHasher; } /** * {@inheritdoc} */ public function createRememberMeCookie(UserInterface $user) : void { $expires = \time() + $this->options['lifetime']; $value = $this->signatureHasher->computeSignatureHash($user, $expires); $details = new RememberMeDetails(\get_class($user), \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $expires, $value); $this->createCookie($details); } public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails) : UserInterface { try { $this->signatureHasher->acceptSignatureHash($rememberMeDetails->getUserIdentifier(), $rememberMeDetails->getExpires(), $rememberMeDetails->getValue()); } catch (InvalidSignatureException $e) { throw new AuthenticationException('The cookie\'s hash is invalid.', 0, $e); } catch (ExpiredSignatureException $e) { throw new AuthenticationException('The cookie has expired.', 0, $e); } return parent::consumeRememberMeCookie($rememberMeDetails); } public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user) : void { try { $this->signatureHasher->verifySignatureHash($user, $rememberMeDetails->getExpires(), $rememberMeDetails->getValue()); } catch (InvalidSignatureException $e) { throw new AuthenticationException('The cookie\'s hash is invalid.', 0, $e); } catch (ExpiredSignatureException $e) { throw new AuthenticationException('The cookie has expired.', 0, $e); } $this->createRememberMeCookie($user); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EntryPoint; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; /** * Implement this interface for any classes that will be called to "start" * the authentication process (see method for more details). * * @author Fabien Potencier */ interface AuthenticationEntryPointInterface { /** * Returns a response that directs the user to authenticate. * * This is called when an anonymous request accesses a resource that * requires authentication. The job of this method is to return some * response that "helps" the user start into the authentication process. * * Examples: * * - For a form login, you might redirect to the login page * * return new RedirectResponse('/login'); * * - For an API token authentication system, you return a 401 response * * return new Response('Auth header required', 401); * * @return Response */ public function start(Request $request, ?AuthenticationException $authException = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EntryPoint; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ChannelListener; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use "%s" directly (and optionally configure the HTTP(s) ports there).', RetryAuthenticationEntryPoint::class, ChannelListener::class); /** * RetryAuthenticationEntryPoint redirects URL based on the configured scheme. * * This entry point is not intended to work with HTTP post requests. * * @author Fabien Potencier * * @deprecated since Symfony 5.4 */ class RetryAuthenticationEntryPoint implements AuthenticationEntryPointInterface { private $httpPort; private $httpsPort; public function __construct(int $httpPort = 80, int $httpsPort = 443) { $this->httpPort = $httpPort; $this->httpsPort = $httpsPort; } /** * {@inheritdoc} */ public function start(Request $request, ?AuthenticationException $authException = null) { $scheme = $request->isSecure() ? 'http' : 'https'; if ('http' === $scheme && 80 != $this->httpPort) { $port = ':' . $this->httpPort; } elseif ('https' === $scheme && 443 != $this->httpsPort) { $port = ':' . $this->httpsPort; } else { $port = ''; } $qs = $request->getQueryString(); if (null !== $qs) { $qs = '?' . $qs; } $url = $scheme . '://' . $request->getHost() . $port . $request->getBaseUrl() . $request->getPathInfo() . $qs; return new RedirectResponse($url, 301); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EntryPoint; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use the new security system with "%s" instead.', BasicAuthenticationEntryPoint::class, HttpBasicAuthenticator::class); /** * BasicAuthenticationEntryPoint starts an HTTP Basic authentication. * * @author Fabien Potencier * * @deprecated since Symfony 5.4 */ class BasicAuthenticationEntryPoint implements AuthenticationEntryPointInterface { private $realmName; public function __construct(string $realmName) { $this->realmName = $realmName; } /** * {@inheritdoc} */ public function start(Request $request, ?AuthenticationException $authException = null) { $response = new Response(); $response->headers->set('WWW-Authenticate', \sprintf('Basic realm="%s"', $this->realmName)); $response->setStatusCode(401); return $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EntryPoint\Exception; /** * Thrown by generic decorators when a decorated authenticator does not implement * {@see AuthenticationEntryPointInterface}. * * @author Robin Chalas */ class NotAnEntryPointException extends \RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EntryPoint; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated, use the new security system with "%s" instead.', FormAuthenticationEntryPoint::class, FormLoginAuthenticator::class); /** * FormAuthenticationEntryPoint starts an authentication via a login form. * * @author Fabien Potencier * * @deprecated since Symfony 5.4 */ class FormAuthenticationEntryPoint implements AuthenticationEntryPointInterface { private $loginPath; private $useForward; private $httpKernel; private $httpUtils; /** * @param string $loginPath The path to the login form * @param bool $useForward Whether to forward or redirect to the login form */ public function __construct(HttpKernelInterface $kernel, HttpUtils $httpUtils, string $loginPath, bool $useForward = \false) { $this->httpKernel = $kernel; $this->httpUtils = $httpUtils; $this->loginPath = $loginPath; $this->useForward = $useForward; } /** * {@inheritdoc} */ public function start(Request $request, ?AuthenticationException $authException = null) { if ($this->useForward) { $subRequest = $this->httpUtils->createRequest($request, $this->loginPath); $response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); if (200 === $response->getStatusCode()) { $response->setStatusCode(401); } return $response; } return $this->httpUtils->createRedirectResponse($request, $this->loginPath); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Routing\Exception\MethodNotAllowedException; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\RequestMatcherInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\UrlMatcherInterface; use _ContaoManager\Symfony\Component\Security\Core\Security; /** * Encapsulates the logic needed to create sub-requests, redirect the user, and match URLs. * * @author Fabien Potencier */ class HttpUtils { private $urlGenerator; private $urlMatcher; private $domainRegexp; private $secureDomainRegexp; /** * @param UrlMatcherInterface|RequestMatcherInterface $urlMatcher The URL or Request matcher * @param string|null $domainRegexp A regexp the target of HTTP redirections must match, scheme included * @param string|null $secureDomainRegexp A regexp the target of HTTP redirections must match when the scheme is "https" * * @throws \InvalidArgumentException */ public function __construct(?UrlGeneratorInterface $urlGenerator = null, $urlMatcher = null, ?string $domainRegexp = null, ?string $secureDomainRegexp = null) { $this->urlGenerator = $urlGenerator; if (null !== $urlMatcher && !$urlMatcher instanceof UrlMatcherInterface && !$urlMatcher instanceof RequestMatcherInterface) { throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); } $this->urlMatcher = $urlMatcher; $this->domainRegexp = $domainRegexp; $this->secureDomainRegexp = $secureDomainRegexp; } /** * Creates a redirect Response. * * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) * @param int $status The status code * * @return RedirectResponse */ public function createRedirectResponse(Request $request, string $path, int $status = 302) { if (null !== $this->secureDomainRegexp && 'https' === $this->urlMatcher->getContext()->getScheme() && \preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !\preg_match(\sprintf($this->secureDomainRegexp, \preg_quote($request->getHttpHost())), $host[0])) { $path = '/'; } if (null !== $this->domainRegexp && \preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !\preg_match(\sprintf($this->domainRegexp, \preg_quote($request->getHttpHost())), $host[0])) { $path = '/'; } return new RedirectResponse($this->generateUri($request, $path), $status); } /** * Creates a Request. * * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) * * @return Request */ public function createRequest(Request $request, string $path) { $newRequest = Request::create($this->generateUri($request, $path), 'get', [], $request->cookies->all(), [], $request->server->all()); static $setSession; if (null === $setSession) { $setSession = \Closure::bind(static function ($newRequest, $request) { $newRequest->session = $request->session; }, null, Request::class); } $setSession($newRequest, $request); if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { $newRequest->attributes->set(Security::AUTHENTICATION_ERROR, $request->attributes->get(Security::AUTHENTICATION_ERROR)); } if ($request->attributes->has(Security::ACCESS_DENIED_ERROR)) { $newRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $request->attributes->get(Security::ACCESS_DENIED_ERROR)); } if ($request->attributes->has(Security::LAST_USERNAME)) { $newRequest->attributes->set(Security::LAST_USERNAME, $request->attributes->get(Security::LAST_USERNAME)); } if ($request->get('_format')) { $newRequest->attributes->set('_format', $request->get('_format')); } if ($request->getDefaultLocale() !== $request->getLocale()) { $newRequest->setLocale($request->getLocale()); } return $newRequest; } /** * Checks that a given path matches the Request. * * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) * * @return bool true if the path is the same as the one from the Request, false otherwise */ public function checkRequestPath(Request $request, string $path) { if ('/' !== $path[0]) { // Shortcut if request has already been matched before if ($request->attributes->has('_route')) { return $path === $request->attributes->get('_route'); } try { // matching a request is more powerful than matching a URL path + context, so try that first if ($this->urlMatcher instanceof RequestMatcherInterface) { $parameters = $this->urlMatcher->matchRequest($request); } else { $parameters = $this->urlMatcher->match($request->getPathInfo()); } return isset($parameters['_route']) && $path === $parameters['_route']; } catch (MethodNotAllowedException $e) { return \false; } catch (ResourceNotFoundException $e) { return \false; } } return $path === \rawurldecode($request->getPathInfo()); } /** * Generates a URI, based on the given path or absolute URL. * * @param string $path A path (an absolute path (/foo), an absolute URL (http://...), or a route name (foo)) * * @return string * * @throws \LogicException */ public function generateUri(Request $request, string $path) { $url = \parse_url($path); if ('' === $path || isset($url['scheme'], $url['host'])) { return $path; } if ('/' === $path[0]) { return $request->getUriForPath($path); } if (null === $this->urlGenerator) { throw new \LogicException('You must provide a UrlGeneratorInterface instance to be able to use routes.'); } $url = $this->urlGenerator->generate($path, $request->attributes->all(), UrlGeneratorInterface::ABSOLUTE_URL); // unnecessary query string parameters must be removed from URL // (ie. query parameters that are presents in $attributes) // fortunately, they all are, so we have to remove entire query string $position = \strpos($url, '?'); if (\false !== $position) { $fragment = \parse_url($url, \PHP_URL_FRAGMENT); $url = \substr($url, 0, $position); // fragment must be preserved if ($fragment) { $url .= "#{$fragment}"; } } return $url; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Http\Firewall\ExceptionListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\LogoutListener; /** * This interface must be implemented by firewall maps. * * @author Johannes M. Schmitt */ interface FirewallMapInterface { /** * Returns the authentication listeners, and the exception listener to use * for the given request. * * If there are no authentication listeners, the first inner array must be * empty. * * If there is no exception listener, the second element of the outer array * must be null. * * If there is no logout listener, the third element of the outer array * must be null. * * @return array{iterable, ExceptionListener, LogoutListener} */ public function getListeners(Request $request); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Token; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; class PostAuthenticationToken extends AbstractToken { private $firewallName; /** * @param string[] $roles An array of roles * * @throws \InvalidArgumentException */ public function __construct(UserInterface $user, string $firewallName, array $roles) { parent::__construct($roles); if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); } $this->setUser($user); $this->firewallName = $firewallName; // @deprecated since Symfony 5.4 if (\method_exists($this, 'setAuthenticated')) { // this token is meant to be used after authentication success, so it is always authenticated $this->setAuthenticated(\true, \false); } } /** * This is meant to be only a token, where credentials * have already been used and are thus cleared. * * {@inheritdoc} */ public function getCredentials() { return []; } public function getFirewallName() : string { return $this->firewallName; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->firewallName, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->firewallName, $parentData] = $data; parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; /** * This is an extension of the authenticator interface that must * be used by interactive authenticators. * * Interactive login requires explicit user action (e.g. a login * form or HTTP basic authentication). Implementing this interface * will dispatch the InteractiveLoginEvent upon successful login. * * @author Wouter de Jong */ interface InteractiveAuthenticatorInterface extends AuthenticatorInterface { /** * Should return true to make this authenticator perform * an interactive login. */ public function isInteractive() : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * A base class to make form login authentication easier! * * @author Ryan Weaver */ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface { /** * Return the URL to the login page. */ protected abstract function getLoginUrl(Request $request) : string; /** * {@inheritdoc} * * Override to change the request conditions that have to be * matched in order to handle the login form submit. * * This default implementation handles all POST requests to the * login path (@see getLoginUrl()). */ public function supports(Request $request) : bool { return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getBaseUrl() . $request->getPathInfo(); } /** * Override to change what happens after a bad username/password is submitted. */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : Response { if ($request->hasSession()) { $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); } $url = $this->getLoginUrl($request); return new RedirectResponse($url); } /** * Override to control what happens when the user hits a secure page * but isn't logged in yet. */ public function start(Request $request, ?AuthenticationException $authException = null) : Response { $url = $this->getLoginUrl($request); return new RedirectResponse($url); } public function isInteractive() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; /** * The interface for all authenticators. * * @author Ryan Weaver * @author Amaury Leroux de Lens * @author Wouter de Jong * * @method TokenInterface createToken(Passport $passport, string $firewallName) Creates a token for the given user. * If you don't care about which token class is used, you can skip this method by extending * the AbstractAuthenticator class from your authenticator. */ interface AuthenticatorInterface { /** * Does the authenticator support the given Request? * * If this returns true, authenticate() will be called. If false, the authenticator will be skipped. * * Returning null means authenticate() can be called lazily when accessing the token storage. */ public function supports(Request $request) : ?bool; /** * Create a passport for the current request. * * The passport contains the user, credentials and any additional information * that has to be checked by the Symfony Security system. For example, a login * form authenticator will probably return a passport containing the user, the * presented password and the CSRF token value. * * You may throw any AuthenticationException in this method in case of error (e.g. * a UserNotFoundException when the user cannot be found). * * @return Passport * * @throws AuthenticationException */ public function authenticate(Request $request); /* : Passport; */ /** * Create an authenticated token for the given user. * * If you don't care about which token class is used or don't really * understand what a "token" is, you can skip this method by extending * the AbstractAuthenticator class from your authenticator. * * @see AbstractAuthenticator * * @param PassportInterface $passport The passport returned from authenticate() * * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface; /** * Called when authentication executed and was successful! * * This should return the Response sent back to the user, like a * RedirectResponse to the last page they visited. * * If you return null, the current request will continue, and the user * will be authenticated. This makes sense, for example, with an API. */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response; /** * Called when authentication executed, but failed (e.g. wrong username password). * * This should return the Response sent back to the user, like a * RedirectResponse to the login page or a 403 response. * * If you return null, the request will continue, but the user will * not be authenticated. This is probably not what you want to do. */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; /** * An optional base class that creates the necessary tokens for you. * * @author Ryan Weaver */ abstract class AbstractAuthenticator implements AuthenticatorInterface { /** * Shortcut to create a PostAuthenticationToken for you, if you don't really * care about which authenticated token you're using. */ public function createToken(Passport $passport, string $firewallName) : TokenInterface { if (self::class !== (new \ReflectionMethod($this, 'createAuthenticatedToken'))->getDeclaringClass()->getName() && self::class === (new \ReflectionMethod($this, 'createToken'))->getDeclaringClass()->getName()) { return $this->createAuthenticatedToken($passport, $firewallName); } return new PostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); } /** * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { // @deprecated since Symfony 5.4 if (!$passport instanceof UserPassportInterface) { throw new LogicException(\sprintf('Passport does not contain a user, overwrite "createToken()" in "%s" to create a custom authentication token.', static::class)); } \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__); return new PostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * @author Wouter de Jong * @author Fabien Potencier * * @final */ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface { private $realmName; private $userProvider; private $logger; public function __construct(string $realmName, UserProviderInterface $userProvider, ?LoggerInterface $logger = null) { $this->realmName = $realmName; $this->userProvider = $userProvider; $this->logger = $logger; } public function start(Request $request, ?AuthenticationException $authException = null) : Response { $response = new Response(); $response->headers->set('WWW-Authenticate', \sprintf('Basic realm="%s"', $this->realmName)); $response->setStatusCode(401); return $response; } public function supports(Request $request) : ?bool { return $request->headers->has('PHP_AUTH_USER'); } public function authenticate(Request $request) : PassportInterface { $username = $request->headers->get('PHP_AUTH_USER'); $password = $request->headers->get('PHP_AUTH_PW', ''); // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 $method = 'loadUserByIdentifier'; if (!\method_exists($this->userProvider, 'loadUserByIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $method = 'loadUserByUsername'; } $passport = new Passport(new UserBadge($username, [$this->userProvider, $method]), new PasswordCredentials($password)); if ($this->userProvider instanceof PasswordUpgraderInterface) { $passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider)); } return $passport; } /** * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__); return $this->createToken($passport, $firewallName); } public function createToken(Passport $passport, string $firewallName) : TokenInterface { return new UsernamePasswordToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response { if (null !== $this->logger) { $this->logger->info('Basic authentication failed for user.', ['username' => $request->headers->get('PHP_AUTH_USER'), 'exception' => $exception]); } return $this->start($request, $exception); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception\InvalidLoginLinkAuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception\InvalidLoginLinkExceptionInterface; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; /** * @author Ryan Weaver */ final class LoginLinkAuthenticator extends AbstractAuthenticator implements InteractiveAuthenticatorInterface { private $loginLinkHandler; private $httpUtils; private $successHandler; private $failureHandler; private $options; public function __construct(LoginLinkHandlerInterface $loginLinkHandler, HttpUtils $httpUtils, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options) { $this->loginLinkHandler = $loginLinkHandler; $this->httpUtils = $httpUtils; $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; $this->options = $options + ['check_post_only' => \false]; } public function supports(Request $request) : ?bool { return ($this->options['check_post_only'] ? $request->isMethod('POST') : \true) && $this->httpUtils->checkRequestPath($request, $this->options['check_route']); } public function authenticate(Request $request) : PassportInterface { $username = $request->get('user'); if (!$username) { throw new InvalidLoginLinkAuthenticationException('Missing user from link.'); } return new SelfValidatingPassport(new UserBadge($username, function () use($request) { try { $user = $this->loginLinkHandler->consumeLoginLink($request); } catch (InvalidLoginLinkExceptionInterface $e) { throw new InvalidLoginLinkAuthenticationException('Login link could not be validated.', 0, $e); } return $user; }), [new RememberMeBadge()]); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return $this->successHandler->onAuthenticationSuccess($request, $token); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : Response { return $this->failureHandler->onAuthenticationFailure($request, $exception); } public function isInteractive() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\JsonResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\AccessException; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccess; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessorInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Contracts\Translation\TranslatorInterface; /** * Provides a stateless implementation of an authentication via * a JSON document composed of a username and a password. * * @author Kévin Dunglas * @author Wouter de Jong * * @final */ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface { private $options; private $httpUtils; private $userProvider; private $propertyAccessor; private $successHandler; private $failureHandler; /** * @var TranslatorInterface|null */ private $translator; public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, ?AuthenticationSuccessHandlerInterface $successHandler = null, ?AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], ?PropertyAccessorInterface $propertyAccessor = null) { $this->options = \array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); $this->httpUtils = $httpUtils; $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; $this->userProvider = $userProvider; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } public function supports(Request $request) : ?bool { if (\false === \strpos($request->getRequestFormat() ?? '', 'json') && \false === \strpos($request->getContentType() ?? '', 'json')) { return \false; } if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) { return \false; } return \true; } public function authenticate(Request $request) : PassportInterface { try { $credentials = $this->getCredentials($request); } catch (BadRequestHttpException $e) { $request->setRequestFormat('json'); throw $e; } // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 $method = 'loadUserByIdentifier'; if (!\method_exists($this->userProvider, 'loadUserByIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $method = 'loadUserByUsername'; } $passport = new Passport(new UserBadge($credentials['username'], [$this->userProvider, $method]), new PasswordCredentials($credentials['password'])); if ($this->userProvider instanceof PasswordUpgraderInterface) { $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider)); } return $passport; } /** * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__); return $this->createToken($passport, $firewallName); } public function createToken(Passport $passport, string $firewallName) : TokenInterface { return new UsernamePasswordToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { if (null === $this->successHandler) { return null; // let the original request continue } return $this->successHandler->onAuthenticationSuccess($request, $token); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response { if (null === $this->failureHandler) { if (null !== $this->translator) { $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'); } else { $errorMessage = \strtr($exception->getMessageKey(), $exception->getMessageData()); } return new JsonResponse(['error' => $errorMessage], JsonResponse::HTTP_UNAUTHORIZED); } return $this->failureHandler->onAuthenticationFailure($request, $exception); } public function isInteractive() : bool { return \true; } public function setTranslator(TranslatorInterface $translator) { $this->translator = $translator; } private function getCredentials(Request $request) { $data = \json_decode($request->getContent()); if (!$data instanceof \stdClass) { throw new BadRequestHttpException('Invalid JSON.'); } $credentials = []; try { $credentials['username'] = $this->propertyAccessor->getValue($data, $this->options['username_path']); if (!\is_string($credentials['username'])) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', $this->options['username_path'])); } if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Invalid username.'); } } catch (AccessException $e) { throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', $this->options['username_path']), $e); } try { $credentials['password'] = $this->propertyAccessor->getValue($data, $this->options['password_path']); if (!\is_string($credentials['password'])) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string.', $this->options['password_path'])); } } catch (AccessException $e) { throw new BadRequestHttpException(\sprintf('The key "%s" must be provided.', $this->options['password_path']), $e); } return $credentials; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; /** * An implementation used when there are no credentials to be checked (e.g. * API token authentication). * * @author Wouter de Jong */ class SelfValidatingPassport extends Passport { /** * @param BadgeInterface[] $badges */ public function __construct(UserBadge $userBadge, array $badges = []) { $this->addBadge($userBadge); foreach ($badges as $badge) { $this->addBadge($badge); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; /** * A Passport contains all security-related information that needs to be * validated during authentication. * * A passport badge can be used to add any additional information to the * passport. * * @author Wouter de Jong * * @deprecated since Symfony 5.4, use {@link Passport} instead */ interface PassportInterface { /** * Adds a new security badge. * * A passport can hold only one instance of the same security badge. * This method replaces the current badge if it is already set on this * passport. * * @return $this */ public function addBadge(BadgeInterface $badge) : self; public function hasBadge(string $badgeFqcn) : bool; public function getBadge(string $badgeFqcn) : ?BadgeInterface; /** * @return array, BadgeInterface> */ public function getBadges() : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CredentialsInterface; /** * A Passport contains all security-related information that needs to be * validated during authentication. * * A passport badge can be used to add any additional information to the * passport. * * @author Wouter de Jong */ class Passport implements UserPassportInterface { protected $user; private $badges = []; private $attributes = []; /** * @param CredentialsInterface $credentials the credentials to check for this authentication, use * SelfValidatingPassport if no credentials should be checked * @param BadgeInterface[] $badges */ public function __construct(UserBadge $userBadge, CredentialsInterface $credentials, array $badges = []) { $this->addBadge($userBadge); $this->addBadge($credentials); foreach ($badges as $badge) { $this->addBadge($badge); } } /** * {@inheritdoc} */ public function getUser() : UserInterface { if (null === $this->user) { if (!$this->hasBadge(UserBadge::class)) { throw new \LogicException('Cannot get the Security user, no username or UserBadge configured for this passport.'); } $this->user = $this->getBadge(UserBadge::class)->getUser(); } return $this->user; } /** * Adds a new security badge. * * A passport can hold only one instance of the same security badge. * This method replaces the current badge if it is already set on this * passport. * * @return $this */ public function addBadge(BadgeInterface $badge) : PassportInterface { $this->badges[\get_class($badge)] = $badge; return $this; } public function hasBadge(string $badgeFqcn) : bool { return isset($this->badges[$badgeFqcn]); } public function getBadge(string $badgeFqcn) : ?BadgeInterface { return $this->badges[$badgeFqcn] ?? null; } /** * @return array, BadgeInterface> */ public function getBadges() : array { return $this->badges; } /** * @param mixed $value */ public function setAttribute(string $name, $value) : void { $this->attributes[$name] = $value; } /** * @param mixed $default * * @return mixed */ public function getAttribute(string $name, $default = null) { return $this->attributes[$name] ?? $default; } public function getAttributes() : array { return $this->attributes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" trait is deprecated, you must extend from "%s" instead.', PassportTrait::class, Passport::class); /** * @author Wouter de Jong * * @deprecated since Symfony 5.4, use {@see Passport} instead */ trait PassportTrait { private $badges = []; /** * @return $this */ public function addBadge(BadgeInterface $badge) : PassportInterface { $this->badges[\get_class($badge)] = $badge; return $this; } public function hasBadge(string $badgeFqcn) : bool { return isset($this->badges[$badgeFqcn]); } public function getBadge(string $badgeFqcn) : ?BadgeInterface { return $this->badges[$badgeFqcn] ?? null; } /** * @return array, BadgeInterface> */ public function getBadges() : array { return $this->badges; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Represents a passport for a Security User. * * @author Wouter de Jong * * @deprecated since Symfony 5.4, use {@link Passport} instead */ interface UserPassportInterface extends PassportInterface { /** * @throws AuthenticationException when the user cannot be found */ public function getUser() : UserInterface; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; /** * Credentials are a special badge used to explicitly mark the * credential check of an authenticator. * * @author Wouter de Jong */ interface CredentialsInterface extends BadgeInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Implements credentials checking using a custom checker function. * * @author Wouter de Jong * * @final */ class CustomCredentials implements CredentialsInterface { private $customCredentialsChecker; private $credentials; private $resolved = \false; /** * @param callable $customCredentialsChecker the check function. If this function does not return `true`, a * BadCredentialsException is thrown. You may also throw a more * specific exception in the function. * @param mixed $credentials */ public function __construct(callable $customCredentialsChecker, $credentials) { $this->customCredentialsChecker = $customCredentialsChecker; $this->credentials = $credentials; } public function executeCustomChecker(UserInterface $user) : void { $checker = $this->customCredentialsChecker; if (\true !== $checker($this->credentials, $user)) { throw new BadCredentialsException('Credentials check failed as the callable passed to CustomCredentials did not return "true".'); } $this->resolved = \true; } public function isResolved() : bool { return $this->resolved; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; /** * Implements password credentials. * * These plaintext passwords are checked by the UserPasswordHasher during * authentication. * * @author Wouter de Jong * * @final */ class PasswordCredentials implements CredentialsInterface { private $password; private $resolved = \false; public function __construct(string $password) { $this->password = $password; } public function getPassword() : string { if (null === $this->password) { throw new LogicException('The credentials are erased as another listener already verified these credentials.'); } return $this->password; } /** * @internal */ public function markResolved() : void { $this->resolved = \true; $this->password = null; } public function isResolved() : bool { return $this->resolved; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AbstractPreAuthenticatedAuthenticator; /** * Marks the authentication as being pre-authenticated. * * This disables pre-authentication user checkers. * * @see AbstractPreAuthenticatedAuthenticator * * @author Wouter de Jong * * @final */ class PreAuthenticatedUserBadge implements BadgeInterface { public function isResolved() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CsrfProtectionListener; /** * Adds automatic CSRF tokens checking capabilities to this authenticator. * * @see CsrfProtectionListener * * @author Wouter de Jong * * @final */ class CsrfTokenBadge implements BadgeInterface { private $resolved = \false; private $csrfTokenId; private $csrfToken; /** * @param string $csrfTokenId An arbitrary string used to generate the value of the CSRF token. * Using a different string for each authenticator improves its security. * @param string|null $csrfToken The CSRF token presented in the request, if any */ public function __construct(string $csrfTokenId, ?string $csrfToken) { $this->csrfTokenId = $csrfTokenId; $this->csrfToken = $csrfToken; } public function getCsrfTokenId() : string { return $this->csrfTokenId; } public function getCsrfToken() : ?string { return $this->csrfToken; } /** * @internal */ public function markResolved() : void { $this->resolved = \true; } public function isResolved() : bool { return $this->resolved; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; /** * Adds automatic password migration, if enabled and required in the password encoder. * * @see PasswordUpgraderInterface * * @author Wouter de Jong * * @final */ class PasswordUpgradeBadge implements BadgeInterface { private $plaintextPassword; private $passwordUpgrader; /** * @param string $plaintextPassword The presented password, used in the rehash * @param PasswordUpgraderInterface|null $passwordUpgrader The password upgrader, defaults to the UserProvider if null */ public function __construct(string $plaintextPassword, ?PasswordUpgraderInterface $passwordUpgrader = null) { $this->plaintextPassword = $plaintextPassword; $this->passwordUpgrader = $passwordUpgrader; } public function getAndErasePlaintextPassword() : string { $password = $this->plaintextPassword; if (null === $password) { throw new LogicException('The password is erased as another listener already used this badge.'); } $this->plaintextPassword = null; return $password; } public function getPasswordUpgrader() : ?PasswordUpgraderInterface { return $this->passwordUpgrader; } public function isResolved() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\EventListener\UserProviderListener; /** * Represents the user in the authentication process. * * It uses an identifier (e.g. email, or username) and * "user loader" to load the related User object. * * @author Wouter de Jong */ class UserBadge implements BadgeInterface { private $userIdentifier; private $userLoader; private $user; /** * Initializes the user badge. * * You must provide a $userIdentifier. This is a unique string representing the * user for this authentication (e.g. the email if authentication is done using * email + password; or a string combining email+company if authentication is done * based on email *and* company name). This string can be used for e.g. login throttling. * * Optionally, you may pass a user loader. This callable receives the $userIdentifier * as argument and must return a UserInterface object (otherwise an AuthenticationServiceException * is thrown). If this is not set, the default user provider will be used with * $userIdentifier as username. */ public function __construct(string $userIdentifier, ?callable $userLoader = null) { $this->userIdentifier = $userIdentifier; $this->userLoader = $userLoader; } public function getUserIdentifier() : string { return $this->userIdentifier; } /** * @throws AuthenticationException when the user cannot be found */ public function getUser() : UserInterface { if (null !== $this->user) { return $this->user; } if (null === $this->userLoader) { throw new \LogicException(\sprintf('No user loader is configured, did you forget to register the "%s" listener?', UserProviderListener::class)); } $user = ($this->userLoader)($this->userIdentifier); // No user has been found via the $this->userLoader callback if (null === $user) { $exception = new UserNotFoundException(); $exception->setUserIdentifier($this->userIdentifier); throw $exception; } if (!$user instanceof UserInterface) { throw new AuthenticationServiceException(\sprintf('The user provider must return a UserInterface object, "%s" given.', \get_debug_type($user))); } return $this->user = $user; } public function getUserLoader() : ?callable { return $this->userLoader; } public function setUserLoader(callable $userLoader) : void { $this->userLoader = $userLoader; } public function isResolved() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge; use _ContaoManager\Symfony\Component\Security\Http\EventListener\CheckRememberMeConditionsListener; /** * Adds support for remember me to this authenticator. * * The presence of this badge doesn't create the remember-me cookie. The actual * cookie is only created if this badge is enabled. By default, this is done * by the {@see CheckRememberMeConditionsListener} if all conditions are met. * * @author Wouter de Jong * * @final */ class RememberMeBadge implements BadgeInterface { private $enabled = \false; /** * Enables remember-me cookie creation. * * In most cases, {@see CheckRememberMeConditionsListener} enables this * automatically if always_remember_me is true or the remember_me_parameter * exists in the request. * * @return $this */ public function enable() : self { $this->enabled = \true; return $this; } /** * Disables remember-me cookie creation. * * The default is disabled, this can be called to suppress creation * after it was enabled. * * @return $this */ public function disable() : self { $this->enabled = \false; return $this; } public function isEnabled() : bool { return $this->enabled; } public function isResolved() : bool { return \true; // remember me does not need to be explicitly resolved } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge; /** * Passport badges allow to add more information to a passport (e.g. a CSRF token). * * @author Wouter de Jong */ interface BadgeInterface { /** * Checks if this badge is resolved by the security system. * * After authentication, all badges must return `true` in this method in order * for the authentication to succeed. */ public function isResolved() : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; /** * This authenticator authenticates pre-authenticated (by the * webserver) X.509 certificates. * * @author Wouter de Jong * @author Fabien Potencier * * @final */ class X509Authenticator extends AbstractPreAuthenticatedAuthenticator { private $userKey; private $credentialsKey; public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', ?LoggerInterface $logger = null) { parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); $this->userKey = $userKey; $this->credentialsKey = $credentialsKey; } protected function extractUsername(Request $request) : string { $username = null; if ($request->server->has($this->userKey)) { $username = $request->server->get($this->userKey); } elseif ($request->server->has($this->credentialsKey) && \preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $request->server->get($this->credentialsKey), $matches)) { $username = $matches[1]; } if (null === $username) { throw new BadCredentialsException(\sprintf('SSL credentials not found: %s, %s', $this->userKey, $this->credentialsKey)); } return $username; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; /** * This authenticator authenticates a remote user. * * @author Wouter de Jong * @author Fabien Potencier * @author Maxime Douailin * * @final * * @internal in Symfony 5.1 */ class RemoteUserAuthenticator extends AbstractPreAuthenticatedAuthenticator { private $userKey; public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'REMOTE_USER', ?LoggerInterface $logger = null) { parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); $this->userKey = $userKey; } protected function extractUsername(Request $request) : ?string { if (!$request->server->has($this->userKey)) { throw new BadCredentialsException(\sprintf('User key was not found: "%s".', $this->userKey)); } return $request->server->get($this->userKey); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; /** * @author Wouter de Jong * @author Fabien Potencier * * @final */ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator { private $httpUtils; private $userProvider; private $successHandler; private $failureHandler; private $options; private $httpKernel; public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options) { $this->httpUtils = $httpUtils; $this->userProvider = $userProvider; $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; $this->options = \array_merge(['username_parameter' => '_username', 'password_parameter' => '_password', 'check_path' => '/login_check', 'post_only' => \true, 'form_only' => \false, 'enable_csrf' => \false, 'csrf_parameter' => '_csrf_token', 'csrf_token_id' => 'authenticate'], $options); } protected function getLoginUrl(Request $request) : string { return $this->httpUtils->generateUri($request, $this->options['login_path']); } public function supports(Request $request) : bool { return ($this->options['post_only'] ? $request->isMethod('POST') : \true) && $this->httpUtils->checkRequestPath($request, $this->options['check_path']) && ($this->options['form_only'] ? 'form' === $request->getContentType() : \true); } public function authenticate(Request $request) : Passport { $credentials = $this->getCredentials($request); // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 $method = 'loadUserByIdentifier'; if (!\method_exists($this->userProvider, 'loadUserByIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $method = 'loadUserByUsername'; } $passport = new Passport(new UserBadge($credentials['username'], [$this->userProvider, $method]), new PasswordCredentials($credentials['password']), [new RememberMeBadge()]); if ($this->options['enable_csrf']) { $passport->addBadge(new CsrfTokenBadge($this->options['csrf_token_id'], $credentials['csrf_token'])); } if ($this->userProvider instanceof PasswordUpgraderInterface) { $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider)); } return $passport; } /** * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__); return $this->createToken($passport, $firewallName); } public function createToken(Passport $passport, string $firewallName) : TokenInterface { return new UsernamePasswordToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return $this->successHandler->onAuthenticationSuccess($request, $token); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : Response { return $this->failureHandler->onAuthenticationFailure($request, $exception); } private function getCredentials(Request $request) : array { $credentials = []; $credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); if ($this->options['post_only']) { $credentials['username'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']); $credentials['password'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']) ?? ''; } else { $credentials['username'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']); $credentials['password'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']) ?? ''; } if (!\is_string($credentials['username']) && (!\is_object($credentials['username']) || !\method_exists($credentials['username'], '__toString'))) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($credentials['username']))); } $credentials['username'] = \trim($credentials['username']); if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Invalid username.'); } $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']); if (!\is_string($credentials['password']) && (!\is_object($credentials['password']) || !\method_exists($credentials['password'], '__toString'))) { throw new BadRequestHttpException(\sprintf('The key "%s" must be a string, "%s" given.', $this->options['password_parameter'], \gettype($credentials['password']))); } return $credentials; } public function setHttpKernel(HttpKernelInterface $httpKernel) : void { $this->httpKernel = $httpKernel; } public function start(Request $request, ?AuthenticationException $authException = null) : Response { if (!$this->options['use_forward']) { return parent::start($request, $authException); } $subRequest = $this->httpUtils->createRequest($request, $this->options['login_path']); $response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); if (200 === $response->getStatusCode()) { $response->setStatusCode(401); } return $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; /** * The base authenticator for authenticators to use pre-authenticated * requests (e.g. using certificates). * * @author Wouter de Jong * @author Fabien Potencier * * @internal */ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface { private $userProvider; private $tokenStorage; private $firewallName; private $logger; public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, ?LoggerInterface $logger = null) { $this->userProvider = $userProvider; $this->tokenStorage = $tokenStorage; $this->firewallName = $firewallName; $this->logger = $logger; } /** * Returns the username of the pre-authenticated user. * * This authenticator is skipped if null is returned or a custom * BadCredentialsException is thrown. */ protected abstract function extractUsername(Request $request) : ?string; public function supports(Request $request) : ?bool { try { $username = $this->extractUsername($request); } catch (BadCredentialsException $e) { $this->clearToken($e); if (null !== $this->logger) { $this->logger->debug('Skipping pre-authenticated authenticator as a BadCredentialsException is thrown.', ['exception' => $e, 'authenticator' => static::class]); } return \false; } if (null === $username) { if (null !== $this->logger) { $this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => static::class]); } return \false; } // do not overwrite already stored tokens from the same user (i.e. from the session) $token = $this->tokenStorage->getToken(); if ($token instanceof PreAuthenticatedToken && $this->firewallName === $token->getFirewallName() && $token->getUserIdentifier() === $username) { if (null !== $this->logger) { $this->logger->debug('Skipping pre-authenticated authenticator as the user already has an existing session.', ['authenticator' => static::class]); } return \false; } $request->attributes->set('_pre_authenticated_username', $username); return \true; } public function authenticate(Request $request) : Passport { // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 $method = 'loadUserByIdentifier'; if (!\method_exists($this->userProvider, 'loadUserByIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $method = 'loadUserByUsername'; } return new SelfValidatingPassport(new UserBadge($request->attributes->get('_pre_authenticated_username'), [$this->userProvider, $method]), [new PreAuthenticatedUserBadge()]); } /** * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__); return $this->createToken($passport, $firewallName); } public function createToken(Passport $passport, string $firewallName) : TokenInterface { return new PreAuthenticatedToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles()); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return null; // let the original request continue } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response { $this->clearToken($exception); return null; } public function isInteractive() : bool { return \true; } private function clearToken(AuthenticationException $exception) : void { $token = $this->tokenStorage->getToken(); if ($token instanceof PreAuthenticatedToken && $this->firewallName === $token->getFirewallName()) { $this->tokenStorage->setToken(null); if (null !== $this->logger) { $this->logger->info('Cleared pre-authenticated token due to an exception.', ['exception' => $exception]); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Debug; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException; use _ContaoManager\Symfony\Component\VarDumper\Caster\ClassStub; /** * Collects info about an authenticator for debugging purposes. * * @author Robin Chalas */ final class TraceableAuthenticator implements AuthenticatorInterface, InteractiveAuthenticatorInterface, AuthenticationEntryPointInterface { private $authenticator; private $passport; private $duration; private $stub; public function __construct(AuthenticatorInterface $authenticator) { $this->authenticator = $authenticator; } public function getInfo() : array { $class = \get_class($this->authenticator instanceof GuardBridgeAuthenticator ? $this->authenticator->getGuardAuthenticator() : $this->authenticator); return ['supports' => \true, 'passport' => $this->passport, 'duration' => $this->duration, 'stub' => $this->stub ?? ($this->stub = \class_exists(ClassStub::class) ? new ClassStub($class) : $class)]; } public function supports(Request $request) : ?bool { return $this->authenticator->supports($request); } public function authenticate(Request $request) : PassportInterface { $startTime = \microtime(\true); $this->passport = $this->authenticator->authenticate($request); $this->duration = \microtime(\true) - $startTime; return $this->passport; } public function createToken(PassportInterface $passport, string $firewallName) : TokenInterface { return \method_exists($this->authenticator, 'createToken') ? $this->authenticator->createToken($passport, $firewallName) : $this->authenticator->createAuthenticatedToken($passport, $firewallName); } public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { return $this->authenticator->createAuthenticatedToken($passport, $firewallName); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return $this->authenticator->onAuthenticationSuccess($request, $token, $firewallName); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response { return $this->authenticator->onAuthenticationFailure($request, $exception); } public function start(Request $request, ?AuthenticationException $authException = null) : Response { if (!$this->authenticator instanceof AuthenticationEntryPointInterface) { throw new NotAnEntryPointException(); } return $this->authenticator->start($request, $authException); } public function isInteractive() : bool { return $this->authenticator instanceof InteractiveAuthenticatorInterface && $this->authenticator->isInteractive(); } public function getAuthenticator() : AuthenticatorInterface { return $this->authenticator; } public function __call($method, $args) { return $this->authenticator->{$method}(...$args); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator\Debug; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AbstractListener; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener; use _ContaoManager\Symfony\Component\VarDumper\Caster\ClassStub; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Decorates the AuthenticatorManagerListener to collect information about security authenticators. * * @author Robin Chalas */ final class TraceableAuthenticatorManagerListener extends AbstractListener implements ResetInterface { private $authenticationManagerListener; private $authenticatorsInfo = []; private $hasVardumper; public function __construct(AuthenticatorManagerListener $authenticationManagerListener) { $this->authenticationManagerListener = $authenticationManagerListener; $this->hasVardumper = \class_exists(ClassStub::class); } public function supports(Request $request) : ?bool { return $this->authenticationManagerListener->supports($request); } public function authenticate(RequestEvent $event) : void { $request = $event->getRequest(); if (!($authenticators = $request->attributes->get('_security_authenticators'))) { return; } foreach ($request->attributes->get('_security_skipped_authenticators') as $skippedAuthenticator) { $this->authenticatorsInfo[] = ['supports' => \false, 'stub' => $this->hasVardumper ? new ClassStub(\get_class($skippedAuthenticator)) : \get_class($skippedAuthenticator), 'passport' => null, 'duration' => 0]; } foreach ($authenticators as $key => $authenticator) { $authenticators[$key] = new TraceableAuthenticator($authenticator); } $request->attributes->set('_security_authenticators', $authenticators); $this->authenticationManagerListener->authenticate($event); foreach ($authenticators as $authenticator) { $this->authenticatorsInfo[] = $authenticator->getInfo(); } } public function getAuthenticatorManagerListener() : AuthenticatorManagerListener { return $this->authenticationManagerListener; } public function getAuthenticatorsInfo() : array { return $this->authenticatorsInfo; } public function reset() : void { $this->authenticatorsInfo = []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authenticator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CookieTheftException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UnsupportedUserException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeDetails; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\ResponseListener; /** * The RememberMe *Authenticator* performs remember me authentication. * * This authenticator is executed whenever a user's session * expired and a remember-me cookie was found. This authenticator * then "re-authenticates" the user using the information in the * cookie. * * @author Johannes M. Schmitt * @author Wouter de Jong * * @final */ class RememberMeAuthenticator implements InteractiveAuthenticatorInterface { private $rememberMeHandler; private $secret; private $tokenStorage; private $cookieName; private $logger; public function __construct(RememberMeHandlerInterface $rememberMeHandler, string $secret, TokenStorageInterface $tokenStorage, string $cookieName, ?LoggerInterface $logger = null) { $this->rememberMeHandler = $rememberMeHandler; $this->secret = $secret; $this->tokenStorage = $tokenStorage; $this->cookieName = $cookieName; $this->logger = $logger; } public function supports(Request $request) : ?bool { // do not overwrite already stored tokens (i.e. from the session) if (null !== $this->tokenStorage->getToken()) { return \false; } if (($cookie = $request->attributes->get(ResponseListener::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) { return \false; } if (!$request->cookies->has($this->cookieName) || !\is_scalar($request->cookies->all()[$this->cookieName] ?: null)) { return \false; } if (null !== $this->logger) { $this->logger->debug('Remember-me cookie detected.'); } // the `null` return value indicates that this authenticator supports lazy firewalls return null; } public function authenticate(Request $request) : PassportInterface { $rawCookie = $request->cookies->get($this->cookieName); if (!$rawCookie) { throw new \LogicException('No remember-me cookie is found.'); } $rememberMeCookie = RememberMeDetails::fromRawCookie($rawCookie); return new SelfValidatingPassport(new UserBadge($rememberMeCookie->getUserIdentifier(), function () use($rememberMeCookie) { return $this->rememberMeHandler->consumeRememberMeCookie($rememberMeCookie); })); } /** * @deprecated since Symfony 5.4, use {@link createToken()} instead */ public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { \trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__); return $this->createToken($passport, $firewallName); } public function createToken(Passport $passport, string $firewallName) : TokenInterface { return new RememberMeToken($passport->getUser(), $firewallName, $this->secret); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return null; // let the original request continue } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response { if (null !== $this->logger) { if ($exception instanceof UserNotFoundException) { $this->logger->info('User for remember-me cookie not found.', ['exception' => $exception]); } elseif ($exception instanceof UnsupportedUserException) { $this->logger->warning('User class for remember-me cookie not supported.', ['exception' => $exception]); } elseif (!$exception instanceof CookieTheftException) { $this->logger->debug('Remember me authentication failed.', ['exception' => $exception]); } } return null; } public function isInteractive() : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authorization; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccessDeniedException; /** * This is used by the ExceptionListener to translate an AccessDeniedException * to a Response object. * * @author Johannes M. Schmitt */ interface AccessDeniedHandlerInterface { /** * Handles an access denied failure. * * @return Response|null */ public function handle(Request $request, AccessDeniedException $accessDeniedException); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * AccessMap allows configuration of different access control rules for * specific parts of the website. * * @author Fabien Potencier * @author Kris Wallsmith */ interface AccessMapInterface { /** * Returns security attributes and required channel for the supplied request. * * @return array{0: array|null, 1: string|null} A tuple of security attributes and the required channel */ public function getPatterns(Request $request); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink; /** * @author Ryan Weaver */ class LoginLinkDetails { private $url; private $expiresAt; public function __construct(string $url, \DateTimeImmutable $expiresAt) { $this->url = $url; $this->expiresAt = $expiresAt; } public function getUrl() : string { return $this->url; } public function getExpiresAt() : \DateTimeImmutable { return $this->expiresAt; } public function __toString() { return $this->url; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; use _ContaoManager\Symfony\Component\Security\Core\Signature\SignatureHasher; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception\ExpiredLoginLinkException; use _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception\InvalidLoginLinkException; /** * @author Ryan Weaver */ final class LoginLinkHandler implements LoginLinkHandlerInterface { private $urlGenerator; private $userProvider; private $options; private $signatureHasher; public function __construct(UrlGeneratorInterface $urlGenerator, UserProviderInterface $userProvider, SignatureHasher $signatureHasher, array $options) { $this->urlGenerator = $urlGenerator; $this->userProvider = $userProvider; $this->signatureHasher = $signatureHasher; $this->options = \array_merge(['route_name' => null, 'lifetime' => 600], $options); } public function createLoginLink(UserInterface $user, ?Request $request = null) : LoginLinkDetails { $expires = \time() + $this->options['lifetime']; $expiresAt = new \DateTimeImmutable('@' . $expires); $parameters = [ // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 'user' => \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), 'expires' => $expires, 'hash' => $this->signatureHasher->computeSignatureHash($user, $expires), ]; if ($request) { $currentRequestContext = $this->urlGenerator->getContext(); $this->urlGenerator->setContext((new RequestContext())->fromRequest($request)->setParameter('_locale', $request->getLocale())); } try { $url = $this->urlGenerator->generate($this->options['route_name'], $parameters, UrlGeneratorInterface::ABSOLUTE_URL); } finally { if ($request) { $this->urlGenerator->setContext($currentRequestContext); } } return new LoginLinkDetails($url, $expiresAt); } public function consumeLoginLink(Request $request) : UserInterface { $userIdentifier = $request->get('user'); if (!($hash = $request->get('hash'))) { throw new InvalidLoginLinkException('Missing "hash" parameter.'); } if (!($expires = $request->get('expires'))) { throw new InvalidLoginLinkException('Missing "expires" parameter.'); } try { $this->signatureHasher->acceptSignatureHash($userIdentifier, $expires, $hash); // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($this->userProvider, 'loadUserByIdentifier')) { $user = $this->userProvider->loadUserByIdentifier($userIdentifier); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $user = $this->userProvider->loadUserByUsername($userIdentifier); } $this->signatureHasher->verifySignatureHash($user, $expires, $hash); } catch (UserNotFoundException $e) { throw new InvalidLoginLinkException('User not found.', 0, $e); } catch (ExpiredSignatureException $e) { throw new ExpiredLoginLinkException(\ucfirst(\str_ireplace('signature', 'login link', $e->getMessage())), 0, $e); } catch (InvalidSignatureException $e) { throw new InvalidLoginLinkException(\ucfirst(\str_ireplace('signature', 'login link', $e->getMessage())), 0, $e); } return $user; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink; use _ContaoManager\Symfony\Bridge\Twig\Mime\NotificationEmail; use _ContaoManager\Symfony\Component\Notifier\Message\EmailMessage; use _ContaoManager\Symfony\Component\Notifier\Message\SmsMessage; use _ContaoManager\Symfony\Component\Notifier\Notification\EmailNotificationInterface; use _ContaoManager\Symfony\Component\Notifier\Notification\Notification; use _ContaoManager\Symfony\Component\Notifier\Notification\SmsNotificationInterface; use _ContaoManager\Symfony\Component\Notifier\Recipient\EmailRecipientInterface; use _ContaoManager\Symfony\Component\Notifier\Recipient\SmsRecipientInterface; /** * Use this notification to ease sending login link * emails/SMS using the Notifier component. * * @author Wouter de Jong */ class LoginLinkNotification extends Notification implements EmailNotificationInterface, SmsNotificationInterface { private $loginLinkDetails; public function __construct(LoginLinkDetails $loginLinkDetails, string $subject, array $channels = []) { parent::__construct($subject, $channels); $this->loginLinkDetails = $loginLinkDetails; } public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null) : ?EmailMessage { if (!\class_exists(NotificationEmail::class)) { throw new \LogicException(\sprintf('The "%s" method requires "symfony/twig-bridge:>4.4".', __METHOD__)); } $email = NotificationEmail::asPublicEmail()->to($recipient->getEmail())->subject($this->getSubject())->content($this->getContent() ?: $this->getDefaultContent('button below'))->action('Sign in', $this->loginLinkDetails->getUrl()); return new EmailMessage($email); } public function asSmsMessage(SmsRecipientInterface $recipient, ?string $transport = null) : ?SmsMessage { return new SmsMessage($recipient->getPhone(), $this->getDefaultContent('link') . ' ' . $this->loginLinkDetails->getUrl()); } private function getDefaultContent(string $target) : string { $duration = $this->loginLinkDetails->getExpiresAt()->getTimestamp() - \time(); $durationString = \floor($duration / 60) . ' minute' . ($duration > 60 ? 's' : ''); if (($hours = $duration / 3600) >= 1) { $durationString = \floor($hours) . ' hour' . ($hours >= 2 ? 's' : ''); } return \sprintf('Click on the %s to confirm you want to sign in. This link will expire in %s.', $target, $durationString); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * A class that is able to create and handle "magic" login links. * * @author Ryan Weaver */ interface LoginLinkHandlerInterface { /** * Generate a link that can be used to authenticate as the given user. */ public function createLoginLink(UserInterface $user, ?Request $request = null) : LoginLinkDetails; /** * Validates if this request contains a login link and returns the associated User. * * Throw InvalidLoginLinkExceptionInterface if the link is invalid. */ public function consumeLoginLink(Request $request) : UserInterface; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception; /** * @author Ryan Weaver */ interface InvalidLoginLinkExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception; /** * @author Ryan Weaver */ class InvalidLoginLinkException extends \RuntimeException implements InvalidLoginLinkExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; /** * Thrown when a login link is invalid. * * @author Ryan Weaver */ class InvalidLoginLinkAuthenticationException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Invalid or expired login link.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\LoginLink\Exception; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; /** * @author Ryan Weaver */ class ExpiredLoginLinkException extends ExpiredSignatureException implements InvalidLoginLinkExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * This class is used when the authenticator system is activated. * * This is used to not break AuthenticationChecker and ContextListener when * using the authenticator system. * * @author Wouter de Jong * * @internal */ class NoopAuthenticationManager implements AuthenticationManagerInterface { public function authenticate(TokenInterface $token) : TokenInterface { return $token; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; /** * Class with the default authentication failure handling logic. * * Can be optionally be extended from by the developer to alter the behavior * while keeping the default behavior. * * @author Fabien Potencier * @author Johannes M. Schmitt * @author Alexander */ class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface { protected $httpKernel; protected $httpUtils; protected $logger; protected $options; protected $defaultOptions = ['failure_path' => null, 'failure_forward' => \false, 'login_path' => '/login', 'failure_path_parameter' => '_failure_path']; public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options = [], ?LoggerInterface $logger = null) { $this->httpKernel = $httpKernel; $this->httpUtils = $httpUtils; $this->logger = $logger; $this->setOptions($options); } /** * Gets the options. * * @return array */ public function getOptions() { return $this->options; } public function setOptions(array $options) { $this->options = \array_merge($this->defaultOptions, $options); } /** * {@inheritdoc} */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { $options = $this->options; $failureUrl = ParameterBagUtils::getRequestParameterValue($request, $options['failure_path_parameter']); if (\is_string($failureUrl) && (\str_starts_with($failureUrl, '/') || \str_starts_with($failureUrl, 'http'))) { $options['failure_path'] = $failureUrl; } elseif ($this->logger && $failureUrl) { $this->logger->debug(\sprintf('Ignoring query parameter "%s": not a valid URL.', $options['failure_path_parameter'])); } $options['failure_path'] ?? ($options['failure_path'] = $options['login_path']); if ($options['failure_forward']) { if (null !== $this->logger) { $this->logger->debug('Authentication failure, forward triggered.', ['failure_path' => $options['failure_path']]); } $subRequest = $this->httpUtils->createRequest($request, $options['failure_path']); $subRequest->attributes->set(Security::AUTHENTICATION_ERROR, $exception); return $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); } if (null !== $this->logger) { $this->logger->debug('Authentication failure, redirect triggered.', ['failure_path' => $options['failure_path']]); } $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); return $this->httpUtils->createRedirectResponse($request, $options['failure_path']); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; /** * @author Wouter de Jong * @author Ryan Weaver */ interface AuthenticatorManagerInterface { /** * Called to see if authentication should be attempted on this request. * * @see FirewallListenerInterface::supports() */ public function supports(Request $request) : ?bool; /** * Tries to authenticate the request and returns a response - if any authenticator set one. */ public function authenticateRequest(Request $request) : ?Response; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * @author Fabien Potencier */ class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface { private $handler; /** * @param array $options Options for processing a successful authentication attempt */ public function __construct(AuthenticationSuccessHandlerInterface $handler, array $options, string $firewallName) { $this->handler = $handler; if (\method_exists($handler, 'setOptions')) { $this->handler->setOptions($options); } if (\method_exists($handler, 'setFirewallName')) { $this->handler->setFirewallName($firewallName); } elseif (\method_exists($handler, 'setProviderKey')) { \trigger_deprecation('symfony/security-http', '5.2', 'Method "%s::setProviderKey()" is deprecated, rename the method to "setFirewallName()" instead.', \get_class($handler)); $this->handler->setProviderKey($firewallName); } } /** * {@inheritdoc} */ public function onAuthenticationSuccess(Request $request, TokenInterface $token) { return $this->handler->onAuthenticationSuccess($request, $token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; /** * Interface for custom authentication failure handlers. * * If you want to customize the failure handling process, instead of * overwriting the respective listener globally, you can set a custom failure * handler which implements this interface. * * @author Johannes M. Schmitt */ interface AuthenticationFailureHandlerInterface { /** * This is called when an interactive authentication attempt fails. This is * called by authentication listeners inheriting from * AbstractAuthenticationListener. * * @return Response */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Security; /** * Extracts Security Errors from Request. * * @author Boris Vujicic */ class AuthenticationUtils { private $requestStack; public function __construct(RequestStack $requestStack) { $this->requestStack = $requestStack; } /** * @return AuthenticationException|null */ public function getLastAuthenticationError(bool $clearSession = \true) { $request = $this->getRequest(); $authenticationException = null; if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { $authenticationException = $request->attributes->get(Security::AUTHENTICATION_ERROR); } elseif ($request->hasSession() && ($session = $request->getSession())->has(Security::AUTHENTICATION_ERROR)) { $authenticationException = $session->get(Security::AUTHENTICATION_ERROR); if ($clearSession) { $session->remove(Security::AUTHENTICATION_ERROR); } } return $authenticationException; } /** * @return string */ public function getLastUsername() { $request = $this->getRequest(); if ($request->attributes->has(Security::LAST_USERNAME)) { return $request->attributes->get(Security::LAST_USERNAME) ?? ''; } return $request->hasSession() ? $request->getSession()->get(Security::LAST_USERNAME) ?? '' : ''; } /** * @throws \LogicException */ private function getRequest() : Request { $request = $this->requestStack->getCurrentRequest(); if (null === $request) { throw new \LogicException('Request should exist so it can be processed for error.'); } return $request; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; /** * @author Fabien Potencier */ class CustomAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface { private $handler; /** * @param array $options Options for processing a successful authentication attempt */ public function __construct(AuthenticationFailureHandlerInterface $handler, array $options) { $this->handler = $handler; if (\method_exists($handler, 'setOptions')) { $this->handler->setOptions($options); } } /** * {@inheritdoc} */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { return $this->handler->onAuthenticationFailure($request, $exception); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * Interface for a custom authentication success handler. * * If you want to customize the success handling process, instead of * overwriting the respective listener globally, you can set a custom success * handler which implements this interface. * * @author Johannes M. Schmitt */ interface AuthenticationSuccessHandlerInterface { /** * This is called when an interactive authentication attempt succeeds. This * is called by authentication listeners inheriting from * AbstractAuthenticationListener. * * @return Response|null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; /** * @author Wouter de Jong */ interface UserAuthenticatorInterface { /** * Convenience method to programmatically login a user and return a * Response *if any* for success. * * @param BadgeInterface[] $badges Optionally, pass some Passport badges to use for the manual login */ public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []) : ?Response; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\AuthenticationEvents; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use _ContaoManager\Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginFailureEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * @author Wouter de Jong * @author Ryan Weaver * @author Amaury Leroux de Lens */ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthenticatorInterface { private $authenticators; private $tokenStorage; private $eventDispatcher; private $eraseCredentials; private $logger; private $firewallName; private $hideUserNotFoundExceptions; private $requiredBadges; /** * @param iterable $authenticators */ public function __construct(iterable $authenticators, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, string $firewallName, ?LoggerInterface $logger = null, bool $eraseCredentials = \true, bool $hideUserNotFoundExceptions = \true, array $requiredBadges = []) { $this->authenticators = $authenticators; $this->tokenStorage = $tokenStorage; $this->eventDispatcher = $eventDispatcher; $this->firewallName = $firewallName; $this->logger = $logger; $this->eraseCredentials = $eraseCredentials; $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; $this->requiredBadges = $requiredBadges; } /** * @param BadgeInterface[] $badges Optionally, pass some Passport badges to use for the manual login */ public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []) : ?Response { // create an authentication token for the User // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 $passport = new SelfValidatingPassport(new UserBadge(\method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), function () use($user) { return $user; }), $badges); $token = \method_exists($authenticator, 'createToken') ? $authenticator->createToken($passport, $this->firewallName) : $authenticator->createAuthenticatedToken($passport, $this->firewallName); // announce the authentication token $token = $this->eventDispatcher->dispatch(new AuthenticationTokenCreatedEvent($token, $passport))->getAuthenticatedToken(); // authenticate this in the system return $this->handleAuthenticationSuccess($token, $passport, $request, $authenticator, $this->tokenStorage->getToken()); } public function supports(Request $request) : ?bool { if (null !== $this->logger) { $context = ['firewall_name' => $this->firewallName]; if ($this->authenticators instanceof \Countable || \is_array($this->authenticators)) { $context['authenticators'] = \count($this->authenticators); } $this->logger->debug('Checking for authenticator support.', $context); } $authenticators = []; $skippedAuthenticators = []; $lazy = \true; foreach ($this->authenticators as $authenticator) { if (null !== $this->logger) { $this->logger->debug('Checking support on authenticator.', ['firewall_name' => $this->firewallName, 'authenticator' => \get_class($authenticator)]); } if (\false !== ($supports = $authenticator->supports($request))) { $authenticators[] = $authenticator; $lazy = $lazy && null === $supports; } else { if (null !== $this->logger) { $this->logger->debug('Authenticator does not support the request.', ['firewall_name' => $this->firewallName, 'authenticator' => \get_class($authenticator)]); } $skippedAuthenticators[] = $authenticator; } } if (!$authenticators) { return \false; } $request->attributes->set('_security_authenticators', $authenticators); $request->attributes->set('_security_skipped_authenticators', $skippedAuthenticators); return $lazy ? null : \true; } public function authenticateRequest(Request $request) : ?Response { $authenticators = $request->attributes->get('_security_authenticators'); $request->attributes->remove('_security_authenticators'); $request->attributes->remove('_security_skipped_authenticators'); if (!$authenticators) { return null; } return $this->executeAuthenticators($authenticators, $request); } /** * @param AuthenticatorInterface[] $authenticators */ private function executeAuthenticators(array $authenticators, Request $request) : ?Response { foreach ($authenticators as $authenticator) { // recheck if the authenticator still supports the listener. supports() is called // eagerly (before token storage is initialized), whereas authenticate() is called // lazily (after initialization). if (\false === $authenticator->supports($request)) { if (null !== $this->logger) { $this->logger->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); } continue; } $response = $this->executeAuthenticator($authenticator, $request); if (null !== $response) { if (null !== $this->logger) { $this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); } return $response; } } return null; } private function executeAuthenticator(AuthenticatorInterface $authenticator, Request $request) : ?Response { $passport = null; $previousToken = $this->tokenStorage->getToken(); try { // get the passport from the Authenticator $passport = $authenticator->authenticate($request); // check the passport (e.g. password checking) $event = new CheckPassportEvent($authenticator, $passport); $this->eventDispatcher->dispatch($event); // check if all badges are resolved $resolvedBadges = []; foreach ($passport->getBadges() as $badge) { if (!$badge->isResolved()) { throw new BadCredentialsException(\sprintf('Authentication failed: Security badge "%s" is not resolved, did you forget to register the correct listeners?', \get_debug_type($badge))); } $resolvedBadges[] = \get_class($badge); } $missingRequiredBadges = \array_diff($this->requiredBadges, $resolvedBadges); if ($missingRequiredBadges) { throw new BadCredentialsException(\sprintf('Authentication failed; Some badges marked as required by the firewall config are not available on the passport: "%s".', \implode('", "', $missingRequiredBadges))); } // create the authentication token $authenticatedToken = \method_exists($authenticator, 'createToken') ? $authenticator->createToken($passport, $this->firewallName) : $authenticator->createAuthenticatedToken($passport, $this->firewallName); // announce the authentication token $authenticatedToken = $this->eventDispatcher->dispatch(new AuthenticationTokenCreatedEvent($authenticatedToken, $passport))->getAuthenticatedToken(); if (\true === $this->eraseCredentials) { $authenticatedToken->eraseCredentials(); } $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS); if (null !== $this->logger) { $this->logger->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); } } catch (AuthenticationException $e) { // oh no! Authentication failed! $response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport); if ($response instanceof Response) { return $response; } return null; } // success! (sets the token on the token storage, etc) $response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator, $previousToken); if ($response instanceof Response) { return $response; } if (null !== $this->logger) { $this->logger->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); } return null; } private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator, ?TokenInterface $previousToken) : ?Response { // @deprecated since Symfony 5.3 $user = $authenticatedToken->getUser(); if ($user instanceof UserInterface && !\method_exists($user, 'getUserIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in user class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', \get_debug_type($authenticatedToken->getUser())); } $this->tokenStorage->setToken($authenticatedToken); $response = $authenticator->onAuthenticationSuccess($request, $authenticatedToken, $this->firewallName); if ($authenticator instanceof InteractiveAuthenticatorInterface && $authenticator->isInteractive()) { $loginEvent = new InteractiveLoginEvent($request, $authenticatedToken); $this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } $this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $passport, $authenticatedToken, $request, $response, $this->firewallName, $previousToken)); return $loginSuccessEvent->getResponse(); } /** * Handles an authentication failure and returns the Response for the authenticator. */ private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator, ?PassportInterface $passport) : ?Response { if (null !== $this->logger) { $this->logger->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); } // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status) // to prevent user enumeration via response content comparison if ($this->hideUserNotFoundExceptions && ($authenticationException instanceof UserNotFoundException || $authenticationException instanceof AccountStatusException && !$authenticationException instanceof CustomUserMessageAccountStatusException)) { $authenticationException = new BadCredentialsException('Bad credentials.', 0, $authenticationException); } $response = $authenticator->onAuthenticationFailure($request, $authenticationException); if (null !== $response && null !== $this->logger) { $this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); } $this->eventDispatcher->dispatch($loginFailureEvent = new LoginFailureEvent($authenticationException, $authenticator, $request, $response, $this->firewallName, $passport)); // returning null is ok, it means they want the request to continue return $loginFailureEvent->getResponse(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Authentication; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; use _ContaoManager\Symfony\Component\Security\Http\Util\TargetPathTrait; /** * Class with the default authentication success handling logic. * * @author Fabien Potencier * @author Johannes M. Schmitt * @author Alexander */ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface { use TargetPathTrait; protected $httpUtils; protected $logger; protected $options; /** @deprecated since Symfony 5.2, use $firewallName instead */ protected $providerKey; protected $firewallName; protected $defaultOptions = ['always_use_default_target_path' => \false, 'default_target_path' => '/', 'login_path' => '/login', 'target_path_parameter' => '_target_path', 'use_referer' => \false]; /** * @param array $options Options for processing a successful authentication attempt */ public function __construct(HttpUtils $httpUtils, array $options = [], ?LoggerInterface $logger = null) { $this->httpUtils = $httpUtils; $this->logger = $logger; $this->setOptions($options); } /** * {@inheritdoc} */ public function onAuthenticationSuccess(Request $request, TokenInterface $token) { return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request)); } /** * Gets the options. * * @return array */ public function getOptions() { return $this->options; } public function setOptions(array $options) { $this->options = \array_merge($this->defaultOptions, $options); } /** * Get the provider key. * * @return string * * @deprecated since Symfony 5.2, use getFirewallName() instead */ public function getProviderKey() { if (1 !== \func_num_args() || \true !== \func_get_arg(0)) { \trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__); } if ($this->providerKey !== $this->firewallName) { \trigger_deprecation('symfony/security-core', '5.2', 'The "%1$s::$providerKey" property is deprecated, use "%1$s::$firewallName" instead.', __CLASS__); return $this->providerKey; } return $this->firewallName; } public function setProviderKey(string $providerKey) { if (2 !== \func_num_args() || \true !== \func_get_arg(1)) { \trigger_deprecation('symfony/security-http', '5.2', 'Method "%s" is deprecated, use "setFirewallName()" instead.', __METHOD__); } $this->providerKey = $providerKey; } public function getFirewallName() : ?string { return $this->getProviderKey(\true); } public function setFirewallName(string $firewallName) : void { $this->setProviderKey($firewallName, \true); $this->firewallName = $firewallName; } /** * Builds the target URL according to the defined options. * * @return string */ protected function determineTargetUrl(Request $request) { if ($this->options['always_use_default_target_path']) { return $this->options['default_target_path']; } $targetUrl = ParameterBagUtils::getRequestParameterValue($request, $this->options['target_path_parameter']); if (\is_string($targetUrl) && (\str_starts_with($targetUrl, '/') || \str_starts_with($targetUrl, 'http'))) { return $targetUrl; } if ($this->logger && $targetUrl) { $this->logger->debug(\sprintf('Ignoring query parameter "%s": not a valid URL.', $this->options['target_path_parameter'])); } $firewallName = $this->getFirewallName(); if (null !== $firewallName && ($targetUrl = $this->getTargetPath($request->getSession(), $firewallName))) { $this->removeTargetPath($request->getSession(), $firewallName); return $targetUrl; } if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer'))) { if (\false !== ($pos = \strpos($targetUrl, '?'))) { $targetUrl = \substr($targetUrl, 0, $pos); } if ($targetUrl && $targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) { return $targetUrl; } } return $this->options['default_target_path']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestMatcherInterface; /** * AccessMap allows configuration of different access control rules for * specific parts of the website. * * @author Fabien Potencier */ class AccessMap implements AccessMapInterface { private $map = []; /** * @param array $attributes An array of attributes to pass to the access decision manager (like roles) * @param string|null $channel The channel to enforce (http, https, or null) */ public function add(RequestMatcherInterface $requestMatcher, array $attributes = [], ?string $channel = null) { $this->map[] = [$requestMatcher, $attributes, $channel]; } /** * {@inheritdoc} */ public function getPatterns(Request $request) { foreach ($this->map as $elements) { if (null === $elements[0] || $elements[0]->matches($request)) { return [$elements[1], $elements[2]]; } } return [null, null]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * This event is dispatched after an error during authentication. * * Listeners to this event can change state based on authentication * failure (e.g. to implement login throttling). * * @author Wouter de Jong */ class LoginFailureEvent extends Event { private $exception; private $authenticator; private $request; private $response; private $firewallName; private $passport; /** * @param Passport|null $passport */ public function __construct(AuthenticationException $exception, AuthenticatorInterface $authenticator, Request $request, ?Response $response, string $firewallName, ?PassportInterface $passport = null) { if (null !== $passport && !$passport instanceof Passport) { \trigger_deprecation('symfony/security-http', '5.4', 'Not passing an instance of "%s" or "null" as "$passport" argument of "%s()" is deprecated, "%s" given.', Passport::class, __METHOD__, \get_debug_type($passport)); } $this->exception = $exception; $this->authenticator = $authenticator; $this->request = $request; $this->response = $response; $this->firewallName = $firewallName; $this->passport = $passport; } public function getException() : AuthenticationException { return $this->exception; } public function getAuthenticator() : AuthenticatorInterface { return $this->authenticator; } public function getFirewallName() : string { return $this->firewallName; } public function getRequest() : Request { return $this->request; } public function setResponse(?Response $response) { $this->response = $response; } public function getResponse() : ?Response { return $this->response; } public function getPassport() : ?PassportInterface { return $this->passport; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * This event is dispatched after authentication has successfully completed. * * At this stage, the authenticator created a token and * generated an authentication success response. Listeners to * this event can do actions related to successful authentication * (such as migrating the password). * * @author Wouter de Jong */ class LoginSuccessEvent extends Event { private $authenticator; private $passport; private $authenticatedToken; private $previousToken; private $request; private $response; private $firewallName; /** * @param Passport $passport */ public function __construct(AuthenticatorInterface $authenticator, PassportInterface $passport, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $firewallName, ?TokenInterface $previousToken = null) { if (!$passport instanceof Passport) { \trigger_deprecation('symfony/security-http', '5.4', 'Not passing an instance of "%s" as "$passport" argument of "%s()" is deprecated, "%s" given.', Passport::class, __METHOD__, \get_debug_type($passport)); } $this->authenticator = $authenticator; $this->passport = $passport; $this->authenticatedToken = $authenticatedToken; $this->previousToken = $previousToken; $this->request = $request; $this->response = $response; $this->firewallName = $firewallName; } public function getAuthenticator() : AuthenticatorInterface { return $this->authenticator; } public function getPassport() : PassportInterface { return $this->passport; } public function getUser() : UserInterface { // @deprecated since Symfony 5.4, passport will always have a user in 6.0 if (!$this->passport instanceof UserPassportInterface) { throw new LogicException(\sprintf('Cannot call "%s" as the authenticator ("%s") did not set a user.', __METHOD__, \get_class($this->authenticator))); } return $this->passport->getUser(); } public function getAuthenticatedToken() : TokenInterface { return $this->authenticatedToken; } public function getPreviousToken() : ?TokenInterface { return $this->previousToken; } public function getRequest() : Request { return $this->request; } public function getFirewallName() : string { return $this->firewallName; } public function setResponse(?Response $response) : void { $this->response = $response; } public function getResponse() : ?Response { return $this->response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * Deauthentication happens in case the user has changed when trying to * refresh the token. * * Use {@see TokenDeauthenticatedEvent} if you want to cover all cases where * a session is deauthenticated. * * @author Hamza Amrouche * * @deprecated since Symfony 5.4, use TokenDeauthenticatedEvent instead */ final class DeauthenticatedEvent extends Event { private $originalToken; private $refreshedToken; public function __construct(TokenInterface $originalToken, TokenInterface $refreshedToken, bool $triggerDeprecation = \true) { if ($triggerDeprecation) { @\trigger_deprecation('symfony/security-http', '5.4', 'Class "%s" is deprecated, use "%s" instead.', __CLASS__, TokenDeauthenticatedEvent::class); } $this->originalToken = $originalToken; $this->refreshedToken = $refreshedToken; } public function getRefreshedToken() : TokenInterface { @\trigger_deprecation('symfony/security-http', '5.4', 'Class "%s" is deprecated, use "%s" instead.', __CLASS__, TokenDeauthenticatedEvent::class); return $this->refreshedToken; } public function getOriginalToken() : TokenInterface { @\trigger_deprecation('symfony/security-http', '5.4', 'Class "%s" is deprecated, use "%s" instead.', __CLASS__, TokenDeauthenticatedEvent::class); return $this->originalToken; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\LazyResponseException; /** * Wraps a lazily computed response in a signaling exception. * * @author Nicolas Grekas */ final class LazyResponseEvent extends RequestEvent { private $event; public function __construct(parent $event) { $this->event = $event; } /** * {@inheritdoc} */ public function setResponse(Response $response) { $this->stopPropagation(); $this->event->stopPropagation(); throw new LazyResponseException($response); } /** * {@inheritdoc} */ public function getKernel() : HttpKernelInterface { return $this->event->getKernel(); } /** * {@inheritdoc} */ public function getRequest() : Request { return $this->event->getRequest(); } /** * {@inheritdoc} */ public function getRequestType() : int { return $this->event->getRequestType(); } /** * {@inheritdoc} */ public function isMainRequest() : bool { return $this->event->isMainRequest(); } /** * {@inheritdoc} */ public function isMasterRequest() : bool { \trigger_deprecation('symfony/security-http', '5.3', '"%s()" is deprecated, use "isMainRequest()" instead.', __METHOD__); return $this->event->isMainRequest(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * SwitchUserEvent. * * @author Fabien Potencier */ final class SwitchUserEvent extends Event { private $request; private $targetUser; private $token; public function __construct(Request $request, UserInterface $targetUser, ?TokenInterface $token = null) { $this->request = $request; $this->targetUser = $targetUser; $this->token = $token; } public function getRequest() : Request { return $this->request; } public function getTargetUser() : UserInterface { return $this->targetUser; } public function getToken() : ?TokenInterface { return $this->token; } public function setToken(TokenInterface $token) { $this->token = $token; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * When a newly authenticated security token was created, before it becomes effective in the security system. * * @author Christian Scheb */ class AuthenticationTokenCreatedEvent extends Event { private $authenticatedToken; private $passport; /** * @param Passport $passport */ public function __construct(TokenInterface $token, PassportInterface $passport) { if (!$passport instanceof Passport) { \trigger_deprecation('symfony/security-http', '5.4', 'Not passing an instance of "%s" as "$passport" argument of "%s()" is deprecated, "%s" given.', Passport::class, __METHOD__, \get_debug_type($passport)); } $this->authenticatedToken = $token; $this->passport = $passport; } public function getAuthenticatedToken() : TokenInterface { return $this->authenticatedToken; } public function setAuthenticatedToken(TokenInterface $authenticatedToken) : void { $this->authenticatedToken = $authenticatedToken; } public function getPassport() : PassportInterface { return $this->passport; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * This event is dispatched when the credentials have to be checked. * * Listeners to this event must validate the user and the * credentials (e.g. default listeners do password verification and * user checking) * * @author Wouter de Jong */ class CheckPassportEvent extends Event { private $authenticator; private $passport; /** * @param Passport $passport */ public function __construct(AuthenticatorInterface $authenticator, PassportInterface $passport) { if (!$passport instanceof Passport) { \trigger_deprecation('symfony/security-http', '5.4', 'Not passing an instance of "%s" as "$passport" argument of "%s()" is deprecated, "%s" given.', Passport::class, __METHOD__, \get_debug_type($passport)); } $this->authenticator = $authenticator; $this->passport = $passport; } public function getAuthenticator() : AuthenticatorInterface { return $this->authenticator; } public function getPassport() : PassportInterface { return $this->passport; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * @author Fabien Potencier */ final class InteractiveLoginEvent extends Event { private $request; private $authenticationToken; public function __construct(Request $request, TokenInterface $authenticationToken) { $this->request = $request; $this->authenticationToken = $authenticationToken; } public function getRequest() : Request { return $this->request; } public function getAuthenticationToken() : TokenInterface { return $this->authenticationToken; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * @author Wouter de Jong */ class LogoutEvent extends Event { private $request; private $response; private $token; public function __construct(Request $request, ?TokenInterface $token) { $this->request = $request; $this->token = $token; } public function getRequest() : Request { return $this->request; } public function getToken() : ?TokenInterface { return $this->token; } public function setResponse(Response $response) : void { $this->response = $response; } public function getResponse() : ?Response { return $this->response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * This event is dispatched when the current security token is deauthenticated * when trying to reference the token. * * This includes changes in the user ({@see DeauthenticatedEvent}), but * also cases where there is no user provider available to refresh the user. * * Use this event if you want to trigger some actions whenever a user is * deauthenticated and redirected back to the authentication entry point * (e.g. clearing all remember-me cookies). * * @author Wouter de Jong */ final class TokenDeauthenticatedEvent extends Event { private $originalToken; private $request; public function __construct(TokenInterface $originalToken, Request $request) { $this->originalToken = $originalToken; $this->request = $request; } public function getOriginalToken() : TokenInterface { return $this->originalToken; } public function getRequest() : Request { return $this->request; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Impersonate; use _ContaoManager\Symfony\Bundle\SecurityBundle\Security\FirewallMap; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Http\Firewall\SwitchUserListener; /** * Provides generator functions for the impersonate url exit. * * @author Amrouche Hamza * @author Damien Fayet */ class ImpersonateUrlGenerator { private $requestStack; private $tokenStorage; private $firewallMap; public function __construct(RequestStack $requestStack, FirewallMap $firewallMap, TokenStorageInterface $tokenStorage) { $this->requestStack = $requestStack; $this->tokenStorage = $tokenStorage; $this->firewallMap = $firewallMap; } public function generateExitPath(?string $targetUri = null) : string { return $this->buildExitPath($targetUri); } public function generateExitUrl(?string $targetUri = null) : string { if (null === ($request = $this->requestStack->getCurrentRequest())) { return ''; } return $request->getUriForPath($this->buildExitPath($targetUri)); } private function isImpersonatedUser() : bool { return $this->tokenStorage->getToken() instanceof SwitchUserToken; } private function buildExitPath(?string $targetUri = null) : string { if (null === ($request = $this->requestStack->getCurrentRequest()) || !$this->isImpersonatedUser()) { return ''; } if (null === ($switchUserConfig = $this->firewallMap->getFirewallConfig($request)->getSwitchUser())) { throw new \LogicException('Unable to generate the impersonate exit URL without a firewall configured for the user switch.'); } if (null === $targetUri) { $targetUri = $request->getRequestUri(); } $targetUri .= (\parse_url($targetUri, \PHP_URL_QUERY) ? '&' : '?') . \http_build_query([$switchUserConfig['parameter'] => SwitchUserListener::EXIT_VALUE], '', '&'); return $targetUri; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http; use _ContaoManager\Symfony\Component\HttpFoundation\ParameterBag; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\AccessException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccess; /** * @internal */ final class ParameterBagUtils { private static $propertyAccessor; /** * Returns a "parameter" value. * * Paths like foo[bar] will be evaluated to find deeper items in nested data structures. * * @return mixed * * @throws InvalidArgumentException when the given path is malformed */ public static function getParameterBagValue(ParameterBag $parameters, string $path) { if (\false === ($pos = \strpos($path, '['))) { return $parameters->all()[$path] ?? null; } $root = \substr($path, 0, $pos); if (null === ($value = $parameters->all()[$root] ?? null)) { return null; } if (null === self::$propertyAccessor) { self::$propertyAccessor = PropertyAccess::createPropertyAccessor(); } try { return self::$propertyAccessor->getValue($value, \substr($path, $pos)); } catch (AccessException $e) { return null; } } /** * Returns a request "parameter" value. * * Paths like foo[bar] will be evaluated to find deeper items in nested data structures. * * @return mixed * * @throws InvalidArgumentException when the given path is malformed */ public static function getRequestParameterValue(Request $request, string $path) { if (\false === ($pos = \strpos($path, '['))) { return $request->get($path); } $root = \substr($path, 0, $pos); if (null === ($value = $request->get($root))) { return null; } if (null === self::$propertyAccessor) { self::$propertyAccessor = PropertyAccess::createPropertyAccessor(); } try { return self::$propertyAccessor->getValue($value, \substr($path, $pos)); } catch (AccessException $e) { return null; } } } { "name": "symfony\/security-http", "type": "library", "description": "Symfony Security Component - HTTP Integration", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/http-foundation": "^5.3|^6.0", "symfony\/http-kernel": "^5.3|^6.0", "symfony\/polyfill-mbstring": "~1.0", "symfony\/polyfill-php80": "^1.16", "symfony\/property-access": "^4.4|^5.0|^6.0", "symfony\/security-core": "^5.4.19|~6.0.19|~6.1.11|^6.2.5", "symfony\/service-contracts": "^1.10|^2|^3" }, "require-dev": { "symfony\/cache": "^4.4|^5.0|^6.0", "symfony\/rate-limiter": "^5.2|^6.0", "symfony\/routing": "^4.4|^5.0|^6.0", "symfony\/security-csrf": "^4.4|^5.0|^6.0", "symfony\/translation": "^4.4|^5.0|^6.0", "psr\/log": "^1|^2|^3" }, "conflict": { "symfony\/event-dispatcher": "<4.3", "symfony\/security-bundle": "<5.3", "symfony\/security-csrf": "<4.4" }, "suggest": { "symfony\/security-csrf": "For using tokens to protect authentication\/logout attempts", "symfony\/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Security\\Http\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; /** * Handler for clearing invalidating the current session. * * @author Johannes M. Schmitt * * @final */ class SessionLogoutListener implements EventSubscriberInterface { public function onLogout(LogoutEvent $event) : void { if ($event->getRequest()->hasSession()) { $event->getRequest()->getSession()->invalidate(); } } public static function getSubscribedEvents() : array { return [LogoutEvent::class => 'onLogout']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\HttpUtils; /** * Default logout listener will redirect users to a configured path. * * @author Fabien Potencier * @author Alexander * * @final */ class DefaultLogoutListener implements EventSubscriberInterface { private $httpUtils; private $targetUrl; public function __construct(HttpUtils $httpUtils, string $targetUrl = '/') { $this->httpUtils = $httpUtils; $this->targetUrl = $targetUrl; } public function onLogout(LogoutEvent $event) : void { if (null !== $event->getResponse()) { return; } $event->setResponse($this->httpUtils->createRedirectResponse($event->getRequest(), $this->targetUrl)); } public static function getSubscribedEvents() : array { return [LogoutEvent::class => ['onLogout', 64]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; /** * @author Wouter de Jong * * @final */ class PasswordMigratingListener implements EventSubscriberInterface { private $hasherFactory; /** * @param PasswordHasherFactoryInterface $hasherFactory */ public function __construct($hasherFactory) { if ($hasherFactory instanceof EncoderFactoryInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); } $this->hasherFactory = $hasherFactory; } public function onLoginSuccess(LoginSuccessEvent $event) : void { $passport = $event->getPassport(); if (!$passport instanceof UserPassportInterface || !$passport->hasBadge(PasswordUpgradeBadge::class)) { return; } /** @var PasswordUpgradeBadge $badge */ $badge = $passport->getBadge(PasswordUpgradeBadge::class); $plaintextPassword = $badge->getAndErasePlaintextPassword(); if ('' === $plaintextPassword) { return; } $user = $passport->getUser(); if (null === $user->getPassword()) { return; } $passwordHasher = $this->hasherFactory instanceof EncoderFactoryInterface ? $this->hasherFactory->getEncoder($user) : $this->hasherFactory->getPasswordHasher($user); if (!$passwordHasher->needsRehash($user->getPassword())) { return; } $passwordUpgrader = $badge->getPasswordUpgrader(); if (null === $passwordUpgrader) { if (!$passport->hasBadge(UserBadge::class)) { return; } /** @var UserBadge $userBadge */ $userBadge = $passport->getBadge(UserBadge::class); $userLoader = $userBadge->getUserLoader(); if (\is_array($userLoader) && $userLoader[0] instanceof PasswordUpgraderInterface) { $passwordUpgrader = $userLoader[0]; } elseif (!$userLoader instanceof \Closure || !($passwordUpgrader = (new \ReflectionFunction($userLoader))->getClosureThis()) instanceof PasswordUpgraderInterface) { return; } } $passwordUpgrader->upgradePassword($user, $passwordHasher instanceof PasswordHasherInterface ? $passwordHasher->hash($plaintextPassword, $user->getSalt()) : $passwordHasher->encodePassword($plaintextPassword, $user->getSalt())); } public static function getSubscribedEvents() : array { return [LoginSuccessEvent::class => 'onLoginSuccess']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginFailureEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; /** * The RememberMe *listener* creates and deletes remember-me cookies. * * Upon login success or failure and support for remember me * in the firewall and authenticator, this listener will create * a remember-me cookie. * Upon login failure, all remember-me cookies are removed. * * @author Wouter de Jong * * @final */ class RememberMeListener implements EventSubscriberInterface { private $rememberMeHandler; private $logger; public function __construct(RememberMeHandlerInterface $rememberMeHandler, ?LoggerInterface $logger = null) { $this->rememberMeHandler = $rememberMeHandler; $this->logger = $logger; } public function onSuccessfulLogin(LoginSuccessEvent $event) : void { $passport = $event->getPassport(); if (!$passport->hasBadge(RememberMeBadge::class)) { if (null !== $this->logger) { $this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($event->getAuthenticator())]); } return; } // Make sure any old remember-me cookies are cancelled $this->rememberMeHandler->clearRememberMeCookie(); /** @var RememberMeBadge $badge */ $badge = $passport->getBadge(RememberMeBadge::class); if (!$badge->isEnabled()) { if (null !== $this->logger) { $this->logger->debug('Remember me skipped: the RememberMeBadge is not enabled.'); } return; } if (null !== $this->logger) { $this->logger->debug('Remember-me was requested; setting cookie.'); } $this->rememberMeHandler->createRememberMeCookie($event->getUser()); } public function clearCookie() : void { $this->rememberMeHandler->clearRememberMeCookie(); } public static function getSubscribedEvents() : array { return [LoginSuccessEvent::class => ['onSuccessfulLogin', -64], LoginFailureEvent::class => 'clearCookie', LogoutEvent::class => 'clearCookie', TokenDeauthenticatedEvent::class => 'clearCookie']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Exception\TooManyLoginAttemptsAuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; /** * @author Wouter de Jong */ final class LoginThrottlingListener implements EventSubscriberInterface { private $requestStack; private $limiter; public function __construct(RequestStack $requestStack, RequestRateLimiterInterface $limiter) { $this->requestStack = $requestStack; $this->limiter = $limiter; } public function checkPassport(CheckPassportEvent $event) : void { $passport = $event->getPassport(); if (!$passport->hasBadge(UserBadge::class)) { return; } $request = $this->requestStack->getMainRequest(); $request->attributes->set(Security::LAST_USERNAME, $passport->getBadge(UserBadge::class)->getUserIdentifier()); $limit = $this->limiter->consume($request); if (!$limit->isAccepted()) { throw new TooManyLoginAttemptsAuthenticationException(\ceil(($limit->getRetryAfter()->getTimestamp() - \time()) / 60)); } } public function onSuccessfulLogin(LoginSuccessEvent $event) : void { $this->limiter->reset($event->getRequest()); } public static function getSubscribedEvents() : array { return [CheckPassportEvent::class => ['checkPassport', 2080], LoginSuccessEvent::class => 'onSuccessfulLogin']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; /** * Configures the user provider as user loader, if no user load * has been explicitly set. * * @author Wouter de Jong * * @final */ class UserProviderListener { private $userProvider; public function __construct(UserProviderInterface $userProvider) { $this->userProvider = $userProvider; } public function checkPassport(CheckPassportEvent $event) : void { $passport = $event->getPassport(); if (!$passport->hasBadge(UserBadge::class)) { return; } /** @var UserBadge $badge */ $badge = $passport->getBadge(UserBadge::class); if (null !== $badge->getUserLoader()) { return; } // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($this->userProvider, 'loadUserByIdentifier')) { $badge->setUserLoader([$this->userProvider, 'loadUserByIdentifier']); } else { \trigger_deprecation('symfony/security-http', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $badge->setUserLoader([$this->userProvider, 'loadUserByUsername']); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; /** * This listener clears the passed cookies when a user logs out. * * @author Johannes M. Schmitt * * @final */ class CookieClearingLogoutListener implements EventSubscriberInterface { private $cookies; /** * @param array $cookies An array of cookies (keys are names, values contain path and domain) to unset */ public function __construct(array $cookies) { $this->cookies = $cookies; } public function onLogout(LogoutEvent $event) : void { if (!($response = $event->getResponse())) { return; } foreach ($this->cookies as $cookieName => $cookieData) { $response->headers->clearCookie($cookieName, $cookieData['path'], $cookieData['domain'], $cookieData['secure'] ?? \false, \true, $cookieData['samesite'] ?? null); } } public static function getSubscribedEvents() : array { return [LogoutEvent::class => ['onLogout', -255]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; /** * This listeners uses the interfaces of authenticators to * determine how to check credentials. * * @author Wouter de Jong * * @final */ class CheckCredentialsListener implements EventSubscriberInterface { private $hasherFactory; /** * @param PasswordHasherFactoryInterface $hasherFactory */ public function __construct($hasherFactory) { if ($hasherFactory instanceof EncoderFactoryInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); } $this->hasherFactory = $hasherFactory; } public function checkPassport(CheckPassportEvent $event) : void { $passport = $event->getPassport(); if ($passport instanceof UserPassportInterface && $passport->hasBadge(PasswordCredentials::class)) { // Use the password hasher to validate the credentials $user = $passport->getUser(); if (!$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-http', '5.3', 'Not implementing the "%s" interface in class "%s" while using password-based authentication is deprecated.', PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } /** @var PasswordCredentials $badge */ $badge = $passport->getBadge(PasswordCredentials::class); if ($badge->isResolved()) { return; } $presentedPassword = $badge->getPassword(); if ('' === $presentedPassword) { throw new BadCredentialsException('The presented password cannot be empty.'); } if (null === $user->getPassword()) { throw new BadCredentialsException('The presented password is invalid.'); } $salt = \method_exists($user, 'getSalt') ? $user->getSalt() : ''; if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-http', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } // @deprecated since Symfony 5.3 if ($this->hasherFactory instanceof EncoderFactoryInterface) { if (!$this->hasherFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $salt)) { throw new BadCredentialsException('The presented password is invalid.'); } } else { if (!$this->hasherFactory->getPasswordHasher($user)->verify($user->getPassword(), $presentedPassword, $salt)) { throw new BadCredentialsException('The presented password is invalid.'); } } $badge->markResolved(); if (!$passport->hasBadge(PasswordUpgradeBadge::class)) { $passport->addBadge(new PasswordUpgradeBadge($presentedPassword)); } return; } if ($passport->hasBadge(CustomCredentials::class)) { /** @var CustomCredentials $badge */ $badge = $passport->getBadge(CustomCredentials::class); if ($badge->isResolved()) { return; } $badge->executeCustomChecker($passport->getUser()); return; } } public static function getSubscribedEvents() : array { return [CheckPassportEvent::class => 'checkPassport']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; \trigger_deprecation('symfony/security-http', '5.4', 'The "%s" class is deprecated.', RememberMeLogoutListener::class); /** * @author Wouter de Jong * * @final * * @deprecated since Symfony 5.4 */ class RememberMeLogoutListener implements EventSubscriberInterface { private $rememberMeServices; public function __construct(RememberMeServicesInterface $rememberMeServices) { if (!\method_exists($rememberMeServices, 'logout')) { \trigger_deprecation('symfony/security-core', '5.1', '"%s" should implement the "logout(Request $request, Response $response, TokenInterface $token)" method, this method will be added to the "%s" in version 6.0.', \get_class($rememberMeServices), RememberMeServicesInterface::class); } $this->rememberMeServices = $rememberMeServices; } public function onLogout(LogoutEvent $event) : void { if (!\method_exists($this->rememberMeServices, 'logout')) { return; } if (!$event->getToken()) { return; } if (null === $event->getResponse()) { throw new LogicException(\sprintf('No response was set for this logout action. Make sure the DefaultLogoutListener or another listener has set the response before "%s" is called.', __CLASS__)); } $this->rememberMeServices->logout($event->getRequest(), $event->getResponse(), $event->getToken()); } public static function getSubscribedEvents() : array { return [LogoutEvent::class => 'onLogout']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; /** * Migrates/invalidates the session after successful login. * * This should be registered as subscriber to any "stateful" firewalls. * * @see SessionAuthenticationStrategy * * @author Wouter de Jong */ class SessionStrategyListener implements EventSubscriberInterface { private $sessionAuthenticationStrategy; public function __construct(SessionAuthenticationStrategyInterface $sessionAuthenticationStrategy) { $this->sessionAuthenticationStrategy = $sessionAuthenticationStrategy; } public function onSuccessfulLogin(LoginSuccessEvent $event) : void { $request = $event->getRequest(); $token = $event->getAuthenticatedToken(); if (!$request->hasSession() || !$request->hasPreviousSession()) { return; } if ($previousToken = $event->getPreviousToken()) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 $user = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = \method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); if ('' !== ($user ?? '') && $user === $previousUser && \get_class($token) === \get_class($previousToken)) { return; } } $this->sessionAuthenticationStrategy->onAuthentication($request, $token); } public static function getSubscribedEvents() : array { return [LoginSuccessEvent::class => 'onSuccessfulLogin']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\LogoutEvent; /** * @author Christian Flothmann * * @final */ class CsrfTokenClearingLogoutListener implements EventSubscriberInterface { private $csrfTokenStorage; public function __construct(ClearableTokenStorageInterface $csrfTokenStorage) { $this->csrfTokenStorage = $csrfTokenStorage; } public function onLogout(LogoutEvent $event) : void { $this->csrfTokenStorage->clear(); } public static function getSubscribedEvents() : array { return [LogoutEvent::class => 'onLogout']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; /** * @author Wouter de Jong * * @final */ class UserCheckerListener implements EventSubscriberInterface { private $userChecker; public function __construct(UserCheckerInterface $userChecker) { $this->userChecker = $userChecker; } public function preCheckCredentials(CheckPassportEvent $event) : void { $passport = $event->getPassport(); if (!$passport instanceof UserPassportInterface || $passport->hasBadge(PreAuthenticatedUserBadge::class)) { return; } $this->userChecker->checkPreAuth($passport->getUser()); } public function postCheckCredentials(AuthenticationSuccessEvent $event) : void { $user = $event->getAuthenticationToken()->getUser(); if (!$user instanceof UserInterface) { return; } $this->userChecker->checkPostAuth($user); } public static function getSubscribedEvents() : array { return [CheckPassportEvent::class => ['preCheckCredentials', 256], AuthenticationSuccessEvent::class => ['postCheckCredentials', 256]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfToken; use _ContaoManager\Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; use _ContaoManager\Symfony\Component\Security\Http\Event\CheckPassportEvent; /** * @author Wouter de Jong * * @final */ class CsrfProtectionListener implements EventSubscriberInterface { private $csrfTokenManager; public function __construct(CsrfTokenManagerInterface $csrfTokenManager) { $this->csrfTokenManager = $csrfTokenManager; } public function checkPassport(CheckPassportEvent $event) : void { $passport = $event->getPassport(); if (!$passport->hasBadge(CsrfTokenBadge::class)) { return; } /** @var CsrfTokenBadge $badge */ $badge = $passport->getBadge(CsrfTokenBadge::class); if ($badge->isResolved()) { return; } $csrfToken = new CsrfToken($badge->getCsrfTokenId(), $badge->getCsrfToken()); if (\false === $this->csrfTokenManager->isTokenValid($csrfToken)) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } $badge->markResolved(); } public static function getSubscribedEvents() : array { return [CheckPassportEvent::class => ['checkPassport', 512]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\EventListener; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginSuccessEvent; use _ContaoManager\Symfony\Component\Security\Http\ParameterBagUtils; /** * Checks if all conditions are met for remember me. * * The conditions that must be met for this listener to enable remember me: * A) This badge is present in the Passport * B) The remember_me key under your firewall is configured * C) The "remember me" functionality is activated. This is usually * done by having a _remember_me checkbox in your form, but * can be configured by the "always_remember_me" and "remember_me_parameter" * parameters under the "remember_me" firewall key (or "always_remember_me" * is enabled) * * @author Wouter de Jong * * @final */ class CheckRememberMeConditionsListener implements EventSubscriberInterface { private $options; private $logger; public function __construct(array $options = [], ?LoggerInterface $logger = null) { $this->options = $options + ['always_remember_me' => \false, 'remember_me_parameter' => '_remember_me']; $this->logger = $logger; } public function onSuccessfulLogin(LoginSuccessEvent $event) : void { $passport = $event->getPassport(); if (!$passport->hasBadge(RememberMeBadge::class)) { return; } /** @var RememberMeBadge $badge */ $badge = $passport->getBadge(RememberMeBadge::class); if (!$this->options['always_remember_me']) { $parameter = ParameterBagUtils::getRequestParameterValue($event->getRequest(), $this->options['remember_me_parameter']); if (!('true' === $parameter || 'on' === $parameter || '1' === $parameter || 'yes' === $parameter || \true === $parameter)) { if (null !== $this->logger) { $this->logger->debug('Remember me disabled; request does not contain remember me parameter ("{parameter}").', ['parameter' => $this->options['remember_me_parameter']]); } return; } } $badge->enable(); } public static function getSubscribedEvents() : array { return [LoginSuccessEvent::class => ['onSuccessfulLogin', -32]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * SessionAuthenticationStrategyInterface. * * Implementation are responsible for updating the session after an interactive * authentication attempt was successful. * * @author Johannes M. Schmitt */ interface SessionAuthenticationStrategyInterface { /** * This performs any necessary changes to the session. * * This method should be called before the TokenStorage is populated with a * Token. It should be used by authentication listeners when a session is used. */ public function onAuthentication(Request $request, TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Http\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; /** * The default session strategy implementation. * * Supports the following strategies: * NONE: the session is not changed * MIGRATE: the session id is updated, attributes are kept * INVALIDATE: the session id is updated, attributes are lost * * @author Johannes M. Schmitt */ class SessionAuthenticationStrategy implements SessionAuthenticationStrategyInterface { public const NONE = 'none'; public const MIGRATE = 'migrate'; public const INVALIDATE = 'invalidate'; private $strategy; private $csrfTokenStorage = null; public function __construct(string $strategy, ?ClearableTokenStorageInterface $csrfTokenStorage = null) { $this->strategy = $strategy; if (self::MIGRATE === $strategy) { $this->csrfTokenStorage = $csrfTokenStorage; } } /** * {@inheritdoc} */ public function onAuthentication(Request $request, TokenInterface $token) { switch ($this->strategy) { case self::NONE: return; case self::MIGRATE: $request->getSession()->migrate(\true); if ($this->csrfTokenStorage) { $this->csrfTokenStorage->clear(); } return; case self::INVALIDATE: $request->getSession()->invalidate(); return; default: throw new \RuntimeException(\sprintf('Invalid session authentication strategy "%s".', $this->strategy)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; /** * The EventDispatcherInterface is the central point of Symfony's event listener system. * Listeners are registered on the manager and events are dispatched through the * manager. * * @author Bernhard Schussek */ interface EventDispatcherInterface extends ContractsEventDispatcherInterface { /** * Adds an event listener that listens on the specified events. * * @param int $priority The higher this value, the earlier an event * listener will be triggered in the chain (defaults to 0) */ public function addListener(string $eventName, callable $listener, int $priority = 0); /** * Adds an event subscriber. * * The subscriber is asked for all the events it is * interested in and added as a listener for these events. */ public function addSubscriber(EventSubscriberInterface $subscriber); /** * Removes an event listener from the specified events. */ public function removeListener(string $eventName, callable $listener); public function removeSubscriber(EventSubscriberInterface $subscriber); /** * Gets the listeners of a specific event or all listeners sorted by descending priority. * * @return array */ public function getListeners(?string $eventName = null); /** * Gets the listener priority for a specific event. * * Returns null if the event or the listener does not exist. * * @return int|null */ public function getListenerPriority(string $eventName, callable $listener); /** * Checks whether an event has any registered listeners. * * @return bool */ public function hasListeners(?string $eventName = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher\Attribute; /** * Service tag to autoconfigure event listeners. * * @author Alexander M. Turek */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AsEventListener { public function __construct(public ?string $event = null, public ?string $method = null, public int $priority = 0, public ?string $dispatcher = null) { } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Allow `#[AsEventListener]` attribute on methods 5.3 --- * Add `#[AsEventListener]` attribute for declaring listeners on PHP 8 5.1.0 ----- * The `LegacyEventDispatcherProxy` class has been deprecated. * Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`. 5.0.0 ----- * The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`. * The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`. * The `TraceableEventDispatcherInterface` has been removed. * The `WrappedListener` class is now final. 4.4.0 ----- * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events. 4.3.0 ----- * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead 4.1.0 ----- * added support for invokable event listeners tagged with `kernel.event_listener` by default * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added. * The `TraceableEventDispatcherInterface` has been deprecated. 4.0.0 ----- * removed the `ContainerAwareEventDispatcher` class * added the `reset()` method to the `TraceableEventDispatcherInterface` 3.4.0 ----- * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. 3.3.0 ----- * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. 3.0.0 ----- * The method `getListenerPriority($eventName, $listener)` has been added to the `EventDispatcherInterface`. * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` and `Event::getName()` have been removed. The event dispatcher and the event name are passed to the listener call. 2.5.0 ----- * added Debug\TraceableEventDispatcher (originally in HttpKernel) * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface * added RegisterListenersPass (originally in HttpKernel) 2.1.0 ----- * added TraceableEventDispatcherInterface * added ContainerAwareEventDispatcher * added a reference to the EventDispatcher on the Event * added a reference to the Event name on the event * added fluid interface to the dispatch() method which now returns the Event object * added GenericEvent event class * added the possibility for subscribers to subscribe several times for the same event * added ImmutableEventDispatcher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class); /** * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). * * @author Nicolas Grekas * * @deprecated since Symfony 5.1 */ final class LegacyEventDispatcherProxy { public static function decorate(?EventDispatcherInterface $dispatcher) : ?EventDispatcherInterface { return $dispatcher; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher; use _ContaoManager\Psr\EventDispatcher\StoppableEventInterface; use _ContaoManager\Symfony\Component\EventDispatcher\Debug\WrappedListener; /** * The EventDispatcherInterface is the central point of Symfony's event listener system. * * Listeners are registered on the manager and events are dispatched through the * manager. * * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek * @author Fabien Potencier * @author Jordi Boggiano * @author Jordan Alliot * @author Nicolas Grekas */ class EventDispatcher implements EventDispatcherInterface { private $listeners = []; private $sorted = []; private $optimized; public function __construct() { if (__CLASS__ === static::class) { $this->optimized = []; } } /** * {@inheritdoc} */ public function dispatch(object $event, ?string $eventName = null) : object { $eventName = $eventName ?? \get_class($event); if (null !== $this->optimized) { $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); } else { $listeners = $this->getListeners($eventName); } if ($listeners) { $this->callListeners($listeners, $eventName, $event); } return $event; } /** * {@inheritdoc} */ public function getListeners(?string $eventName = null) { if (null !== $eventName) { if (empty($this->listeners[$eventName])) { return []; } if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } return $this->sorted[$eventName]; } foreach ($this->listeners as $eventName => $eventListeners) { if (!isset($this->sorted[$eventName])) { $this->sortListeners($eventName); } } return \array_filter($this->sorted); } /** * {@inheritdoc} */ public function getListenerPriority(string $eventName, $listener) { if (empty($this->listeners[$eventName])) { return null; } if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } foreach ($this->listeners[$eventName] as $priority => &$listeners) { foreach ($listeners as &$v) { if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); $v[1] = $v[1] ?? '__invoke'; } if ($v === $listener || $listener instanceof \Closure && $v == $listener) { return $priority; } } } return null; } /** * {@inheritdoc} */ public function hasListeners(?string $eventName = null) { if (null !== $eventName) { return !empty($this->listeners[$eventName]); } foreach ($this->listeners as $eventListeners) { if ($eventListeners) { return \true; } } return \false; } /** * {@inheritdoc} */ public function addListener(string $eventName, $listener, int $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName], $this->optimized[$eventName]); } /** * {@inheritdoc} */ public function removeListener(string $eventName, $listener) { if (empty($this->listeners[$eventName])) { return; } if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } foreach ($this->listeners[$eventName] as $priority => &$listeners) { foreach ($listeners as $k => &$v) { if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); $v[1] = $v[1] ?? '__invoke'; } if ($v === $listener || $listener instanceof \Closure && $v == $listener) { unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); } } if (!$listeners) { unset($this->listeners[$eventName][$priority]); } } } /** * {@inheritdoc} */ public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (\is_string($params)) { $this->addListener($eventName, [$subscriber, $params]); } elseif (\is_string($params[0])) { $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); } else { foreach ($params as $listener) { $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); } } } } /** * {@inheritdoc} */ public function removeSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (\is_array($params) && \is_array($params[0])) { foreach ($params as $listener) { $this->removeListener($eventName, [$subscriber, $listener[0]]); } } else { $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); } } } /** * Triggers the listeners of an event. * * This method can be overridden to add functionality that is executed * for each listener. * * @param callable[] $listeners The event listeners * @param string $eventName The name of the event to dispatch * @param object $event The event object to pass to the event handlers/listeners */ protected function callListeners(iterable $listeners, string $eventName, object $event) { $stoppable = $event instanceof StoppableEventInterface; foreach ($listeners as $listener) { if ($stoppable && $event->isPropagationStopped()) { break; } $listener($event, $eventName, $this); } } /** * Sorts the internal list of listeners for the given event by priority. */ private function sortListeners(string $eventName) { \krsort($this->listeners[$eventName]); $this->sorted[$eventName] = []; foreach ($this->listeners[$eventName] as &$listeners) { foreach ($listeners as $k => &$listener) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } $this->sorted[$eventName][] = $listener; } } } /** * Optimizes the internal list of listeners for the given event by priority. */ private function optimizeListeners(string $eventName) : array { \krsort($this->listeners[$eventName]); $this->optimized[$eventName] = []; foreach ($this->listeners[$eventName] as &$listeners) { foreach ($listeners as &$listener) { $closure =& $this->optimized[$eventName][]; if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $closure = static function (...$args) use(&$listener, &$closure) { if ($listener[0] instanceof \Closure) { $listener[0] = $listener[0](); $listener[1] = $listener[1] ?? '__invoke'; } ($closure = \Closure::fromCallable($listener))(...$args); }; } else { $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); } } } return $this->optimized[$eventName]; } } EventDispatcher Component ========================= The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them. Resources --------- * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher; /** * An EventSubscriber knows itself what events it is interested in. * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. * * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek */ interface EventSubscriberInterface { /** * Returns an array of event names this subscriber wants to listen to. * * The array keys are event names and the value can be: * * * The method name to call (priority defaults to 0) * * An array composed of the method name to call and the priority * * An array of arrays composed of the method names to call and respective * priorities, or 0 if unset * * For instance: * * * ['eventName' => 'methodName'] * * ['eventName' => ['methodName', $priority]] * * ['eventName' => [['methodName1', $priority], ['methodName2']]] * * The code must not depend on runtime state as it will only be called at compile time. * All logic depending on runtime state must be put into the individual methods handling the events. * * @return array> */ public static function getSubscribedEvents(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher; /** * A read-only proxy for an event dispatcher. * * @author Bernhard Schussek */ class ImmutableEventDispatcher implements EventDispatcherInterface { private $dispatcher; public function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } /** * {@inheritdoc} */ public function dispatch(object $event, ?string $eventName = null) : object { return $this->dispatcher->dispatch($event, $eventName); } /** * {@inheritdoc} */ public function addListener(string $eventName, $listener, int $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } /** * {@inheritdoc} */ public function addSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } /** * {@inheritdoc} */ public function removeListener(string $eventName, $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } /** * {@inheritdoc} */ public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } /** * {@inheritdoc} */ public function getListeners(?string $eventName = null) { return $this->dispatcher->getListeners($eventName); } /** * {@inheritdoc} */ public function getListenerPriority(string $eventName, $listener) { return $this->dispatcher->getListenerPriority($eventName, $listener); } /** * {@inheritdoc} */ public function hasListeners(?string $eventName = null) { return $this->dispatcher->hasListeners($eventName); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * Event encapsulation class. * * Encapsulates events thus decoupling the observer from the subject they encapsulate. * * @author Drak * * @implements \ArrayAccess * @implements \IteratorAggregate */ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate { protected $subject; protected $arguments; /** * Encapsulate an event with $subject and $arguments. * * @param mixed $subject The subject of the event, usually an object or a callable * @param array $arguments Arguments to store in the event */ public function __construct($subject = null, array $arguments = []) { $this->subject = $subject; $this->arguments = $arguments; } /** * Getter for subject property. * * @return mixed */ public function getSubject() { return $this->subject; } /** * Get argument by key. * * @return mixed * * @throws \InvalidArgumentException if key is not found */ public function getArgument(string $key) { if ($this->hasArgument($key)) { return $this->arguments[$key]; } throw new \InvalidArgumentException(\sprintf('Argument "%s" not found.', $key)); } /** * Add argument to event. * * @param mixed $value Value * * @return $this */ public function setArgument(string $key, $value) { $this->arguments[$key] = $value; return $this; } /** * Getter for all arguments. * * @return array */ public function getArguments() { return $this->arguments; } /** * Set args property. * * @return $this */ public function setArguments(array $args = []) { $this->arguments = $args; return $this; } /** * Has argument. * * @return bool */ public function hasArgument(string $key) { return \array_key_exists($key, $this->arguments); } /** * ArrayAccess for argument getter. * * @param string $key Array key * * @return mixed * * @throws \InvalidArgumentException if key does not exist in $this->args */ #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->getArgument($key); } /** * ArrayAccess for argument setter. * * @param string $key Array key to set * @param mixed $value Value * * @return void */ #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->setArgument($key, $value); } /** * ArrayAccess for unset argument. * * @param string $key Array key * * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($key) { if ($this->hasArgument($key)) { unset($this->arguments[$key]); } } /** * ArrayAccess has argument. * * @param string $key Array key * * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->hasArgument($key); } /** * IteratorAggregate for iterating over the object like an array. * * @return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->arguments); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcher; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * Compiler pass to register tagged services for an event dispatcher. */ class RegisterListenersPass implements CompilerPassInterface { protected $dispatcherService; protected $listenerTag; protected $subscriberTag; protected $eventAliasesParameter; private $hotPathEvents = []; private $hotPathTagName = 'container.hot_path'; private $noPreloadEvents = []; private $noPreloadTagName = 'container.no_preload'; public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->dispatcherService = $dispatcherService; $this->listenerTag = $listenerTag; $this->subscriberTag = $subscriberTag; $this->eventAliasesParameter = $eventAliasesParameter; } /** * @return $this */ public function setHotPathEvents(array $hotPathEvents) { $this->hotPathEvents = \array_flip($hotPathEvents); if (1 < \func_num_args()) { \trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); $this->hotPathTagName = \func_get_arg(1); } return $this; } /** * @return $this */ public function setNoPreloadEvents(array $noPreloadEvents) : self { $this->noPreloadEvents = \array_flip($noPreloadEvents); if (1 < \func_num_args()) { \trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); $this->noPreloadTagName = \func_get_arg(1); } return $this; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { return; } $aliases = []; if ($container->hasParameter($this->eventAliasesParameter)) { $aliases = $container->getParameter($this->eventAliasesParameter); } $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService); foreach ($container->findTaggedServiceIds($this->listenerTag, \true) as $id => $events) { $noPreload = 0; foreach ($events as $event) { $priority = $event['priority'] ?? 0; if (!isset($event['event'])) { if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { continue; } $event['method'] = $event['method'] ?? '__invoke'; $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); } $event['event'] = $aliases[$event['event']] ?? $event['event']; if (!isset($event['method'])) { $event['method'] = 'on' . \preg_replace_callback(['/(?<=\\b|_)[a-z]/i', '/[^a-z0-9]/i'], function ($matches) { return \strtoupper($matches[0]); }, $event['event']); $event['method'] = \preg_replace('/[^a-z0-9]/i', '', $event['method']); if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, \false)) && !$r->hasMethod($event['method'])) { if (!$r->hasMethod('__invoke')) { throw new InvalidArgumentException(\sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "%s" tags.', $event['method'], $id, $this->listenerTag)); } $event['method'] = '__invoke'; } } $dispatcherDefinition = $globalDispatcherDefinition; if (isset($event['dispatcher'])) { $dispatcherDefinition = $container->findDefinition($event['dispatcher']); } $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); if (isset($this->hotPathEvents[$event['event']])) { $container->getDefinition($id)->addTag($this->hotPathTagName); } elseif (isset($this->noPreloadEvents[$event['event']])) { ++$noPreload; } } if ($noPreload && \count($events) === $noPreload) { $container->getDefinition($id)->addTag($this->noPreloadTagName); } } $extractingDispatcher = new ExtractingEventDispatcher(); foreach ($container->findTaggedServiceIds($this->subscriberTag, \true) as $id => $tags) { $def = $container->getDefinition($id); // We must assume that the class value has been correctly filled, even if the service is created by a factory $class = $def->getClass(); if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(EventSubscriberInterface::class)) { throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); } $class = $r->name; $dispatcherDefinitions = []; foreach ($tags as $attributes) { if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) { continue; } $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']); } if (!$dispatcherDefinitions) { $dispatcherDefinitions = [$globalDispatcherDefinition]; } $noPreload = 0; ExtractingEventDispatcher::$aliases = $aliases; ExtractingEventDispatcher::$subscriber = $class; $extractingDispatcher->addSubscriber($extractingDispatcher); foreach ($extractingDispatcher->listeners as $args) { $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; foreach ($dispatcherDefinitions as $dispatcherDefinition) { $dispatcherDefinition->addMethodCall('addListener', $args); } if (isset($this->hotPathEvents[$args[0]])) { $container->getDefinition($id)->addTag($this->hotPathTagName); } elseif (isset($this->noPreloadEvents[$args[0]])) { ++$noPreload; } } if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { $container->getDefinition($id)->addTag($this->noPreloadTagName); } $extractingDispatcher->listeners = []; ExtractingEventDispatcher::$aliases = []; } } private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method) : string { if (null === ($class = $container->getDefinition($id)->getClass()) || !($r = $container->getReflectionClass($class, \false)) || !$r->hasMethod($method) || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType || $type->isBuiltin() || Event::class === ($name = $type->getName())) { throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); } return $name; } } /** * @internal */ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface { public $listeners = []; public static $aliases = []; public static $subscriber; public function addListener(string $eventName, $listener, int $priority = 0) { $this->listeners[] = [$eventName, $listener[1], $priority]; } public static function getSubscribedEvents() : array { $events = []; foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { $events[self::$aliases[$eventName] ?? $eventName] = $params; } return $events; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * This pass allows bundles to extend the list of event aliases. * * @author Alexander M. Turek */ class AddEventAliasesPass implements CompilerPassInterface { private $eventAliases; private $eventAliasesParameter; public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') { if (1 < \func_num_args()) { \trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->eventAliases = $eventAliases; $this->eventAliasesParameter = $eventAliasesParameter; } public function process(ContainerBuilder $container) : void { $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; $container->setParameter($this->eventAliasesParameter, \array_merge($eventAliases, $this->eventAliases)); } } { "name": "symfony\/event-dispatcher", "type": "library", "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/event-dispatcher-contracts": "^2|^3", "symfony\/polyfill-php80": "^1.16" }, "require-dev": { "symfony\/dependency-injection": "^4.4|^5.0|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/config": "^4.4|^5.0|^6.0", "symfony\/error-handler": "^4.4|^5.0|^6.0", "symfony\/http-foundation": "^4.4|^5.0|^6.0", "symfony\/service-contracts": "^1.1|^2|^3", "symfony\/stopwatch": "^4.4|^5.0|^6.0", "psr\/log": "^1|^2|^3" }, "conflict": { "symfony\/dependency-injection": "<4.4" }, "provide": { "psr\/event-dispatcher-implementation": "1.0", "symfony\/event-dispatcher-implementation": "2.0" }, "suggest": { "symfony\/dependency-injection": "", "symfony\/http-kernel": "" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\EventDispatcher\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher\Debug; use _ContaoManager\Psr\EventDispatcher\StoppableEventInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Collects some data about event listeners. * * This event dispatcher delegates the dispatching to another one. * * @author Fabien Potencier */ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface { protected $logger; protected $stopwatch; /** * @var \SplObjectStorage */ private $callStack; private $dispatcher; private $wrappedListeners; private $orphanedEvents; private $requestStack; private $currentRequestHash = ''; public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null) { $this->dispatcher = $dispatcher; $this->stopwatch = $stopwatch; $this->logger = $logger; $this->wrappedListeners = []; $this->orphanedEvents = []; $this->requestStack = $requestStack; } /** * {@inheritdoc} */ public function addListener(string $eventName, $listener, int $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } /** * {@inheritdoc} */ public function addSubscriber(EventSubscriberInterface $subscriber) { $this->dispatcher->addSubscriber($subscriber); } /** * {@inheritdoc} */ public function removeListener(string $eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { if ($wrappedListener->getWrappedListener() === $listener || $listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener) { $listener = $wrappedListener; unset($this->wrappedListeners[$eventName][$index]); break; } } } return $this->dispatcher->removeListener($eventName, $listener); } /** * {@inheritdoc} */ public function removeSubscriber(EventSubscriberInterface $subscriber) { return $this->dispatcher->removeSubscriber($subscriber); } /** * {@inheritdoc} */ public function getListeners(?string $eventName = null) { return $this->dispatcher->getListeners($eventName); } /** * {@inheritdoc} */ public function getListenerPriority(string $eventName, $listener) { // we might have wrapped listeners for the event (if called while dispatching) // in that case get the priority by wrapper if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { if ($wrappedListener->getWrappedListener() === $listener || $listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener) { return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); } } } return $this->dispatcher->getListenerPriority($eventName, $listener); } /** * {@inheritdoc} */ public function hasListeners(?string $eventName = null) { return $this->dispatcher->hasListeners($eventName); } /** * {@inheritdoc} */ public function dispatch(object $event, ?string $eventName = null) : object { $eventName = $eventName ?? \get_class($event); if (null === $this->callStack) { $this->callStack = new \SplObjectStorage(); } $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? \spl_object_hash($request) : ''; if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) { $this->logger->debug(\sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); } $this->preProcess($eventName); try { $this->beforeDispatch($eventName, $event); try { $e = $this->stopwatch->start($eventName, 'section'); try { $this->dispatcher->dispatch($event, $eventName); } finally { if ($e->isStarted()) { $e->stop(); } } } finally { $this->afterDispatch($eventName, $event); } } finally { $this->currentRequestHash = $currentRequestHash; $this->postProcess($eventName); } return $event; } /** * @return array */ public function getCalledListeners(?Request $request = null) { if (null === $this->callStack) { return []; } $hash = $request ? \spl_object_hash($request) : null; $called = []; foreach ($this->callStack as $listener) { [$eventName, $requestHash] = $this->callStack->getInfo(); if (null === $hash || $hash === $requestHash) { $called[] = $listener->getInfo($eventName); } } return $called; } /** * @return array */ public function getNotCalledListeners(?Request $request = null) { try { $allListeners = $this->getListeners(); } catch (\Exception $e) { if (null !== $this->logger) { $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); } // unable to retrieve the uncalled listeners return []; } $hash = $request ? \spl_object_hash($request) : null; $calledListeners = []; if (null !== $this->callStack) { foreach ($this->callStack as $calledListener) { [, $requestHash] = $this->callStack->getInfo(); if (null === $hash || $hash === $requestHash) { $calledListeners[] = $calledListener->getWrappedListener(); } } } $notCalled = []; foreach ($allListeners as $eventName => $listeners) { foreach ($listeners as $listener) { if (!\in_array($listener, $calledListeners, \true)) { if (!$listener instanceof WrappedListener) { $listener = new WrappedListener($listener, null, $this->stopwatch, $this); } $notCalled[] = $listener->getInfo($eventName); } } } \uasort($notCalled, [$this, 'sortNotCalledListeners']); return $notCalled; } public function getOrphanedEvents(?Request $request = null) : array { if ($request) { return $this->orphanedEvents[\spl_object_hash($request)] ?? []; } if (!$this->orphanedEvents) { return []; } return \array_merge(...\array_values($this->orphanedEvents)); } public function reset() { $this->callStack = null; $this->orphanedEvents = []; $this->currentRequestHash = ''; } /** * Proxies all method calls to the original event dispatcher. * * @param string $method The method name * @param array $arguments The method arguments * * @return mixed */ public function __call(string $method, array $arguments) { return $this->dispatcher->{$method}(...$arguments); } /** * Called before dispatching the event. */ protected function beforeDispatch(string $eventName, object $event) { } /** * Called after dispatching the event. */ protected function afterDispatch(string $eventName, object $event) { } private function preProcess(string $eventName) : void { if (!$this->dispatcher->hasListeners($eventName)) { $this->orphanedEvents[$this->currentRequestHash][] = $eventName; return; } foreach ($this->dispatcher->getListeners($eventName) as $listener) { $priority = $this->getListenerPriority($eventName, $listener); $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); $this->wrappedListeners[$eventName][] = $wrappedListener; $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $wrappedListener, $priority); $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); } } private function postProcess(string $eventName) : void { unset($this->wrappedListeners[$eventName]); $skipped = \false; foreach ($this->dispatcher->getListeners($eventName) as $listener) { if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. continue; } // Unwrap listener $priority = $this->getListenerPriority($eventName, $listener); $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); if (null !== $this->logger) { $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; } if ($listener->wasCalled()) { if (null !== $this->logger) { $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); } } else { $this->callStack->detach($listener); } if (null !== $this->logger && $skipped) { $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); } if ($listener->stoppedPropagation()) { if (null !== $this->logger) { $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); } $skipped = \true; } } } private function sortNotCalledListeners(array $a, array $b) { if (0 !== ($cmp = \strcmp($a['event'], $b['event']))) { return $cmp; } if (\is_int($a['priority']) && !\is_int($b['priority'])) { return 1; } if (!\is_int($a['priority']) && \is_int($b['priority'])) { return -1; } if ($a['priority'] === $b['priority']) { return 0; } if ($a['priority'] > $b['priority']) { return -1; } return 1; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\EventDispatcher\Debug; use _ContaoManager\Psr\EventDispatcher\StoppableEventInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; use _ContaoManager\Symfony\Component\VarDumper\Caster\ClassStub; /** * @author Fabien Potencier */ final class WrappedListener { private $listener; private $optimizedListener; private $name; private $called; private $stoppedPropagation; private $stopwatch; private $dispatcher; private $pretty; private $stub; private $priority; private static $hasClassStub; public function __construct($listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null) { $this->listener = $listener; $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = \false; $this->stoppedPropagation = \false; if (\is_array($listener)) { $this->name = \is_object($listener[0]) ? \get_debug_type($listener[0]) : $listener[0]; $this->pretty = $this->name . '::' . $listener[1]; } elseif ($listener instanceof \Closure) { $r = new \ReflectionFunction($listener); if (\str_contains($r->name, '{closure}')) { $this->pretty = $this->name = 'closure'; } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $this->name = $class->name; $this->pretty = $this->name . '::' . $r->name; } else { $this->pretty = $this->name = $r->name; } } elseif (\is_string($listener)) { $this->pretty = $this->name = $listener; } else { $this->name = \get_debug_type($listener); $this->pretty = $this->name . '::__invoke'; } if (null !== $name) { $this->name = $name; } if (null === self::$hasClassStub) { self::$hasClassStub = \class_exists(ClassStub::class); } } public function getWrappedListener() { return $this->listener; } public function wasCalled() : bool { return $this->called; } public function stoppedPropagation() : bool { return $this->stoppedPropagation; } public function getPretty() : string { return $this->pretty; } public function getInfo(string $eventName) : array { if (null === $this->stub) { $this->stub = self::$hasClassStub ? new ClassStub($this->pretty . '()', $this->listener) : $this->pretty . '()'; } return ['event' => $eventName, 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), 'pretty' => $this->pretty, 'stub' => $this->stub]; } public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher) : void { $dispatcher = $this->dispatcher ?: $dispatcher; $this->called = \true; $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); $e = $this->stopwatch->start($this->name, 'event_listener'); try { ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); } finally { if ($e->isStarted()) { $e->stop(); } } if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { $this->stoppedPropagation = \true; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; /** * Represents the current state of a dumper while dumping. * * @author Nicolas Grekas */ class Cursor { public const HASH_INDEXED = Stub::ARRAY_INDEXED; public const HASH_ASSOC = Stub::ARRAY_ASSOC; public const HASH_OBJECT = Stub::TYPE_OBJECT; public const HASH_RESOURCE = Stub::TYPE_RESOURCE; public $depth = 0; public $refIndex = 0; public $softRefTo = 0; public $softRefCount = 0; public $softRefHandle = 0; public $hardRefTo = 0; public $hardRefCount = 0; public $hardRefHandle = 0; public $hashType; public $hashKey; public $hashKeyIsBinary; public $hashIndex = 0; public $hashLength = 0; public $hashCut = 0; public $stop = \false; public $attr = []; public $skipChildren = \false; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; /** * @author Nicolas Grekas */ class VarCloner extends AbstractCloner { private static $gid; private static $arrayCache = []; /** * {@inheritdoc} */ protected function doClone($var) { $len = 1; // Length of $queue $pos = 0; // Number of cloned items past the minimum depth $refsCounter = 0; // Hard references counter $queue = [[$var]]; // This breadth-first queue is the return value $hardRefs = []; // Map of original zval ids to stub objects $objRefs = []; // Map of original object handles to their stub object counterpart $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning $resRefs = []; // Map of original resource handles to their stub object counterpart $values = []; // Map of stub objects' ids to original values $maxItems = $this->maxItems; $maxString = $this->maxString; $minDepth = $this->minDepth; $currentDepth = 0; // Current tree depth $currentDepthFinalIndex = 0; // Final $queue index for current tree depth $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached $cookie = (object) []; // Unique object used to detect hard references $a = null; // Array cast for nested structures $stub = null; // Stub capturing the main properties of an original item value // or null if the original value is used directly if (!($gid = self::$gid)) { $gid = self::$gid = \md5(\random_bytes(6)); // Unique string used to detect the special $GLOBALS variable } $arrayStub = new Stub(); $arrayStub->type = Stub::TYPE_ARRAY; $fromObjCast = \false; for ($i = 0; $i < $len; ++$i) { // Detect when we move on to the next tree depth if ($i > $currentDepthFinalIndex) { ++$currentDepth; $currentDepthFinalIndex = $len - 1; if ($currentDepth >= $minDepth) { $minimumDepthReached = \true; } } $refs = $vals = $queue[$i]; foreach ($vals as $k => $v) { // $v is the original value or a stub object in case of hard references if (\PHP_VERSION_ID >= 70400) { $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; } else { $refs[$k] = $cookie; $zvalRef = $vals[$k] === $cookie; } if ($zvalRef) { $vals[$k] =& $stub; // Break hard references to make $queue completely unset($stub); // independent from the original structure if (\PHP_VERSION_ID >= 70400 ? null !== ($vals[$k] = $hardRefs[$zvalRef] ?? null) : $v instanceof Stub && isset($hardRefs[\spl_object_id($v)])) { if (\PHP_VERSION_ID >= 70400) { $v = $vals[$k]; } else { $refs[$k] = $vals[$k] = $v; } if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { ++$v->value->refCount; } ++$v->refCount; continue; } $vals[$k] = new Stub(); $vals[$k]->value = $v; $vals[$k]->handle = ++$refsCounter; if (\PHP_VERSION_ID >= 70400) { $hardRefs[$zvalRef] = $vals[$k]; } else { $refs[$k] = $vals[$k]; $h = \spl_object_id($refs[$k]); $hardRefs[$h] =& $refs[$k]; $values[$h] = $v; } } // Create $stub when the original value $v cannot be used directly // If $v is a nested structure, put that structure in array $a switch (\true) { case null === $v: case \is_bool($v): case \is_int($v): case \is_float($v): continue 2; case \is_string($v): if ('' === $v) { continue 2; } if (!\preg_match('//u', $v)) { $stub = new Stub(); $stub->type = Stub::TYPE_STRING; $stub->class = Stub::STRING_BINARY; if (0 <= $maxString && 0 < ($cut = \strlen($v) - $maxString)) { $stub->cut = $cut; $stub->value = \substr($v, 0, -$cut); } else { $stub->value = $v; } } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < ($cut = \mb_strlen($v, 'UTF-8') - $maxString)) { $stub = new Stub(); $stub->type = Stub::TYPE_STRING; $stub->class = Stub::STRING_UTF8; $stub->cut = $cut; $stub->value = \mb_substr($v, 0, $maxString, 'UTF-8'); } else { continue 2; } $a = null; break; case \is_array($v): if (!$v) { continue 2; } $stub = $arrayStub; if (\PHP_VERSION_ID >= 80100) { $stub->class = \array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; $a = $v; break; } $stub->class = Stub::ARRAY_INDEXED; $j = -1; foreach ($v as $gk => $gv) { if ($gk !== ++$j) { $stub->class = Stub::ARRAY_ASSOC; $a = $v; $a[$gid] = \true; break; } } // Copies of $GLOBALS have very strange behavior, // let's detect them with some black magic if (isset($v[$gid])) { unset($v[$gid]); $a = []; foreach ($v as $gk => &$gv) { if ($v === $gv && (\PHP_VERSION_ID < 70400 || !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()]))) { unset($v); $v = new Stub(); $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; $v->handle = -1; if (\PHP_VERSION_ID >= 70400) { $gv =& $a[$gk]; $hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] =& $gv; } else { $gv =& $hardRefs[\spl_object_id($v)]; } $gv = $v; } $a[$gk] =& $gv; } unset($gv); } else { $a = $v; } break; case \is_object($v): if (empty($objRefs[$h = \spl_object_id($v)])) { $stub = new Stub(); $stub->type = Stub::TYPE_OBJECT; $stub->class = \get_class($v); $stub->value = $v; $stub->handle = $h; $a = $this->castObject($stub, 0 < $i); if ($v !== $stub->value) { if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { break; } $stub->handle = $h = \spl_object_id($stub->value); } $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { $stub->cut = \count($a); $a = null; } } if (empty($objRefs[$h])) { $objRefs[$h] = $stub; $objects[] = $v; } else { $stub = $objRefs[$h]; ++$stub->refCount; $a = null; } break; default: // resource if (empty($resRefs[$h = (int) $v])) { $stub = new Stub(); $stub->type = Stub::TYPE_RESOURCE; if ('Unknown' === ($stub->class = @\get_resource_type($v))) { $stub->class = 'Closed'; } $stub->value = $v; $stub->handle = $h; $a = $this->castResource($stub, 0 < $i); $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { $stub->cut = \count($a); $a = null; } } if (empty($resRefs[$h])) { $resRefs[$h] = $stub; } else { $stub = $resRefs[$h]; ++$stub->refCount; $a = null; } break; } if ($a) { if (!$minimumDepthReached || 0 > $maxItems) { $queue[$len] = $a; $stub->position = $len++; } elseif ($pos < $maxItems) { if ($maxItems < ($pos += \count($a))) { $a = \array_slice($a, 0, $maxItems - $pos, \true); if ($stub->cut >= 0) { $stub->cut += $pos - $maxItems; } } $queue[$len] = $a; $stub->position = $len++; } elseif ($stub->cut >= 0) { $stub->cut += \count($a); $stub->position = 0; } } if ($arrayStub === $stub) { if ($arrayStub->cut) { $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; $arrayStub->cut = 0; } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; } else { self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; } } if (!$zvalRef) { $vals[$k] = $stub; } elseif (\PHP_VERSION_ID >= 70400) { $hardRefs[$zvalRef]->value = $stub; } else { $refs[$k]->value = $stub; } } if ($fromObjCast) { $fromObjCast = \false; $refs = $vals; $vals = []; $j = -1; foreach ($queue[$i] as $k => $v) { foreach ([$k => \true] as $gk => $gv) { } if ($gk !== $k) { $vals = (object) $vals; $vals->{$k} = $refs[++$j]; $vals = (array) $vals; } else { $vals[$k] = $refs[++$j]; } } } $queue[$i] = $vals; } foreach ($values as $h => $v) { $hardRefs[$h] = $v; } return $queue; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; /** * @author Nicolas Grekas */ interface ClonerInterface { /** * Clones a PHP variable. * * @param mixed $var Any PHP variable * * @return Data */ public function cloneVar($var); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; /** * DumperInterface used by Data objects. * * @author Nicolas Grekas */ interface DumperInterface { /** * Dumps a scalar value. * * @param string $type The PHP type of the value being dumped * @param string|int|float|bool $value The scalar value being dumped */ public function dumpScalar(Cursor $cursor, string $type, $value); /** * Dumps a string. * * @param string $str The string being dumped * @param bool $bin Whether $str is UTF-8 or binary encoded * @param int $cut The number of characters $str has been cut by */ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut); /** * Dumps while entering an hash. * * @param int $type A Cursor::HASH_* const for the type of hash * @param string|int $class The object class, resource type or array count * @param bool $hasChild When the dump of the hash has child item */ public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild); /** * Dumps while leaving an hash. * * @param int $type A Cursor::HASH_* const for the type of hash * @param string|int $class The object class, resource type or array count * @param bool $hasChild When the dump of the hash has child item * @param int $cut The number of items the hash has been cut by */ public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; use _ContaoManager\Symfony\Component\VarDumper\Caster\Caster; use _ContaoManager\Symfony\Component\VarDumper\Exception\ThrowingCasterException; /** * AbstractCloner implements a generic caster mechanism for objects and resources. * * @author Nicolas Grekas */ abstract class AbstractCloner implements ClonerInterface { public static $defaultCasters = ['__PHP_Incomplete_Class' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\Caster', 'castPhpIncompleteClass'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\CutStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'castStub'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'castCutArray'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ConstStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'castStub'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\EnumStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'castEnum'], 'Fiber' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\FiberCaster', 'castFiber'], 'Closure' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castClosure'], 'Generator' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castGenerator'], 'ReflectionType' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castType'], 'ReflectionAttribute' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castAttribute'], 'ReflectionGenerator' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castReflectionGenerator'], 'ReflectionClass' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castClass'], 'ReflectionClassConstant' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castClassConstant'], 'ReflectionFunctionAbstract' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castFunctionAbstract'], 'ReflectionMethod' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castMethod'], 'ReflectionParameter' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castParameter'], 'ReflectionProperty' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castProperty'], 'ReflectionReference' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castReference'], 'ReflectionExtension' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castExtension'], 'ReflectionZendExtension' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster', 'castZendExtension'], '_ContaoManager\\Doctrine\\Common\\Persistence\\ObjectManager' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Doctrine\\Common\\Proxy\\Proxy' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster', 'castCommonProxy'], '_ContaoManager\\Doctrine\\ORM\\Proxy\\Proxy' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster', 'castOrmProxy'], '_ContaoManager\\Doctrine\\ORM\\PersistentCollection' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster', 'castPersistentCollection'], '_ContaoManager\\Doctrine\\Persistence\\ObjectManager' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], 'DOMException' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castException'], 'DOMStringList' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castLength'], 'DOMNameList' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castLength'], 'DOMImplementation' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castImplementation'], 'DOMImplementationList' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castLength'], 'DOMNode' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castNode'], 'DOMNameSpaceNode' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castNameSpaceNode'], 'DOMDocument' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castDocument'], 'DOMNodeList' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castLength'], 'DOMNamedNodeMap' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castLength'], 'DOMCharacterData' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castCharacterData'], 'DOMAttr' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castAttr'], 'DOMElement' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castElement'], 'DOMText' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castText'], 'DOMTypeinfo' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castTypeinfo'], 'DOMDomError' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castDomError'], 'DOMLocator' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castLocator'], 'DOMDocumentType' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castDocumentType'], 'DOMNotation' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castNotation'], 'DOMEntity' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castEntity'], 'DOMProcessingInstruction' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castProcessingInstruction'], 'DOMXPath' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DOMCaster', 'castXPath'], 'XMLReader' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster', 'castXmlReader'], 'ErrorException' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castErrorException'], 'Exception' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castException'], 'Error' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castError'], '_ContaoManager\\Symfony\\Bridge\\Monolog\\Logger' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Symfony\\Component\\HttpClient\\AmpHttpClient' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castHttpClient'], '_ContaoManager\\Symfony\\Component\\HttpClient\\CurlHttpClient' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castHttpClient'], '_ContaoManager\\Symfony\\Component\\HttpClient\\NativeHttpClient' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castHttpClient'], '_ContaoManager\\Symfony\\Component\\HttpClient\\Response\\AmpResponse' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castHttpClientResponse'], '_ContaoManager\\Symfony\\Component\\HttpClient\\Response\\CurlResponse' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castHttpClientResponse'], '_ContaoManager\\Symfony\\Component\\HttpClient\\Response\\NativeResponse' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castHttpClientResponse'], '_ContaoManager\\Symfony\\Component\\HttpFoundation\\Request' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castRequest'], '_ContaoManager\\Symfony\\Component\\Uid\\Ulid' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castUlid'], '_ContaoManager\\Symfony\\Component\\Uid\\Uuid' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster', 'castUuid'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castThrowingCasterException'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\TraceStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castTraceStub'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\FrameStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castFrameStub'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Symfony\\Component\\ErrorHandler\\Exception\\SilencedErrorContext' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster', 'castSilencedErrorContext'], '_ContaoManager\\Imagine\\Image\\ImageInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ImagineCaster', 'castImage'], '_ContaoManager\\Ramsey\\Uuid\\UuidInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\UuidCaster', 'castRamseyUuid'], '_ContaoManager\\ProxyManager\\Proxy\\ProxyInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster', 'castProxy'], 'PHPUnit_Framework_MockObject_MockObject' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\PHPUnit\\Framework\\MockObject\\MockObject' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\PHPUnit\\Framework\\MockObject\\Stub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Prophecy\\Prophecy\\ProphecySubjectInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], '_ContaoManager\\Mockery\\MockInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\StubCaster', 'cutInternals'], 'PDO' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PdoCaster', 'castPdo'], 'PDOStatement' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PdoCaster', 'castPdoStatement'], 'AMQPConnection' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster', 'castConnection'], 'AMQPChannel' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster', 'castChannel'], 'AMQPQueue' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster', 'castQueue'], 'AMQPExchange' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster', 'castExchange'], 'AMQPEnvelope' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\AmqpCaster', 'castEnvelope'], 'ArrayObject' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castArrayObject'], 'ArrayIterator' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castArrayIterator'], 'SplDoublyLinkedList' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castDoublyLinkedList'], 'SplFileInfo' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castFileInfo'], 'SplFileObject' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castFileObject'], 'SplHeap' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castHeap'], 'SplObjectStorage' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castObjectStorage'], 'SplPriorityQueue' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castHeap'], 'OuterIterator' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castOuterIterator'], 'WeakReference' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\SplCaster', 'castWeakReference'], 'Redis' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RedisCaster', 'castRedis'], 'RedisArray' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RedisCaster', 'castRedisArray'], 'RedisCluster' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RedisCaster', 'castRedisCluster'], 'DateTimeInterface' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DateCaster', 'castDateTime'], 'DateInterval' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DateCaster', 'castInterval'], 'DateTimeZone' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DateCaster', 'castTimeZone'], 'DatePeriod' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DateCaster', 'castPeriod'], 'GMP' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\GmpCaster', 'castGmp'], 'MessageFormatter' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster', 'castMessageFormatter'], 'NumberFormatter' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster', 'castNumberFormatter'], 'IntlTimeZone' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster', 'castIntlTimeZone'], 'IntlCalendar' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster', 'castIntlCalendar'], 'IntlDateFormatter' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\IntlCaster', 'castIntlDateFormatter'], 'Memcached' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster', 'castMemcached'], '_ContaoManager\\Ds\\Collection' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsCaster', 'castCollection'], '_ContaoManager\\Ds\\Map' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsCaster', 'castMap'], '_ContaoManager\\Ds\\Pair' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsCaster', 'castPair'], '_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\DsCaster', 'castPairStub'], 'mysqli_driver' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\MysqliCaster', 'castMysqliDriver'], 'CurlHandle' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castCurl'], ':curl' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castCurl'], ':dba' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castDba'], ':dba persistent' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castDba'], 'GdImage' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castGd'], ':gd' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castGd'], ':mysql link' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castMysqlLink'], ':pgsql large object' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster', 'castLargeObject'], ':pgsql link' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster', 'castLink'], ':pgsql link persistent' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster', 'castLink'], ':pgsql result' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster', 'castResult'], ':process' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castProcess'], ':stream' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castStream'], 'OpenSSLCertificate' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castOpensslX509'], ':OpenSSL X.509' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castOpensslX509'], ':persistent stream' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castStream'], ':stream-context' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\ResourceCaster', 'castStreamContext'], 'XmlParser' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster', 'castXml'], ':xml' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster', 'castXml'], 'RdKafka' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castRdKafka'], '_ContaoManager\\RdKafka\\Conf' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castConf'], '_ContaoManager\\RdKafka\\KafkaConsumer' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castKafkaConsumer'], '_ContaoManager\\RdKafka\\Metadata\\Broker' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castBrokerMetadata'], '_ContaoManager\\RdKafka\\Metadata\\Collection' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castCollectionMetadata'], '_ContaoManager\\RdKafka\\Metadata\\Partition' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castPartitionMetadata'], '_ContaoManager\\RdKafka\\Metadata\\Topic' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castTopicMetadata'], '_ContaoManager\\RdKafka\\Message' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castMessage'], '_ContaoManager\\RdKafka\\Topic' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castTopic'], '_ContaoManager\\RdKafka\\TopicPartition' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castTopicPartition'], '_ContaoManager\\RdKafka\\TopicConf' => ['_ContaoManager\\Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster', 'castTopicConf']]; protected $maxItems = 2500; protected $maxString = -1; protected $minDepth = 1; /** * @var array> */ private $casters = []; /** * @var callable|null */ private $prevErrorHandler; private $classInfo = []; private $filter = 0; /** * @param callable[]|null $casters A map of casters * * @see addCasters */ public function __construct(?array $casters = null) { if (null === $casters) { $casters = static::$defaultCasters; } $this->addCasters($casters); } /** * Adds casters for resources and objects. * * Maps resources or objects types to a callback. * Types are in the key, with a callable caster for value. * Resource types are to be prefixed with a `:`, * see e.g. static::$defaultCasters. * * @param callable[] $casters A map of casters */ public function addCasters(array $casters) { foreach ($casters as $type => $callback) { $this->casters[$type][] = $callback; } } /** * Sets the maximum number of items to clone past the minimum depth in nested structures. */ public function setMaxItems(int $maxItems) { $this->maxItems = $maxItems; } /** * Sets the maximum cloned length for strings. */ public function setMaxString(int $maxString) { $this->maxString = $maxString; } /** * Sets the minimum tree depth where we are guaranteed to clone all the items. After this * depth is reached, only setMaxItems items will be cloned. */ public function setMinDepth(int $minDepth) { $this->minDepth = $minDepth; } /** * Clones a PHP variable. * * @param mixed $var Any PHP variable * @param int $filter A bit field of Caster::EXCLUDE_* constants * * @return Data */ public function cloneVar($var, int $filter = 0) { $this->prevErrorHandler = \set_error_handler(function ($type, $msg, $file, $line, $context = []) { if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { // Cloner never dies throw new \ErrorException($msg, 0, $type, $file, $line); } if ($this->prevErrorHandler) { return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); } return \false; }); $this->filter = $filter; if ($gc = \gc_enabled()) { \gc_disable(); } try { return new Data($this->doClone($var)); } finally { if ($gc) { \gc_enable(); } \restore_error_handler(); $this->prevErrorHandler = null; } } /** * Effectively clones the PHP variable. * * @param mixed $var Any PHP variable * * @return array */ protected abstract function doClone($var); /** * Casts an object to an array representation. * * @param bool $isNested True if the object is nested in the dumped structure * * @return array */ protected function castObject(Stub $stub, bool $isNested) { $obj = $stub->value; $class = $stub->class; if (\PHP_VERSION_ID < 80000 ? "\x00" === ($class[15] ?? null) : \str_contains($class, "@anonymous\x00")) { $stub->class = \get_debug_type($obj); } if (isset($this->classInfo[$class])) { [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; } else { $i = 2; $parents = [$class]; $hasDebugInfo = \method_exists($class, '__debugInfo'); foreach (\class_parents($class) as $p) { $parents[] = $p; ++$i; } foreach (\class_implements($class) as $p) { $parents[] = $p; ++$i; } $parents[] = '*'; $r = new \ReflectionClass($class); $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : ['file' => $r->getFileName(), 'line' => $r->getStartLine()]; $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; } $stub->attr += $fileInfo; $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); try { while ($i--) { if (!empty($this->casters[$p = $parents[$i]])) { foreach ($this->casters[$p] as $callback) { $a = $callback($obj, $a, $stub, $isNested, $this->filter); } } } } catch (\Exception $e) { $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '') . '⚠' => new ThrowingCasterException($e)] + $a; } return $a; } /** * Casts a resource to an array representation. * * @param bool $isNested True if the object is nested in the dumped structure * * @return array */ protected function castResource(Stub $stub, bool $isNested) { $a = []; $res = $stub->value; $type = $stub->class; try { if (!empty($this->casters[':' . $type])) { foreach ($this->casters[':' . $type] as $callback) { $a = $callback($res, $a, $stub, $isNested, $this->filter); } } } catch (\Exception $e) { $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '') . '⚠' => new ThrowingCasterException($e)] + $a; } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; use _ContaoManager\Symfony\Component\VarDumper\Caster\Caster; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; /** * @author Nicolas Grekas */ class Data implements \ArrayAccess, \Countable, \IteratorAggregate { private $data; private $position = 0; private $key = 0; private $maxDepth = 20; private $maxItemsPerDepth = -1; private $useRefHandles = -1; private $context = []; /** * @param array $data An array as returned by ClonerInterface::cloneVar() */ public function __construct(array $data) { $this->data = $data; } /** * @return string|null */ public function getType() { $item = $this->data[$this->position][$this->key]; if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { $item = $item->value; } if (!$item instanceof Stub) { return \gettype($item); } if (Stub::TYPE_STRING === $item->type) { return 'string'; } if (Stub::TYPE_ARRAY === $item->type) { return 'array'; } if (Stub::TYPE_OBJECT === $item->type) { return $item->class; } if (Stub::TYPE_RESOURCE === $item->type) { return $item->class . ' resource'; } return null; } /** * Returns a native representation of the original value. * * @param array|bool $recursive Whether values should be resolved recursively or not * * @return string|int|float|bool|array|Data[]|null */ public function getValue($recursive = \false) { $item = $this->data[$this->position][$this->key]; if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { $item = $item->value; } if (!($item = $this->getStub($item)) instanceof Stub) { return $item; } if (Stub::TYPE_STRING === $item->type) { return $item->value; } $children = $item->position ? $this->data[$item->position] : []; foreach ($children as $k => $v) { if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { continue; } $children[$k] = clone $this; $children[$k]->key = $k; $children[$k]->position = $item->position; if ($recursive) { if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { $recursive = (array) $recursive; if (isset($recursive[$v->position])) { continue; } $recursive[$v->position] = \true; } $children[$k] = $children[$k]->getValue($recursive); } } return $children; } /** * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->getValue()); } /** * @return \Traversable */ #[\ReturnTypeWillChange] public function getIterator() { if (!\is_array($value = $this->getValue())) { throw new \LogicException(\sprintf('"%s" object holds non-iterable type "%s".', self::class, \get_debug_type($value))); } yield from $value; } public function __get(string $key) { if (null !== ($data = $this->seek($key))) { $item = $this->getStub($data->data[$data->position][$data->key]); return $item instanceof Stub || [] === $item ? $data : $item; } return null; } /** * @return bool */ public function __isset(string $key) { return null !== $this->seek($key); } /** * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->__isset($key); } /** * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->__get($key); } /** * @return void */ #[\ReturnTypeWillChange] public function offsetSet($key, $value) { throw new \BadMethodCallException(self::class . ' objects are immutable.'); } /** * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($key) { throw new \BadMethodCallException(self::class . ' objects are immutable.'); } /** * @return string */ public function __toString() { $value = $this->getValue(); if (!\is_array($value)) { return (string) $value; } return \sprintf('%s (count=%d)', $this->getType(), \count($value)); } /** * Returns a depth limited clone of $this. * * @return static */ public function withMaxDepth(int $maxDepth) { $data = clone $this; $data->maxDepth = $maxDepth; return $data; } /** * Limits the number of elements per depth level. * * @return static */ public function withMaxItemsPerDepth(int $maxItemsPerDepth) { $data = clone $this; $data->maxItemsPerDepth = $maxItemsPerDepth; return $data; } /** * Enables/disables objects' identifiers tracking. * * @param bool $useRefHandles False to hide global ref. handles * * @return static */ public function withRefHandles(bool $useRefHandles) { $data = clone $this; $data->useRefHandles = $useRefHandles ? -1 : 0; return $data; } /** * @return static */ public function withContext(array $context) { $data = clone $this; $data->context = $context; return $data; } /** * Seeks to a specific key in nested data structures. * * @param string|int $key The key to seek to * * @return static|null */ public function seek($key) { $item = $this->data[$this->position][$this->key]; if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { $item = $item->value; } if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { return null; } $keys = [$key]; switch ($item->type) { case Stub::TYPE_OBJECT: $keys[] = Caster::PREFIX_DYNAMIC . $key; $keys[] = Caster::PREFIX_PROTECTED . $key; $keys[] = Caster::PREFIX_VIRTUAL . $key; $keys[] = "\x00{$item->class}\x00{$key}"; // no break case Stub::TYPE_ARRAY: case Stub::TYPE_RESOURCE: break; default: return null; } $data = null; $children = $this->data[$item->position]; foreach ($keys as $key) { if (isset($children[$key]) || \array_key_exists($key, $children)) { $data = clone $this; $data->key = $key; $data->position = $item->position; break; } } return $data; } /** * Dumps data with a DumperInterface dumper. */ public function dump(DumperInterface $dumper) { $refs = [0]; $cursor = new Cursor(); if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { $cursor->attr['if_links'] = \true; $cursor->hashType = -1; $dumper->dumpScalar($cursor, 'default', '^'); $cursor->attr = ['if_links' => \true]; $dumper->dumpScalar($cursor, 'default', ' '); $cursor->hashType = 0; } $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); } /** * Depth-first dumping of items. * * @param mixed $item A Stub object or the original value being dumped */ private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) { $cursor->refIndex = 0; $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; $firstSeen = \true; if (!$item instanceof Stub) { $cursor->attr = []; $type = \gettype($item); if ($item && 'array' === $type) { $item = $this->getStub($item); } } elseif (Stub::TYPE_REF === $item->type) { if ($item->handle) { if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; } else { $firstSeen = \false; } $cursor->hardRefTo = $refs[$r]; $cursor->hardRefHandle = $this->useRefHandles & $item->handle; $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; } $cursor->attr = $item->attr; $type = $item->class ?: \gettype($item->value); $item = $this->getStub($item->value); } if ($item instanceof Stub) { if ($item->refCount) { if (!isset($refs[$r = $item->handle])) { $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; } else { $firstSeen = \false; } $cursor->softRefTo = $refs[$r]; } $cursor->softRefHandle = $this->useRefHandles & $item->handle; $cursor->softRefCount = $item->refCount; $cursor->attr = $item->attr; $cut = $item->cut; if ($item->position && $firstSeen) { $children = $this->data[$item->position]; if ($cursor->stop) { if ($cut >= 0) { $cut += \count($children); } $children = []; } } else { $children = []; } switch ($item->type) { case Stub::TYPE_STRING: $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); break; case Stub::TYPE_ARRAY: $item = clone $item; $item->type = $item->class; $item->class = $item->value; // no break case Stub::TYPE_OBJECT: case Stub::TYPE_RESOURCE: $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); if ($withChildren) { if ($cursor->skipChildren) { $withChildren = \false; $cut = -1; } else { $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); } } elseif ($children && 0 <= $cut) { $cut += \count($children); } $cursor->skipChildren = \false; $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); break; default: throw new \RuntimeException(\sprintf('Unexpected Stub type: "%s".', $item->type)); } } elseif ('array' === $type) { $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, \false); $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, \false, 0); } elseif ('string' === $type) { $dumper->dumpString($cursor, $item, \false, 0); } else { $dumper->dumpScalar($cursor, $type, $item); } } /** * Dumps children of hash structures. * * @return int The final number of removed items */ private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys) : int { $cursor = clone $parentCursor; ++$cursor->depth; $cursor->hashType = $hashType; $cursor->hashIndex = 0; $cursor->hashLength = \count($children); $cursor->hashCut = $hashCut; foreach ($children as $key => $child) { $cursor->hashKeyIsBinary = isset($key[0]) && !\preg_match('//u', $key); $cursor->hashKey = $dumpKeys ? $key : null; $this->dumpItem($dumper, $cursor, $refs, $child); if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { $parentCursor->stop = \true; return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; } } return $hashCut; } private function getStub($item) { if (!$item || !\is_array($item)) { return $item; } $stub = new Stub(); $stub->type = Stub::TYPE_ARRAY; foreach ($item as $stub->class => $stub->position) { } if (isset($item[0])) { $stub->cut = $item[0]; } $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); return $stub; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Cloner; /** * Represents the main properties of a PHP variable. * * @author Nicolas Grekas */ class Stub { public const TYPE_REF = 1; public const TYPE_STRING = 2; public const TYPE_ARRAY = 3; public const TYPE_OBJECT = 4; public const TYPE_RESOURCE = 5; public const STRING_BINARY = 1; public const STRING_UTF8 = 2; public const ARRAY_ASSOC = 1; public const ARRAY_INDEXED = 2; public $type = self::TYPE_REF; public $class = ''; public $value; public $cut = 0; public $handle = 0; public $refCount = 0; public $position = 0; public $attr = []; private static $defaultProperties = []; /** * @internal */ public function __sleep() : array { $properties = []; if (!isset(self::$defaultProperties[$c = static::class])) { self::$defaultProperties[$c] = \get_class_vars($c); foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { unset(self::$defaultProperties[$c][$k]); } } foreach (self::$defaultProperties[$c] as $k => $v) { if ($this->{$k} !== $v) { $properties[] = $k; } } return $properties; } } Copyright (c) 2014-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Test; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; /** * @author Nicolas Grekas */ trait VarDumperTestTrait { /** * @internal */ private $varDumperConfig = ['casters' => [], 'flags' => null]; protected function setUpVarDumper(array $casters, ?int $flags = null) : void { $this->varDumperConfig['casters'] = $casters; $this->varDumperConfig['flags'] = $flags; } /** * @after */ protected function tearDownVarDumper() : void { $this->varDumperConfig['casters'] = []; $this->varDumperConfig['flags'] = null; } public function assertDumpEquals($expected, $data, int $filter = 0, string $message = '') { $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); } public function assertDumpMatchesFormat($expected, $data, int $filter = 0, string $message = '') { $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); } protected function getDump($data, $key = null, int $filter = 0) : ?string { if (null === ($flags = $this->varDumperConfig['flags'])) { $flags = \getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; $flags |= \getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; $flags |= \getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; } $cloner = new VarCloner(); $cloner->addCasters($this->varDumperConfig['casters']); $cloner->setMaxItems(-1); $dumper = new CliDumper(null, null, $flags); $dumper->setColors(\false); $data = $cloner->cloneVar($data, $filter)->withRefHandles(\false); if (null !== $key && null === ($data = $data->seek($key))) { return null; } return \rtrim($dumper->dump($data, \true)); } private function prepareExpectation($expected, int $filter) : string { if (!\is_string($expected)) { $expected = $this->getDump($expected, null, $filter); } return \rtrim($expected); } } CHANGELOG ========= 5.4 --- * Add ability to style integer and double values independently * Add casters for Symfony's UUIDs and ULIDs * Add support for `Fiber` 5.2.0 ----- * added support for PHPUnit `--colors` option * added `VAR_DUMPER_FORMAT=server` env var value support * prevent replacing the handler when the `VAR_DUMPER_FORMAT` env var is set 5.1.0 ----- * added `RdKafka` support 4.4.0 ----- * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` to configure casters & flags to use in tests * added `ImagineCaster` and infrastructure to dump images * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data * added `UuidCaster` * made all casters final * added support for the `NO_COLOR` env var (https://no-color.org/) 4.3.0 ----- * added `DsCaster` to support dumping the contents of data structures from the Ds extension 4.2.0 ----- * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` 4.1.0 ----- * added a `ServerDumper` to send serialized Data clones to a server * added a `ServerDumpCommand` and `DumpServer` to run a server collecting and displaying dumps on a single place with multiple formats support * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support 4.0.0 ----- * support for passing `\ReflectionClass` instances to the `Caster::castObject()` method has been dropped, pass class names as strings instead * the `Data::getRawData()` method has been removed * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` argument and moves `$message = ''` argument at 4th position. * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` argument and moves `$message = ''` argument at 4th position. 3.4.0 ----- * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth * deprecated `MongoCaster` 2.7.0 ----- * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts GMP objects to array representation. * * @author Hamza Amrouche * @author Nicolas Grekas * * @final */ class GmpCaster { public static function castGmp(\GMP $gmp, array $a, Stub $stub, bool $isNested, int $filter) : array { $a[Caster::PREFIX_VIRTUAL . 'value'] = new ConstStub(\gmp_strval($gmp), \gmp_strval($gmp)); return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Reflector related classes to array representation. * * @author Nicolas Grekas * * @final */ class ReflectionCaster { public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__ . '::unsetClosureFileInfo']; private const EXTRA_MAP = ['docComment' => 'getDocComment', 'extension' => 'getExtensionName', 'isDisabled' => 'isDisabled', 'isDeprecated' => 'isDeprecated', 'isInternal' => 'isInternal', 'isUserDefined' => 'isUserDefined', 'isGenerator' => 'isGenerator', 'isVariadic' => 'isVariadic']; public static function castClosure(\Closure $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; $c = new \ReflectionFunction($c); $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); if (!\str_contains($c->name, '{closure}')) { $stub->class = isset($a[$prefix . 'class']) ? $a[$prefix . 'class']->value . '::' . $c->name : $c->name; unset($a[$prefix . 'class']); } unset($a[$prefix . 'extra']); $stub->class .= self::getSignature($a); if ($f = $c->getFileName()) { $stub->attr['file'] = $f; $stub->attr['line'] = $c->getStartLine(); } unset($a[$prefix . 'parameters']); if ($filter & Caster::EXCLUDE_VERBOSE) { $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); return []; } if ($f) { $a[$prefix . 'file'] = new LinkStub($f, $c->getStartLine()); $a[$prefix . 'line'] = $c->getStartLine() . ' to ' . $c->getEndLine(); } return $a; } public static function unsetClosureFileInfo(\Closure $c, array $a) { unset($a[Caster::PREFIX_VIRTUAL . 'file'], $a[Caster::PREFIX_VIRTUAL . 'line']); return $a; } public static function castGenerator(\Generator $c, array $a, Stub $stub, bool $isNested) { // Cannot create ReflectionGenerator based on a terminated Generator try { $reflectionGenerator = new \ReflectionGenerator($c); } catch (\Exception $e) { $a[Caster::PREFIX_VIRTUAL . 'closed'] = \true; return $a; } return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); } public static function castType(\ReflectionType $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; if ($c instanceof \ReflectionNamedType || \PHP_VERSION_ID < 80000) { $a += [$prefix . 'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, $prefix . 'allowsNull' => $c->allowsNull(), $prefix . 'isBuiltin' => $c->isBuiltin()]; } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { $a[$prefix . 'allowsNull'] = $c->allowsNull(); self::addMap($a, $c, ['types' => 'getTypes']); } else { $a[$prefix . 'allowsNull'] = $c->allowsNull(); } return $a; } public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested) { self::addMap($a, $c, ['name' => 'getName', 'arguments' => 'getArguments']); return $a; } public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; if ($c->getThis()) { $a[$prefix . 'this'] = new CutStub($c->getThis()); } $function = $c->getFunction(); $frame = ['class' => $function->class ?? null, 'type' => isset($function->class) ? $function->isStatic() ? '::' : '->' : null, 'function' => $function->name, 'file' => $c->getExecutingFile(), 'line' => $c->getExecutingLine()]; if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { $function = new \ReflectionGenerator($c->getExecutingGenerator()); \array_unshift($trace, ['function' => 'yield', 'file' => $function->getExecutingFile(), 'line' => $function->getExecutingLine() - (int) (\PHP_VERSION_ID < 80100)]); $trace[] = $frame; $a[$prefix . 'trace'] = new TraceStub($trace, \false, 0, -1, -1); } else { $function = new FrameStub($frame, \false, \true); $function = ExceptionCaster::castFrameStub($function, [], $function, \true); $a[$prefix . 'executing'] = $function[$prefix . 'src']; } $a[Caster::PREFIX_VIRTUAL . 'closed'] = \false; return $a; } public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; if ($n = \Reflection::getModifierNames($c->getModifiers())) { $a[$prefix . 'modifiers'] = \implode(' ', $n); } self::addMap($a, $c, ['extends' => 'getParentClass', 'implements' => 'getInterfaceNames', 'constants' => 'getReflectionConstants']); foreach ($c->getProperties() as $n) { $a[$prefix . 'properties'][$n->name] = $n; } foreach ($c->getMethods() as $n) { $a[$prefix . 'methods'][$n->name] = $n; } self::addAttributes($a, $c, $prefix); if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { self::addExtra($a, $c); } return $a; } public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; self::addMap($a, $c, ['returnsReference' => 'returnsReference', 'returnType' => 'getReturnType', 'class' => \PHP_VERSION_ID >= 80111 ? 'getClosureCalledClass' : 'getClosureScopeClass', 'this' => 'getClosureThis']); if (isset($a[$prefix . 'returnType'])) { $v = $a[$prefix . 'returnType']; $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; $a[$prefix . 'returnType'] = new ClassStub($a[$prefix . 'returnType'] instanceof \ReflectionNamedType && $a[$prefix . 'returnType']->allowsNull() && 'mixed' !== $v ? '?' . $v : $v, [\class_exists($v, \false) || \interface_exists($v, \false) || \trait_exists($v, \false) ? $v : '', '']); } if (isset($a[$prefix . 'class'])) { $a[$prefix . 'class'] = new ClassStub($a[$prefix . 'class']); } if (isset($a[$prefix . 'this'])) { $a[$prefix . 'this'] = new CutStub($a[$prefix . 'this']); } foreach ($c->getParameters() as $v) { $k = '$' . $v->name; if ($v->isVariadic()) { $k = '...' . $k; } if ($v->isPassedByReference()) { $k = '&' . $k; } $a[$prefix . 'parameters'][$k] = $v; } if (isset($a[$prefix . 'parameters'])) { $a[$prefix . 'parameters'] = new EnumStub($a[$prefix . 'parameters']); } self::addAttributes($a, $c, $prefix); if (!($filter & Caster::EXCLUDE_VERBOSE) && ($v = $c->getStaticVariables())) { foreach ($v as $k => &$v) { if (\is_object($v)) { $a[$prefix . 'use']['$' . $k] = new CutStub($v); } else { $a[$prefix . 'use']['$' . $k] =& $v; } } unset($v); $a[$prefix . 'use'] = new EnumStub($a[$prefix . 'use']); } if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { self::addExtra($a, $c); } return $a; } public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'modifiers'] = \implode(' ', \Reflection::getModifierNames($c->getModifiers())); $a[Caster::PREFIX_VIRTUAL . 'value'] = $c->getValue(); self::addAttributes($a, $c); return $a; } public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'modifiers'] = \implode(' ', \Reflection::getModifierNames($c->getModifiers())); return $a; } public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; self::addMap($a, $c, ['position' => 'getPosition', 'isVariadic' => 'isVariadic', 'byReference' => 'isPassedByReference', 'allowsNull' => 'allowsNull']); self::addAttributes($a, $c, $prefix); if ($v = $c->getType()) { $a[$prefix . 'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; } if (isset($a[$prefix . 'typeHint'])) { $v = $a[$prefix . 'typeHint']; $a[$prefix . 'typeHint'] = new ClassStub($v, [\class_exists($v, \false) || \interface_exists($v, \false) || \trait_exists($v, \false) ? $v : '', '']); } else { unset($a[$prefix . 'allowsNull']); } if ($c->isOptional()) { try { $a[$prefix . 'default'] = $v = $c->getDefaultValue(); if ($c->isDefaultValueConstant() && !\is_object($v)) { $a[$prefix . 'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); } if (null === $v) { unset($a[$prefix . 'allowsNull']); } } catch (\ReflectionException $e) { } } return $a; } public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'modifiers'] = \implode(' ', \Reflection::getModifierNames($c->getModifiers())); self::addAttributes($a, $c); self::addExtra($a, $c); return $a; } public static function castReference(\ReflectionReference $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'id'] = $c->getId(); return $a; } public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, bool $isNested) { self::addMap($a, $c, ['version' => 'getVersion', 'dependencies' => 'getDependencies', 'iniEntries' => 'getIniEntries', 'isPersistent' => 'isPersistent', 'isTemporary' => 'isTemporary', 'constants' => 'getConstants', 'functions' => 'getFunctions', 'classes' => 'getClasses']); return $a; } public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, bool $isNested) { self::addMap($a, $c, ['version' => 'getVersion', 'author' => 'getAuthor', 'copyright' => 'getCopyright', 'url' => 'getURL']); return $a; } public static function getSignature(array $a) { $prefix = Caster::PREFIX_VIRTUAL; $signature = ''; if (isset($a[$prefix . 'parameters'])) { foreach ($a[$prefix . 'parameters']->value as $k => $param) { $signature .= ', '; if ($type = $param->getType()) { if (!$type instanceof \ReflectionNamedType) { $signature .= $type . ' '; } else { if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) { $signature .= '?'; } $signature .= \substr(\strrchr('\\' . $type->getName(), '\\'), 1) . ' '; } } $signature .= $k; if (!$param->isDefaultValueAvailable()) { continue; } $v = $param->getDefaultValue(); $signature .= ' = '; if ($param->isDefaultValueConstant()) { $signature .= \substr(\strrchr('\\' . $param->getDefaultValueConstantName(), '\\'), 1); } elseif (null === $v) { $signature .= 'null'; } elseif (\is_array($v)) { $signature .= $v ? '[…' . \count($v) . ']' : '[]'; } elseif (\is_string($v)) { $signature .= 10 > \strlen($v) && !\str_contains($v, '\\') ? "'{$v}'" : "'…" . \strlen($v) . "'"; } elseif (\is_bool($v)) { $signature .= $v ? 'true' : 'false'; } elseif (\is_object($v)) { $signature .= 'new ' . \substr(\strrchr('\\' . \get_debug_type($v), '\\'), 1); } else { $signature .= $v; } } } $signature = (empty($a[$prefix . 'returnsReference']) ? '' : '&') . '(' . \substr($signature, 2) . ')'; if (isset($a[$prefix . 'returnType'])) { $signature .= ': ' . \substr(\strrchr('\\' . $a[$prefix . 'returnType'], '\\'), 1); } return $signature; } private static function addExtra(array &$a, \Reflector $c) { $x = isset($a[Caster::PREFIX_VIRTUAL . 'extra']) ? $a[Caster::PREFIX_VIRTUAL . 'extra']->value : []; if (\method_exists($c, 'getFileName') && ($m = $c->getFileName())) { $x['file'] = new LinkStub($m, $c->getStartLine()); $x['line'] = $c->getStartLine() . ' to ' . $c->getEndLine(); } self::addMap($x, $c, self::EXTRA_MAP, ''); if ($x) { $a[Caster::PREFIX_VIRTUAL . 'extra'] = new EnumStub($x); } } private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) { foreach ($map as $k => $m) { if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { continue; } if (\method_exists($c, $m) && \false !== ($m = $c->{$m}()) && null !== $m) { $a[$prefix . $k] = $m instanceof \Reflector ? $m->name : $m; } } } private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL) : void { if (\PHP_VERSION_ID >= 80000) { foreach ($c->getAttributes() as $n) { $a[$prefix . 'attributes'][] = $n; } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts a caster's Stub. * * @author Nicolas Grekas * * @final */ class StubCaster { public static function castStub(Stub $c, array $a, Stub $stub, bool $isNested) { if ($isNested) { $stub->type = $c->type; $stub->class = $c->class; $stub->value = $c->value; $stub->handle = $c->handle; $stub->cut = $c->cut; $stub->attr = $c->attr; if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !\preg_match('//u', $c->value)) { $stub->type = Stub::TYPE_STRING; $stub->class = Stub::STRING_BINARY; } $a = []; } return $a; } public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, bool $isNested) { return $isNested ? $c->preservedSubset : $a; } public static function cutInternals($obj, array $a, Stub $stub, bool $isNested) { if ($isNested) { $stub->cut += \count($a); return []; } return $a; } public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNested) { if ($isNested) { $stub->class = $c->dumpKeys ? '' : null; $stub->handle = 0; $stub->value = null; $stub->cut = $c->cut; $stub->attr = $c->attr; $a = []; if ($c->value) { foreach (\array_keys($c->value) as $k) { $keys[] = !isset($k[0]) || "\x00" !== $k[0] ? Caster::PREFIX_VIRTUAL . $k : $k; } // Preserve references with array_combine() $a = \array_combine($keys, $c->value); } } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Uid\Ulid; use _ContaoManager\Symfony\Component\Uid\Uuid; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @final */ class SymfonyCaster { private const REQUEST_GETTERS = ['pathInfo' => 'getPathInfo', 'requestUri' => 'getRequestUri', 'baseUrl' => 'getBaseUrl', 'basePath' => 'getBasePath', 'method' => 'getMethod', 'format' => 'getRequestFormat']; public static function castRequest(Request $request, array $a, Stub $stub, bool $isNested) { $clone = null; foreach (self::REQUEST_GETTERS as $prop => $getter) { $key = Caster::PREFIX_PROTECTED . $prop; if (\array_key_exists($key, $a) && null === $a[$key]) { if (null === $clone) { $clone = clone $request; } $a[Caster::PREFIX_VIRTUAL . $prop] = $clone->{$getter}(); } } return $a; } public static function castHttpClient($client, array $a, Stub $stub, bool $isNested) { $multiKey = \sprintf("\x00%s\x00multi", \get_class($client)); if (isset($a[$multiKey])) { $a[$multiKey] = new CutStub($a[$multiKey]); } return $a; } public static function castHttpClientResponse($response, array $a, Stub $stub, bool $isNested) { $stub->cut += \count($a); $a = []; foreach ($response->getInfo() as $k => $v) { $a[Caster::PREFIX_VIRTUAL . $k] = $v; } return $a; } public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'toBase58'] = $uuid->toBase58(); $a[Caster::PREFIX_VIRTUAL . 'toBase32'] = $uuid->toBase32(); // symfony/uid >= 5.3 if (\method_exists($uuid, 'getDateTime')) { $a[Caster::PREFIX_VIRTUAL . 'time'] = $uuid->getDateTime()->format('_ContaoManager\\Y-m-d H:i:s.u \\U\\T\\C'); } return $a; } public static function castUlid(Ulid $ulid, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'toBase58'] = $ulid->toBase58(); $a[Caster::PREFIX_VIRTUAL . 'toRfc4122'] = $ulid->toRfc4122(); // symfony/uid >= 5.3 if (\method_exists($ulid, 'getDateTime')) { $a[Caster::PREFIX_VIRTUAL . 'time'] = $ulid->getDateTime()->format('_ContaoManager\\Y-m-d H:i:s.v \\U\\T\\C'); } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; /** * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). * * @author Nicolas Grekas */ class FrameStub extends EnumStub { public $keepArgs; public $inTraceStub; public function __construct(array $frame, bool $keepArgs = \true, bool $inTraceStub = \false) { $this->value = $frame; $this->keepArgs = $keepArgs; $this->inTraceStub = $inTraceStub; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\ProxyManager\Proxy\ProxyInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Nicolas Grekas * * @final */ class ProxyManagerCaster { public static function castProxy(ProxyInterface $c, array $a, Stub $stub, bool $isNested) { if ($parent = \get_parent_class($c)) { $stub->class .= ' - ' . $parent; } $stub->class .= '@proxy'; return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts DateTimeInterface related classes to array representation. * * @author Dany Maillard * * @final */ class DateCaster { private const PERIOD_LIMIT = 3; public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter) { $prefix = Caster::PREFIX_VIRTUAL; $location = $d->getTimezone() ? $d->getTimezone()->getLocation() : null; $fromNow = (new \DateTime())->diff($d); $title = $d->format('l, F j, Y') . "\n" . self::formatInterval($fromNow) . ' from now' . ($location ? $d->format('I') ? "\nDST On" : "\nDST Off" : ''); unset($a[Caster::PREFIX_DYNAMIC . 'date'], $a[Caster::PREFIX_DYNAMIC . 'timezone'], $a[Caster::PREFIX_DYNAMIC . 'timezone_type']); $a[$prefix . 'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); $stub->class .= $d->format(' @U'); return $a; } public static function castInterval(\DateInterval $interval, array $a, Stub $stub, bool $isNested, int $filter) { $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); $title = \number_format($numberOfSeconds, 0, '.', ' ') . 's'; $i = [Caster::PREFIX_VIRTUAL . 'interval' => new ConstStub(self::formatInterval($interval), $title)]; return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; } private static function formatInterval(\DateInterval $i) : string { $format = '%R '; if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); $i = $d->diff($d->add($i)); // recalculate carry over points $format .= 0 < $i->days ? '%ad ' : ''; } else { $format .= ($i->y ? '%yy ' : '') . ($i->m ? '%mm ' : '') . ($i->d ? '%dd ' : ''); } $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:' . self::formatSeconds($i->s, \substr($i->f, 2)) : ''; $format = '%R ' === $format ? '0s' : $format; return $i->format(\rtrim($format)); } public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, bool $isNested, int $filter) { $location = $timeZone->getLocation(); $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P'); $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-' . $location['country_code']) : ''; $z = [Caster::PREFIX_VIRTUAL . 'timezone' => new ConstStub($formatted, $title)]; return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; } public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, bool $isNested, int $filter) { $dates = []; foreach (clone $p as $i => $d) { if (self::PERIOD_LIMIT === $i) { $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); $dates[] = \sprintf('%s more', ($end = $p->getEndDate()) ? \ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) : $p->recurrences - $i); break; } $dates[] = \sprintf('%s) %s', $i + 1, self::formatDateTime($d)); } $period = \sprintf('every %s, from %s%s %s', self::formatInterval($p->getDateInterval()), $p->include_start_date ? '[' : ']', self::formatDateTime($p->getStartDate()), ($end = $p->getEndDate()) ? 'to ' . self::formatDateTime($end) . (\PHP_VERSION_ID >= 80200 && $p->include_end_date ? ']' : '[') : 'recurring ' . $p->recurrences . ' time/s'); $p = [Caster::PREFIX_VIRTUAL . 'period' => new ConstStub($period, \implode("\n", $dates))]; return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; } private static function formatDateTime(\DateTimeInterface $d, string $extra = '') : string { return $d->format('Y-m-d H:i:' . self::formatSeconds($d->format('s'), $d->format('u')) . $extra); } private static function formatSeconds(string $s, string $us) : string { return \sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = \rtrim($us, '0'))) ? '0' : ($len <= 3 ? \str_pad($t, 3, '0') : $us)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts PDO related classes to array representation. * * @author Nicolas Grekas * * @final */ class PdoCaster { private const PDO_ATTRIBUTES = ['CASE' => [\PDO::CASE_LOWER => 'LOWER', \PDO::CASE_NATURAL => 'NATURAL', \PDO::CASE_UPPER => 'UPPER'], 'ERRMODE' => [\PDO::ERRMODE_SILENT => 'SILENT', \PDO::ERRMODE_WARNING => 'WARNING', \PDO::ERRMODE_EXCEPTION => 'EXCEPTION'], 'TIMEOUT', 'PREFETCH', 'AUTOCOMMIT', 'PERSISTENT', 'DRIVER_NAME', 'SERVER_INFO', 'ORACLE_NULLS' => [\PDO::NULL_NATURAL => 'NATURAL', \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', \PDO::NULL_TO_STRING => 'TO_STRING'], 'CLIENT_VERSION', 'SERVER_VERSION', 'STATEMENT_CLASS', 'EMULATE_PREPARES', 'CONNECTION_STATUS', 'STRINGIFY_FETCHES', 'DEFAULT_FETCH_MODE' => [\PDO::FETCH_ASSOC => 'ASSOC', \PDO::FETCH_BOTH => 'BOTH', \PDO::FETCH_LAZY => 'LAZY', \PDO::FETCH_NUM => 'NUM', \PDO::FETCH_OBJ => 'OBJ']]; public static function castPdo(\PDO $c, array $a, Stub $stub, bool $isNested) { $attr = []; $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); foreach (self::PDO_ATTRIBUTES as $k => $v) { if (!isset($k[0])) { $k = $v; $v = []; } try { $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_' . $k)); if ($v && isset($v[$attr[$k]])) { $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); } } catch (\Exception $e) { } } if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { if ($attr[$k][1]) { $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); } $attr[$k][0] = new ClassStub($attr[$k][0]); } $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'inTransaction' => \method_exists($c, 'inTransaction'), $prefix . 'errorInfo' => $c->errorInfo(), $prefix . 'attributes' => new EnumStub($attr)]; if ($a[$prefix . 'inTransaction']) { $a[$prefix . 'inTransaction'] = $c->inTransaction(); } else { unset($a[$prefix . 'inTransaction']); } if (!isset($a[$prefix . 'errorInfo'][1], $a[$prefix . 'errorInfo'][2])) { unset($a[$prefix . 'errorInfo']); } $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); return $a; } public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a[$prefix . 'errorInfo'] = $c->errorInfo(); if (!isset($a[$prefix . 'errorInfo'][1], $a[$prefix . 'errorInfo'][2])) { unset($a[$prefix . 'errorInfo']); } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts XML resources to array representation. * * @author Nicolas Grekas * * @final */ class XmlResourceCaster { private const XML_ERRORS = [\XML_ERROR_NONE => 'XML_ERROR_NONE', \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING']; public static function castXml($h, array $a, Stub $stub, bool $isNested) { $a['current_byte_index'] = \xml_get_current_byte_index($h); $a['current_column_number'] = \xml_get_current_column_number($h); $a['current_line_number'] = \xml_get_current_line_number($h); $a['error_code'] = \xml_get_error_code($h); if (isset(self::XML_ERRORS[$a['error_code']])) { $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts common resource types to array representation. * * @author Nicolas Grekas * * @final */ class ResourceCaster { /** * @param \CurlHandle|resource $h */ public static function castCurl($h, array $a, Stub $stub, bool $isNested) : array { return \curl_getinfo($h); } public static function castDba($dba, array $a, Stub $stub, bool $isNested) { $list = \dba_list(); $a['file'] = $list[(int) $dba]; return $a; } public static function castProcess($process, array $a, Stub $stub, bool $isNested) { return \proc_get_status($process); } public static function castStream($stream, array $a, Stub $stub, bool $isNested) { $a = \stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); if ($a['uri'] ?? \false) { $a['uri'] = new LinkStub($a['uri']); } return $a; } public static function castStreamContext($stream, array $a, Stub $stub, bool $isNested) { return @\stream_context_get_params($stream) ?: $a; } public static function castGd($gd, array $a, Stub $stub, bool $isNested) { $a['size'] = \imagesx($gd) . 'x' . \imagesy($gd); $a['trueColor'] = \imageistruecolor($gd); return $a; } public static function castMysqlLink($h, array $a, Stub $stub, bool $isNested) { $a['host'] = \mysql_get_host_info($h); $a['protocol'] = \mysql_get_proto_info($h); $a['server'] = \mysql_get_server_info($h); return $a; } public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested) { $stub->cut = -1; $info = \openssl_x509_parse($h, \false); $pin = \openssl_pkey_get_public($h); $pin = \openssl_pkey_get_details($pin)['key']; $pin = \array_slice(\explode("\n", $pin), 1, -2); $pin = \base64_decode(\implode('', $pin)); $pin = \base64_encode(\hash('sha256', $pin, \true)); $a += ['subject' => new EnumStub(\array_intersect_key($info['subject'], ['organizationName' => \true, 'commonName' => \true])), 'issuer' => new EnumStub(\array_intersect_key($info['issuer'], ['organizationName' => \true, 'commonName' => \true])), 'expiry' => new ConstStub(\date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), 'fingerprint' => new EnumStub(['md5' => new ConstStub(\wordwrap(\strtoupper(\openssl_x509_fingerprint($h, 'md5')), 2, ':', \true)), 'sha1' => new ConstStub(\wordwrap(\strtoupper(\openssl_x509_fingerprint($h, 'sha1')), 2, ':', \true)), 'sha256' => new ConstStub(\wordwrap(\strtoupper(\openssl_x509_fingerprint($h, 'sha256')), 2, ':', \true)), 'pin-sha256' => new ConstStub($pin)])]; return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; use _ContaoManager\Symfony\Component\VarDumper\Exception\ThrowingCasterException; /** * Casts common Exception classes to array representation. * * @author Nicolas Grekas * * @final */ class ExceptionCaster { public static $srcContext = 1; public static $traceArgs = \true; public static $errorTypes = [\E_DEPRECATED => 'E_DEPRECATED', \E_USER_DEPRECATED => 'E_USER_DEPRECATED', \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', \E_ERROR => 'E_ERROR', \E_WARNING => 'E_WARNING', \E_PARSE => 'E_PARSE', \E_NOTICE => 'E_NOTICE', \E_CORE_ERROR => 'E_CORE_ERROR', \E_CORE_WARNING => 'E_CORE_WARNING', \E_COMPILE_ERROR => 'E_COMPILE_ERROR', \E_COMPILE_WARNING => 'E_COMPILE_WARNING', \E_USER_ERROR => 'E_USER_ERROR', \E_USER_WARNING => 'E_USER_WARNING', \E_USER_NOTICE => 'E_USER_NOTICE', \E_STRICT => 'E_STRICT']; private static $framesCache = []; public static function castError(\Error $e, array $a, Stub $stub, bool $isNested, int $filter = 0) { return self::filterExceptionArray($stub->class, $a, "\x00Error\x00", $filter); } public static function castException(\Exception $e, array $a, Stub $stub, bool $isNested, int $filter = 0) { return self::filterExceptionArray($stub->class, $a, "\x00Exception\x00", $filter); } public static function castErrorException(\ErrorException $e, array $a, Stub $stub, bool $isNested) { if (isset($a[$s = Caster::PREFIX_PROTECTED . 'severity'], self::$errorTypes[$a[$s]])) { $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); } return $a; } public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, bool $isNested) { $trace = Caster::PREFIX_VIRTUAL . 'trace'; $prefix = Caster::PREFIX_PROTECTED; $xPrefix = "\x00Exception\x00"; if (isset($a[$xPrefix . 'previous'], $a[$trace]) && $a[$xPrefix . 'previous'] instanceof \Exception) { $b = (array) $a[$xPrefix . 'previous']; $class = \get_debug_type($a[$xPrefix . 'previous']); self::traceUnshift($b[$xPrefix . 'trace'], $class, $b[$prefix . 'file'], $b[$prefix . 'line']); $a[$trace] = new TraceStub($b[$xPrefix . 'trace'], \false, 0, -\count($a[$trace]->value)); } unset($a[$xPrefix . 'previous'], $a[$prefix . 'code'], $a[$prefix . 'file'], $a[$prefix . 'line']); return $a; } public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, bool $isNested) { $sPrefix = "\x00" . SilencedErrorContext::class . "\x00"; if (!isset($a[$s = $sPrefix . 'severity'])) { return $a; } if (isset(self::$errorTypes[$a[$s]])) { $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); } $trace = [['file' => $a[$sPrefix . 'file'], 'line' => $a[$sPrefix . 'line']]]; if (isset($a[$sPrefix . 'trace'])) { $trace = \array_merge($trace, $a[$sPrefix . 'trace']); } unset($a[$sPrefix . 'file'], $a[$sPrefix . 'line'], $a[$sPrefix . 'trace']); $a[Caster::PREFIX_VIRTUAL . 'trace'] = new TraceStub($trace, self::$traceArgs); return $a; } public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, bool $isNested) { if (!$isNested) { return $a; } $stub->class = ''; $stub->handle = 0; $frames = $trace->value; $prefix = Caster::PREFIX_VIRTUAL; $a = []; $j = \count($frames); if (0 > ($i = $trace->sliceOffset)) { $i = \max(0, $j + $i); } if (!isset($trace->value[$i])) { return []; } $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'] . $frames[$i]['type'] : '') . $frames[$i]['function'] . '()' : ''; $frames[] = ['function' => '']; $collapse = \false; for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { $f = $frames[$i]; $call = isset($f['function']) ? (isset($f['class']) ? $f['class'] . $f['type'] : '') . $f['function'] : '???'; $frame = new FrameStub(['object' => $f['object'] ?? null, 'class' => $f['class'] ?? null, 'type' => $f['type'] ?? null, 'function' => $f['function'] ?? null] + $frames[$i - 1], \false, \true); $f = self::castFrameStub($frame, [], $frame, \true); if (isset($f[$prefix . 'src'])) { foreach ($f[$prefix . 'src']->value as $label => $frame) { if (\str_starts_with($label, "\x00~collapse=0")) { if ($collapse) { $label = \substr_replace($label, '1', 11, 1); } else { $collapse = \true; } } $label = \substr_replace($label, "title=Stack level {$j}.&", 2, 0); } $f = $frames[$i - 1]; if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); } } elseif ('???' !== $lastCall) { $label = new ClassStub($lastCall); if (isset($label->attr['ellipsis'])) { $label->attr['ellipsis'] += 2; $label = \substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level {$j}.", 2, 0) . $label->value . '()'; } else { $label = \substr_replace($prefix, "title=Stack level {$j}.", 2, 0) . $label->value . '()'; } } else { $label = \substr_replace($prefix, "title=Stack level {$j}.", 2, 0) . $lastCall; } $a[\substr_replace($label, \sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; $lastCall = $call; } if (null !== $trace->sliceLength) { $a = \array_slice($a, 0, $trace->sliceLength, \true); } return $a; } public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, bool $isNested) { if (!$isNested) { return $a; } $f = $frame->value; $prefix = Caster::PREFIX_VIRTUAL; if (isset($f['file'], $f['line'])) { $cacheKey = $f; unset($cacheKey['object'], $cacheKey['args']); $cacheKey[] = self::$srcContext; $cacheKey = \implode('-', $cacheKey); if (isset(self::$framesCache[$cacheKey])) { $a[$prefix . 'src'] = self::$framesCache[$cacheKey]; } else { if (\preg_match('/\\((\\d+)\\)(?:\\([\\da-f]{32}\\))? : (?:eval\\(\\)\'d code|runtime-created function)$/', $f['file'], $match)) { $f['file'] = \substr($f['file'], 0, -\strlen($match[0])); $f['line'] = (int) $match[1]; } $src = $f['line']; $srcKey = $f['file']; $ellipsis = new LinkStub($srcKey, 0); $srcAttr = 'collapse=' . (int) $ellipsis->inVendor; $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; if (\is_file($f['file']) && 0 <= self::$srcContext) { if (!empty($f['class']) && (\is_subclass_of($f['class'], '_ContaoManager\\Twig\\Template') || \is_subclass_of($f['class'], '_ContaoManager\\Twig_Template')) && \method_exists($f['class'], 'getDebugInfo')) { $template = null; if (isset($f['object'])) { $template = $f['object']; } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { $template = \unserialize(\sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); } if (null !== $template) { $ellipsis = 0; $templateSrc = \method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (\method_exists($template, 'getSource') ? $template->getSource() : ''); $templateInfo = $template->getDebugInfo(); if (isset($templateInfo[$f['line']])) { if (!\method_exists($template, 'getSourceContext') || !\is_file($templatePath = $template->getSourceContext()->getPath())) { $templatePath = null; } if ($templateSrc) { $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); $srcKey = ($templatePath ?: $template->getTemplateName()) . ':' . $templateInfo[$f['line']]; } } } } if ($srcKey == $f['file']) { $src = self::extractSource(\file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); $srcKey .= ':' . $f['line']; if ($ellipsis) { $ellipsis += 1 + \strlen($f['line']); } } $srcAttr .= \sprintf('&separator= &file=%s&line=%d', \rawurlencode($f['file']), $f['line']); } else { $srcAttr .= '&separator=:'; } $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis=' . $ellipsis . '&ellipsis-tail=' . $ellipsisTail : ''; self::$framesCache[$cacheKey] = $a[$prefix . 'src'] = new EnumStub(["\x00~{$srcAttr}\x00{$srcKey}" => $src]); } } unset($a[$prefix . 'args'], $a[$prefix . 'line'], $a[$prefix . 'file']); if ($frame->inTraceStub) { unset($a[$prefix . 'class'], $a[$prefix . 'type'], $a[$prefix . 'function']); } foreach ($a as $k => $v) { if (!$v) { unset($a[$k]); } } if ($frame->keepArgs && !empty($f['args'])) { $a[$prefix . 'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); } return $a; } private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter) : array { if (isset($a[$xPrefix . 'trace'])) { $trace = $a[$xPrefix . 'trace']; unset($a[$xPrefix . 'trace']); // Ensures the trace is always last } else { $trace = []; } if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { if (isset($a[Caster::PREFIX_PROTECTED . 'file'], $a[Caster::PREFIX_PROTECTED . 'line'])) { self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED . 'file'], $a[Caster::PREFIX_PROTECTED . 'line']); } $a[Caster::PREFIX_VIRTUAL . 'trace'] = new TraceStub($trace, self::$traceArgs); } if (empty($a[$xPrefix . 'previous'])) { unset($a[$xPrefix . 'previous']); } unset($a[$xPrefix . 'string'], $a[Caster::PREFIX_DYNAMIC . 'xdebug_message'], $a[Caster::PREFIX_DYNAMIC . '__destructorException']); if (isset($a[Caster::PREFIX_PROTECTED . 'message']) && \str_contains($a[Caster::PREFIX_PROTECTED . 'message'], "@anonymous\x00")) { $a[Caster::PREFIX_PROTECTED . 'message'] = \preg_replace_callback('/[a-zA-Z_\\x7f-\\xff][\\\\a-zA-Z0-9_\\x7f-\\xff]*+@anonymous\\x00.*?\\.php(?:0x?|:[0-9]++\\$)[0-9a-fA-F]++/', function ($m) { return \class_exists($m[0], \false) ? ((\get_parent_class($m[0]) ?: \key(\class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0]; }, $a[Caster::PREFIX_PROTECTED . 'message']); } if (isset($a[Caster::PREFIX_PROTECTED . 'file'], $a[Caster::PREFIX_PROTECTED . 'line'])) { $a[Caster::PREFIX_PROTECTED . 'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED . 'file'], $a[Caster::PREFIX_PROTECTED . 'line']); } return $a; } private static function traceUnshift(array &$trace, ?string $class, string $file, int $line) : void { if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { return; } \array_unshift($trace, ['function' => $class ? 'new ' . $class : null, 'file' => $file, 'line' => $line]); } private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame) : EnumStub { $srcLines = \explode("\n", $srcLines); $src = []; for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { $src[] = ($srcLines[$i] ?? '') . "\n"; } if ($frame['function'] ?? \false) { $stub = new CutStub(new \stdClass()); $stub->class = (isset($frame['class']) ? $frame['class'] . $frame['type'] : '') . $frame['function']; $stub->type = Stub::TYPE_OBJECT; $stub->attr['cut_hash'] = \true; $stub->attr['file'] = $frame['file']; $stub->attr['line'] = $frame['line']; try { $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, \true, Caster::EXCLUDE_VERBOSE)); if ($f = $caller->getFileName()) { $stub->attr['file'] = $f; $stub->attr['line'] = $caller->getStartLine(); } } catch (\ReflectionException $e) { // ignore fake class/function } $srcLines = ["\x00~separator=\x00" => $stub]; } else { $stub = null; $srcLines = []; } $ltrim = 0; do { $pad = null; for ($i = $srcContext << 1; $i >= 0; --$i) { if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { if (null === $pad) { $pad = $c; } if (' ' !== $c && "\t" !== $c || $pad !== $c) { break; } } } ++$ltrim; } while (0 > $i && null !== $pad); --$ltrim; foreach ($src as $i => $c) { if ($ltrim) { $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? \substr($c, $ltrim) : \ltrim($c, " \t"); } $c = \substr($c, 0, -1); if ($i !== $srcContext) { $c = new ConstStub('default', $c); } else { $c = new ConstStub($c, $stub ? 'in ' . $stub->class : ''); if (null !== $file) { $c->attr['file'] = $file; $c->attr['line'] = $line; } } $c->attr['lang'] = $lang; $srcLines[\sprintf("\x00~separator=› &%d\x00", $i + $line - $srcContext)] = $c; } return new EnumStub($srcLines); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use Ds\Collection; use Ds\Map; use Ds\Pair; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Ds extension classes to array representation. * * @author Jáchym Toušek * * @final */ class DsCaster { public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested) : array { $a[Caster::PREFIX_VIRTUAL . 'count'] = $c->count(); $a[Caster::PREFIX_VIRTUAL . 'capacity'] = $c->capacity(); if (!$c instanceof Map) { $a += $c->toArray(); } return $a; } public static function castMap(Map $c, array $a, Stub $stub, bool $isNested) : array { foreach ($c as $k => $v) { $a[] = new DsPairStub($k, $v); } return $a; } public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested) : array { foreach ($c->toArray() as $k => $v) { $a[Caster::PREFIX_VIRTUAL . $k] = $v; } return $a; } public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested) : array { if ($isNested) { $stub->class = Pair::class; $stub->value = null; $stub->handle = 0; $a = $c->value; } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; /** * Represents a file or a URL. * * @author Nicolas Grekas */ class LinkStub extends ConstStub { public $inVendor = \false; private static $vendorRoots; private static $composerRoots; public function __construct(string $label, int $line = 0, ?string $href = null) { $this->value = $label; if (null === $href) { $href = $label; } if (!\is_string($href)) { return; } if (\str_starts_with($href, 'file://')) { if ($href === $label) { $label = \substr($label, 7); } $href = \substr($href, 7); } elseif (\str_contains($href, '://')) { $this->attr['href'] = $href; return; } if (!\is_file($href)) { return; } if ($line) { $this->attr['line'] = $line; } if ($label !== ($this->attr['file'] = \realpath($href) ?: $href)) { return; } if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; $this->attr['ellipsis-type'] = 'path'; $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(\implode('', \array_slice(\explode(\DIRECTORY_SEPARATOR, \substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); } elseif (3 < \count($ellipsis = \explode(\DIRECTORY_SEPARATOR, $href))) { $this->attr['ellipsis'] = 2 + \strlen(\implode('', \array_slice($ellipsis, -2))); $this->attr['ellipsis-type'] = 'path'; $this->attr['ellipsis-tail'] = 1; } } private function getComposerRoot(string $file, bool &$inVendor) { if (null === self::$vendorRoots) { self::$vendorRoots = []; foreach (\get_declared_classes() as $class) { if ('C' === $class[0] && \str_starts_with($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); $v = \dirname($r->getFileName(), 2); if (\is_file($v . '/composer/installed.json')) { self::$vendorRoots[] = $v . \DIRECTORY_SEPARATOR; } } } } $inVendor = \false; if (isset(self::$composerRoots[$dir = \dirname($file)])) { return self::$composerRoots[$dir]; } foreach (self::$vendorRoots as $root) { if ($inVendor = \str_starts_with($file, $root)) { return $root; } } $parent = $dir; while (!@\is_file($parent . '/composer.json')) { if (!@\file_exists($parent)) { // open_basedir restriction in effect break; } if ($parent === \dirname($parent)) { return self::$composerRoots[$dir] = \false; } $parent = \dirname($parent); } return self::$composerRoots[$dir] = $parent . \DIRECTORY_SEPARATOR; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Jan Schädlich * * @final */ class MemcachedCaster { private static $optionConstants; private static $defaultOptions; public static function castMemcached(\Memcached $c, array $a, Stub $stub, bool $isNested) { $a += [Caster::PREFIX_VIRTUAL . 'servers' => $c->getServerList(), Caster::PREFIX_VIRTUAL . 'options' => new EnumStub(self::getNonDefaultOptions($c))]; return $a; } private static function getNonDefaultOptions(\Memcached $c) : array { self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions(); self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); $nonDefaultOptions = []; foreach (self::$optionConstants as $constantKey => $value) { if (self::$defaultOptions[$constantKey] !== ($option = $c->getOption($value))) { $nonDefaultOptions[$constantKey] = $option; } } return $nonDefaultOptions; } private static function discoverDefaultOptions() : array { $defaultMemcached = new \Memcached(); $defaultMemcached->addServer('127.0.0.1', 11211); $defaultOptions = []; self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); foreach (self::$optionConstants as $constantKey => $value) { $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); } return $defaultOptions; } private static function getOptionConstants() : array { $reflectedMemcached = new \ReflectionClass(\Memcached::class); $optionConstants = []; foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { if (\str_starts_with($constantKey, 'OPT_')) { $optionConstants[$constantKey] = $value; } } return $optionConstants; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Doctrine\Common\Proxy\Proxy as CommonProxy; use _ContaoManager\Doctrine\ORM\PersistentCollection; use _ContaoManager\Doctrine\ORM\Proxy\Proxy as OrmProxy; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Doctrine related classes to array representation. * * @author Nicolas Grekas * * @final */ class DoctrineCaster { public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, bool $isNested) { foreach (['__cloner__', '__initializer__'] as $k) { if (\array_key_exists($k, $a)) { unset($a[$k]); ++$stub->cut; } } return $a; } public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, bool $isNested) { foreach (['_entityPersister', '_identifier'] as $k) { if (\array_key_exists($k = "\x00Doctrine\\ORM\\Proxy\\Proxy\x00" . $k, $a)) { unset($a[$k]); ++$stub->cut; } } return $a; } public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, bool $isNested) { foreach (['snapshot', 'association', 'typeClass'] as $k) { if (\array_key_exists($k = "\x00Doctrine\\ORM\\PersistentCollection\x00" . $k, $a)) { $a[$k] = new CutStub($a[$k]); } } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use RdKafka\Conf; use RdKafka\Exception as RdKafkaException; use RdKafka\KafkaConsumer; use RdKafka\Message; use RdKafka\Metadata\Broker as BrokerMetadata; use RdKafka\Metadata\Collection as CollectionMetadata; use RdKafka\Metadata\Partition as PartitionMetadata; use RdKafka\Metadata\Topic as TopicMetadata; use RdKafka\Topic; use RdKafka\TopicConf; use RdKafka\TopicPartition; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts RdKafka related classes to array representation. * * @author Romain Neutron */ class RdKafkaCaster { public static function castKafkaConsumer(KafkaConsumer $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; try { $assignment = $c->getAssignment(); } catch (RdKafkaException $e) { $assignment = []; } $a += [$prefix . 'subscription' => $c->getSubscription(), $prefix . 'assignment' => $assignment]; $a += self::extractMetadata($c); return $a; } public static function castTopic(Topic $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'name' => $c->getName()]; return $a; } public static function castTopicPartition(TopicPartition $c, array $a) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'offset' => $c->getOffset(), $prefix . 'partition' => $c->getPartition(), $prefix . 'topic' => $c->getTopic()]; return $a; } public static function castMessage(Message $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'errstr' => $c->errstr()]; return $a; } public static function castConf(Conf $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; foreach ($c->dump() as $key => $value) { $a[$prefix . $key] = $value; } return $a; } public static function castTopicConf(TopicConf $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; foreach ($c->dump() as $key => $value) { $a[$prefix . $key] = $value; } return $a; } public static function castRdKafka(\RdKafka $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'out_q_len' => $c->getOutQLen()]; $a += self::extractMetadata($c); return $a; } public static function castCollectionMetadata(CollectionMetadata $c, array $a, Stub $stub, bool $isNested) { $a += \iterator_to_array($c); return $a; } public static function castTopicMetadata(TopicMetadata $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'name' => $c->getTopic(), $prefix . 'partitions' => $c->getPartitions()]; return $a; } public static function castPartitionMetadata(PartitionMetadata $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'id' => $c->getId(), $prefix . 'err' => $c->getErr(), $prefix . 'leader' => $c->getLeader()]; return $a; } public static function castBrokerMetadata(BrokerMetadata $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'id' => $c->getId(), $prefix . 'host' => $c->getHost(), $prefix . 'port' => $c->getPort()]; return $a; } private static function extractMetadata($c) { $prefix = Caster::PREFIX_VIRTUAL; try { $m = $c->getMetadata(\true, null, 500); } catch (RdKafkaException $e) { return []; } return [$prefix . 'orig_broker_id' => $m->getOrigBrokerId(), $prefix . 'orig_broker_name' => $m->getOrigBrokerName(), $prefix . 'brokers' => $m->getBrokers(), $prefix . 'topics' => $m->getTopics()]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Nicolas Grekas * * @internal */ final class MysqliCaster { public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested) : array { foreach ($a as $k => $v) { if (isset($c->{$k})) { $a[$k] = $c->{$k}; } } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; /** * @author Grégoire Pineau */ class ImgStub extends ConstStub { public function __construct(string $data, string $contentType, string $size = '') { $this->value = ''; $this->attr['img-data'] = $data; $this->attr['img-size'] = $size; $this->attr['content-type'] = $contentType; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Represents a PHP class identifier. * * @author Nicolas Grekas */ class ClassStub extends ConstStub { /** * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier */ public function __construct(string $identifier, $callable = null) { $this->value = $identifier; try { if (null !== $callable) { if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); } elseif (\is_object($callable)) { $r = [$callable, '__invoke']; } elseif (\is_array($callable)) { $r = $callable; } elseif (\false !== ($i = \strpos($callable, '::'))) { $r = [\substr($callable, 0, $i), \substr($callable, 2 + $i)]; } else { $r = new \ReflectionFunction($callable); } } elseif (0 < ($i = \strpos($identifier, '::') ?: \strpos($identifier, '->'))) { $r = [\substr($identifier, 0, $i), \substr($identifier, 2 + $i)]; } else { $r = new \ReflectionClass($identifier); } if (\is_array($r)) { try { $r = new \ReflectionMethod($r[0], $r[1]); } catch (\ReflectionException $e) { $r = new \ReflectionClass($r[0]); } } if (\str_contains($identifier, "@anonymous\x00")) { $this->value = $identifier = \preg_replace_callback('/[a-zA-Z_\\x7f-\\xff][\\\\a-zA-Z0-9_\\x7f-\\xff]*+@anonymous\\x00.*?\\.php(?:0x?|:[0-9]++\\$)[0-9a-fA-F]++/', function ($m) { return \class_exists($m[0], \false) ? ((\get_parent_class($m[0]) ?: \key(\class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0]; }, $identifier); } if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), \true, Caster::EXCLUDE_VERBOSE); $s = ReflectionCaster::getSignature($s); if (\str_ends_with($identifier, '()')) { $this->value = \substr_replace($identifier, $s, -2); } else { $this->value .= $s; } } } catch (\ReflectionException $e) { return; } finally { if (0 < ($i = \strrpos($this->value, '\\'))) { $this->attr['ellipsis'] = \strlen($this->value) - $i; $this->attr['ellipsis-type'] = 'class'; $this->attr['ellipsis-tail'] = 1; } } if ($f = $r->getFileName()) { $this->attr['file'] = $f; $this->attr['line'] = $r->getStartLine(); } } public static function wrapCallable($callable) { if (\is_object($callable) || !\is_callable($callable)) { return $callable; } if (!\is_array($callable)) { $callable = new static($callable, $callable); } elseif (\is_string($callable[0])) { $callable[0] = new static($callable[0], $callable); } else { $callable[1] = new static($callable[1], $callable); } return $callable; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). * * @author Nicolas Grekas */ class TraceStub extends Stub { public $keepArgs; public $sliceOffset; public $sliceLength; public $numberingOffset; public function __construct(array $trace, bool $keepArgs = \true, int $sliceOffset = 0, ?int $sliceLength = null, int $numberingOffset = 0) { $this->value = $trace; $this->keepArgs = $keepArgs; $this->sliceOffset = $sliceOffset; $this->sliceLength = $sliceLength; $this->numberingOffset = $numberingOffset; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; /** * Represents a cut array. * * @author Nicolas Grekas */ class CutArrayStub extends CutStub { public $preservedSubset; public function __construct(array $value, array $preservedKeys) { parent::__construct($value); $this->preservedSubset = \array_intersect_key($value, \array_flip($preservedKeys)); $this->cut -= \count($this->preservedSubset); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts pqsql resources to array representation. * * @author Nicolas Grekas * * @final */ class PgSqlCaster { private const PARAM_CODES = ['server_encoding', 'client_encoding', 'is_superuser', 'session_authorization', 'DateStyle', 'TimeZone', 'IntervalStyle', 'integer_datetimes', 'application_name', 'standard_conforming_strings']; private const TRANSACTION_STATUS = [\PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN']; private const RESULT_STATUS = [\PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', \PGSQL_COPY_IN => 'PGSQL_COPY_IN', \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR']; private const DIAG_CODES = ['severity' => \PGSQL_DIAG_SEVERITY, 'sqlstate' => \PGSQL_DIAG_SQLSTATE, 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, 'hint' => \PGSQL_DIAG_MESSAGE_HINT, 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, 'context' => \PGSQL_DIAG_CONTEXT, 'file' => \PGSQL_DIAG_SOURCE_FILE, 'line' => \PGSQL_DIAG_SOURCE_LINE, 'function' => \PGSQL_DIAG_SOURCE_FUNCTION]; public static function castLargeObject($lo, array $a, Stub $stub, bool $isNested) { $a['seek position'] = \pg_lo_tell($lo); return $a; } public static function castLink($link, array $a, Stub $stub, bool $isNested) { $a['status'] = \pg_connection_status($link); $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); $a['busy'] = \pg_connection_busy($link); $a['transaction'] = \pg_transaction_status($link); if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); } $a['pid'] = \pg_get_pid($link); $a['last error'] = \pg_last_error($link); $a['last notice'] = \pg_last_notice($link); $a['host'] = \pg_host($link); $a['port'] = \pg_port($link); $a['dbname'] = \pg_dbname($link); $a['options'] = \pg_options($link); $a['version'] = \pg_version($link); foreach (self::PARAM_CODES as $v) { if (\false !== ($s = \pg_parameter_status($link, $v))) { $a['param'][$v] = $s; } } $a['param']['client_encoding'] = \pg_client_encoding($link); $a['param'] = new EnumStub($a['param']); return $a; } public static function castResult($result, array $a, Stub $stub, bool $isNested) { $a['num rows'] = \pg_num_rows($result); $a['status'] = \pg_result_status($result); if (isset(self::RESULT_STATUS[$a['status']])) { $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); } $a['command-completion tag'] = \pg_result_status($result, \PGSQL_STATUS_STRING); if (-1 === $a['num rows']) { foreach (self::DIAG_CODES as $k => $v) { $a['error'][$k] = \pg_result_error_field($result, $v); } } $a['affected rows'] = \pg_affected_rows($result); $a['last OID'] = \pg_last_oid($result); $fields = \pg_num_fields($result); for ($i = 0; $i < $fields; ++$i) { $field = ['name' => \pg_field_name($result, $i), 'table' => \sprintf('%s (OID: %s)', \pg_field_table($result, $i), \pg_field_table($result, $i, \true)), 'type' => \sprintf('%s (OID: %s)', \pg_field_type($result, $i), \pg_field_type_oid($result, $i)), 'nullable' => (bool) \pg_field_is_null($result, $i), 'storage' => \pg_field_size($result, $i) . ' bytes', 'display' => \pg_field_prtlen($result, $i) . ' chars']; if (' (OID: )' === $field['table']) { $field['table'] = null; } if ('-1 bytes' === $field['storage']) { $field['storage'] = 'variable size'; } elseif ('1 bytes' === $field['storage']) { $field['storage'] = '1 byte'; } if ('1 chars' === $field['display']) { $field['display'] = '1 char'; } $a['fields'][] = new EnumStub($field); } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Helper for filtering out properties in casters. * * @author Nicolas Grekas * * @final */ class Caster { public const EXCLUDE_VERBOSE = 1; public const EXCLUDE_VIRTUAL = 2; public const EXCLUDE_DYNAMIC = 4; public const EXCLUDE_PUBLIC = 8; public const EXCLUDE_PROTECTED = 16; public const EXCLUDE_PRIVATE = 32; public const EXCLUDE_NULL = 64; public const EXCLUDE_EMPTY = 128; public const EXCLUDE_NOT_IMPORTANT = 256; public const EXCLUDE_STRICT = 512; public const PREFIX_VIRTUAL = "\x00~\x00"; public const PREFIX_DYNAMIC = "\x00+\x00"; public const PREFIX_PROTECTED = "\x00*\x00"; /** * Casts objects to arrays and adds the dynamic property prefix. * * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not */ public static function castObject(object $obj, string $class, bool $hasDebugInfo = \false, ?string $debugClass = null) : array { if ($hasDebugInfo) { try { $debugInfo = $obj->__debugInfo(); } catch (\Throwable $e) { // ignore failing __debugInfo() $hasDebugInfo = \false; } } $a = $obj instanceof \Closure ? [] : (array) $obj; if ($obj instanceof \__PHP_Incomplete_Class) { return $a; } if ($a) { static $publicProperties = []; $debugClass = $debugClass ?? \get_debug_type($obj); $i = 0; $prefixedKeys = []; foreach ($a as $k => $v) { if ("\x00" !== ($k[0] ?? '')) { if (!isset($publicProperties[$class])) { foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { $publicProperties[$class][$prop->name] = \true; } } if (!isset($publicProperties[$class][$k])) { $prefixedKeys[$i] = self::PREFIX_DYNAMIC . $k; } } elseif ($debugClass !== $class && 1 === \strpos($k, $class)) { $prefixedKeys[$i] = "\x00" . $debugClass . \strrchr($k, "\x00"); } ++$i; } if ($prefixedKeys) { $keys = \array_keys($a); foreach ($prefixedKeys as $i => $k) { $keys[$i] = $k; } $a = \array_combine($keys, $a); } } if ($hasDebugInfo && \is_array($debugInfo)) { foreach ($debugInfo as $k => $v) { if (!isset($k[0]) || "\x00" !== $k[0]) { if (\array_key_exists(self::PREFIX_DYNAMIC . $k, $a)) { continue; } $k = self::PREFIX_VIRTUAL . $k; } unset($a[$k]); $a[$k] = $v; } } return $a; } /** * Filters out the specified properties. * * By default, a single match in the $filter bit field filters properties out, following an "or" logic. * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. * * @param array $a The array containing the properties to filter * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set * @param int|null &$count Set to the number of removed properties */ public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0) : array { $count = 0; foreach ($a as $k => $v) { $type = self::EXCLUDE_STRICT & $filter; if (null === $v) { $type |= self::EXCLUDE_NULL & $filter; $type |= self::EXCLUDE_EMPTY & $filter; } elseif (\false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { $type |= self::EXCLUDE_EMPTY & $filter; } if (self::EXCLUDE_NOT_IMPORTANT & $filter && !\in_array($k, $listedProperties, \true)) { $type |= self::EXCLUDE_NOT_IMPORTANT; } if (self::EXCLUDE_VERBOSE & $filter && \in_array($k, $listedProperties, \true)) { $type |= self::EXCLUDE_VERBOSE; } if (!isset($k[1]) || "\x00" !== $k[0]) { $type |= self::EXCLUDE_PUBLIC & $filter; } elseif ('~' === $k[1]) { $type |= self::EXCLUDE_VIRTUAL & $filter; } elseif ('+' === $k[1]) { $type |= self::EXCLUDE_DYNAMIC & $filter; } elseif ('*' === $k[1]) { $type |= self::EXCLUDE_PROTECTED & $filter; } else { $type |= self::EXCLUDE_PRIVATE & $filter; } if (self::EXCLUDE_STRICT & $filter ? $type === $filter : $type) { unset($a[$k]); ++$count; } } return $a; } public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested) : array { if (isset($a['__PHP_Incomplete_Class_Name'])) { $stub->class .= '(' . $a['__PHP_Incomplete_Class_Name'] . ')'; unset($a['__PHP_Incomplete_Class_Name']); } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts SPL related classes to array representation. * * @author Nicolas Grekas * * @final */ class SplCaster { private const SPL_FILE_OBJECT_FLAGS = [\SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', \SplFileObject::READ_AHEAD => 'READ_AHEAD', \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', \SplFileObject::READ_CSV => 'READ_CSV']; public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, bool $isNested) { return self::castSplArray($c, $a, $stub, $isNested); } public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, bool $isNested) { return self::castSplArray($c, $a, $stub, $isNested); } public static function castHeap(\Iterator $c, array $a, Stub $stub, bool $isNested) { $a += [Caster::PREFIX_VIRTUAL . 'heap' => \iterator_to_array(clone $c)]; return $a; } public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $mode = $c->getIteratorMode(); $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); $a += [$prefix . 'mode' => new ConstStub(($mode & \SplDoublyLinkedList::IT_MODE_LIFO ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO') . ' | ' . ($mode & \SplDoublyLinkedList::IT_MODE_DELETE ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), $prefix . 'dllist' => \iterator_to_array($c)]; $c->setIteratorMode($mode); return $a; } public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, bool $isNested) { static $map = ['path' => 'getPath', 'filename' => 'getFilename', 'basename' => 'getBasename', 'pathname' => 'getPathname', 'extension' => 'getExtension', 'realPath' => 'getRealPath', 'aTime' => 'getATime', 'mTime' => 'getMTime', 'cTime' => 'getCTime', 'inode' => 'getInode', 'size' => 'getSize', 'perms' => 'getPerms', 'owner' => 'getOwner', 'group' => 'getGroup', 'type' => 'getType', 'writable' => 'isWritable', 'readable' => 'isReadable', 'executable' => 'isExecutable', 'file' => 'isFile', 'dir' => 'isDir', 'link' => 'isLink', 'linkTarget' => 'getLinkTarget']; $prefix = Caster::PREFIX_VIRTUAL; unset($a["\x00SplFileInfo\x00fileName"]); unset($a["\x00SplFileInfo\x00pathName"]); if (\PHP_VERSION_ID < 80000) { if (\false === $c->getPathname()) { $a[$prefix . '⚠'] = 'The parent constructor was not called: the object is in an invalid state'; return $a; } } else { try { $c->isReadable(); } catch (\RuntimeException $e) { if ('Object not initialized' !== $e->getMessage()) { throw $e; } $a[$prefix . '⚠'] = 'The parent constructor was not called: the object is in an invalid state'; return $a; } catch (\Error $e) { if ('Object not initialized' !== $e->getMessage()) { throw $e; } $a[$prefix . '⚠'] = 'The parent constructor was not called: the object is in an invalid state'; return $a; } } foreach ($map as $key => $accessor) { try { $a[$prefix . $key] = $c->{$accessor}(); } catch (\Exception $e) { } } if ($a[$prefix . 'realPath'] ?? \false) { $a[$prefix . 'realPath'] = new LinkStub($a[$prefix . 'realPath']); } if (isset($a[$prefix . 'perms'])) { $a[$prefix . 'perms'] = new ConstStub(\sprintf('0%o', $a[$prefix . 'perms']), $a[$prefix . 'perms']); } static $mapDate = ['aTime', 'mTime', 'cTime']; foreach ($mapDate as $key) { if (isset($a[$prefix . $key])) { $a[$prefix . $key] = new ConstStub(\date('Y-m-d H:i:s', $a[$prefix . $key]), $a[$prefix . $key]); } } return $a; } public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, bool $isNested) { static $map = ['csvControl' => 'getCsvControl', 'flags' => 'getFlags', 'maxLineLen' => 'getMaxLineLen', 'fstat' => 'fstat', 'eof' => 'eof', 'key' => 'key']; $prefix = Caster::PREFIX_VIRTUAL; foreach ($map as $key => $accessor) { try { $a[$prefix . $key] = $c->{$accessor}(); } catch (\Exception $e) { } } if (isset($a[$prefix . 'flags'])) { $flagsArray = []; foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { if ($a[$prefix . 'flags'] & $value) { $flagsArray[] = $name; } } $a[$prefix . 'flags'] = new ConstStub(\implode('|', $flagsArray), $a[$prefix . 'flags']); } if (isset($a[$prefix . 'fstat'])) { $a[$prefix . 'fstat'] = new CutArrayStub($a[$prefix . 'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); } return $a; } public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, bool $isNested) { $storage = []; unset($a[Caster::PREFIX_DYNAMIC . "\x00gcdata"]); // Don't hit https://bugs.php.net/65967 unset($a["\x00SplObjectStorage\x00storage"]); $clone = clone $c; foreach ($clone as $obj) { $storage[] = ['object' => $obj, 'info' => $clone->getInfo()]; } $a += [Caster::PREFIX_VIRTUAL . 'storage' => $storage]; return $a; } public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'innerIterator'] = $c->getInnerIterator(); return $a; } public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL . 'object'] = $c->get(); return $a; } private static function castSplArray($c, array $a, Stub $stub, bool $isNested) : array { $prefix = Caster::PREFIX_VIRTUAL; $flags = $c->getFlags(); if (!($flags & \ArrayObject::STD_PROP_LIST)) { $c->setFlags(\ArrayObject::STD_PROP_LIST); $a = Caster::castObject($c, \get_class($c), \method_exists($c, '__debugInfo'), $stub->class); $c->setFlags($flags); } unset($a["\x00ArrayObject\x00storage"], $a["\x00ArrayIterator\x00storage"]); $a += [$prefix . 'storage' => $c->getArrayCopy(), $prefix . 'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), $prefix . 'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS)]; if ($c instanceof \ArrayObject) { $a[$prefix . 'iteratorClass'] = new ClassStub($c->getIteratorClass()); } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Represents a PHP constant and its value. * * @author Nicolas Grekas */ class ConstStub extends Stub { public function __construct(string $name, $value = null) { $this->class = $name; $this->value = 1 < \func_num_args() ? $value : $name; } /** * @return string */ public function __toString() { return (string) $this->value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Nicolas Grekas */ class DsPairStub extends Stub { public function __construct($key, $value) { $this->value = [Caster::PREFIX_VIRTUAL . 'key' => $key, Caster::PREFIX_VIRTUAL . 'value' => $value]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Nicolas Grekas * @author Jan Schädlich * * @final */ class IntlCaster { public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, bool $isNested) { $a += [Caster::PREFIX_VIRTUAL . 'locale' => $c->getLocale(), Caster::PREFIX_VIRTUAL . 'pattern' => $c->getPattern()]; return self::castError($c, $a); } public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $a += [Caster::PREFIX_VIRTUAL . 'locale' => $c->getLocale(), Caster::PREFIX_VIRTUAL . 'pattern' => $c->getPattern()]; if ($filter & Caster::EXCLUDE_VERBOSE) { $stub->cut += 3; return self::castError($c, $a); } $a += [Caster::PREFIX_VIRTUAL . 'attributes' => new EnumStub(['PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE)]), Caster::PREFIX_VIRTUAL . 'text_attributes' => new EnumStub(['POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS)]), Caster::PREFIX_VIRTUAL . 'symbols' => new EnumStub(['DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL)])]; return self::castError($c, $a); } public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, bool $isNested) { $a += [Caster::PREFIX_VIRTUAL . 'display_name' => $c->getDisplayName(), Caster::PREFIX_VIRTUAL . 'id' => $c->getID(), Caster::PREFIX_VIRTUAL . 'raw_offset' => $c->getRawOffset()]; if ($c->useDaylightTime()) { $a += [Caster::PREFIX_VIRTUAL . 'dst_savings' => $c->getDSTSavings()]; } return self::castError($c, $a); } public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $a += [Caster::PREFIX_VIRTUAL . 'type' => $c->getType(), Caster::PREFIX_VIRTUAL . 'first_day_of_week' => $c->getFirstDayOfWeek(), Caster::PREFIX_VIRTUAL . 'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), Caster::PREFIX_VIRTUAL . 'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), Caster::PREFIX_VIRTUAL . 'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), Caster::PREFIX_VIRTUAL . 'time' => $c->getTime(), Caster::PREFIX_VIRTUAL . 'in_daylight_time' => $c->inDaylightTime(), Caster::PREFIX_VIRTUAL . 'is_lenient' => $c->isLenient(), Caster::PREFIX_VIRTUAL . 'time_zone' => $filter & Caster::EXCLUDE_VERBOSE ? new CutStub($c->getTimeZone()) : $c->getTimeZone()]; return self::castError($c, $a); } public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $a += [Caster::PREFIX_VIRTUAL . 'locale' => $c->getLocale(), Caster::PREFIX_VIRTUAL . 'pattern' => $c->getPattern(), Caster::PREFIX_VIRTUAL . 'calendar' => $c->getCalendar(), Caster::PREFIX_VIRTUAL . 'time_zone_id' => $c->getTimeZoneId(), Caster::PREFIX_VIRTUAL . 'time_type' => $c->getTimeType(), Caster::PREFIX_VIRTUAL . 'date_type' => $c->getDateType(), Caster::PREFIX_VIRTUAL . 'calendar_object' => $filter & Caster::EXCLUDE_VERBOSE ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), Caster::PREFIX_VIRTUAL . 'time_zone' => $filter & Caster::EXCLUDE_VERBOSE ? new CutStub($c->getTimeZone()) : $c->getTimeZone()]; return self::castError($c, $a); } private static function castError(object $c, array $a) : array { if ($errorCode = $c->getErrorCode()) { $a += [Caster::PREFIX_VIRTUAL . 'error_code' => $errorCode, Caster::PREFIX_VIRTUAL . 'error_message' => $c->getErrorMessage()]; } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Redis class from ext-redis to array representation. * * @author Nicolas Grekas * * @final */ class RedisCaster { private const SERIALIZERS = [\Redis::SERIALIZER_NONE => 'NONE', \Redis::SERIALIZER_PHP => 'PHP', 2 => 'IGBINARY']; private const MODES = [\Redis::ATOMIC => 'ATOMIC', \Redis::MULTI => 'MULTI', \Redis::PIPELINE => 'PIPELINE']; private const COMPRESSION_MODES = [ 0 => 'NONE', // Redis::COMPRESSION_NONE 1 => 'LZF', ]; private const FAILOVER_OPTIONS = [\RedisCluster::FAILOVER_NONE => 'NONE', \RedisCluster::FAILOVER_ERROR => 'ERROR', \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES']; public static function castRedis(\Redis $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; if (!($connected = $c->isConnected())) { return $a + [$prefix . 'isConnected' => $connected]; } $mode = $c->getMode(); return $a + [$prefix . 'isConnected' => $connected, $prefix . 'host' => $c->getHost(), $prefix . 'port' => $c->getPort(), $prefix . 'auth' => $c->getAuth(), $prefix . 'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, $prefix . 'dbNum' => $c->getDbNum(), $prefix . 'timeout' => $c->getTimeout(), $prefix . 'lastError' => $c->getLastError(), $prefix . 'persistentId' => $c->getPersistentID(), $prefix . 'options' => self::getRedisOptions($c)]; } public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; return $a + [$prefix . 'hosts' => $c->_hosts(), $prefix . 'function' => ClassStub::wrapCallable($c->_function()), $prefix . 'lastError' => $c->getLastError(), $prefix . 'options' => self::getRedisOptions($c)]; } public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); $a += [$prefix . '_masters' => $c->_masters(), $prefix . '_redir' => $c->_redir(), $prefix . 'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), $prefix . 'lastError' => $c->getLastError(), $prefix . 'options' => self::getRedisOptions($c, ['SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover])]; return $a; } /** * @param \Redis|\RedisArray|\RedisCluster $redis */ private static function getRedisOptions($redis, array $options = []) : EnumStub { $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); if (\is_array($serializer)) { foreach ($serializer as &$v) { if (isset(self::SERIALIZERS[$v])) { $v = new ConstStub(self::SERIALIZERS[$v], $v); } } } elseif (isset(self::SERIALIZERS[$serializer])) { $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); } $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; if (\is_array($compression)) { foreach ($compression as &$v) { if (isset(self::COMPRESSION_MODES[$v])) { $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); } } } elseif (isset(self::COMPRESSION_MODES[$compression])) { $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); } $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; if (\is_array($retry)) { foreach ($retry as &$v) { $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); } } else { $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); } $options += ['TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), 'COMPRESSION' => $compression, 'SERIALIZER' => $serializer, 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), 'SCAN' => $retry]; return new EnumStub($options); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Fiber related classes to array representation. * * @author Grégoire Pineau */ final class FiberCaster { public static function castFiber(\Fiber $fiber, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; if ($fiber->isTerminated()) { $status = 'terminated'; } elseif ($fiber->isRunning()) { $status = 'running'; } elseif ($fiber->isSuspended()) { $status = 'suspended'; } elseif ($fiber->isStarted()) { $status = 'started'; } else { $status = 'not started'; } $a[$prefix . 'status'] = $status; return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Imagine\Image\ImageInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Grégoire Pineau */ final class ImagineCaster { public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested) : array { $imgData = $c->get('png'); if (\strlen($imgData) > 1 * 1000 * 1000) { $a += [Caster::PREFIX_VIRTUAL . 'image' => new ConstStub($c->getSize())]; } else { $a += [Caster::PREFIX_VIRTUAL . 'image' => new ImgStub($imgData, 'image/png', $c->getSize())]; } return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Amqp related classes to array representation. * * @author Grégoire Pineau * * @final */ class AmqpCaster { private const FLAGS = [\AMQP_DURABLE => 'AMQP_DURABLE', \AMQP_PASSIVE => 'AMQP_PASSIVE', \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', \AMQP_AUTODELETE => 'AMQP_AUTODELETE', \AMQP_INTERNAL => 'AMQP_INTERNAL', \AMQP_NOLOCAL => 'AMQP_NOLOCAL', \AMQP_AUTOACK => 'AMQP_AUTOACK', \AMQP_IFEMPTY => 'AMQP_IFEMPTY', \AMQP_IFUNUSED => 'AMQP_IFUNUSED', \AMQP_MANDATORY => 'AMQP_MANDATORY', \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', \AMQP_MULTIPLE => 'AMQP_MULTIPLE', \AMQP_NOWAIT => 'AMQP_NOWAIT', \AMQP_REQUEUE => 'AMQP_REQUEUE']; private const EXCHANGE_TYPES = [\AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS']; public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'is_connected' => $c->isConnected()]; // Recent version of the extension already expose private properties if (isset($a["\x00AMQPConnection\x00login"])) { return $a; } // BC layer in the amqp lib if (\method_exists($c, 'getReadTimeout')) { $timeout = $c->getReadTimeout(); } else { $timeout = $c->getTimeout(); } $a += [$prefix . 'is_connected' => $c->isConnected(), $prefix . 'login' => $c->getLogin(), $prefix . 'password' => $c->getPassword(), $prefix . 'host' => $c->getHost(), $prefix . 'vhost' => $c->getVhost(), $prefix . 'port' => $c->getPort(), $prefix . 'read_timeout' => $timeout]; return $a; } public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'is_connected' => $c->isConnected(), $prefix . 'channel_id' => $c->getChannelId()]; // Recent version of the extension already expose private properties if (isset($a["\x00AMQPChannel\x00connection"])) { return $a; } $a += [$prefix . 'connection' => $c->getConnection(), $prefix . 'prefetch_size' => $c->getPrefetchSize(), $prefix . 'prefetch_count' => $c->getPrefetchCount()]; return $a; } public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'flags' => self::extractFlags($c->getFlags())]; // Recent version of the extension already expose private properties if (isset($a["\x00AMQPQueue\x00name"])) { return $a; } $a += [$prefix . 'connection' => $c->getConnection(), $prefix . 'channel' => $c->getChannel(), $prefix . 'name' => $c->getName(), $prefix . 'arguments' => $c->getArguments()]; return $a; } public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a += [$prefix . 'flags' => self::extractFlags($c->getFlags())]; $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); // Recent version of the extension already expose private properties if (isset($a["\x00AMQPExchange\x00name"])) { $a["\x00AMQPExchange\x00type"] = $type; return $a; } $a += [$prefix . 'connection' => $c->getConnection(), $prefix . 'channel' => $c->getChannel(), $prefix . 'name' => $c->getName(), $prefix . 'type' => $type, $prefix . 'arguments' => $c->getArguments()]; return $a; } public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; $deliveryMode = new ConstStub($c->getDeliveryMode() . (2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); // Recent version of the extension already expose private properties if (isset($a["\x00AMQPEnvelope\x00body"])) { $a["\x00AMQPEnvelope\x00delivery_mode"] = $deliveryMode; return $a; } if (!($filter & Caster::EXCLUDE_VERBOSE)) { $a += [$prefix . 'body' => $c->getBody()]; } $a += [$prefix . 'delivery_tag' => $c->getDeliveryTag(), $prefix . 'is_redelivery' => $c->isRedelivery(), $prefix . 'exchange_name' => $c->getExchangeName(), $prefix . 'routing_key' => $c->getRoutingKey(), $prefix . 'content_type' => $c->getContentType(), $prefix . 'content_encoding' => $c->getContentEncoding(), $prefix . 'headers' => $c->getHeaders(), $prefix . 'delivery_mode' => $deliveryMode, $prefix . 'priority' => $c->getPriority(), $prefix . 'correlation_id' => $c->getCorrelationId(), $prefix . 'reply_to' => $c->getReplyTo(), $prefix . 'expiration' => $c->getExpiration(), $prefix . 'message_id' => $c->getMessageId(), $prefix . 'timestamp' => $c->getTimeStamp(), $prefix . 'type' => $c->getType(), $prefix . 'user_id' => $c->getUserId(), $prefix . 'app_id' => $c->getAppId()]; return $a; } private static function extractFlags(int $flags) : ConstStub { $flagsArray = []; foreach (self::FLAGS as $value => $name) { if ($flags & $value) { $flagsArray[] = $name; } } if (!$flagsArray) { $flagsArray = ['AMQP_NOPARAM']; } return new ConstStub(\implode('|', $flagsArray), $flags); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts DOM related classes to array representation. * * @author Nicolas Grekas * * @final */ class DOMCaster { private const ERROR_CODES = [\DOM_PHP_ERR => 'DOM_PHP_ERR', \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR']; private const NODE_TYPES = [\XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', \XML_TEXT_NODE => 'XML_TEXT_NODE', \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', \XML_ENTITY_NODE => 'XML_ENTITY_NODE', \XML_PI_NODE => 'XML_PI_NODE', \XML_COMMENT_NODE => 'XML_COMMENT_NODE', \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', \XML_NOTATION_NODE => 'XML_NOTATION_NODE', \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', \XML_DTD_NODE => 'XML_DTD_NODE', \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE']; public static function castException(\DOMException $e, array $a, Stub $stub, bool $isNested) { $k = Caster::PREFIX_PROTECTED . 'code'; if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); } return $a; } public static function castLength($dom, array $a, Stub $stub, bool $isNested) { $a += ['length' => $dom->length]; return $a; } public static function castImplementation(\DOMImplementation $dom, array $a, Stub $stub, bool $isNested) { $a += [Caster::PREFIX_VIRTUAL . 'Core' => '1.0', Caster::PREFIX_VIRTUAL . 'XML' => '2.0']; return $a; } public static function castNode(\DOMNode $dom, array $a, Stub $stub, bool $isNested) { $a += ['nodeName' => $dom->nodeName, 'nodeValue' => new CutStub($dom->nodeValue), 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), 'parentNode' => new CutStub($dom->parentNode), 'childNodes' => $dom->childNodes, 'firstChild' => new CutStub($dom->firstChild), 'lastChild' => new CutStub($dom->lastChild), 'previousSibling' => new CutStub($dom->previousSibling), 'nextSibling' => new CutStub($dom->nextSibling), 'attributes' => $dom->attributes, 'ownerDocument' => new CutStub($dom->ownerDocument), 'namespaceURI' => $dom->namespaceURI, 'prefix' => $dom->prefix, 'localName' => $dom->localName, 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, 'textContent' => new CutStub($dom->textContent)]; return $a; } public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested) { $a += ['nodeName' => $dom->nodeName, 'nodeValue' => new CutStub($dom->nodeValue), 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), 'prefix' => $dom->prefix, 'localName' => $dom->localName, 'namespaceURI' => $dom->namespaceURI, 'ownerDocument' => new CutStub($dom->ownerDocument), 'parentNode' => new CutStub($dom->parentNode)]; return $a; } public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0) { $a += ['doctype' => $dom->doctype, 'implementation' => $dom->implementation, 'documentElement' => new CutStub($dom->documentElement), 'actualEncoding' => $dom->actualEncoding, 'encoding' => $dom->encoding, 'xmlEncoding' => $dom->xmlEncoding, 'standalone' => $dom->standalone, 'xmlStandalone' => $dom->xmlStandalone, 'version' => $dom->version, 'xmlVersion' => $dom->xmlVersion, 'strictErrorChecking' => $dom->strictErrorChecking, 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, 'config' => $dom->config, 'formatOutput' => $dom->formatOutput, 'validateOnParse' => $dom->validateOnParse, 'resolveExternals' => $dom->resolveExternals, 'preserveWhiteSpace' => $dom->preserveWhiteSpace, 'recover' => $dom->recover, 'substituteEntities' => $dom->substituteEntities]; if (!($filter & Caster::EXCLUDE_VERBOSE)) { $formatOutput = $dom->formatOutput; $dom->formatOutput = \true; $a += [Caster::PREFIX_VIRTUAL . 'xml' => $dom->saveXML()]; $dom->formatOutput = $formatOutput; } return $a; } public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, bool $isNested) { $a += ['data' => $dom->data, 'length' => $dom->length]; return $a; } public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, bool $isNested) { $a += ['name' => $dom->name, 'specified' => $dom->specified, 'value' => $dom->value, 'ownerElement' => $dom->ownerElement, 'schemaTypeInfo' => $dom->schemaTypeInfo]; return $a; } public static function castElement(\DOMElement $dom, array $a, Stub $stub, bool $isNested) { $a += ['tagName' => $dom->tagName, 'schemaTypeInfo' => $dom->schemaTypeInfo]; return $a; } public static function castText(\DOMText $dom, array $a, Stub $stub, bool $isNested) { $a += ['wholeText' => $dom->wholeText]; return $a; } public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, bool $isNested) { $a += ['typeName' => $dom->typeName, 'typeNamespace' => $dom->typeNamespace]; return $a; } public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, bool $isNested) { $a += ['severity' => $dom->severity, 'message' => $dom->message, 'type' => $dom->type, 'relatedException' => $dom->relatedException, 'related_data' => $dom->related_data, 'location' => $dom->location]; return $a; } public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, bool $isNested) { $a += ['lineNumber' => $dom->lineNumber, 'columnNumber' => $dom->columnNumber, 'offset' => $dom->offset, 'relatedNode' => $dom->relatedNode, 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri]; return $a; } public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, bool $isNested) { $a += ['name' => $dom->name, 'entities' => $dom->entities, 'notations' => $dom->notations, 'publicId' => $dom->publicId, 'systemId' => $dom->systemId, 'internalSubset' => $dom->internalSubset]; return $a; } public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, bool $isNested) { $a += ['publicId' => $dom->publicId, 'systemId' => $dom->systemId]; return $a; } public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, bool $isNested) { $a += ['publicId' => $dom->publicId, 'systemId' => $dom->systemId, 'notationName' => $dom->notationName, 'actualEncoding' => $dom->actualEncoding, 'encoding' => $dom->encoding, 'version' => $dom->version]; return $a; } public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, bool $isNested) { $a += ['target' => $dom->target, 'data' => $dom->data]; return $a; } public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, bool $isNested) { $a += ['document' => $dom->document]; return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Ramsey\Uuid\UuidInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * @author Grégoire Pineau */ final class UuidCaster { public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested) : array { $a += [Caster::PREFIX_VIRTUAL . 'uuid' => (string) $c]; return $a; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Casts XmlReader class to array representation. * * @author Baptiste Clavié * * @final */ class XmlReaderCaster { private const NODE_TYPES = [\XMLReader::NONE => 'NONE', \XMLReader::ELEMENT => 'ELEMENT', \XMLReader::ATTRIBUTE => 'ATTRIBUTE', \XMLReader::TEXT => 'TEXT', \XMLReader::CDATA => 'CDATA', \XMLReader::ENTITY_REF => 'ENTITY_REF', \XMLReader::ENTITY => 'ENTITY', \XMLReader::PI => 'PI (Processing Instruction)', \XMLReader::COMMENT => 'COMMENT', \XMLReader::DOC => 'DOC', \XMLReader::DOC_TYPE => 'DOC_TYPE', \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', \XMLReader::NOTATION => 'NOTATION', \XMLReader::WHITESPACE => 'WHITESPACE', \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', \XMLReader::END_ELEMENT => 'END_ELEMENT', \XMLReader::END_ENTITY => 'END_ENTITY', \XMLReader::XML_DECLARATION => 'XML_DECLARATION']; public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, bool $isNested) { try { $properties = ['LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES)]; } catch (\Error $e) { $properties = ['LOADDTD' => \false, 'DEFAULTATTRS' => \false, 'VALIDATE' => \false, 'SUBST_ENTITIES' => \false]; } $props = Caster::PREFIX_VIRTUAL . 'parserProperties'; $info = ['localName' => $reader->localName, 'prefix' => $reader->prefix, 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), 'depth' => $reader->depth, 'isDefault' => $reader->isDefault, 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, 'xmlLang' => $reader->xmlLang, 'attributeCount' => $reader->attributeCount, 'value' => $reader->value, 'namespaceURI' => $reader->namespaceURI, 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, $props => $properties]; if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { $info[$props] = new EnumStub($info[$props]); $info[$props]->cut = $count; } $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); // +2 because hasValue and hasAttributes are always filtered $stub->cut += $count + 2; return $a + $info; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Represents a list of function arguments. * * @author Nicolas Grekas */ class ArgsStub extends EnumStub { private static $parameters = []; public function __construct(array $args, string $function, ?string $class) { [$variadic, $params] = self::getParameters($function, $class); $values = []; foreach ($args as $k => $v) { $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; } if (null === $params) { parent::__construct($values, \false); return; } if (\count($values) < \count($params)) { $params = \array_slice($params, 0, \count($values)); } elseif (\count($values) > \count($params)) { $values[] = new EnumStub(\array_splice($values, \count($params)), \false); $params[] = $variadic; } if (['...'] === $params) { $this->dumpKeys = \false; $this->value = $values[0]->value; } else { $this->value = \array_combine($params, $values); } } private static function getParameters(string $function, ?string $class) : array { if (isset(self::$parameters[$k = $class . '::' . $function])) { return self::$parameters[$k]; } try { $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); } catch (\ReflectionException $e) { return [null, null]; } $variadic = '...'; $params = []; foreach ($r->getParameters() as $v) { $k = '$' . $v->name; if ($v->isPassedByReference()) { $k = '&' . $k; } if ($v->isVariadic()) { $variadic .= $k; } else { $params[] = $k; } } return self::$parameters[$k] = [$variadic, $params]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Represents an enumeration of values. * * @author Nicolas Grekas */ class EnumStub extends Stub { public $dumpKeys = \true; public function __construct(array $values, bool $dumpKeys = \true) { $this->value = $values; $this->dumpKeys = $dumpKeys; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Caster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * Represents the main properties of a PHP variable, pre-casted by a caster. * * @author Nicolas Grekas */ class CutStub extends Stub { public function __construct($value) { $this->value = $value; switch (\gettype($value)) { case 'object': $this->type = self::TYPE_OBJECT; $this->class = \get_class($value); if ($value instanceof \Closure) { ReflectionCaster::castClosure($value, [], $this, \true, Caster::EXCLUDE_VERBOSE); } $this->cut = -1; break; case 'array': $this->type = self::TYPE_ARRAY; $this->class = self::ARRAY_ASSOC; $this->cut = $this->value = \count($value); break; case 'resource': case 'unknown type': case 'resource (closed)': $this->type = self::TYPE_RESOURCE; $this->handle = (int) $value; if ('Unknown' === ($this->class = @\get_resource_type($value))) { $this->class = 'Closed'; } $this->cut = -1; break; case 'string': $this->type = self::TYPE_STRING; $this->class = \preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : \mb_strlen($value, 'UTF-8'); $this->value = ''; break; } } } #!/usr/bin/env php * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ('cli' !== \PHP_SAPI) { throw new \Exception('This script must be run from the command line.'); } /** * Starts a dump server to collect and output dumps on a single place with multiple formats support. * * @author Maxime Steinhausser */ use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Input\ArgvInput; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Logger\ConsoleLogger; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutput; use _ContaoManager\Symfony\Component\VarDumper\Command\ServerDumpCommand; use _ContaoManager\Symfony\Component\VarDumper\Server\DumpServer; function includeIfExists(string $file) : bool { return \file_exists($file) && (include $file); } if (!includeIfExists(__DIR__ . '/../../../../autoload.php') && !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')) { \fwrite(\STDERR, 'Install dependencies using Composer.' . \PHP_EOL); exit(1); } if (!\class_exists(Application::class)) { \fwrite(\STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.' . \PHP_EOL); exit(1); } $input = new ArgvInput(); $output = new ConsoleOutput(); $defaultHost = '127.0.0.1:9912'; $host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, \true); $logger = \interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; $app = new Application(); $app->getDefinition()->addOption(new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost)); $app->add($command = new ServerDumpCommand(new DumpServer($host, $logger)))->getApplication()->setDefaultCommand($command->getName(), \true)->run($input, $output); body { display: flex; flex-direction: column-reverse; justify-content: flex-end; max-width: 1140px; margin: auto; padding: 15px; word-wrap: break-word; background-color: #F9F9F9; color: #222; font-family: Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.4; } p { margin: 0; } a { color: #218BC3; text-decoration: none; } a:hover { text-decoration: underline; } .text-small { font-size: 12px !important; } article { margin: 5px; margin-bottom: 10px; } article > header > .row { display: flex; flex-direction: row; align-items: baseline; margin-bottom: 10px; } article > header > .row > .col { flex: 1; display: flex; align-items: baseline; } article > header > .row > h2 { font-size: 14px; color: #222; font-weight: normal; font-family: "Lucida Console", monospace, sans-serif; word-break: break-all; margin: 20px 5px 0 0; user-select: all; } article > header > .row > h2 > code { white-space: nowrap; user-select: none; color: #cc2255; background-color: #f7f7f9; border: 1px solid #e1e1e8; border-radius: 3px; margin-right: 5px; padding: 0 3px; } article > header > .row > time.col { flex: 0; text-align: right; white-space: nowrap; color: #999; font-style: italic; } article > header ul.tags { list-style: none; padding: 0; margin: 0; font-size: 12px; } article > header ul.tags > li { user-select: all; margin-bottom: 2px; } article > header ul.tags > li > span.badge { display: inline-block; padding: .25em .4em; margin-right: 5px; border-radius: 4px; background-color: #6c757d3b; color: #524d4d; font-size: 12px; text-align: center; font-weight: 700; line-height: 1; white-space: nowrap; vertical-align: baseline; user-select: none; } article > section.body { border: 1px solid #d8d8d8; background: #FFF; padding: 10px; border-radius: 3px; } pre.sf-dump { border-radius: 3px; margin-bottom: 0; } .hidden { display: none !important; } .dumped-tag > .sf-dump { display: inline-block; margin: 0; padding: 1px 5px; line-height: 1.4; vertical-align: top; background-color: transparent; user-select: auto; } .dumped-tag > pre.sf-dump, .dumped-tag > .sf-dump-default { color: #CC7832; background: none; } .dumped-tag > .sf-dump .sf-dump-str { color: #629755; } .dumped-tag > .sf-dump .sf-dump-private, .dumped-tag > .sf-dump .sf-dump-protected, .dumped-tag > .sf-dump .sf-dump-public { color: #262626; } .dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } .dumped-tag > .sf-dump .sf-dump-key { color: #789339; } .dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } .dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } .dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } .dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } document.addEventListener('DOMContentLoaded', function() { let prev = null; Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { const dedupId = article.dataset.dedupId; if (dedupId === prev) { article.getElementsByTagName('header')[0].classList.add('hidden'); } prev = dedupId; }); }); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use _ContaoManager\Symfony\Component\VarDumper\VarDumper; if (!\function_exists('_ContaoManager\\dump')) { /** * @author Nicolas Grekas */ function dump($var, ...$moreVars) { VarDumper::dump($var); foreach ($moreVars as $v) { VarDumper::dump($v); } if (1 < \func_num_args()) { return \func_get_args(); } return $var; } } if (!\function_exists('_ContaoManager\\dd')) { /** * @return never */ function dd(...$vars) { if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) && !\headers_sent()) { \header('HTTP/1.1 500 Internal Server Error'); } foreach ($vars as $v) { VarDumper::dump($v); } exit(1); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Server; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; /** * Forwards serialized Data clones to a server. * * @author Maxime Steinhausser */ class Connection { private $host; private $contextProviders; /** * @var resource|null */ private $socket; /** * @param string $host The server host * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name */ public function __construct(string $host, array $contextProviders = []) { if (!\str_contains($host, '://')) { $host = 'tcp://' . $host; } $this->host = $host; $this->contextProviders = $contextProviders; } public function getContextProviders() : array { return $this->contextProviders; } public function write(Data $data) : bool { $socketIsFresh = !$this->socket; if (!($this->socket = $this->socket ?: $this->createSocket())) { return \false; } $context = ['timestamp' => \microtime(\true)]; foreach ($this->contextProviders as $name => $provider) { $context[$name] = $provider->getContext(); } $context = \array_filter($context); $encodedPayload = \base64_encode(\serialize([$data, $context])) . "\n"; \set_error_handler([self::class, 'nullErrorHandler']); try { if (-1 !== \stream_socket_sendto($this->socket, $encodedPayload)) { return \true; } if (!$socketIsFresh) { \stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); \fclose($this->socket); $this->socket = $this->createSocket(); } if (-1 !== \stream_socket_sendto($this->socket, $encodedPayload)) { return \true; } } finally { \restore_error_handler(); } return \false; } private static function nullErrorHandler(int $t, string $m) { // no-op } private function createSocket() { \set_error_handler([self::class, 'nullErrorHandler']); try { return \stream_socket_client($this->host, $errno, $errstr, 3); } finally { \restore_error_handler(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Server; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * A server collecting Data clones sent by a ServerDumper. * * @author Maxime Steinhausser * * @final */ class DumpServer { private $host; private $logger; /** * @var resource|null */ private $socket; public function __construct(string $host, ?LoggerInterface $logger = null) { if (!\str_contains($host, '://')) { $host = 'tcp://' . $host; } $this->host = $host; $this->logger = $logger; } public function start() : void { if (!($this->socket = \stream_socket_server($this->host, $errno, $errstr))) { throw new \RuntimeException(\sprintf('Server start failed on "%s": ', $this->host) . $errstr . ' ' . $errno); } } public function listen(callable $callback) : void { if (null === $this->socket) { $this->start(); } foreach ($this->getMessages() as $clientId => $message) { if ($this->logger) { $this->logger->info('Received a payload from client {clientId}', ['clientId' => $clientId]); } $payload = @\unserialize(\base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); // Impossible to decode the message, give up. if (\false === $payload) { if ($this->logger) { $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); } continue; } if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { if ($this->logger) { $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); } continue; } [$data, $context] = $payload; $callback($data, $context, $clientId); } } public function getHost() : string { return $this->host; } private function getMessages() : iterable { $sockets = [(int) $this->socket => $this->socket]; $write = []; while (\true) { $read = $sockets; \stream_select($read, $write, $write, null); foreach ($read as $stream) { if ($this->socket === $stream) { $stream = \stream_socket_accept($this->socket); $sockets[(int) $stream] = $stream; } elseif (\feof($stream)) { unset($sockets[(int) $stream]); \fclose($stream); } else { (yield (int) $stream => \fgets($stream)); } } } } } VarDumper Component =================== The VarDumper component provides mechanisms for walking through any arbitrary PHP variable. It provides a better `dump()` function that you can use instead of `var_dump()`. Resources --------- * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; use _ContaoManager\Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; use _ContaoManager\Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; use _ContaoManager\Symfony\Component\VarDumper\Dumper\HtmlDumper; use _ContaoManager\Symfony\Component\VarDumper\Server\DumpServer; /** * Starts a dump server to collect and output dumps on a single place with multiple formats support. * * @author Maxime Steinhausser * * @final */ class ServerDumpCommand extends Command { protected static $defaultName = 'server:dump'; protected static $defaultDescription = 'Start a dump server that collects and displays dumps in a single place'; private $server; /** @var DumpDescriptorInterface[] */ private $descriptors; public function __construct(DumpServer $server, array $descriptors = []) { $this->server = $server; $this->descriptors = $descriptors + ['cli' => new CliDescriptor(new CliDumper()), 'html' => new HtmlDescriptor(new HtmlDumper())]; parent::__construct(); } protected function configure() { $this->addOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format (%s)', \implode(', ', $this->getAvailableFormats())), 'cli')->setDescription(self::$defaultDescription)->setHelp(<<<'EOF' %command.name% starts a dump server that collects and displays dumps in a single place for debugging you application: php %command.full_name% You can consult dumped data in HTML format in your browser by providing the --format=html option and redirecting the output to a file: php %command.full_name% --format="html" > dump.html EOF ); } protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $format = $input->getOption('format'); if (!($descriptor = $this->descriptors[$format] ?? null)) { throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $format)); } $errorIo = $io->getErrorStyle(); $errorIo->title('Symfony Var Dumper Server'); $this->server->start(); $errorIo->success(\sprintf('Server listening on %s', $this->server->getHost())); $errorIo->comment('Quit the server with CONTROL-C.'); $this->server->listen(function (Data $data, array $context, int $clientId) use($descriptor, $io) { $descriptor->describe($io, $data, $context, $clientId); }); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestOptionValuesFor('format')) { $suggestions->suggestValues($this->getAvailableFormats()); } } private function getAvailableFormats() : array { return \array_keys($this->descriptors); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Command\Descriptor; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; /** * @author Maxime Steinhausser */ interface DumpDescriptorInterface { public function describe(OutputInterface $output, Data $data, array $context, int $clientId) : void; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Command\Descriptor; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; /** * Describe collected data clones for cli output. * * @author Maxime Steinhausser * * @final */ class CliDescriptor implements DumpDescriptorInterface { private $dumper; private $lastIdentifier; public function __construct(CliDumper $dumper) { $this->dumper = $dumper; } public function describe(OutputInterface $output, Data $data, array $context, int $clientId) : void { $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); $this->dumper->setColors($output->isDecorated()); $rows = [['date', \date('r', (int) $context['timestamp'])]]; $lastIdentifier = $this->lastIdentifier; $this->lastIdentifier = $clientId; $section = "Received from client #{$clientId}"; if (isset($context['request'])) { $request = $context['request']; $this->lastIdentifier = $request['identifier']; $section = \sprintf('%s %s', $request['method'], $request['uri']); if ($controller = $request['controller']) { $rows[] = ['controller', \rtrim($this->dumper->dump($controller, \true), "\n")]; } } elseif (isset($context['cli'])) { $this->lastIdentifier = $context['cli']['identifier']; $section = '$ ' . $context['cli']['command_line']; } if ($this->lastIdentifier !== $lastIdentifier) { $io->section($section); } if (isset($context['source'])) { $source = $context['source']; $sourceInfo = \sprintf('%s on line %d', $source['name'], $source['line']); if ($fileLink = $source['file_link'] ?? null) { $sourceInfo = \sprintf('%s', $fileLink, $sourceInfo); } $rows[] = ['source', $sourceInfo]; $file = $source['file_relative'] ?? $source['file']; $rows[] = ['file', $file]; } $io->table([], $rows); $this->dumper->dump($data); $io->newLine(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Command\Descriptor; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Dumper\HtmlDumper; /** * Describe collected data clones for html output. * * @author Maxime Steinhausser * * @final */ class HtmlDescriptor implements DumpDescriptorInterface { private $dumper; private $initialized = \false; public function __construct(HtmlDumper $dumper) { $this->dumper = $dumper; } public function describe(OutputInterface $output, Data $data, array $context, int $clientId) : void { if (!$this->initialized) { $styles = \file_get_contents(__DIR__ . '/../../Resources/css/htmlDescriptor.css'); $scripts = \file_get_contents(__DIR__ . '/../../Resources/js/htmlDescriptor.js'); $output->writeln(""); $this->initialized = \true; } $title = '-'; if (isset($context['request'])) { $request = $context['request']; $controller = "{$this->dumper->dump($request['controller'], \true, ['maxDepth' => 0])}"; $title = \sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); $dedupIdentifier = $request['identifier']; } elseif (isset($context['cli'])) { $title = '$ ' . $context['cli']['command_line']; $dedupIdentifier = $context['cli']['identifier']; } else { $dedupIdentifier = \uniqid('', \true); } $sourceDescription = ''; if (isset($context['source'])) { $source = $context['source']; $projectDir = $source['project_dir'] ?? null; $sourceDescription = \sprintf('%s on line %d', $source['name'], $source['line']); if (isset($source['file_link'])) { $sourceDescription = \sprintf('%s', $source['file_link'], $sourceDescription); } } $isoDate = $this->extractDate($context, 'c'); $tags = \array_filter(['controller' => $controller ?? null, 'project dir' => $projectDir ?? null]); $output->writeln(<<

{$title}

{$this->renderTags($tags)}

{$sourceDescription}

{$this->dumper->dump($data, \true)}
HTML ); } private function extractDate(array $context, string $format = 'r') : string { return \date($format, (int) $context['timestamp']); } private function renderTags(array $tags) : string { if (!$tags) { return ''; } $renderedTags = ''; foreach ($tags as $key => $value) { $renderedTags .= \sprintf('
  • %s%s
  • ', $key, $value); } return <<
      {$renderedTags}
    HTML; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Exception; /** * @author Nicolas Grekas */ class ThrowingCasterException extends \Exception { /** * @param \Throwable $prev The exception thrown from the caster */ public function __construct(\Throwable $prev) { parent::__construct('Unexpected ' . \get_class($prev) . ' thrown from a caster: ' . $prev->getMessage(), 0, $prev); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; use _ContaoManager\Symfony\Component\VarDumper\Server\Connection; /** * ServerDumper forwards serialized Data clones to a server. * * @author Maxime Steinhausser */ class ServerDumper implements DataDumperInterface { private $connection; private $wrappedDumper; /** * @param string $host The server host * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name */ public function __construct(string $host, ?DataDumperInterface $wrappedDumper = null, array $contextProviders = []) { $this->connection = new Connection($host, $contextProviders); $this->wrappedDumper = $wrappedDumper; } public function getContextProviders() : array { return $this->connection->getContextProviders(); } /** * {@inheritdoc} */ public function dump(Data $data) { if (!$this->connection->write($data) && $this->wrappedDumper) { $this->wrappedDumper->dump($data); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; /** * DataDumperInterface for dumping Data objects. * * @author Nicolas Grekas */ interface DataDumperInterface { public function dump(Data $data); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider; /** * Interface to provide contextual data about dump data clones sent to a server. * * @author Maxime Steinhausser */ interface ContextProviderInterface { public function getContext() : ?array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\VarDumper\Caster\ReflectionCaster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; /** * Tries to provide context from a request. * * @author Maxime Steinhausser */ final class RequestContextProvider implements ContextProviderInterface { private $requestStack; private $cloner; public function __construct(RequestStack $requestStack) { $this->requestStack = $requestStack; $this->cloner = new VarCloner(); $this->cloner->setMaxItems(0); $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); } public function getContext() : ?array { if (null === ($request = $this->requestStack->getCurrentRequest())) { return null; } $controller = $request->attributes->get('_controller'); return ['uri' => $request->getUri(), 'method' => $request->getMethod(), 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, 'identifier' => \spl_object_hash($request)]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider; /** * Tries to provide context on CLI. * * @author Maxime Steinhausser */ final class CliContextProvider implements ContextProviderInterface { public function getContext() : ?array { if ('cli' !== \PHP_SAPI) { return null; } return ['command_line' => $commandLine = \implode(' ', $_SERVER['argv'] ?? []), 'identifier' => \hash('crc32b', $commandLine . $_SERVER['REQUEST_TIME_FLOAT'])]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\HtmlDumper; use _ContaoManager\Symfony\Component\VarDumper\VarDumper; use _ContaoManager\Twig\Template; /** * Tries to provide context from sources (class name, file, line, code excerpt, ...). * * @author Nicolas Grekas * @author Maxime Steinhausser */ final class SourceContextProvider implements ContextProviderInterface { private $limit; private $charset; private $projectDir; private $fileLinkFormatter; public function __construct(?string $charset = null, ?string $projectDir = null, ?FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) { $this->charset = $charset; $this->projectDir = $projectDir; $this->fileLinkFormatter = $fileLinkFormatter; $this->limit = $limit; } public function getContext() : ?array { $trace = \debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); $file = $trace[1]['file']; $line = $trace[1]['line']; $name = '-' === $file || 'Standard input code' === $file ? 'Standard input code' : \false; $fileExcerpt = \false; for ($i = 2; $i < $this->limit; ++$i) { if (isset($trace[$i]['class'], $trace[$i]['function']) && 'dump' === $trace[$i]['function'] && VarDumper::class === $trace[$i]['class']) { $file = $trace[$i]['file'] ?? $file; $line = $trace[$i]['line'] ?? $line; while (++$i < $this->limit) { if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !\str_starts_with($trace[$i]['function'], 'call_user_func')) { $file = $trace[$i]['file']; $line = $trace[$i]['line']; break; } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { $template = $trace[$i]['object']; $name = $template->getTemplateName(); $src = \method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (\method_exists($template, 'getSource') ? $template->getSource() : \false); $info = $template->getDebugInfo(); if (isset($info[$trace[$i - 1]['line']])) { $line = $info[$trace[$i - 1]['line']]; $file = \method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; if ($src) { $src = \explode("\n", $src); $fileExcerpt = []; for ($i = \max($line - 3, 1), $max = \min($line + 3, \count($src)); $i <= $max; ++$i) { $fileExcerpt[] = '' . $this->htmlEncode($src[$i - 1]) . ''; } $fileExcerpt = '
      ' . \implode("\n", $fileExcerpt) . '
    '; } } break; } } break; } } if (\false === $name) { $name = \str_replace('\\', '/', $file); $name = \substr($name, \strrpos($name, '/') + 1); } $context = ['name' => $name, 'file' => $file, 'line' => $line]; $context['file_excerpt'] = $fileExcerpt; if (null !== $this->projectDir) { $context['project_dir'] = $this->projectDir; if (\str_starts_with($file, $this->projectDir)) { $context['file_relative'] = \ltrim(\substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); } } if ($this->fileLinkFormatter && ($fileLink = $this->fileLinkFormatter->format($context['file'], $context['line']))) { $context['file_link'] = $fileLink; } return $context; } private function htmlEncode(string $s) : string { $html = ''; $dumper = new HtmlDumper(function ($line) use(&$html) { $html .= $line; }, $this->charset); $dumper->setDumpHeader(''); $dumper->setDumpBoundaries('', ''); $cloner = new VarCloner(); $dumper->dump($cloner->cloneVar($s)); return \substr(\strip_tags($html), 1, -1); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Cloner\DumperInterface; /** * Abstract mechanism for dumping a Data object. * * @author Nicolas Grekas */ abstract class AbstractDumper implements DataDumperInterface, DumperInterface { public const DUMP_LIGHT_ARRAY = 1; public const DUMP_STRING_LENGTH = 2; public const DUMP_COMMA_SEPARATOR = 4; public const DUMP_TRAILING_COMMA = 8; public static $defaultOutput = 'php://output'; protected $line = ''; protected $lineDumper; protected $outputStream; protected $decimalPoint; // This is locale dependent protected $indentPad = ' '; protected $flags; private $charset = ''; /** * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput * @param string|null $charset The default character encoding to use for non-UTF8 strings * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation */ public function __construct($output = null, ?string $charset = null, int $flags = 0) { $this->flags = $flags; $this->setCharset((($charset ?: \ini_get('php.output_encoding')) ?: \ini_get('default_charset')) ?: 'UTF-8'); $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : \localeconv()['decimal_point']; $this->setOutput($output ?: static::$defaultOutput); if (!$output && \is_string(static::$defaultOutput)) { static::$defaultOutput = $this->outputStream; } } /** * Sets the output destination of the dumps. * * @param callable|resource|string $output A line dumper callable, an opened stream or an output path * * @return callable|resource|string The previous output destination */ public function setOutput($output) { $prev = $this->outputStream ?? $this->lineDumper; if (\is_callable($output)) { $this->outputStream = null; $this->lineDumper = $output; } else { if (\is_string($output)) { $output = \fopen($output, 'w'); } $this->outputStream = $output; $this->lineDumper = [$this, 'echoLine']; } return $prev; } /** * Sets the default character encoding to use for non-UTF8 strings. * * @return string The previous charset */ public function setCharset(string $charset) { $prev = $this->charset; $charset = \strtoupper($charset); $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; $this->charset = $charset; return $prev; } /** * Sets the indentation pad string. * * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level * * @return string The previous indent pad */ public function setIndentPad(string $pad) { $prev = $this->indentPad; $this->indentPad = $pad; return $prev; } /** * Dumps a Data object. * * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump * * @return string|null The dump as string when $output is true */ public function dump(Data $data, $output = null) { $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : \localeconv()['decimal_point']; if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? \setlocale(\LC_NUMERIC, 0) : null) { \setlocale(\LC_NUMERIC, 'C'); } if ($returnDump = \true === $output) { $output = \fopen('php://memory', 'r+'); } if ($output) { $prevOutput = $this->setOutput($output); } try { $data->dump($this); $this->dumpLine(-1); if ($returnDump) { $result = \stream_get_contents($output, -1, 0); \fclose($output); return $result; } } finally { if ($output) { $this->setOutput($prevOutput); } if ($locale) { \setlocale(\LC_NUMERIC, $locale); } } return null; } /** * Dumps the current line. * * @param int $depth The recursive depth in the dumped structure for the line being dumped, * or -1 to signal the end-of-dump to the line dumper callable */ protected function dumpLine(int $depth) { ($this->lineDumper)($this->line, $depth, $this->indentPad); $this->line = ''; } /** * Generic line dumper callback. */ protected function echoLine(string $line, int $depth, string $indentPad) { if (-1 !== $depth) { \fwrite($this->outputStream, \str_repeat($indentPad, $depth) . $line . "\n"); } } /** * Converts a non-UTF-8 string to UTF-8. * * @return string|null */ protected function utf8Encode(?string $s) { if (null === $s || \preg_match('//u', $s)) { return $s; } if (!\function_exists('iconv')) { throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); } if (\false !== ($c = @\iconv($this->charset, 'UTF-8', $s))) { return $c; } if ('CP1252' !== $this->charset && \false !== ($c = @\iconv('CP1252', 'UTF-8', $s))) { return $c; } return \iconv('CP850', 'UTF-8', $s); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Cursor; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; /** * CliDumper dumps variables for command line output. * * @author Nicolas Grekas */ class CliDumper extends AbstractDumper { public static $defaultColors; public static $defaultOutput = 'php://stdout'; protected $colors; protected $maxStringWidth = 0; protected $styles = [ // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics 'default' => '0;38;5;208', 'num' => '1;38;5;38', 'const' => '1;38;5;208', 'str' => '1;38;5;113', 'note' => '38;5;38', 'ref' => '38;5;247', 'public' => '', 'protected' => '', 'private' => '', 'meta' => '38;5;170', 'key' => '38;5;113', 'index' => '38;5;38', ]; protected static $controlCharsRx = '/[\\x00-\\x1F\\x7F]+/'; protected static $controlCharsMap = ["\t" => '\\t', "\n" => '\\n', "\v" => '\\v', "\f" => '\\f', "\r" => '\\r', "\x1b" => '\\e']; protected $collapseNextHash = \false; protected $expandNextHash = \false; private $displayOptions = ['fileLinkFormat' => null]; private $handlesHrefGracefully; /** * {@inheritdoc} */ public function __construct($output = null, ?string $charset = null, int $flags = 0) { parent::__construct($output, $charset, $flags); if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI $this->setStyles(['default' => '31', 'num' => '1;34', 'const' => '1;31', 'str' => '1;32', 'note' => '34', 'ref' => '1;30', 'meta' => '35', 'key' => '32', 'index' => '34']); } $this->displayOptions['fileLinkFormat'] = (\ini_get('xdebug.file_link_format') ?: \get_cfg_var('xdebug.file_link_format')) ?: 'file://%f#L%l'; } /** * Enables/disables colored output. */ public function setColors(bool $colors) { $this->colors = $colors; } /** * Sets the maximum number of characters per line for dumped strings. */ public function setMaxStringWidth(int $maxStringWidth) { $this->maxStringWidth = $maxStringWidth; } /** * Configures styles. * * @param array $styles A map of style names to style definitions */ public function setStyles(array $styles) { $this->styles = $styles + $this->styles; } /** * Configures display options. * * @param array $displayOptions A map of display options to customize the behavior */ public function setDisplayOptions(array $displayOptions) { $this->displayOptions = $displayOptions + $this->displayOptions; } /** * {@inheritdoc} */ public function dumpScalar(Cursor $cursor, string $type, $value) { $this->dumpKey($cursor); $this->collapseNextHash = $this->expandNextHash = \false; $style = 'const'; $attr = $cursor->attr; switch ($type) { case 'default': $style = 'default'; break; case 'integer': $style = 'num'; if (isset($this->styles['integer'])) { $style = 'integer'; } break; case 'double': $style = 'num'; if (isset($this->styles['float'])) { $style = 'float'; } switch (\true) { case \INF === $value: $value = 'INF'; break; case -\INF === $value: $value = '-INF'; break; case \is_nan($value): $value = 'NAN'; break; default: $value = (string) $value; if (!\str_contains($value, $this->decimalPoint)) { $value .= $this->decimalPoint . '0'; } break; } break; case 'NULL': $value = 'null'; break; case 'boolean': $value = $value ? 'true' : 'false'; break; default: $attr += ['value' => $this->utf8Encode($value)]; $value = $this->utf8Encode($type); break; } $this->line .= $this->style($style, $value, $attr); $this->endValue($cursor); } /** * {@inheritdoc} */ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) { $this->dumpKey($cursor); $this->collapseNextHash = $this->expandNextHash = \false; $attr = $cursor->attr; if ($bin) { $str = $this->utf8Encode($str); } if ('' === $str) { $this->line .= '""'; if ($cut) { $this->line .= '…' . $cut; } $this->endValue($cursor); } else { $attr += ['length' => 0 <= $cut ? \mb_strlen($str, 'UTF-8') + $cut : 0, 'binary' => $bin]; $str = $bin && \false !== \strpos($str, "\x00") ? [$str] : \explode("\n", $str); if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { unset($str[1]); $str[0] .= "\n"; } $m = \count($str) - 1; $i = $lineCut = 0; if (self::DUMP_STRING_LENGTH & $this->flags) { $this->line .= '(' . $attr['length'] . ') '; } if ($bin) { $this->line .= 'b'; } if ($m) { $this->line .= '"""'; $this->dumpLine($cursor->depth); } else { $this->line .= '"'; } foreach ($str as $str) { if ($i < $m) { $str .= "\n"; } if (0 < $this->maxStringWidth && $this->maxStringWidth < ($len = \mb_strlen($str, 'UTF-8'))) { $str = \mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); $lineCut = $len - $this->maxStringWidth; } if ($m && 0 < $cursor->depth) { $this->line .= $this->indentPad; } if ('' !== $str) { $this->line .= $this->style('str', $str, $attr); } if ($i++ == $m) { if ($m) { if ('' !== $str) { $this->dumpLine($cursor->depth); if (0 < $cursor->depth) { $this->line .= $this->indentPad; } } $this->line .= '"""'; } else { $this->line .= '"'; } if ($cut < 0) { $this->line .= '…'; $lineCut = 0; } elseif ($cut) { $lineCut += $cut; } } if ($lineCut) { $this->line .= '…' . $lineCut; $lineCut = 0; } if ($i > $m) { $this->endValue($cursor); } else { $this->dumpLine($cursor->depth); } } } } /** * {@inheritdoc} */ public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) { if (null === $this->colors) { $this->colors = $this->supportsColors(); } $this->dumpKey($cursor); $this->expandNextHash = \false; $attr = $cursor->attr; if ($this->collapseNextHash) { $cursor->skipChildren = \true; $this->collapseNextHash = $hasChild = \false; } $class = $this->utf8Encode($class); if (Cursor::HASH_OBJECT === $type) { $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr) . (empty($attr['cut_hash']) ? ' {' : '') : '{'; } elseif (Cursor::HASH_RESOURCE === $type) { $prefix = $this->style('note', $class . ' resource', $attr) . ($hasChild ? ' {' : ' '); } else { $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:' . $class) . ' [' : '['; } if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#') . (0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { $prefix .= $this->style('ref', '&' . $cursor->hardRefTo, ['count' => $cursor->hardRefCount]); } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { $prefix = \substr($prefix, 0, -1); } $this->line .= $prefix; if ($hasChild) { $this->dumpLine($cursor->depth); } } /** * {@inheritdoc} */ public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) { if (empty($cursor->attr['cut_hash'])) { $this->dumpEllipsis($cursor, $hasChild, $cut); $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); } $this->endValue($cursor); } /** * Dumps an ellipsis for cut children. * * @param bool $hasChild When the dump of the hash has child item * @param int $cut The number of items the hash has been cut by */ protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut) { if ($cut) { $this->line .= ' …'; if (0 < $cut) { $this->line .= $cut; } if ($hasChild) { $this->dumpLine($cursor->depth + 1); } } } /** * Dumps a key in a hash structure. */ protected function dumpKey(Cursor $cursor) { if (null !== ($key = $cursor->hashKey)) { if ($cursor->hashKeyIsBinary) { $key = $this->utf8Encode($key); } $attr = ['binary' => $cursor->hashKeyIsBinary]; $bin = $cursor->hashKeyIsBinary ? 'b' : ''; $style = 'key'; switch ($cursor->hashType) { default: case Cursor::HASH_INDEXED: if (self::DUMP_LIGHT_ARRAY & $this->flags) { break; } $style = 'index'; // no break case Cursor::HASH_ASSOC: if (\is_int($key)) { $this->line .= $this->style($style, $key) . ' => '; } else { $this->line .= $bin . '"' . $this->style($style, $key) . '" => '; } break; case Cursor::HASH_RESOURCE: $key = "\x00~\x00" . $key; // no break case Cursor::HASH_OBJECT: if (!isset($key[0]) || "\x00" !== $key[0]) { $this->line .= '+' . $bin . $this->style('public', $key) . ': '; } elseif (0 < \strpos($key, "\x00", 1)) { $key = \explode("\x00", \substr($key, 1), 2); switch ($key[0][0]) { case '+': // User inserted keys $attr['dynamic'] = \true; $this->line .= '+' . $bin . '"' . $this->style('public', $key[1], $attr) . '": '; break 2; case '~': $style = 'meta'; if (isset($key[0][1])) { \parse_str(\substr($key[0], 1), $attr); $attr += ['binary' => $cursor->hashKeyIsBinary]; } break; case '*': $style = 'protected'; $bin = '#' . $bin; break; default: $attr['class'] = $key[0]; $style = 'private'; $bin = '-' . $bin; break; } if (isset($attr['collapse'])) { if ($attr['collapse']) { $this->collapseNextHash = \true; } else { $this->expandNextHash = \true; } } $this->line .= $bin . $this->style($style, $key[1], $attr) . ($attr['separator'] ?? ': '); } else { // This case should not happen $this->line .= '-' . $bin . '"' . $this->style('private', $key, ['class' => '']) . '": '; } break; } if ($cursor->hardRefTo) { $this->line .= $this->style('ref', '&' . ($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]) . ' '; } } } /** * Decorates a value with some style. * * @param string $style The type of style being applied * @param string $value The value being styled * @param array $attr Optional context information * * @return string */ protected function style(string $style, string $value, array $attr = []) { if (null === $this->colors) { $this->colors = $this->supportsColors(); } if (null === $this->handlesHrefGracefully) { $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== \getenv('TERMINAL_EMULATOR') && (!\getenv('KONSOLE_VERSION') || (int) \getenv('KONSOLE_VERSION') > 201100) && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); } if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { $prefix = \substr($value, 0, -$attr['ellipsis']); if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && \str_starts_with($prefix, $_SERVER[$pwd])) { $prefix = '.' . \substr($prefix, \strlen($_SERVER[$pwd])); } if (!empty($attr['ellipsis-tail'])) { $prefix .= \substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); $value = \substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); } else { $value = \substr($value, -$attr['ellipsis']); } $value = $this->style('default', $prefix) . $this->style($style, $value); goto href; } $map = static::$controlCharsMap; $startCchr = $this->colors ? "\x1b[m\x1b[{$this->styles['default']}m" : ''; $endCchr = $this->colors ? "\x1b[m\x1b[{$this->styles[$style]}m" : ''; $value = \preg_replace_callback(static::$controlCharsRx, function ($c) use($map, $startCchr, $endCchr) { $s = $startCchr; $c = $c[$i = 0]; do { $s .= $map[$c[$i]] ?? \sprintf('\\x%02X', \ord($c[$i])); } while (isset($c[++$i])); return $s . $endCchr; }, $value, -1, $cchrCount); if ($this->colors) { if ($cchrCount && "\x1b" === $value[0]) { $value = \substr($value, \strlen($startCchr)); } else { $value = "\x1b[{$this->styles[$style]}m" . $value; } if ($cchrCount && \str_ends_with($value, $endCchr)) { $value = \substr($value, 0, -\strlen($endCchr)); } else { $value .= "\x1b[{$this->styles['default']}m"; } } href: if ($this->colors && $this->handlesHrefGracefully) { if (isset($attr['file']) && ($href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0))) { if ('note' === $style) { $value .= "\x1b]8;;{$href}\x1b\\^\x1b]8;;\x1b\\"; } else { $attr['href'] = $href; } } if (isset($attr['href'])) { $value = "\x1b]8;;{$attr['href']}\x1b\\{$value}\x1b]8;;\x1b\\"; } } elseif ($attr['if_links'] ?? \false) { return ''; } return $value; } /** * @return bool */ protected function supportsColors() { if ($this->outputStream !== static::$defaultOutput) { return $this->hasColorSupport($this->outputStream); } if (null !== static::$defaultColors) { return static::$defaultColors; } if (isset($_SERVER['argv'][1])) { $colors = $_SERVER['argv']; $i = \count($colors); while (--$i > 0) { if (isset($colors[$i][5])) { switch ($colors[$i]) { case '--ansi': case '--color': case '--color=yes': case '--color=force': case '--color=always': case '--colors=always': return static::$defaultColors = \true; case '--no-ansi': case '--color=no': case '--color=none': case '--color=never': case '--colors=never': return static::$defaultColors = \false; } } } } $h = \stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? \fopen('php://stdout', 'w') : $this->outputStream; return static::$defaultColors = $this->hasColorSupport($h); } /** * {@inheritdoc} */ protected function dumpLine(int $depth, bool $endOfValue = \false) { if (null === $this->colors) { $this->colors = $this->supportsColors(); } if ($this->colors) { $this->line = \sprintf("\x1b[%sm%s\x1b[m", $this->styles['default'], $this->line); } parent::dumpLine($depth); } protected function endValue(Cursor $cursor) { if (-1 === $cursor->hashType) { return; } if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { $this->line .= ','; } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { $this->line .= ','; } } $this->dumpLine($cursor->depth, \true); } /** * Returns true if the stream supports colorization. * * Reference: Composer\XdebugHandler\Process::supportsColor * https://github.com/composer/xdebug-handler * * @param mixed $stream A CLI output stream */ private function hasColorSupport($stream) : bool { if (!\is_resource($stream) || 'stream' !== \get_resource_type($stream)) { return \false; } // Follow https://no-color.org/ if (isset($_SERVER['NO_COLOR']) || \false !== \getenv('NO_COLOR')) { return \false; } if ('Hyper' === \getenv('TERM_PROGRAM')) { return \true; } if (\DIRECTORY_SEPARATOR === '\\') { return \function_exists('sapi_windows_vt100_support') && @\sapi_windows_vt100_support($stream) || \false !== \getenv('ANSICON') || 'ON' === \getenv('ConEmuANSI') || 'xterm' === \getenv('TERM'); } return \stream_isatty($stream); } /** * Returns true if the Windows terminal supports true color. * * Note that this does not check an output stream, but relies on environment * variables from known implementations, or a PHP and Windows version that * supports true color. */ private function isWindowsTrueColor() : bool { $result = 183 <= \getenv('ANSICON_VER') || 'ON' === \getenv('ConEmuANSI') || 'xterm' === \getenv('TERM') || 'Hyper' === \getenv('TERM_PROGRAM'); if (!$result) { $version = \sprintf('%s.%s.%s', \PHP_WINDOWS_VERSION_MAJOR, \PHP_WINDOWS_VERSION_MINOR, \PHP_WINDOWS_VERSION_BUILD); $result = $version >= '10.0.15063'; } return $result; } private function getSourceLink(string $file, int $line) { if ($fmt = $this->displayOptions['fileLinkFormat']) { return \is_string($fmt) ? \strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://' . $file . '#L' . $line); } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; /** * @author Kévin Thérage */ class ContextualizedDumper implements DataDumperInterface { private $wrappedDumper; private $contextProviders; /** * @param ContextProviderInterface[] $contextProviders */ public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) { $this->wrappedDumper = $wrappedDumper; $this->contextProviders = $contextProviders; } public function dump(Data $data) { $context = []; foreach ($this->contextProviders as $contextProvider) { $context[\get_class($contextProvider)] = $contextProvider->getContext(); } $this->wrappedDumper->dump($data->withContext($context)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper\Dumper; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Cursor; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; /** * HtmlDumper dumps variables as HTML. * * @author Nicolas Grekas */ class HtmlDumper extends CliDumper { public static $defaultOutput = 'php://output'; protected static $themes = ['dark' => ['default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', 'num' => 'font-weight:bold; color:#1299DA', 'const' => 'font-weight:bold', 'str' => 'font-weight:bold; color:#56DB3A', 'note' => 'color:#1299DA', 'ref' => 'color:#A0A0A0', 'public' => 'color:#FFFFFF', 'protected' => 'color:#FFFFFF', 'private' => 'color:#FFFFFF', 'meta' => 'color:#B729D9', 'key' => 'color:#56DB3A', 'index' => 'color:#1299DA', 'ellipsis' => 'color:#FF8400', 'ns' => 'user-select:none;'], 'light' => ['default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', 'num' => 'font-weight:bold; color:#1299DA', 'const' => 'font-weight:bold', 'str' => 'font-weight:bold; color:#629755;', 'note' => 'color:#6897BB', 'ref' => 'color:#6E6E6E', 'public' => 'color:#262626', 'protected' => 'color:#262626', 'private' => 'color:#262626', 'meta' => 'color:#B729D9', 'key' => 'color:#789339', 'index' => 'color:#1299DA', 'ellipsis' => 'color:#CC7832', 'ns' => 'user-select:none;']]; protected $dumpHeader; protected $dumpPrefix = '
    ';
        protected $dumpSuffix = '
    '; protected $dumpId = 'sf-dump'; protected $colors = \true; protected $headerIsDumped = \false; protected $lastDepth = -1; protected $styles; private $displayOptions = ['maxDepth' => 1, 'maxStringLength' => 160, 'fileLinkFormat' => null]; private $extraDisplayOptions = []; /** * {@inheritdoc} */ public function __construct($output = null, ?string $charset = null, int $flags = 0) { AbstractDumper::__construct($output, $charset, $flags); $this->dumpId = 'sf-dump-' . \mt_rand(); $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: \get_cfg_var('xdebug.file_link_format'); $this->styles = static::$themes['dark'] ?? self::$themes['dark']; } /** * {@inheritdoc} */ public function setStyles(array $styles) { $this->headerIsDumped = \false; $this->styles = $styles + $this->styles; } public function setTheme(string $themeName) { if (!isset(static::$themes[$themeName])) { throw new \InvalidArgumentException(\sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); } $this->setStyles(static::$themes[$themeName]); } /** * Configures display options. * * @param array $displayOptions A map of display options to customize the behavior */ public function setDisplayOptions(array $displayOptions) { $this->headerIsDumped = \false; $this->displayOptions = $displayOptions + $this->displayOptions; } /** * Sets an HTML header that will be dumped once in the output stream. */ public function setDumpHeader(?string $header) { $this->dumpHeader = $header; } /** * Sets an HTML prefix and suffix that will encapse every single dump. */ public function setDumpBoundaries(string $prefix, string $suffix) { $this->dumpPrefix = $prefix; $this->dumpSuffix = $suffix; } /** * {@inheritdoc} */ public function dump(Data $data, $output = null, array $extraDisplayOptions = []) { $this->extraDisplayOptions = $extraDisplayOptions; $result = parent::dump($data, $output); $this->dumpId = 'sf-dump-' . \mt_rand(); return $result; } /** * Dumps the HTML header. */ protected function getDumpHeader() { $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; if (null !== $this->dumpHeader) { return $this->dumpHeader; } $line = \str_replace('{$options}', \json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' ' . $this->dumpHeader; } /** * {@inheritdoc} */ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) { if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { $this->dumpKey($cursor); $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []); $this->line .= $cursor->depth >= $this->displayOptions['maxDepth'] ? ' ' : ' '; $this->endValue($cursor); $this->line .= $this->indentPad; $this->line .= \sprintf('', $cursor->attr['content-type'], \base64_encode($cursor->attr['img-data'])); $this->endValue($cursor); } else { parent::dumpString($cursor, $str, $bin, $cut); } } /** * {@inheritdoc} */ public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) { if (Cursor::HASH_OBJECT === $type) { $cursor->attr['depth'] = $cursor->depth; } parent::enterHash($cursor, $type, $class, \false); if ($cursor->skipChildren || $cursor->depth >= $this->displayOptions['maxDepth']) { $cursor->skipChildren = \false; $eol = ' class=sf-dump-compact>'; } else { $this->expandNextHash = \false; $eol = ' class=sf-dump-expanded>'; } if ($hasChild) { $this->line .= 'dumpId, $r); } $this->line .= $eol; $this->dumpLine($cursor->depth); } } /** * {@inheritdoc} */ public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) { $this->dumpEllipsis($cursor, $hasChild, $cut); if ($hasChild) { $this->line .= ''; } parent::leaveHash($cursor, $type, $class, $hasChild, 0); } /** * {@inheritdoc} */ protected function style(string $style, string $value, array $attr = []) { if ('' === $value) { return ''; } $v = esc($value); if ('ref' === $style) { if (empty($attr['count'])) { return \sprintf('%s', $v); } $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2) . \substr($value, 1); return \sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); } if ('const' === $style && isset($attr['value'])) { $style .= \sprintf(' title="%s"', esc(\is_scalar($attr['value']) ? $attr['value'] : \json_encode($attr['value']))); } elseif ('public' === $style) { $style .= \sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); } elseif ('str' === $style && 1 < $attr['length']) { $style .= \sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && \false !== ($c = \strrpos($value, '\\'))) { $style .= ' title=""'; $attr += ['ellipsis' => \strlen($value) - $c, 'ellipsis-type' => 'note', 'ellipsis-tail' => 1]; } elseif ('protected' === $style) { $style .= ' title="Protected property"'; } elseif ('meta' === $style && isset($attr['title'])) { $style .= \sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); } elseif ('private' === $style) { $style .= \sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); } $map = static::$controlCharsMap; if (isset($attr['ellipsis'])) { $class = 'sf-dump-ellipsis'; if (isset($attr['ellipsis-type'])) { $class = \sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); } $label = esc(\substr($value, -$attr['ellipsis'])); $style = \str_replace(' title="', " title=\"{$v}\n", $style); $v = \sprintf('%s', $class, \substr($v, 0, -\strlen($label))); if (!empty($attr['ellipsis-tail'])) { $tail = \strlen(esc(\substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); $v .= \sprintf('%s%s', $class, \substr($label, 0, $tail), \substr($label, $tail)); } else { $v .= $label; } } $v = "" . \preg_replace_callback(static::$controlCharsRx, function ($c) use($map) { $s = $b = ''; }, $v) . ''; if (isset($attr['file']) && ($href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0))) { $attr['href'] = $href; } if (isset($attr['href'])) { $target = isset($attr['file']) ? '' : ' target="_blank"'; $v = \sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); } if (isset($attr['lang'])) { $v = \sprintf('%s', esc($attr['lang']), $v); } return $v; } /** * {@inheritdoc} */ protected function dumpLine(int $depth, bool $endOfValue = \false) { if (-1 === $this->lastDepth) { $this->line = \sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad) . $this->line; } if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { $this->line = $this->getDumpHeader() . $this->line; } if (-1 === $depth) { $args = ['"' . $this->dumpId . '"']; if ($this->extraDisplayOptions) { $args[] = \json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); } // Replace is for BC $this->line .= \sprintf(\str_replace('"%s"', '%s', $this->dumpSuffix), \implode(', ', $args)); } $this->lastDepth = $depth; $this->line = \mb_encode_numericentity($this->line, [0x80, 0x10ffff, 0, 0x1fffff], 'UTF-8'); if (-1 === $depth) { AbstractDumper::dumpLine(0); } AbstractDumper::dumpLine($depth); } private function getSourceLink(string $file, int $line) { $options = $this->extraDisplayOptions + $this->displayOptions; if ($fmt = $options['fileLinkFormat']) { return \is_string($fmt) ? \strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); } return \false; } } function esc(string $str) { return \htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); } { "name": "symfony\/var-dumper", "type": "library", "description": "Provides mechanisms for walking through any arbitrary PHP variable", "keywords": [ "dump", "debug" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/polyfill-mbstring": "~1.0", "symfony\/polyfill-php80": "^1.16" }, "require-dev": { "ext-iconv": "*", "symfony\/console": "^4.4|^5.0|^6.0", "symfony\/http-kernel": "^4.4|^5.0|^6.0", "symfony\/process": "^4.4|^5.0|^6.0", "symfony\/uid": "^5.1|^6.0", "twig\/twig": "^2.13|^3.0.4" }, "conflict": { "symfony\/console": "<4.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony\/polyfill-iconv in case ext-iconv cannot be used).", "ext-intl": "To show region name in time zone dump", "symfony\/console": "To use the ServerDumpCommand and\/or the bin\/var-dump-server script" }, "autoload": { "files": [ "Resources\/functions\/dump.php" ], "psr-4": { "_ContaoManager\\Symfony\\Component\\VarDumper\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "bin": [ "Resources\/bin\/var-dump-server" ], "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarDumper; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\VarDumper\Caster\ReflectionCaster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextualizedDumper; use _ContaoManager\Symfony\Component\VarDumper\Dumper\HtmlDumper; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ServerDumper; // Load the global dump() function require_once __DIR__ . '/Resources/functions/dump.php'; /** * @author Nicolas Grekas */ class VarDumper { /** * @var callable|null */ private static $handler; public static function dump($var) { if (null === self::$handler) { self::register(); } return (self::$handler)($var); } /** * @return callable|null */ public static function setHandler(?callable $callable = null) { $prevHandler = self::$handler; // Prevent replacing the handler with expected format as soon as the env var was set: if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { return $prevHandler; } self::$handler = $callable; return $prevHandler; } private static function register() : void { $cloner = new VarCloner(); $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); $format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null; switch (\true) { case 'html' === $format: $dumper = new HtmlDumper(); break; case 'cli' === $format: $dumper = new CliDumper(); break; case 'server' === $format: case $format && 'tcp' === \parse_url($format, \PHP_URL_SCHEME): $host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) ? new CliDumper() : new HtmlDumper(); $dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders()); break; default: $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) ? new CliDumper() : new HtmlDumper(); } if (!$dumper instanceof ServerDumper) { $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); } self::$handler = function ($var) use($cloner, $dumper) { $dumper->dump($cloner->cloneVar($var)); }; } private static function getDefaultContextProviders() : array { $contextProviders = []; if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) && \class_exists(Request::class)) { $requestStack = new RequestStack(); $requestStack->push(Request::createFromGlobals()); $contextProviders['request'] = new RequestContextProvider($requestStack); } $fileLinkFormatter = \class_exists(FileLinkFormatter::class) ? new FileLinkFormatter(null, $requestStack ?? null) : null; return $contextProviders + ['cli' => new CliContextProvider(), 'source' => new SourceContextProvider(null, null, $fileLinkFormatter)]; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Add `Path` class * Add `$lock` argument to `Filesystem::appendToFile()` 5.0.0 ----- * `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore 4.4.0 ----- * support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0 * `tempnam()` now accepts a third argument `$suffix`. 4.3.0 ----- * support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0 * support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0 4.0.0 ----- * removed `LockHandler` * Support for passing relative paths to `Filesystem::makePathRelative()` has been removed. 3.4.0 ----- * support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0 3.3.0 ----- * added `appendToFile()` to append contents to existing files 3.2.0 ----- * added `readlink()` as a platform independent method to read links 3.0.0 ----- * removed `$mode` argument from `Filesystem::dumpFile()` 2.8.0 ----- * added tempnam() a stream aware version of PHP's native tempnam() 2.6.0 ----- * added LockHandler 2.3.12 ------ * deprecated dumpFile() file mode argument. 2.3.0 ----- * added the dumpFile() method to atomically write files 2.2.0 ----- * added a delete option for the mirror() method 2.1.0 ----- * 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value * created the component * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem; use _ContaoManager\Symfony\Component\Filesystem\Exception\FileNotFoundException; use _ContaoManager\Symfony\Component\Filesystem\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Filesystem\Exception\IOException; /** * Provides basic utility to manipulate the file system. * * @author Fabien Potencier */ class Filesystem { private static $lastError; /** * Copies a file. * * If the target file is older than the origin file, it's always overwritten. * If the target file is newer, it is overwritten only when the * $overwriteNewerFiles option is set to true. * * @throws FileNotFoundException When originFile doesn't exist * @throws IOException When copy fails */ public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = \false) { $originIsLocal = \stream_is_local($originFile) || 0 === \stripos($originFile, 'file://'); if ($originIsLocal && !\is_file($originFile)) { throw new FileNotFoundException(\sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); } $this->mkdir(\dirname($targetFile)); $doCopy = \true; if (!$overwriteNewerFiles && null === \parse_url($originFile, \PHP_URL_HOST) && \is_file($targetFile)) { $doCopy = \filemtime($originFile) > \filemtime($targetFile); } if ($doCopy) { // https://bugs.php.net/64634 if (!($source = self::box('fopen', $originFile, 'r'))) { throw new IOException(\sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile) . self::$lastError, 0, null, $originFile); } // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default if (!($target = self::box('fopen', $targetFile, 'w', \false, \stream_context_create(['ftp' => ['overwrite' => \true]])))) { throw new IOException(\sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile) . self::$lastError, 0, null, $originFile); } $bytesCopied = \stream_copy_to_stream($source, $target); \fclose($source); \fclose($target); unset($source, $target); if (!\is_file($targetFile)) { throw new IOException(\sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); } if ($originIsLocal) { // Like `cp`, preserve executable permission bits self::box('chmod', $targetFile, \fileperms($targetFile) | \fileperms($originFile) & 0111); if ($bytesCopied !== ($bytesOrigin = \filesize($originFile))) { throw new IOException(\sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); } } } } /** * Creates a directory recursively. * * @param string|iterable $dirs The directory path * * @throws IOException On any directory creation failure */ public function mkdir($dirs, int $mode = 0777) { foreach ($this->toIterable($dirs) as $dir) { if (\is_dir($dir)) { continue; } if (!self::box('mkdir', $dir, $mode, \true) && !\is_dir($dir)) { throw new IOException(\sprintf('Failed to create "%s": ', $dir) . self::$lastError, 0, null, $dir); } } } /** * Checks the existence of files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check * * @return bool */ public function exists($files) { $maxPathLength = \PHP_MAXPATHLEN - 2; foreach ($this->toIterable($files) as $file) { if (\strlen($file) > $maxPathLength) { throw new IOException(\sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file); } if (!\file_exists($file)) { return \false; } } return \true; } /** * Sets access and modification time of file. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used * * @throws IOException When touch fails */ public function touch($files, ?int $time = null, ?int $atime = null) { foreach ($this->toIterable($files) as $file) { if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { throw new IOException(\sprintf('Failed to touch "%s": ', $file) . self::$lastError, 0, null, $file); } } } /** * Removes files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove * * @throws IOException When removal fails */ public function remove($files) { if ($files instanceof \Traversable) { $files = \iterator_to_array($files, \false); } elseif (!\is_array($files)) { $files = [$files]; } self::doRemove($files, \false); } private static function doRemove(array $files, bool $isRecursive) : void { $files = \array_reverse($files); foreach ($files as $file) { if (\is_link($file)) { // See https://bugs.php.net/52176 if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && \file_exists($file)) { throw new IOException(\sprintf('Failed to remove symlink "%s": ', $file) . self::$lastError); } } elseif (\is_dir($file)) { if (!$isRecursive) { $tmpName = \dirname(\realpath($file)) . '/.' . \strrev(\strtr(\base64_encode(\random_bytes(2)), '/=', '-_')); if (\file_exists($tmpName)) { try { self::doRemove([$tmpName], \true); } catch (IOException $e) { } } if (!\file_exists($tmpName) && self::box('rename', $file, $tmpName)) { $origFile = $file; $file = $tmpName; } else { $origFile = null; } } $files = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); self::doRemove(\iterator_to_array($files, \true), \true); if (!self::box('rmdir', $file) && \file_exists($file) && !$isRecursive) { $lastError = self::$lastError; if (null !== $origFile && self::box('rename', $file, $origFile)) { $file = $origFile; } throw new IOException(\sprintf('Failed to remove directory "%s": ', $file) . $lastError); } } elseif (!self::box('unlink', $file) && (\str_contains(self::$lastError, 'Permission denied') || \file_exists($file))) { throw new IOException(\sprintf('Failed to remove file "%s": ', $file) . self::$lastError); } } } /** * Change mode for an array of files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode * @param int $mode The new mode (octal) * @param int $umask The mode mask (octal) * @param bool $recursive Whether change the mod recursively or not * * @throws IOException When the change fails */ public function chmod($files, int $mode, int $umask = 00, bool $recursive = \false) { foreach ($this->toIterable($files) as $file) { if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && !self::box('chmod', $file, $mode & ~$umask)) { throw new IOException(\sprintf('Failed to chmod file "%s": ', $file) . self::$lastError, 0, null, $file); } if ($recursive && \is_dir($file) && !\is_link($file)) { $this->chmod(new \FilesystemIterator($file), $mode, $umask, \true); } } } /** * Change the owner of an array of files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner * @param string|int $user A user name or number * @param bool $recursive Whether change the owner recursively or not * * @throws IOException When the change fails */ public function chown($files, $user, bool $recursive = \false) { foreach ($this->toIterable($files) as $file) { if ($recursive && \is_dir($file) && !\is_link($file)) { $this->chown(new \FilesystemIterator($file), $user, \true); } if (\is_link($file) && \function_exists('lchown')) { if (!self::box('lchown', $file, $user)) { throw new IOException(\sprintf('Failed to chown file "%s": ', $file) . self::$lastError, 0, null, $file); } } else { if (!self::box('chown', $file, $user)) { throw new IOException(\sprintf('Failed to chown file "%s": ', $file) . self::$lastError, 0, null, $file); } } } } /** * Change the group of an array of files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group * @param string|int $group A group name or number * @param bool $recursive Whether change the group recursively or not * * @throws IOException When the change fails */ public function chgrp($files, $group, bool $recursive = \false) { foreach ($this->toIterable($files) as $file) { if ($recursive && \is_dir($file) && !\is_link($file)) { $this->chgrp(new \FilesystemIterator($file), $group, \true); } if (\is_link($file) && \function_exists('lchgrp')) { if (!self::box('lchgrp', $file, $group)) { throw new IOException(\sprintf('Failed to chgrp file "%s": ', $file) . self::$lastError, 0, null, $file); } } else { if (!self::box('chgrp', $file, $group)) { throw new IOException(\sprintf('Failed to chgrp file "%s": ', $file) . self::$lastError, 0, null, $file); } } } } /** * Renames a file or a directory. * * @throws IOException When target file or directory already exists * @throws IOException When origin cannot be renamed */ public function rename(string $origin, string $target, bool $overwrite = \false) { // we check that target does not exist if (!$overwrite && $this->isReadable($target)) { throw new IOException(\sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); } if (!self::box('rename', $origin, $target)) { if (\is_dir($origin)) { // See https://bugs.php.net/54097 & https://php.net/rename#113943 $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]); $this->remove($origin); return; } throw new IOException(\sprintf('Cannot rename "%s" to "%s": ', $origin, $target) . self::$lastError, 0, null, $target); } } /** * Tells whether a file exists and is readable. * * @throws IOException When windows path is longer than 258 characters */ private function isReadable(string $filename) : bool { $maxPathLength = \PHP_MAXPATHLEN - 2; if (\strlen($filename) > $maxPathLength) { throw new IOException(\sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename); } return \is_readable($filename); } /** * Creates a symbolic link or copy a directory. * * @throws IOException When symlink fails */ public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = \false) { self::assertFunctionExists('symlink'); if ('\\' === \DIRECTORY_SEPARATOR) { $originDir = \strtr($originDir, '/', '\\'); $targetDir = \strtr($targetDir, '/', '\\'); if ($copyOnWindows) { $this->mirror($originDir, $targetDir); return; } } $this->mkdir(\dirname($targetDir)); if (\is_link($targetDir)) { if (\readlink($targetDir) === $originDir) { return; } $this->remove($targetDir); } if (!self::box('symlink', $originDir, $targetDir)) { $this->linkException($originDir, $targetDir, 'symbolic'); } } /** * Creates a hard link, or several hard links to a file. * * @param string|string[] $targetFiles The target file(s) * * @throws FileNotFoundException When original file is missing or not a file * @throws IOException When link fails, including if link already exists */ public function hardlink(string $originFile, $targetFiles) { self::assertFunctionExists('link'); if (!$this->exists($originFile)) { throw new FileNotFoundException(null, 0, null, $originFile); } if (!\is_file($originFile)) { throw new FileNotFoundException(\sprintf('Origin file "%s" is not a file.', $originFile)); } foreach ($this->toIterable($targetFiles) as $targetFile) { if (\is_file($targetFile)) { if (\fileinode($originFile) === \fileinode($targetFile)) { continue; } $this->remove($targetFile); } if (!self::box('link', $originFile, $targetFile)) { $this->linkException($originFile, $targetFile, 'hard'); } } } /** * @param string $linkType Name of the link type, typically 'symbolic' or 'hard' */ private function linkException(string $origin, string $target, string $linkType) { if (self::$lastError) { if ('\\' === \DIRECTORY_SEPARATOR && \str_contains(self::$lastError, 'error code(1314)')) { throw new IOException(\sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); } } throw new IOException(\sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target) . self::$lastError, 0, null, $target); } /** * Resolves links in paths. * * With $canonicalize = false (default) * - if $path does not exist or is not a link, returns null * - if $path is a link, returns the next direct target of the link without considering the existence of the target * * With $canonicalize = true * - if $path does not exist, returns null * - if $path exists, returns its absolute fully resolved final version * * @return string|null */ public function readlink(string $path, bool $canonicalize = \false) { if (!$canonicalize && !\is_link($path)) { return null; } if ($canonicalize) { if (!$this->exists($path)) { return null; } if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) { $path = \readlink($path); } return \realpath($path); } if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) { return \realpath($path); } return \readlink($path); } /** * Given an existing path, convert it to a path relative to a given starting path. * * @return string */ public function makePathRelative(string $endPath, string $startPath) { if (!$this->isAbsolutePath($startPath)) { throw new InvalidArgumentException(\sprintf('The start path "%s" is not absolute.', $startPath)); } if (!$this->isAbsolutePath($endPath)) { throw new InvalidArgumentException(\sprintf('The end path "%s" is not absolute.', $endPath)); } // Normalize separators on Windows if ('\\' === \DIRECTORY_SEPARATOR) { $endPath = \str_replace('\\', '/', $endPath); $startPath = \str_replace('\\', '/', $startPath); } $splitDriveLetter = function ($path) { return \strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && \ctype_alpha($path[0]) ? [\substr($path, 2), \strtoupper($path[0])] : [$path, null]; }; $splitPath = function ($path) { $result = []; foreach (\explode('/', \trim($path, '/')) as $segment) { if ('..' === $segment) { \array_pop($result); } elseif ('.' !== $segment && '' !== $segment) { $result[] = $segment; } } return $result; }; [$endPath, $endDriveLetter] = $splitDriveLetter($endPath); [$startPath, $startDriveLetter] = $splitDriveLetter($startPath); $startPathArr = $splitPath($startPath); $endPathArr = $splitPath($endPath); if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) { // End path is on another drive, so no relative path exists return $endDriveLetter . ':/' . ($endPathArr ? \implode('/', $endPathArr) . '/' : ''); } // Find for which directory the common path stops $index = 0; while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { ++$index; } // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) if (1 === \count($startPathArr) && '' === $startPathArr[0]) { $depth = 0; } else { $depth = \count($startPathArr) - $index; } // Repeated "../" for each level need to reach the common path $traverser = \str_repeat('../', $depth); $endPathRemainder = \implode('/', \array_slice($endPathArr, $index)); // Construct $endPath from traversing to the common path, then to the remaining $endPath $relativePath = $traverser . ('' !== $endPathRemainder ? $endPathRemainder . '/' : ''); return '' === $relativePath ? './' : $relativePath; } /** * Mirrors a directory to another. * * Copies files and directories from the origin directory into the target directory. By default: * * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option) * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option) * * @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created * @param array $options An array of boolean options * Valid options are: * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) * * @throws IOException When file type is unknown */ public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = []) { $targetDir = \rtrim($targetDir, '/\\'); $originDir = \rtrim($originDir, '/\\'); $originDirLen = \strlen($originDir); if (!$this->exists($originDir)) { throw new IOException(\sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir); } // Iterate in destination folder to remove obsolete entries if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { $deleteIterator = $iterator; if (null === $deleteIterator) { $flags = \FilesystemIterator::SKIP_DOTS; $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); } $targetDirLen = \strlen($targetDir); foreach ($deleteIterator as $file) { $origin = $originDir . \substr($file->getPathname(), $targetDirLen); if (!$this->exists($origin)) { $this->remove($file); } } } $copyOnWindows = $options['copy_on_windows'] ?? \false; if (null === $iterator) { $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); } $this->mkdir($targetDir); $filesCreatedWhileMirroring = []; foreach ($iterator as $file) { if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) { continue; } $target = $targetDir . \substr($file->getPathname(), $originDirLen); $filesCreatedWhileMirroring[$target] = \true; if (!$copyOnWindows && \is_link($file)) { $this->symlink($file->getLinkTarget(), $target); } elseif (\is_dir($file)) { $this->mkdir($target); } elseif (\is_file($file)) { $this->copy($file, $target, $options['override'] ?? \false); } else { throw new IOException(\sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } } /** * Returns whether the file path is an absolute path. * * @return bool */ public function isAbsolutePath(string $file) { return '' !== $file && (\strspn($file, '/\\', 0, 1) || \strlen($file) > 3 && \ctype_alpha($file[0]) && ':' === $file[1] && \strspn($file, '/\\', 2, 1) || null !== \parse_url($file, \PHP_URL_SCHEME)); } /** * Creates a temporary file with support for custom stream wrappers. * * @param string $prefix The prefix of the generated temporary filename * Note: Windows uses only the first three characters of prefix * @param string $suffix The suffix of the generated temporary filename * * @return string The new temporary filename (with path), or throw an exception on failure */ public function tempnam(string $dir, string $prefix) { $suffix = \func_num_args() > 2 ? \func_get_arg(2) : ''; [$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir); // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) { // If tempnam failed or no scheme return the filename otherwise prepend the scheme if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) { if (null !== $scheme && 'gs' !== $scheme) { return $scheme . '://' . $tmpFile; } return $tmpFile; } throw new IOException('A temporary file could not be created: ' . self::$lastError); } // Loop until we create a valid temp file or have reached 10 attempts for ($i = 0; $i < 10; ++$i) { // Create a unique filename $tmpFile = $dir . '/' . $prefix . \uniqid(\mt_rand(), \true) . $suffix; // Use fopen instead of file_exists as some streams do not support stat // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability if (!($handle = self::box('fopen', $tmpFile, 'x+'))) { continue; } // Close the file if it was successfully opened self::box('fclose', $handle); return $tmpFile; } throw new IOException('A temporary file could not be created: ' . self::$lastError); } /** * Atomically dumps content into a file. * * @param string|resource $content The data to write into the file * * @throws IOException if the file cannot be written to */ public function dumpFile(string $filename, $content) { if (\is_array($content)) { throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); } $dir = \dirname($filename); if (\is_link($filename) && ($linkTarget = $this->readlink($filename))) { $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content); return; } if (!\is_dir($dir)) { $this->mkdir($dir); } // Will create a temp file with 0600 access rights // when the filesystem supports chmod. $tmpFile = $this->tempnam($dir, \basename($filename)); try { if (\false === self::box('file_put_contents', $tmpFile, $content)) { throw new IOException(\sprintf('Failed to write file "%s": ', $filename) . self::$lastError, 0, null, $filename); } self::box('chmod', $tmpFile, \file_exists($filename) ? \fileperms($filename) : 0666 & ~\umask()); $this->rename($tmpFile, $filename, \true); } finally { if (\file_exists($tmpFile)) { self::box('unlink', $tmpFile); } } } /** * Appends content to an existing file. * * @param string|resource $content The content to append * @param bool $lock Whether the file should be locked when writing to it * * @throws IOException If the file is not writable */ public function appendToFile(string $filename, $content) { if (\is_array($content)) { throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); } $dir = \dirname($filename); if (!\is_dir($dir)) { $this->mkdir($dir); } $lock = \func_num_args() > 2 && \func_get_arg(2); if (\false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) { throw new IOException(\sprintf('Failed to write file "%s": ', $filename) . self::$lastError, 0, null, $filename); } } private function toIterable($files) : iterable { return \is_iterable($files) ? $files : [$files]; } /** * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]). */ private function getSchemeAndHierarchy(string $filename) : array { $components = \explode('://', $filename, 2); return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]]; } private static function assertFunctionExists(string $func) : void { if (!\function_exists($func)) { throw new IOException(\sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func)); } } /** * @param mixed ...$args * * @return mixed */ private static function box(string $func, ...$args) { self::assertFunctionExists($func); self::$lastError = null; \set_error_handler(__CLASS__ . '::handleError'); try { return $func(...$args); } finally { \restore_error_handler(); } } /** * @internal */ public static function handleError(int $type, string $msg) { self::$lastError = $msg; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem; use _ContaoManager\Symfony\Component\Filesystem\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Filesystem\Exception\RuntimeException; /** * Contains utility methods for handling path strings. * * The methods in this class are able to deal with both UNIX and Windows paths * with both forward and backward slashes. All methods return normalized parts * containing only forward slashes and no excess "." and ".." segments. * * @author Bernhard Schussek * @author Thomas Schulz * @author Théo Fidry */ final class Path { /** * The number of buffer entries that triggers a cleanup operation. */ private const CLEANUP_THRESHOLD = 1250; /** * The buffer size after the cleanup operation. */ private const CLEANUP_SIZE = 1000; /** * Buffers input/output of {@link canonicalize()}. * * @var array */ private static $buffer = []; /** * @var int */ private static $bufferSize = 0; /** * Canonicalizes the given path. * * During normalization, all slashes are replaced by forward slashes ("/"). * Furthermore, all "." and ".." segments are removed as far as possible. * ".." segments at the beginning of relative paths are not removed. * * ```php * echo Path::canonicalize("\symfony\puli\..\css\style.css"); * // => /symfony/css/style.css * * echo Path::canonicalize("../css/./style.css"); * // => ../css/style.css * ``` * * This method is able to deal with both UNIX and Windows paths. */ public static function canonicalize(string $path) : string { if ('' === $path) { return ''; } // This method is called by many other methods in this class. Buffer // the canonicalized paths to make up for the severe performance // decrease. if (isset(self::$buffer[$path])) { return self::$buffer[$path]; } // Replace "~" with user's home directory. if ('~' === $path[0]) { $path = self::getHomeDirectory() . \substr($path, 1); } $path = self::normalize($path); [$root, $pathWithoutRoot] = self::split($path); $canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot); // Add the root directory again self::$buffer[$path] = $canonicalPath = $root . \implode('/', $canonicalParts); ++self::$bufferSize; // Clean up regularly to prevent memory leaks if (self::$bufferSize > self::CLEANUP_THRESHOLD) { self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, \true); self::$bufferSize = self::CLEANUP_SIZE; } return $canonicalPath; } /** * Normalizes the given path. * * During normalization, all slashes are replaced by forward slashes ("/"). * Contrary to {@link canonicalize()}, this method does not remove invalid * or dot path segments. Consequently, it is much more efficient and should * be used whenever the given path is known to be a valid, absolute system * path. * * This method is able to deal with both UNIX and Windows paths. */ public static function normalize(string $path) : string { return \str_replace('\\', '/', $path); } /** * Returns the directory part of the path. * * This method is similar to PHP's dirname(), but handles various cases * where dirname() returns a weird result: * * - dirname() does not accept backslashes on UNIX * - dirname("C:/symfony") returns "C:", not "C:/" * - dirname("C:/") returns ".", not "C:/" * - dirname("C:") returns ".", not "C:/" * - dirname("symfony") returns ".", not "" * - dirname() does not canonicalize the result * * This method fixes these shortcomings and behaves like dirname() * otherwise. * * The result is a canonical path. * * @return string The canonical directory part. Returns the root directory * if the root directory is passed. Returns an empty string * if a relative path is passed that contains no slashes. * Returns an empty string if an empty string is passed. */ public static function getDirectory(string $path) : string { if ('' === $path) { return ''; } $path = self::canonicalize($path); // Maintain scheme if (\false !== ($schemeSeparatorPosition = \strpos($path, '://'))) { $scheme = \substr($path, 0, $schemeSeparatorPosition + 3); $path = \substr($path, $schemeSeparatorPosition + 3); } else { $scheme = ''; } if (\false === ($dirSeparatorPosition = \strrpos($path, '/'))) { return ''; } // Directory equals root directory "/" if (0 === $dirSeparatorPosition) { return $scheme . '/'; } // Directory equals Windows root "C:/" if (2 === $dirSeparatorPosition && \ctype_alpha($path[0]) && ':' === $path[1]) { return $scheme . \substr($path, 0, 3); } return $scheme . \substr($path, 0, $dirSeparatorPosition); } /** * Returns canonical path of the user's home directory. * * Supported operating systems: * * - UNIX * - Windows8 and upper * * If your operating system or environment isn't supported, an exception is thrown. * * The result is a canonical path. * * @throws RuntimeException If your operating system or environment isn't supported */ public static function getHomeDirectory() : string { // For UNIX support if (\getenv('HOME')) { return self::canonicalize(\getenv('HOME')); } // For >= Windows8 support if (\getenv('HOMEDRIVE') && \getenv('HOMEPATH')) { return self::canonicalize(\getenv('HOMEDRIVE') . \getenv('HOMEPATH')); } throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported."); } /** * Returns the root directory of a path. * * The result is a canonical path. * * @return string The canonical root directory. Returns an empty string if * the given path is relative or empty. */ public static function getRoot(string $path) : string { if ('' === $path) { return ''; } // Maintain scheme if (\false !== ($schemeSeparatorPosition = \strpos($path, '://'))) { $scheme = \substr($path, 0, $schemeSeparatorPosition + 3); $path = \substr($path, $schemeSeparatorPosition + 3); } else { $scheme = ''; } $firstCharacter = $path[0]; // UNIX root "/" or "\" (Windows style) if ('/' === $firstCharacter || '\\' === $firstCharacter) { return $scheme . '/'; } $length = \strlen($path); // Windows root if ($length > 1 && ':' === $path[1] && \ctype_alpha($firstCharacter)) { // Special case: "C:" if (2 === $length) { return $scheme . $path . '/'; } // Normal case: "C:/ or "C:\" if ('/' === $path[2] || '\\' === $path[2]) { return $scheme . $firstCharacter . $path[1] . '/'; } } return ''; } /** * Returns the file name without the extension from a file path. * * @param string|null $extension if specified, only that extension is cut * off (may contain leading dot) */ public static function getFilenameWithoutExtension(string $path, ?string $extension = null) : string { if ('' === $path) { return ''; } if (null !== $extension) { // remove extension and trailing dot return \rtrim(\basename($path, $extension), '.'); } return \pathinfo($path, \PATHINFO_FILENAME); } /** * Returns the extension from a file path (without leading dot). * * @param bool $forceLowerCase forces the extension to be lower-case */ public static function getExtension(string $path, bool $forceLowerCase = \false) : string { if ('' === $path) { return ''; } $extension = \pathinfo($path, \PATHINFO_EXTENSION); if ($forceLowerCase) { $extension = self::toLower($extension); } return $extension; } /** * Returns whether the path has an (or the specified) extension. * * @param string $path the path string * @param string|string[]|null $extensions if null or not provided, checks if * an extension exists, otherwise * checks for the specified extension * or array of extensions (with or * without leading dot) * @param bool $ignoreCase whether to ignore case-sensitivity */ public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = \false) : bool { if ('' === $path) { return \false; } $actualExtension = self::getExtension($path, $ignoreCase); // Only check if path has any extension if ([] === $extensions || null === $extensions) { return '' !== $actualExtension; } if (\is_string($extensions)) { $extensions = [$extensions]; } foreach ($extensions as $key => $extension) { if ($ignoreCase) { $extension = self::toLower($extension); } // remove leading '.' in extensions array $extensions[$key] = \ltrim($extension, '.'); } return \in_array($actualExtension, $extensions, \true); } /** * Changes the extension of a path string. * * @param string $path The path string with filename.ext to change. * @param string $extension new extension (with or without leading dot) * * @return string the path string with new file extension */ public static function changeExtension(string $path, string $extension) : string { if ('' === $path) { return ''; } $actualExtension = self::getExtension($path); $extension = \ltrim($extension, '.'); // No extension for paths if ('/' === \substr($path, -1)) { return $path; } // No actual extension in path if (empty($actualExtension)) { return $path . ('.' === \substr($path, -1) ? '' : '.') . $extension; } return \substr($path, 0, -\strlen($actualExtension)) . $extension; } public static function isAbsolute(string $path) : bool { if ('' === $path) { return \false; } // Strip scheme if (\false !== ($schemeSeparatorPosition = \strpos($path, '://'))) { $path = \substr($path, $schemeSeparatorPosition + 3); } $firstCharacter = $path[0]; // UNIX root "/" or "\" (Windows style) if ('/' === $firstCharacter || '\\' === $firstCharacter) { return \true; } // Windows root if (\strlen($path) > 1 && \ctype_alpha($firstCharacter) && ':' === $path[1]) { // Special case: "C:" if (2 === \strlen($path)) { return \true; } // Normal case: "C:/ or "C:\" if ('/' === $path[2] || '\\' === $path[2]) { return \true; } } return \false; } public static function isRelative(string $path) : bool { return !self::isAbsolute($path); } /** * Turns a relative path into an absolute path in canonical form. * * Usually, the relative path is appended to the given base path. Dot * segments ("." and "..") are removed/collapsed and all slashes turned * into forward slashes. * * ```php * echo Path::makeAbsolute("../style.css", "/symfony/puli/css"); * // => /symfony/puli/style.css * ``` * * If an absolute path is passed, that path is returned unless its root * directory is different than the one of the base path. In that case, an * exception is thrown. * * ```php * Path::makeAbsolute("/style.css", "/symfony/puli/css"); * // => /style.css * * Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css"); * // => C:/style.css * * Path::makeAbsolute("C:/style.css", "/symfony/puli/css"); * // InvalidArgumentException * ``` * * If the base path is not an absolute path, an exception is thrown. * * The result is a canonical path. * * @param string $basePath an absolute base path * * @throws InvalidArgumentException if the base path is not absolute or if * the given path is an absolute path with * a different root than the base path */ public static function makeAbsolute(string $path, string $basePath) : string { if ('' === $basePath) { throw new InvalidArgumentException(\sprintf('The base path must be a non-empty string. Got: "%s".', $basePath)); } if (!self::isAbsolute($basePath)) { throw new InvalidArgumentException(\sprintf('The base path "%s" is not an absolute path.', $basePath)); } if (self::isAbsolute($path)) { return self::canonicalize($path); } if (\false !== ($schemeSeparatorPosition = \strpos($basePath, '://'))) { $scheme = \substr($basePath, 0, $schemeSeparatorPosition + 3); $basePath = \substr($basePath, $schemeSeparatorPosition + 3); } else { $scheme = ''; } return $scheme . self::canonicalize(\rtrim($basePath, '/\\') . '/' . $path); } /** * Turns a path into a relative path. * * The relative path is created relative to the given base path: * * ```php * echo Path::makeRelative("/symfony/style.css", "/symfony/puli"); * // => ../style.css * ``` * * If a relative path is passed and the base path is absolute, the relative * path is returned unchanged: * * ```php * Path::makeRelative("style.css", "/symfony/puli/css"); * // => style.css * ``` * * If both paths are relative, the relative path is created with the * assumption that both paths are relative to the same directory: * * ```php * Path::makeRelative("style.css", "symfony/puli/css"); * // => ../../../style.css * ``` * * If both paths are absolute, their root directory must be the same, * otherwise an exception is thrown: * * ```php * Path::makeRelative("C:/symfony/style.css", "/symfony/puli"); * // InvalidArgumentException * ``` * * If the passed path is absolute, but the base path is not, an exception * is thrown as well: * * ```php * Path::makeRelative("/symfony/style.css", "symfony/puli"); * // InvalidArgumentException * ``` * * If the base path is not an absolute path, an exception is thrown. * * The result is a canonical path. * * @throws InvalidArgumentException if the base path is not absolute or if * the given path has a different root * than the base path */ public static function makeRelative(string $path, string $basePath) : string { $path = self::canonicalize($path); $basePath = self::canonicalize($basePath); [$root, $relativePath] = self::split($path); [$baseRoot, $relativeBasePath] = self::split($basePath); // If the base path is given as absolute path and the path is already // relative, consider it to be relative to the given absolute path // already if ('' === $root && '' !== $baseRoot) { // If base path is already in its root if ('' === $relativeBasePath) { $relativePath = \ltrim($relativePath, './\\'); } return $relativePath; } // If the passed path is absolute, but the base path is not, we // cannot generate a relative path if ('' !== $root && '' === $baseRoot) { throw new InvalidArgumentException(\sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath)); } // Fail if the roots of the two paths are different if ($baseRoot && $root !== $baseRoot) { throw new InvalidArgumentException(\sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot)); } if ('' === $relativeBasePath) { return $relativePath; } // Build a "../../" prefix with as many "../" parts as necessary $parts = \explode('/', $relativePath); $baseParts = \explode('/', $relativeBasePath); $dotDotPrefix = ''; // Once we found a non-matching part in the prefix, we need to add // "../" parts for all remaining parts $match = \true; foreach ($baseParts as $index => $basePart) { if ($match && isset($parts[$index]) && $basePart === $parts[$index]) { unset($parts[$index]); continue; } $match = \false; $dotDotPrefix .= '../'; } return \rtrim($dotDotPrefix . \implode('/', $parts), '/'); } /** * Returns whether the given path is on the local filesystem. */ public static function isLocal(string $path) : bool { return '' !== $path && \false === \strpos($path, '://'); } /** * Returns the longest common base path in canonical form of a set of paths or * `null` if the paths are on different Windows partitions. * * Dot segments ("." and "..") are removed/collapsed and all slashes turned * into forward slashes. * * ```php * $basePath = Path::getLongestCommonBasePath( * '/symfony/css/style.css', * '/symfony/css/..' * ); * // => /symfony * ``` * * The root is returned if no common base path can be found: * * ```php * $basePath = Path::getLongestCommonBasePath( * '/symfony/css/style.css', * '/puli/css/..' * ); * // => / * ``` * * If the paths are located on different Windows partitions, `null` is * returned. * * ```php * $basePath = Path::getLongestCommonBasePath( * 'C:/symfony/css/style.css', * 'D:/symfony/css/..' * ); * // => null * ``` */ public static function getLongestCommonBasePath(string ...$paths) : ?string { [$bpRoot, $basePath] = self::split(self::canonicalize(\reset($paths))); for (\next($paths); null !== \key($paths) && '' !== $basePath; \next($paths)) { [$root, $path] = self::split(self::canonicalize(\current($paths))); // If we deal with different roots (e.g. C:/ vs. D:/), it's time // to quit if ($root !== $bpRoot) { return null; } // Make the base path shorter until it fits into path while (\true) { if ('.' === $basePath) { // No more base paths $basePath = ''; // next path continue 2; } // Prevent false positives for common prefixes // see isBasePath() if (0 === \strpos($path . '/', $basePath . '/')) { // next path continue 2; } $basePath = \dirname($basePath); } } return $bpRoot . $basePath; } /** * Joins two or more path strings into a canonical path. */ public static function join(string ...$paths) : string { $finalPath = null; $wasScheme = \false; foreach ($paths as $path) { if ('' === $path) { continue; } if (null === $finalPath) { // For first part we keep slashes, like '/top', 'C:\' or 'phar://' $finalPath = $path; $wasScheme = \false !== \strpos($path, '://'); continue; } // Only add slash if previous part didn't end with '/' or '\' if (!\in_array(\substr($finalPath, -1), ['/', '\\'])) { $finalPath .= '/'; } // If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim $finalPath .= $wasScheme ? $path : \ltrim($path, '/'); $wasScheme = \false; } if (null === $finalPath) { return ''; } return self::canonicalize($finalPath); } /** * Returns whether a path is a base path of another path. * * Dot segments ("." and "..") are removed/collapsed and all slashes turned * into forward slashes. * * ```php * Path::isBasePath('/symfony', '/symfony/css'); * // => true * * Path::isBasePath('/symfony', '/symfony'); * // => true * * Path::isBasePath('/symfony', '/symfony/..'); * // => false * * Path::isBasePath('/symfony', '/puli'); * // => false * ``` */ public static function isBasePath(string $basePath, string $ofPath) : bool { $basePath = self::canonicalize($basePath); $ofPath = self::canonicalize($ofPath); // Append slashes to prevent false positives when two paths have // a common prefix, for example /base/foo and /base/foobar. // Don't append a slash for the root "/", because then that root // won't be discovered as common prefix ("//" is not a prefix of // "/foobar/"). return 0 === \strpos($ofPath . '/', \rtrim($basePath, '/') . '/'); } /** * @return string[] */ private static function findCanonicalParts(string $root, string $pathWithoutRoot) : array { $parts = \explode('/', $pathWithoutRoot); $canonicalParts = []; // Collapse "." and "..", if possible foreach ($parts as $part) { if ('.' === $part || '' === $part) { continue; } // Collapse ".." with the previous part, if one exists // Don't collapse ".." if the previous part is also ".." if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) { \array_pop($canonicalParts); continue; } // Only add ".." prefixes for relative paths if ('..' !== $part || '' === $root) { $canonicalParts[] = $part; } } return $canonicalParts; } /** * Splits a canonical path into its root directory and the remainder. * * If the path has no root directory, an empty root directory will be * returned. * * If the root directory is a Windows style partition, the resulting root * will always contain a trailing slash. * * list ($root, $path) = Path::split("C:/symfony") * // => ["C:/", "symfony"] * * list ($root, $path) = Path::split("C:") * // => ["C:/", ""] * * @return array{string, string} an array with the root directory and the remaining relative path */ private static function split(string $path) : array { if ('' === $path) { return ['', '']; } // Remember scheme as part of the root, if any if (\false !== ($schemeSeparatorPosition = \strpos($path, '://'))) { $root = \substr($path, 0, $schemeSeparatorPosition + 3); $path = \substr($path, $schemeSeparatorPosition + 3); } else { $root = ''; } $length = \strlen($path); // Remove and remember root directory if (0 === \strpos($path, '/')) { $root .= '/'; $path = $length > 1 ? \substr($path, 1) : ''; } elseif ($length > 1 && \ctype_alpha($path[0]) && ':' === $path[1]) { if (2 === $length) { // Windows special case: "C:" $root .= $path . '/'; $path = ''; } elseif ('/' === $path[2]) { // Windows normal case: "C:/".. $root .= \substr($path, 0, 3); $path = $length > 3 ? \substr($path, 3) : ''; } } return [$root, $path]; } private static function toLower(string $string) : string { if (\false !== ($encoding = \mb_detect_encoding($string, null, \true))) { return \mb_strtolower($string, $encoding); } return \strtolower($string); } private function __construct() { } } Filesystem Component ==================== The Filesystem component provides basic utilities for the filesystem. Resources --------- * [Documentation](https://symfony.com/doc/current/components/filesystem.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem\Exception; /** * IOException interface for file and input/output stream related exceptions thrown by the component. * * @author Christian Gärtner */ interface IOExceptionInterface extends ExceptionInterface { /** * Returns the associated path for the exception. * * @return string|null */ public function getPath(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem\Exception; /** * Exception interface for all exceptions thrown by the component. * * @author Romain Neutron */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem\Exception; /** * @author Théo Fidry */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem\Exception; /** * Exception class thrown when a filesystem operation failure happens. * * @author Romain Neutron * @author Christian Gärtner * @author Fabien Potencier */ class IOException extends \RuntimeException implements IOExceptionInterface { private $path; public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null) { $this->path = $path; parent::__construct($message, $code, $previous); } /** * {@inheritdoc} */ public function getPath() { return $this->path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem\Exception; /** * @author Christian Flothmann */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Filesystem\Exception; /** * Exception class thrown when a file couldn't be found. * * @author Fabien Potencier * @author Christian Gärtner */ class FileNotFoundException extends IOException { public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null) { if (null === $message) { if (null === $path) { $message = 'File could not be found.'; } else { $message = \sprintf('File "%s" could not be found.', $path); } } parent::__construct($message, $code, $previous, $path); } } { "name": "symfony\/filesystem", "type": "library", "description": "Provides basic utilities for the filesystem", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/polyfill-ctype": "~1.8", "symfony\/polyfill-mbstring": "~1.8", "symfony\/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage; use _ContaoManager\Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; /** * Token storage that uses PHP's native session handling. * * @author Bernhard Schussek */ class NativeSessionTokenStorage implements ClearableTokenStorageInterface { /** * The namespace used to store values in the session. */ public const SESSION_NAMESPACE = '_csrf'; private $sessionStarted = \false; private $namespace; /** * Initializes the storage with a session namespace. * * @param string $namespace The namespace under which the token is stored in the session */ public function __construct(string $namespace = self::SESSION_NAMESPACE) { $this->namespace = $namespace; } /** * {@inheritdoc} */ public function getToken(string $tokenId) { if (!$this->sessionStarted) { $this->startSession(); } if (!isset($_SESSION[$this->namespace][$tokenId])) { throw new TokenNotFoundException('The CSRF token with ID ' . $tokenId . ' does not exist.'); } return (string) $_SESSION[$this->namespace][$tokenId]; } /** * {@inheritdoc} */ public function setToken(string $tokenId, string $token) { if (!$this->sessionStarted) { $this->startSession(); } $_SESSION[$this->namespace][$tokenId] = $token; } /** * {@inheritdoc} */ public function hasToken(string $tokenId) { if (!$this->sessionStarted) { $this->startSession(); } return isset($_SESSION[$this->namespace][$tokenId]); } /** * {@inheritdoc} */ public function removeToken(string $tokenId) { if (!$this->sessionStarted) { $this->startSession(); } if (!isset($_SESSION[$this->namespace][$tokenId])) { return null; } $token = (string) $_SESSION[$this->namespace][$tokenId]; unset($_SESSION[$this->namespace][$tokenId]); if (!$_SESSION[$this->namespace]) { unset($_SESSION[$this->namespace]); } return $token; } /** * {@inheritdoc} */ public function clear() { unset($_SESSION[$this->namespace]); } private function startSession() { if (\PHP_SESSION_NONE === \session_status()) { \session_start(); } $this->sessionStarted = \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use _ContaoManager\Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; /** * Token storage that uses a Symfony Session object. * * @author Bernhard Schussek */ class SessionTokenStorage implements ClearableTokenStorageInterface { /** * The namespace used to store values in the session. */ public const SESSION_NAMESPACE = '_csrf'; private $requestStack; private $namespace; /** * To be removed in Symfony 6.0. */ private $session; /** * Initializes the storage with a RequestStack object and a session namespace. * * @param RequestStack $requestStack * @param string $namespace The namespace under which the token is stored in the requestStack */ public function __construct( /* RequestStack */ $requestStack, string $namespace = self::SESSION_NAMESPACE ) { if ($requestStack instanceof SessionInterface) { \trigger_deprecation('symfony/security-csrf', '5.3', 'Passing a "%s" to "%s" is deprecated, use a "%s" instead.', SessionInterface::class, __CLASS__, RequestStack::class); $request = new Request(); $request->setSession($requestStack); $requestStack = new RequestStack(); $requestStack->push($request); } $this->requestStack = $requestStack; $this->namespace = $namespace; } /** * {@inheritdoc} */ public function getToken(string $tokenId) { $session = $this->getSession(); if (!$session->isStarted()) { $session->start(); } if (!$session->has($this->namespace . '/' . $tokenId)) { throw new TokenNotFoundException('The CSRF token with ID ' . $tokenId . ' does not exist.'); } return (string) $session->get($this->namespace . '/' . $tokenId); } /** * {@inheritdoc} */ public function setToken(string $tokenId, string $token) { $session = $this->getSession(); if (!$session->isStarted()) { $session->start(); } $session->set($this->namespace . '/' . $tokenId, $token); } /** * {@inheritdoc} */ public function hasToken(string $tokenId) { $session = $this->getSession(); if (!$session->isStarted()) { $session->start(); } return $session->has($this->namespace . '/' . $tokenId); } /** * {@inheritdoc} */ public function removeToken(string $tokenId) { $session = $this->getSession(); if (!$session->isStarted()) { $session->start(); } return $session->remove($this->namespace . '/' . $tokenId); } /** * {@inheritdoc} */ public function clear() { $session = $this->getSession(); foreach (\array_keys($session->all()) as $key) { if (\str_starts_with($key, $this->namespace . '/')) { $session->remove($key); } } } private function getSession() : SessionInterface { try { return $this->session ?? $this->requestStack->getSession(); } catch (SessionNotFoundException $e) { \trigger_deprecation('symfony/security-csrf', '5.3', 'Using the "%s" without a session has no effect and is deprecated. It will throw a "%s" in Symfony 6.0', __CLASS__, SessionNotFoundException::class); return $this->session ?? ($this->session = new Session(new MockArraySessionStorage())); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage; /** * @author Christian Flothmann */ interface ClearableTokenStorageInterface extends TokenStorageInterface { /** * Removes all CSRF tokens. */ public function clear(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage; /** * Stores CSRF tokens. * * @author Bernhard Schussek */ interface TokenStorageInterface { /** * Reads a stored CSRF token. * * @return string * * @throws \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException If the token ID does not exist */ public function getToken(string $tokenId); /** * Stores a CSRF token. */ public function setToken(string $tokenId, string $token); /** * Removes a CSRF token. * * @return string|null Returns the removed token if one existed, NULL * otherwise */ public function removeToken(string $tokenId); /** * Checks whether a token with the given token ID exists. * * @return bool */ public function hasToken(string $tokenId); } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.3 --- The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf; /** * A CSRF token. * * @author Bernhard Schussek */ class CsrfToken { private $id; private $value; public function __construct(string $id, ?string $value) { $this->id = $id; $this->value = $value ?? ''; } /** * Returns the ID of the CSRF token. * * @return string */ public function getId() { return $this->id; } /** * Returns the value of the CSRF token. * * @return string */ public function getValue() { return $this->value; } /** * Returns the value of the CSRF token. * * @return string */ public function __toString() { return $this->value; } } Security Component - CSRF ========================= The Security CSRF (cross-site request forgery) component provides a class `CsrfTokenManager` for generating and validating CSRF tokens. Sponsor ------- The Security component for Symfony 5.4/6.0 is [backed][1] by [SymfonyCasts][2]. Learn Symfony faster by watching real projects being built and actively coding along with them. SymfonyCasts bridges that learning gap, bringing you video tutorials and coding challenges. Code on! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/security.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://symfonycasts.com [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf; /** * Manages CSRF tokens. * * @author Bernhard Schussek */ interface CsrfTokenManagerInterface { /** * Returns a CSRF token for the given ID. * * If previously no token existed for the given ID, a new token is * generated. Otherwise the existing token is returned (with the same value, * not the same instance). * * @param string $tokenId The token ID. You may choose an arbitrary value * for the ID * * @return CsrfToken */ public function getToken(string $tokenId); /** * Generates a new token value for the given ID. * * This method will generate a new token for the given token ID, independent * of whether a token value previously existed or not. It can be used to * enforce once-only tokens in environments with high security needs. * * @param string $tokenId The token ID. You may choose an arbitrary value * for the ID * * @return CsrfToken */ public function refreshToken(string $tokenId); /** * Invalidates the CSRF token with the given ID, if one exists. * * @return string|null Returns the removed token value if one existed, NULL * otherwise */ public function removeToken(string $tokenId); /** * Returns whether the given CSRF token is valid. * * @return bool */ public function isTokenValid(CsrfToken $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\TokenGenerator; /** * Generates CSRF tokens. * * @author Bernhard Schussek */ class UriSafeTokenGenerator implements TokenGeneratorInterface { private $entropy; /** * Generates URI-safe CSRF tokens. * * @param int $entropy The amount of entropy collected for each token (in bits) */ public function __construct(int $entropy = 256) { if ($entropy <= 7) { throw new \InvalidArgumentException('Entropy should be greater than 7.'); } $this->entropy = $entropy; } /** * {@inheritdoc} */ public function generateToken() { // Generate an URI safe base64 encoded string that does not contain "+", // "/" or "=" which need to be URL encoded and make URLs unnecessarily // longer. $bytes = \random_bytes(\intdiv($this->entropy, 8)); return \rtrim(\strtr(\base64_encode($bytes), '+/', '-_'), '='); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\TokenGenerator; /** * Generates CSRF tokens. * * @author Bernhard Schussek */ interface TokenGeneratorInterface { /** * Generates a CSRF token. * * @return string */ public function generateToken(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Security\Core\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; use _ContaoManager\Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; use _ContaoManager\Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; /** * Default implementation of {@link CsrfTokenManagerInterface}. * * @author Bernhard Schussek * @author Kévin Dunglas */ class CsrfTokenManager implements CsrfTokenManagerInterface { private $generator; private $storage; private $namespace; /** * @param string|RequestStack|callable|null $namespace * * null: generates a namespace using $_SERVER['HTTPS'] * * string: uses the given string * * RequestStack: generates a namespace using the current main request * * callable: uses the result of this callable (must return a string) */ public function __construct(?TokenGeneratorInterface $generator = null, ?TokenStorageInterface $storage = null, $namespace = null) { $this->generator = $generator ?? new UriSafeTokenGenerator(); $this->storage = $storage ?? new NativeSessionTokenStorage(); $superGlobalNamespaceGenerator = function () { return !empty($_SERVER['HTTPS']) && 'off' !== \strtolower($_SERVER['HTTPS']) ? 'https-' : ''; }; if (null === $namespace) { $this->namespace = $superGlobalNamespaceGenerator; } elseif ($namespace instanceof RequestStack) { $this->namespace = function () use($namespace, $superGlobalNamespaceGenerator) { if ($request = $namespace->getMainRequest()) { return $request->isSecure() ? 'https-' : ''; } return $superGlobalNamespaceGenerator(); }; } elseif (\is_callable($namespace) || \is_string($namespace)) { $this->namespace = $namespace; } else { throw new InvalidArgumentException(\sprintf('$namespace must be a string, a callable returning a string, null or an instance of "RequestStack". "%s" given.', \get_debug_type($namespace))); } } /** * {@inheritdoc} */ public function getToken(string $tokenId) { $namespacedId = $this->getNamespace() . $tokenId; if ($this->storage->hasToken($namespacedId)) { $value = $this->storage->getToken($namespacedId); } else { $value = $this->generator->generateToken(); $this->storage->setToken($namespacedId, $value); } return new CsrfToken($tokenId, $this->randomize($value)); } /** * {@inheritdoc} */ public function refreshToken(string $tokenId) { $namespacedId = $this->getNamespace() . $tokenId; $value = $this->generator->generateToken(); $this->storage->setToken($namespacedId, $value); return new CsrfToken($tokenId, $this->randomize($value)); } /** * {@inheritdoc} */ public function removeToken(string $tokenId) { return $this->storage->removeToken($this->getNamespace() . $tokenId); } /** * {@inheritdoc} */ public function isTokenValid(CsrfToken $token) { $namespacedId = $this->getNamespace() . $token->getId(); if (!$this->storage->hasToken($namespacedId)) { return \false; } return \hash_equals($this->storage->getToken($namespacedId), $this->derandomize($token->getValue())); } private function getNamespace() : string { return \is_callable($ns = $this->namespace) ? $ns() : $ns; } private function randomize(string $value) : string { $key = \random_bytes(32); $value = $this->xor($value, $key); return \sprintf('%s.%s.%s', \substr(\md5($key), 0, 1 + \ord($key[0]) % 32), \rtrim(\strtr(\base64_encode($key), '+/', '-_'), '='), \rtrim(\strtr(\base64_encode($value), '+/', '-_'), '=')); } private function derandomize(string $value) : string { $parts = \explode('.', $value); if (3 !== \count($parts)) { return $value; } $key = \base64_decode(\strtr($parts[1], '-_', '+/')); if ('' === $key || \false === $key) { return $value; } $value = \base64_decode(\strtr($parts[2], '-_', '+/')); return $this->xor($value, $key); } private function xor(string $value, string $key) : string { if (\strlen($value) > \strlen($key)) { $key = \str_repeat($key, \ceil(\strlen($value) / \strlen($key))); } return $value ^ $key; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Csrf\Exception; use _ContaoManager\Symfony\Component\Security\Core\Exception\RuntimeException; /** * @author Bernhard Schussek */ class TokenNotFoundException extends RuntimeException { } { "name": "symfony\/security-csrf", "type": "library", "description": "Symfony Security Component - CSRF Library", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16", "symfony\/security-core": "^4.4|^5.0|^6.0" }, "require-dev": { "symfony\/http-foundation": "^5.3|^6.0" }, "conflict": { "symfony\/http-foundation": "<5.3" }, "suggest": { "symfony\/http-foundation": "For using the class SessionTokenStorage." }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Security\\Csrf\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Token; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', PostAuthenticationGuardToken::class); /** * Used as an "authenticated" token, though it could be set to not-authenticated later. * * If you're using Guard authentication, you *must* use a class that implements * GuardTokenInterface as your authenticated token (like this class). * * @author Ryan Weaver * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface { private $providerKey; /** * @param string $providerKey The provider (firewall) key * @param string[] $roles An array of roles * * @throws \InvalidArgumentException */ public function __construct(UserInterface $user, string $providerKey, array $roles) { parent::__construct($roles); if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey (i.e. firewall key) must not be empty.'); } $this->setUser($user); $this->providerKey = $providerKey; // this token is meant to be used after authentication success, so it is always authenticated // you could set it as non authenticated later if you need to $this->setAuthenticated(\true, \false); } /** * This is meant to be only an authenticated token, where credentials * have already been used and are thus cleared. * * {@inheritdoc} */ public function getCredentials() { return []; } /** * Returns the provider (firewall) key. * * @return string */ public function getProviderKey() { return $this->providerKey; } public function getFirewallName() : string { return $this->getProviderKey(); } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->providerKey, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->providerKey, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Token; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AbstractToken; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', PreAuthenticationGuardToken::class); /** * The token used by the guard auth system before authentication. * * The GuardAuthenticationListener creates this, which is then consumed * immediately by the GuardAuthenticationProvider. If authentication is * successful, a different authenticated token is returned * * @author Ryan Weaver * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class PreAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface { private $credentials; private $guardProviderKey; /** * @param mixed $credentials * @param string $guardProviderKey Unique key that bind this token to a specific AuthenticatorInterface */ public function __construct($credentials, string $guardProviderKey) { $this->credentials = $credentials; $this->guardProviderKey = $guardProviderKey; parent::__construct([]); // @deprecated since Symfony 5.4 parent::setAuthenticated(\false); } public function getGuardProviderKey() { return $this->guardProviderKey; } /** * Returns the user credentials, which might be an array of anything you * wanted to put in there (e.g. username, password, favoriteColor). * * @return mixed */ public function getCredentials() { return $this->credentials; } /** * @deprecated since Symfony 5.4 */ public function setAuthenticated(bool $authenticated) { throw new \LogicException('The PreAuthenticationGuardToken is *never* authenticated.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Token; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardTokenInterface::class); /** * A marker interface that both guard tokens implement. * * Any tokens passed to GuardAuthenticationProvider (i.e. any tokens that * are handled by the guard auth system) must implement this * interface. * * @author Ryan Weaver * * @deprecated since Symfony 5.3, use the new authenticator system instead */ interface GuardTokenInterface extends TokenInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Guard\Token\GuardTokenInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * The interface for all "guard" authenticators. * * The methods on this interface are called throughout the guard authentication * process to give you the power to control most parts of the process from * one location. * * @author Ryan Weaver * @author Amaury Leroux de Lens * * @deprecated since Symfony 5.3, use the new authenticator system instead */ interface AuthenticatorInterface extends AuthenticationEntryPointInterface { /** * Does the authenticator support the given Request? * * If this returns false, the authenticator will be skipped. * * @return bool */ public function supports(Request $request); /** * Get the authentication credentials from the request and return them * as any type (e.g. an associate array). * * Whatever value you return here will be passed to getUser() and checkCredentials() * * For example, for a form login, you might: * * return [ * 'username' => $request->request->get('_username'), * 'password' => $request->request->get('_password'), * ]; * * Or for an API token that's on a header, you might use: * * return ['api_key' => $request->headers->get('X-API-TOKEN')]; * * @return mixed Any non-null value * * @throws \UnexpectedValueException If null is returned */ public function getCredentials(Request $request); /** * Return a UserInterface object based on the credentials. * * The *credentials* are the return value from getCredentials() * * You may throw an AuthenticationException if you wish. If you return * null, then a UserNotFoundException is thrown for you. * * @param mixed $credentials * * @return UserInterface|null * * @throws AuthenticationException */ public function getUser($credentials, UserProviderInterface $userProvider); /** * Returns true if the credentials are valid. * * If false is returned, authentication will fail. You may also throw * an AuthenticationException if you wish to cause authentication to fail. * * The *credentials* are the return value from getCredentials() * * @param mixed $credentials * * @return bool * * @throws AuthenticationException */ public function checkCredentials($credentials, UserInterface $user); /** * Create an authenticated token for the given user. * * If you don't care about which token class is used or don't really * understand what a "token" is, you can skip this method by extending * the AbstractGuardAuthenticator class from your authenticator. * * @see AbstractGuardAuthenticator * * @return GuardTokenInterface */ public function createAuthenticatedToken(UserInterface $user, string $providerKey); /** * Called when authentication executed, but failed (e.g. wrong username password). * * This should return the Response sent back to the user, like a * RedirectResponse to the login page or a 401 response. * * If you return null, the request will continue, but the user will * not be authenticated. This is probably not what you want to do. * * @return Response|null */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception); /** * Called when authentication executed and was successful! * * This should return the Response sent back to the user, like a * RedirectResponse to the last page they visited. * * If you return null, the current request will continue, and the user * will be authenticated. This makes sense, for example, with an API. * * @return Response|null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey); /** * Does this method support remember me cookies? * * Remember me cookie will be set if *all* of the following are met: * A) This method returns true * B) The remember_me key under your firewall is configured * C) The "remember me" functionality is activated. This is usually * done by having a _remember_me checkbox in your form, but * can be configured by the "always_remember_me" and "remember_me_parameter" * parameters under the "remember_me" firewall key * D) The onAuthenticationSuccess method returns a Response object * * @return bool */ public function supportsRememberMe(); } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; /** * An optional base class that creates a PostAuthenticationGuardToken for you. * * @author Ryan Weaver * * @deprecated since Symfony 5.3, use the new authenticator system instead */ abstract class AbstractGuardAuthenticator implements AuthenticatorInterface { /** * Shortcut to create a PostAuthenticationGuardToken for you, if you don't really * care about which authenticated token you're using. * * @return PostAuthenticationGuardToken */ public function createAuthenticatedToken(UserInterface $user, string $providerKey) { return new PostAuthenticationGuardToken($user, $providerKey, $user->getRoles()); } } CHANGELOG ========= 5.3 --- The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Provider; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationExpiredException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Guard\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; use _ContaoManager\Symfony\Component\Security\Guard\Token\GuardTokenInterface; use _ContaoManager\Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardAuthenticationProvider::class); /** * Responsible for accepting the PreAuthenticationGuardToken and calling * the correct authenticator to retrieve the authenticated token. * * @author Ryan Weaver * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class GuardAuthenticationProvider implements AuthenticationProviderInterface { private $guardAuthenticators; private $userProvider; private $providerKey; private $userChecker; private $passwordHasher; /** * @param iterable $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener * @param string $providerKey The provider (i.e. firewall) key * @param UserPasswordHasherInterface $passwordHasher */ public function __construct(iterable $guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker, $passwordHasher = null) { $this->guardAuthenticators = $guardAuthenticators; $this->userProvider = $userProvider; $this->providerKey = $providerKey; $this->userChecker = $userChecker; $this->passwordHasher = $passwordHasher; if ($passwordHasher instanceof UserPasswordEncoderInterface) { \trigger_deprecation('symfony/security-core', '5.3', \sprintf('Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, __CLASS__, UserPasswordHasherInterface::class)); } } /** * Finds the correct authenticator for the token and calls it. * * @param GuardTokenInterface $token * * @return TokenInterface */ public function authenticate(TokenInterface $token) { if (!$token instanceof GuardTokenInterface) { throw new \InvalidArgumentException('GuardAuthenticationProvider only supports GuardTokenInterface.'); } if (!$token instanceof PreAuthenticationGuardToken) { /* * The listener *only* passes PreAuthenticationGuardToken instances. * This means that an authenticated token (e.g. PostAuthenticationGuardToken) * is being passed here, which happens if that token becomes * "not authenticated" (e.g. happens if the user changes between * requests). In this case, the user should be logged out, so * we will return an AnonymousToken to accomplish that. */ // this should never happen - but technically, the token is // authenticated... so it could just be returned if ($token->isAuthenticated(\false)) { return $token; } // this causes the user to be logged out throw new AuthenticationExpiredException(); } $guardAuthenticator = $this->findOriginatingAuthenticator($token); if (null === $guardAuthenticator) { throw new AuthenticationException(\sprintf('Token with provider key "%s" did not originate from any of the guard authenticators of provider "%s".', $token->getGuardProviderKey(), $this->providerKey)); } return $this->authenticateViaGuard($guardAuthenticator, $token); } private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token) : GuardTokenInterface { // get the user from the GuardAuthenticator $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider); if (null === $user) { $e = new UserNotFoundException(\sprintf('Null returned from "%s::getUser()".', \get_debug_type($guardAuthenticator))); // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 $e->setUserIdentifier(\method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()); throw $e; } if (!$user instanceof UserInterface) { throw new \UnexpectedValueException(\sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', \get_debug_type($guardAuthenticator), \get_debug_type($user))); } if ($guardAuthenticator instanceof PasswordAuthenticatedInterface && !$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-guard', '5.3', 'Not implementing the "%s" interface in class "%s" while using password-based guard authenticators is deprecated.', PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $this->userChecker->checkPreAuth($user); if (\true !== ($checkCredentialsResult = $guardAuthenticator->checkCredentials($token->getCredentials(), $user))) { if (\false !== $checkCredentialsResult) { throw new \TypeError(\sprintf('"%s::checkCredentials()" must return a boolean value.', \get_debug_type($guardAuthenticator))); } throw new BadCredentialsException(\sprintf('Authentication failed because "%s::checkCredentials()" did not return true.', \get_debug_type($guardAuthenticator))); } if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordHasher && null !== ($password = $guardAuthenticator->getPassword($token->getCredentials())) && $this->passwordHasher->needsRehash($user)) { if ($this->passwordHasher instanceof UserPasswordEncoderInterface) { // @deprecated since Symfony 5.3 $this->userProvider->upgradePassword($user, $this->passwordHasher->encodePassword($user, $password)); } else { $this->userProvider->upgradePassword($user, $this->passwordHasher->hashPassword($user, $password)); } } $this->userChecker->checkPostAuth($user); // turn the UserInterface into a TokenInterface $authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $this->providerKey); if (!$authenticatedToken instanceof TokenInterface) { throw new \UnexpectedValueException(\sprintf('The "%s::createAuthenticatedToken()" method must return a TokenInterface. You returned "%s".', \get_debug_type($guardAuthenticator), \get_debug_type($authenticatedToken))); } return $authenticatedToken; } private function findOriginatingAuthenticator(PreAuthenticationGuardToken $token) : ?AuthenticatorInterface { // find the *one* GuardAuthenticator that this token originated from foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { // get a key that's unique to *this* guard authenticator // this MUST be the same as GuardAuthenticationListener $uniqueGuardKey = $this->providerKey . '_' . $key; if ($uniqueGuardKey === $token->getGuardProviderKey()) { return $guardAuthenticator; } } // no matching authenticator found - but there will be multiple GuardAuthenticationProvider // instances that will be checked if you have multiple firewalls. return null; } public function supports(TokenInterface $token) { if ($token instanceof PreAuthenticationGuardToken) { return null !== $this->findOriginatingAuthenticator($token); } return $token instanceof GuardTokenInterface; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', PasswordAuthenticatedInterface::class); /** * An optional interface for "guard" authenticators that deal with user passwords. * * @deprecated since Symfony 5.3, use the new authenticator system instead */ interface PasswordAuthenticatedInterface { /** * Returns the clear-text password contained in credentials if any. * * @param mixed $credentials The user credentials */ public function getPassword($credentials) : ?string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Firewall; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use _ContaoManager\Symfony\Component\Security\Guard\AuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use _ContaoManager\Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; use _ContaoManager\Symfony\Component\Security\Http\Firewall\AbstractListener; use _ContaoManager\Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardAuthenticationListener::class); /** * Authentication listener for the "guard" system. * * @author Ryan Weaver * @author Amaury Leroux de Lens * * @final * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class GuardAuthenticationListener extends AbstractListener { private $guardHandler; private $authenticationManager; private $providerKey; private $guardAuthenticators; private $logger; private $rememberMeServices; private $hideUserNotFoundExceptions; private $tokenStorage; /** * @param string $providerKey The provider (i.e. firewall) key * @param iterable $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider */ public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, ?LoggerInterface $logger = null, bool $hideUserNotFoundExceptions = \true, ?TokenStorageInterface $tokenStorage = null) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); } $this->guardHandler = $guardHandler; $this->authenticationManager = $authenticationManager; $this->providerKey = $providerKey; $this->guardAuthenticators = $guardAuthenticators; $this->logger = $logger; $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; $this->tokenStorage = $tokenStorage; } /** * {@inheritdoc} */ public function supports(Request $request) : ?bool { if (null !== $this->logger) { $context = ['firewall_key' => $this->providerKey]; if ($this->guardAuthenticators instanceof \Countable || \is_array($this->guardAuthenticators)) { $context['authenticators'] = \count($this->guardAuthenticators); } $this->logger->debug('Checking for guard authentication credentials.', $context); } $guardAuthenticators = []; foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { if (null !== $this->logger) { $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); } if ($guardAuthenticator->supports($request)) { $guardAuthenticators[$key] = $guardAuthenticator; } elseif (null !== $this->logger) { $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); } } if (!$guardAuthenticators) { return \false; } $request->attributes->set('_guard_authenticators', $guardAuthenticators); return \true; } /** * Iterates over each authenticator to see if each wants to authenticate the request. */ public function authenticate(RequestEvent $event) { $request = $event->getRequest(); $guardAuthenticators = $request->attributes->get('_guard_authenticators'); $request->attributes->remove('_guard_authenticators'); foreach ($guardAuthenticators as $key => $guardAuthenticator) { // get a key that's unique to *this* guard authenticator // this MUST be the same as GuardAuthenticationProvider $uniqueGuardKey = $this->providerKey . '_' . $key; $this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event); if ($event->hasResponse()) { if (null !== $this->logger) { $this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($guardAuthenticator)]); } break; } } } private function executeGuardAuthenticator(string $uniqueGuardKey, AuthenticatorInterface $guardAuthenticator, RequestEvent $event) { $request = $event->getRequest(); $previousToken = $this->tokenStorage ? $this->tokenStorage->getToken() : null; try { if (null !== $this->logger) { $this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); } // allow the authenticator to fetch authentication info from the request $credentials = $guardAuthenticator->getCredentials($request); if (null === $credentials) { throw new \UnexpectedValueException(\sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', \get_debug_type($guardAuthenticator))); } // create a token with the unique key, so that the provider knows which authenticator to use $token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey); if (null !== $this->logger) { $this->logger->debug('Passing guard token information to the GuardAuthenticationProvider', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); } // pass the token into the AuthenticationManager system // this indirectly calls GuardAuthenticationProvider::authenticate() $token = $this->authenticationManager->authenticate($token); if (null !== $this->logger) { $this->logger->info('Guard authentication successful!', ['token' => $token, 'authenticator' => \get_class($guardAuthenticator)]); } // sets the token on the token storage, etc if ($this->tokenStorage) { $this->guardHandler->authenticateWithToken($token, $request, $this->providerKey, $previousToken); } else { $this->guardHandler->authenticateWithToken($token, $request, $this->providerKey); } } catch (AuthenticationException $e) { // oh no! Authentication failed! if (null !== $this->logger) { $this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]); } // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status) // to prevent user enumeration via response content if ($this->hideUserNotFoundExceptions && ($e instanceof UsernameNotFoundException || $e instanceof AccountStatusException && !$e instanceof CustomUserMessageAccountStatusException)) { $e = new BadCredentialsException('Bad credentials.', 0, $e); } $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey); if ($response instanceof Response) { $event->setResponse($response); } return; } // success! $response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey); if ($response instanceof Response) { if (null !== $this->logger) { $this->logger->debug('Guard authenticator set success response.', ['response' => $response, 'authenticator' => \get_class($guardAuthenticator)]); } $event->setResponse($response); } else { if (null !== $this->logger) { $this->logger->debug('Guard authenticator set no success response: request continues.', ['authenticator' => \get_class($guardAuthenticator)]); } } // attempt to trigger the remember me functionality $this->triggerRememberMe($guardAuthenticator, $request, $token, $response); } /** * Should be called if this listener will support remember me. */ public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices) { $this->rememberMeServices = $rememberMeServices; } /** * Checks to see if remember me is supported in the authenticator and * on the firewall. If it is, the RememberMeServicesInterface is notified. */ private function triggerRememberMe(AuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, ?Response $response = null) { if (null === $this->rememberMeServices) { if (null !== $this->logger) { $this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($guardAuthenticator)]); } return; } if (!$guardAuthenticator->supportsRememberMe()) { if (null !== $this->logger) { $this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($guardAuthenticator)]); } return; } if (!$response instanceof Response) { throw new \LogicException(\sprintf('"%s::onAuthenticationSuccess()" *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.', \get_debug_type($guardAuthenticator))); } $this->rememberMeServices->loginSuccess($request, $response, $token); } } Security Component - Guard ========================== The Guard component brings many layers of authentication together, making it much easier to create complex authentication systems where you have total control. Sponsor ------- The Security component for Symfony 5.4/6.0 is [backed][1] by [SymfonyCasts][2]. Learn Symfony faster by watching real projects being built and actively coding along with them. SymfonyCasts bridges that learning gap, bringing you video tutorials and coding challenges. Code on! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/security.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://symfonycasts.com [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use _ContaoManager\Symfony\Component\Security\Http\SecurityEvents; use _ContaoManager\Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardAuthenticatorHandler::class); /** * A utility class that does much of the *work* during the guard authentication process. * * By having the logic here instead of the listener, more of the process * can be called directly (e.g. for manual authentication) or overridden. * * @author Ryan Weaver * * @final * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class GuardAuthenticatorHandler { private $tokenStorage; private $dispatcher; private $sessionStrategy; private $statelessProviderKeys; /** * @param array $statelessProviderKeys An array of provider/firewall keys that are "stateless" and so do not need the session migrated on success */ public function __construct(TokenStorageInterface $tokenStorage, ?EventDispatcherInterface $eventDispatcher = null, array $statelessProviderKeys = []) { $this->tokenStorage = $tokenStorage; $this->dispatcher = $eventDispatcher; $this->statelessProviderKeys = $statelessProviderKeys; } /** * Authenticates the given token in the system. */ public function authenticateWithToken(TokenInterface $token, Request $request, ?string $providerKey = null, ?TokenInterface $previousToken = null) { $this->migrateSession($request, $token, $providerKey, 3 < \func_num_args() ? $previousToken : $this->tokenStorage->getToken()); $this->tokenStorage->setToken($token); if (null !== $this->dispatcher) { $loginEvent = new InteractiveLoginEvent($request, $token); $this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } } /** * Returns the "on success" response for the given GuardAuthenticator. */ public function handleAuthenticationSuccess(TokenInterface $token, Request $request, AuthenticatorInterface $guardAuthenticator, string $providerKey) : ?Response { $response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey); // check that it's a Response or null if ($response instanceof Response || null === $response) { return $response; } throw new \UnexpectedValueException(\sprintf('The "%s::onAuthenticationSuccess()" method must return null or a Response object. You returned "%s".', \get_class($guardAuthenticator), \get_debug_type($response))); } /** * Convenience method for authenticating the user and returning the * Response *if any* for success. */ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, AuthenticatorInterface $authenticator, string $providerKey) : ?Response { // create an authenticated token for the User $token = $authenticator->createAuthenticatedToken($user, $providerKey); // authenticate this in the system $this->authenticateWithToken($token, $request, $providerKey, $this->tokenStorage->getToken()); // return the success metric return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey); } /** * Handles an authentication failure and returns the Response for the * GuardAuthenticator. */ public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $guardAuthenticator, string $providerKey) : ?Response { $response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException); if ($response instanceof Response || null === $response) { // returning null is ok, it means they want the request to continue return $response; } throw new \UnexpectedValueException(\sprintf('The "%s::onAuthenticationFailure()" method must return null or a Response object. You returned "%s".', \get_class($guardAuthenticator), \get_debug_type($response))); } /** * Call this method if your authentication token is stored to a session. * * @final */ public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy) { $this->sessionStrategy = $sessionStrategy; } private function migrateSession(Request $request, TokenInterface $token, ?string $providerKey, ?TokenInterface $previousToken) { if (\in_array($providerKey, $this->statelessProviderKeys, \true) || !$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) { return; } if ($previousToken) { $user = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $previousUser = \method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername(); if ('' !== ($user ?? '') && $user === $previousUser) { return; } } $this->sessionStrategy->onAuthentication($request, $token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Security; use _ContaoManager\Symfony\Component\Security\Guard\AbstractGuardAuthenticator; /** * A base class to make form login authentication easier! * * @author Ryan Weaver * * @deprecated since Symfony 5.3, use the new authenticator system instead */ abstract class AbstractFormLoginAuthenticator extends AbstractGuardAuthenticator { /** * Return the URL to the login page. * * @return string */ protected abstract function getLoginUrl(); /** * Override to change what happens after a bad username/password is submitted. * * @return Response */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { if ($request->hasSession()) { $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); } $url = $this->getLoginUrl(); return new RedirectResponse($url); } public function supportsRememberMe() { return \true; } /** * Override to control what happens when the user hits a secure page * but isn't logged in yet. * * @return Response */ public function start(Request $request, ?AuthenticationException $authException = null) { $url = $this->getLoginUrl(); return new RedirectResponse($url); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Guard\Authenticator; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; use _ContaoManager\Symfony\Component\Security\Guard\AuthenticatorInterface as GuardAuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\Passport; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use _ContaoManager\Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use _ContaoManager\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; \trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardBridgeAuthenticator::class); /** * This authenticator is used to bridge Guard authenticators with * the Symfony Authenticator system. * * @author Wouter de Jong * * @deprecated since Symfony 5.3 */ class GuardBridgeAuthenticator implements InteractiveAuthenticatorInterface, AuthenticationEntryPointInterface { private $guard; private $userProvider; public function __construct(GuardAuthenticatorInterface $guard, UserProviderInterface $userProvider) { $this->guard = $guard; $this->userProvider = $userProvider; } public function start(Request $request, ?AuthenticationException $authException = null) { return $this->guard->start($request, $authException); } public function supports(Request $request) : ?bool { return $this->guard->supports($request); } public function authenticate(Request $request) : PassportInterface { $credentials = $this->guard->getCredentials($request); if (null === $credentials) { throw new \UnexpectedValueException(\sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', \get_debug_type($this->guard))); } // get the user from the GuardAuthenticator if (\class_exists(UserBadge::class)) { $user = new UserBadge('guard_authenticator_' . \md5(\serialize($credentials)), function () use($credentials) { return $this->getUser($credentials); }); } else { // BC with symfony/security-http:5.1 $user = $this->getUser($credentials); } if ($this->guard instanceof PasswordAuthenticatedInterface && !$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-guard', '5.3', 'Not implementing the "%s" interface in class "%s" while using password-based guard authenticators is deprecated.', PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $passport = new Passport($user, new CustomCredentials([$this->guard, 'checkCredentials'], $credentials)); if ($this->userProvider instanceof PasswordUpgraderInterface && $this->guard instanceof PasswordAuthenticatedInterface && null !== ($password = $this->guard->getPassword($credentials))) { $passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider)); } if ($this->guard->supportsRememberMe()) { $passport->addBadge(new RememberMeBadge()); } return $passport; } public function getGuardAuthenticator() : GuardAuthenticatorInterface { return $this->guard; } private function getUser($credentials) : UserInterface { $user = $this->guard->getUser($credentials, $this->userProvider); if (null === $user) { throw new UserNotFoundException(\sprintf('Null returned from "%s::getUser()".', \get_debug_type($this->guard))); } if (!$user instanceof UserInterface) { throw new \UnexpectedValueException(\sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', \get_debug_type($this->guard), \get_debug_type($user))); } return $user; } public function createAuthenticatedToken(PassportInterface $passport, string $firewallName) : TokenInterface { if (!$passport instanceof UserPassportInterface) { throw new \LogicException(\sprintf('"%s" does not support non-user passports.', __CLASS__)); } return $this->guard->createAuthenticatedToken($passport->getUser(), $firewallName); } public function createToken(Passport $passport, string $firewallName) : TokenInterface { return $this->guard->createAuthenticatedToken($passport->getUser(), $firewallName); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName) : ?Response { return $this->guard->onAuthenticationSuccess($request, $token, $firewallName); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : ?Response { return $this->guard->onAuthenticationFailure($request, $exception); } public function isInteractive() : bool { // the GuardAuthenticationHandler always dispatches the InteractiveLoginEvent return \true; } } { "name": "symfony\/security-guard", "type": "library", "description": "Symfony Security Component - Guard", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/security-core": "^5.0", "symfony\/security-http": "^5.3", "symfony\/polyfill-php80": "^1.15" }, "require-dev": { "psr\/log": "^1|^2|^3" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Security\\Guard\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Intl\Normalizer as p; if (!function_exists('normalizer_is_normalized')) { function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); } } if (!function_exists('normalizer_normalize')) { function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); } } Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Intl\Normalizer; /** * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. * * It has been validated with Unicode 6.3 Normalization Conformance Test. * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. * * @author Nicolas Grekas * * @internal */ class Normalizer { public const FORM_D = \Normalizer::FORM_D; public const FORM_KD = \Normalizer::FORM_KD; public const FORM_C = \Normalizer::FORM_C; public const FORM_KC = \Normalizer::FORM_KC; public const NFD = \Normalizer::NFD; public const NFKD = \Normalizer::NFKD; public const NFC = \Normalizer::NFC; public const NFKC = \Normalizer::NFKC; private static $C; private static $D; private static $KD; private static $cC; private static $ulenMask = ["\xc0" => 2, "\xd0" => 2, "\xe0" => 3, "\xf0" => 4]; private static $ASCII = " eiasntrolud][cmp'\ng|hv.fb,:=-q10C2*yx)(L9AS/P\"EjMIk3>5T \PHP_VERSION_ID) { return \false; } throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); } if ('' === $s) { return ''; } if ($K && null === self::$KD) { self::$KD = self::getData('compatibilityDecomposition'); } if (null === self::$D) { self::$D = self::getData('canonicalDecomposition'); self::$cC = self::getData('combiningClass'); } if (null !== ($mbEncoding = 2 & (int) \ini_get('mbstring.func_overload') ? \mb_internal_encoding() : null)) { \mb_internal_encoding('8bit'); } $r = self::decompose($s, $K); if ($C) { if (null === self::$C) { self::$C = self::getData('canonicalComposition'); } $r = self::recompose($r); } if (null !== $mbEncoding) { \mb_internal_encoding($mbEncoding); } return $r; } private static function recompose($s) { $ASCII = self::$ASCII; $compMap = self::$C; $combClass = self::$cC; $ulenMask = self::$ulenMask; $result = $tail = ''; $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xf0"]; $len = \strlen($s); $lastUchr = \substr($s, 0, $i); $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; while ($i < $len) { if ($s[$i] < "\x80") { // ASCII chars if ($tail) { $lastUchr .= $tail; $tail = ''; } if ($j = \strspn($s, $ASCII, $i + 1)) { $lastUchr .= \substr($s, $i, $j); $i += $j; } $result .= $lastUchr; $lastUchr = $s[$i]; $lastUcls = 0; ++$i; continue; } $ulen = $ulenMask[$s[$i] & "\xf0"]; $uchr = \substr($s, $i, $ulen); if ($lastUchr < "ᄀ" || "ᄒ" < $lastUchr || $uchr < "ᅡ" || "ᅵ" < $uchr || $lastUcls) { // Table lookup and combining chars composition $ucls = $combClass[$uchr] ?? 0; if (isset($compMap[$lastUchr . $uchr]) && (!$lastUcls || $lastUcls < $ucls)) { $lastUchr = $compMap[$lastUchr . $uchr]; } elseif ($lastUcls = $ucls) { $tail .= $uchr; } else { if ($tail) { $lastUchr .= $tail; $tail = ''; } $result .= $lastUchr; $lastUchr = $uchr; } } else { // Hangul chars $L = \ord($lastUchr[2]) - 0x80; $V = \ord($uchr[2]) - 0xa1; $T = 0; $uchr = \substr($s, $i + $ulen, 3); if ("ᆧ" <= $uchr && $uchr <= "ᇂ") { $T = \ord($uchr[2]) - 0xa7; 0 > $T && ($T += 0x40); $ulen += 3; } $L = 0xac00 + ($L * 21 + $V) * 28 + $T; $lastUchr = \chr(0xe0 | $L >> 12) . \chr(0x80 | $L >> 6 & 0x3f) . \chr(0x80 | $L & 0x3f); } $i += $ulen; } return $result . $lastUchr . $tail; } private static function decompose($s, $c) { $result = ''; $ASCII = self::$ASCII; $decompMap = self::$D; $combClass = self::$cC; $ulenMask = self::$ulenMask; if ($c) { $compatMap = self::$KD; } $c = []; $i = 0; $len = \strlen($s); while ($i < $len) { if ($s[$i] < "\x80") { // ASCII chars if ($c) { \ksort($c); $result .= \implode('', $c); $c = []; } $j = 1 + \strspn($s, $ASCII, $i + 1); $result .= \substr($s, $i, $j); $i += $j; continue; } $ulen = $ulenMask[$s[$i] & "\xf0"]; $uchr = \substr($s, $i, $ulen); $i += $ulen; if ($uchr < "가" || "힣" < $uchr) { // Table lookup if ($uchr !== ($j = $compatMap[$uchr] ?? $decompMap[$uchr] ?? $uchr)) { $uchr = $j; $j = \strlen($uchr); $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xf0"]; if ($ulen != $j) { // Put trailing chars in $s $j -= $ulen; $i -= $j; if (0 > $i) { $s = \str_repeat(' ', -$i) . $s; $len -= $i; $i = 0; } while ($j--) { $s[$i + $j] = $uchr[$ulen + $j]; } $uchr = \substr($uchr, 0, $ulen); } } if (isset($combClass[$uchr])) { // Combining chars, for sorting if (!isset($c[$combClass[$uchr]])) { $c[$combClass[$uchr]] = ''; } $c[$combClass[$uchr]] .= $uchr; continue; } } else { // Hangul chars $uchr = \unpack('C*', $uchr); $j = ($uchr[1] - 224 << 12) + ($uchr[2] - 128 << 6) + $uchr[3] - 0xac80; $uchr = "\xe1\x84" . \chr(0x80 + (int) ($j / 588)) . "\xe1\x85" . \chr(0xa1 + (int) ($j % 588 / 28)); if ($j %= 28) { $uchr .= $j < 25 ? "\xe1\x86" . \chr(0xa7 + $j) : "\xe1\x87" . \chr(0x67 + $j); } } if ($c) { \ksort($c); $result .= \implode('', $c); $c = []; } $result .= $uchr; } if ($c) { \ksort($c); $result .= \implode('', $c); } return $result; } private static function getData($file) { if (\file_exists($file = __DIR__ . '/Resources/unidata/' . $file . '.php')) { return require $file; } return \false; } } 230, '́' => 230, '̂' => 230, '̃' => 230, '̄' => 230, '̅' => 230, '̆' => 230, '̇' => 230, '̈' => 230, '̉' => 230, '̊' => 230, '̋' => 230, '̌' => 230, '̍' => 230, '̎' => 230, '̏' => 230, '̐' => 230, '̑' => 230, '̒' => 230, '̓' => 230, '̔' => 230, '̕' => 232, '̖' => 220, '̗' => 220, '̘' => 220, '̙' => 220, '̚' => 232, '̛' => 216, '̜' => 220, '̝' => 220, '̞' => 220, '̟' => 220, '̠' => 220, '̡' => 202, '̢' => 202, '̣' => 220, '̤' => 220, '̥' => 220, '̦' => 220, '̧' => 202, '̨' => 202, '̩' => 220, '̪' => 220, '̫' => 220, '̬' => 220, '̭' => 220, '̮' => 220, '̯' => 220, '̰' => 220, '̱' => 220, '̲' => 220, '̳' => 220, '̴' => 1, '̵' => 1, '̶' => 1, '̷' => 1, '̸' => 1, '̹' => 220, '̺' => 220, '̻' => 220, '̼' => 220, '̽' => 230, '̾' => 230, '̿' => 230, '̀' => 230, '́' => 230, '͂' => 230, '̓' => 230, '̈́' => 230, 'ͅ' => 240, '͆' => 230, '͇' => 220, '͈' => 220, '͉' => 220, '͊' => 230, '͋' => 230, '͌' => 230, '͍' => 220, '͎' => 220, '͐' => 230, '͑' => 230, '͒' => 230, '͓' => 220, '͔' => 220, '͕' => 220, '͖' => 220, '͗' => 230, '͘' => 232, '͙' => 220, '͚' => 220, '͛' => 230, '͜' => 233, '͝' => 234, '͞' => 234, '͟' => 233, '͠' => 234, '͡' => 234, '͢' => 233, 'ͣ' => 230, 'ͤ' => 230, 'ͥ' => 230, 'ͦ' => 230, 'ͧ' => 230, 'ͨ' => 230, 'ͩ' => 230, 'ͪ' => 230, 'ͫ' => 230, 'ͬ' => 230, 'ͭ' => 230, 'ͮ' => 230, 'ͯ' => 230, '҃' => 230, '҄' => 230, '҅' => 230, '҆' => 230, '҇' => 230, '֑' => 220, '֒' => 230, '֓' => 230, '֔' => 230, '֕' => 230, '֖' => 220, '֗' => 230, '֘' => 230, '֙' => 230, '֚' => 222, '֛' => 220, '֜' => 230, '֝' => 230, '֞' => 230, '֟' => 230, '֠' => 230, '֡' => 230, '֢' => 220, '֣' => 220, '֤' => 220, '֥' => 220, '֦' => 220, '֧' => 220, '֨' => 230, '֩' => 230, '֪' => 220, '֫' => 230, '֬' => 230, '֭' => 222, '֮' => 228, '֯' => 230, 'ְ' => 10, 'ֱ' => 11, 'ֲ' => 12, 'ֳ' => 13, 'ִ' => 14, 'ֵ' => 15, 'ֶ' => 16, 'ַ' => 17, 'ָ' => 18, 'ֹ' => 19, 'ֺ' => 19, 'ֻ' => 20, 'ּ' => 21, 'ֽ' => 22, 'ֿ' => 23, 'ׁ' => 24, 'ׂ' => 25, 'ׄ' => 230, 'ׅ' => 220, 'ׇ' => 18, 'ؐ' => 230, 'ؑ' => 230, 'ؒ' => 230, 'ؓ' => 230, 'ؔ' => 230, 'ؕ' => 230, 'ؖ' => 230, 'ؗ' => 230, 'ؘ' => 30, 'ؙ' => 31, 'ؚ' => 32, 'ً' => 27, 'ٌ' => 28, 'ٍ' => 29, 'َ' => 30, 'ُ' => 31, 'ِ' => 32, 'ّ' => 33, 'ْ' => 34, 'ٓ' => 230, 'ٔ' => 230, 'ٕ' => 220, 'ٖ' => 220, 'ٗ' => 230, '٘' => 230, 'ٙ' => 230, 'ٚ' => 230, 'ٛ' => 230, 'ٜ' => 220, 'ٝ' => 230, 'ٞ' => 230, 'ٟ' => 220, 'ٰ' => 35, 'ۖ' => 230, 'ۗ' => 230, 'ۘ' => 230, 'ۙ' => 230, 'ۚ' => 230, 'ۛ' => 230, 'ۜ' => 230, '۟' => 230, '۠' => 230, 'ۡ' => 230, 'ۢ' => 230, 'ۣ' => 220, 'ۤ' => 230, 'ۧ' => 230, 'ۨ' => 230, '۪' => 220, '۫' => 230, '۬' => 230, 'ۭ' => 220, 'ܑ' => 36, 'ܰ' => 230, 'ܱ' => 220, 'ܲ' => 230, 'ܳ' => 230, 'ܴ' => 220, 'ܵ' => 230, 'ܶ' => 230, 'ܷ' => 220, 'ܸ' => 220, 'ܹ' => 220, 'ܺ' => 230, 'ܻ' => 220, 'ܼ' => 220, 'ܽ' => 230, 'ܾ' => 220, 'ܿ' => 230, '݀' => 230, '݁' => 230, '݂' => 220, '݃' => 230, '݄' => 220, '݅' => 230, '݆' => 220, '݇' => 230, '݈' => 220, '݉' => 230, '݊' => 230, '߫' => 230, '߬' => 230, '߭' => 230, '߮' => 230, '߯' => 230, '߰' => 230, '߱' => 230, '߲' => 220, '߳' => 230, '߽' => 220, 'ࠖ' => 230, 'ࠗ' => 230, '࠘' => 230, '࠙' => 230, 'ࠛ' => 230, 'ࠜ' => 230, 'ࠝ' => 230, 'ࠞ' => 230, 'ࠟ' => 230, 'ࠠ' => 230, 'ࠡ' => 230, 'ࠢ' => 230, 'ࠣ' => 230, 'ࠥ' => 230, 'ࠦ' => 230, 'ࠧ' => 230, 'ࠩ' => 230, 'ࠪ' => 230, 'ࠫ' => 230, 'ࠬ' => 230, '࠭' => 230, '࡙' => 220, '࡚' => 220, '࡛' => 220, '࣓' => 220, 'ࣔ' => 230, 'ࣕ' => 230, 'ࣖ' => 230, 'ࣗ' => 230, 'ࣘ' => 230, 'ࣙ' => 230, 'ࣚ' => 230, 'ࣛ' => 230, 'ࣜ' => 230, 'ࣝ' => 230, 'ࣞ' => 230, 'ࣟ' => 230, '࣠' => 230, '࣡' => 230, 'ࣣ' => 220, 'ࣤ' => 230, 'ࣥ' => 230, 'ࣦ' => 220, 'ࣧ' => 230, 'ࣨ' => 230, 'ࣩ' => 220, '࣪' => 230, '࣫' => 230, '࣬' => 230, '࣭' => 220, '࣮' => 220, '࣯' => 220, 'ࣰ' => 27, 'ࣱ' => 28, 'ࣲ' => 29, 'ࣳ' => 230, 'ࣴ' => 230, 'ࣵ' => 230, 'ࣶ' => 220, 'ࣷ' => 230, 'ࣸ' => 230, 'ࣹ' => 220, 'ࣺ' => 220, 'ࣻ' => 230, 'ࣼ' => 230, 'ࣽ' => 230, 'ࣾ' => 230, 'ࣿ' => 230, '़' => 7, '्' => 9, '॑' => 230, '॒' => 220, '॓' => 230, '॔' => 230, '়' => 7, '্' => 9, '৾' => 230, '਼' => 7, '੍' => 9, '઼' => 7, '્' => 9, '଼' => 7, '୍' => 9, '்' => 9, '్' => 9, 'ౕ' => 84, 'ౖ' => 91, '಼' => 7, '್' => 9, '഻' => 9, '഼' => 9, '്' => 9, '්' => 9, 'ุ' => 103, 'ู' => 103, 'ฺ' => 9, '่' => 107, '้' => 107, '๊' => 107, '๋' => 107, 'ຸ' => 118, 'ູ' => 118, '຺' => 9, '່' => 122, '້' => 122, '໊' => 122, '໋' => 122, '༘' => 220, '༙' => 220, '༵' => 220, '༷' => 220, '༹' => 216, 'ཱ' => 129, 'ི' => 130, 'ུ' => 132, 'ེ' => 130, 'ཻ' => 130, 'ོ' => 130, 'ཽ' => 130, 'ྀ' => 130, 'ྂ' => 230, 'ྃ' => 230, '྄' => 9, '྆' => 230, '྇' => 230, '࿆' => 220, '့' => 7, '္' => 9, '်' => 9, 'ႍ' => 220, '፝' => 230, '፞' => 230, '፟' => 230, '᜔' => 9, '᜴' => 9, '្' => 9, '៝' => 230, 'ᢩ' => 228, '᤹' => 222, '᤺' => 230, '᤻' => 220, 'ᨗ' => 230, 'ᨘ' => 220, '᩠' => 9, '᩵' => 230, '᩶' => 230, '᩷' => 230, '᩸' => 230, '᩹' => 230, '᩺' => 230, '᩻' => 230, '᩼' => 230, '᩿' => 220, '᪰' => 230, '᪱' => 230, '᪲' => 230, '᪳' => 230, '᪴' => 230, '᪵' => 220, '᪶' => 220, '᪷' => 220, '᪸' => 220, '᪹' => 220, '᪺' => 220, '᪻' => 230, '᪼' => 230, '᪽' => 220, 'ᪿ' => 220, 'ᫀ' => 220, '᬴' => 7, '᭄' => 9, '᭫' => 230, '᭬' => 220, '᭭' => 230, '᭮' => 230, '᭯' => 230, '᭰' => 230, '᭱' => 230, '᭲' => 230, '᭳' => 230, '᮪' => 9, '᮫' => 9, '᯦' => 7, '᯲' => 9, '᯳' => 9, '᰷' => 7, '᳐' => 230, '᳑' => 230, '᳒' => 230, '᳔' => 1, '᳕' => 220, '᳖' => 220, '᳗' => 220, '᳘' => 220, '᳙' => 220, '᳚' => 230, '᳛' => 230, '᳜' => 220, '᳝' => 220, '᳞' => 220, '᳟' => 220, '᳠' => 230, '᳢' => 1, '᳣' => 1, '᳤' => 1, '᳥' => 1, '᳦' => 1, '᳧' => 1, '᳨' => 1, '᳭' => 220, '᳴' => 230, '᳸' => 230, '᳹' => 230, '᷀' => 230, '᷁' => 230, '᷂' => 220, '᷃' => 230, '᷄' => 230, '᷅' => 230, '᷆' => 230, '᷇' => 230, '᷈' => 230, '᷉' => 230, '᷊' => 220, '᷋' => 230, '᷌' => 230, '᷍' => 234, '᷎' => 214, '᷏' => 220, '᷐' => 202, '᷑' => 230, '᷒' => 230, 'ᷓ' => 230, 'ᷔ' => 230, 'ᷕ' => 230, 'ᷖ' => 230, 'ᷗ' => 230, 'ᷘ' => 230, 'ᷙ' => 230, 'ᷚ' => 230, 'ᷛ' => 230, 'ᷜ' => 230, 'ᷝ' => 230, 'ᷞ' => 230, 'ᷟ' => 230, 'ᷠ' => 230, 'ᷡ' => 230, 'ᷢ' => 230, 'ᷣ' => 230, 'ᷤ' => 230, 'ᷥ' => 230, 'ᷦ' => 230, 'ᷧ' => 230, 'ᷨ' => 230, 'ᷩ' => 230, 'ᷪ' => 230, 'ᷫ' => 230, 'ᷬ' => 230, 'ᷭ' => 230, 'ᷮ' => 230, 'ᷯ' => 230, 'ᷰ' => 230, 'ᷱ' => 230, 'ᷲ' => 230, 'ᷳ' => 230, 'ᷴ' => 230, '᷵' => 230, '᷶' => 232, '᷷' => 228, '᷸' => 228, '᷹' => 220, '᷻' => 230, '᷼' => 233, '᷽' => 220, '᷾' => 230, '᷿' => 220, '⃐' => 230, '⃑' => 230, '⃒' => 1, '⃓' => 1, '⃔' => 230, '⃕' => 230, '⃖' => 230, '⃗' => 230, '⃘' => 1, '⃙' => 1, '⃚' => 1, '⃛' => 230, '⃜' => 230, '⃡' => 230, '⃥' => 1, '⃦' => 1, '⃧' => 230, '⃨' => 220, '⃩' => 230, '⃪' => 1, '⃫' => 1, '⃬' => 220, '⃭' => 220, '⃮' => 220, '⃯' => 220, '⃰' => 230, '⳯' => 230, '⳰' => 230, '⳱' => 230, '⵿' => 9, 'ⷠ' => 230, 'ⷡ' => 230, 'ⷢ' => 230, 'ⷣ' => 230, 'ⷤ' => 230, 'ⷥ' => 230, 'ⷦ' => 230, 'ⷧ' => 230, 'ⷨ' => 230, 'ⷩ' => 230, 'ⷪ' => 230, 'ⷫ' => 230, 'ⷬ' => 230, 'ⷭ' => 230, 'ⷮ' => 230, 'ⷯ' => 230, 'ⷰ' => 230, 'ⷱ' => 230, 'ⷲ' => 230, 'ⷳ' => 230, 'ⷴ' => 230, 'ⷵ' => 230, 'ⷶ' => 230, 'ⷷ' => 230, 'ⷸ' => 230, 'ⷹ' => 230, 'ⷺ' => 230, 'ⷻ' => 230, 'ⷼ' => 230, 'ⷽ' => 230, 'ⷾ' => 230, 'ⷿ' => 230, '〪' => 218, '〫' => 228, '〬' => 232, '〭' => 222, '〮' => 224, '〯' => 224, '゙' => 8, '゚' => 8, '꙯' => 230, 'ꙴ' => 230, 'ꙵ' => 230, 'ꙶ' => 230, 'ꙷ' => 230, 'ꙸ' => 230, 'ꙹ' => 230, 'ꙺ' => 230, 'ꙻ' => 230, '꙼' => 230, '꙽' => 230, 'ꚞ' => 230, 'ꚟ' => 230, '꛰' => 230, '꛱' => 230, '꠆' => 9, '꠬' => 9, '꣄' => 9, '꣠' => 230, '꣡' => 230, '꣢' => 230, '꣣' => 230, '꣤' => 230, '꣥' => 230, '꣦' => 230, '꣧' => 230, '꣨' => 230, '꣩' => 230, '꣪' => 230, '꣫' => 230, '꣬' => 230, '꣭' => 230, '꣮' => 230, '꣯' => 230, '꣰' => 230, '꣱' => 230, '꤫' => 220, '꤬' => 220, '꤭' => 220, '꥓' => 9, '꦳' => 7, '꧀' => 9, 'ꪰ' => 230, 'ꪲ' => 230, 'ꪳ' => 230, 'ꪴ' => 220, 'ꪷ' => 230, 'ꪸ' => 230, 'ꪾ' => 230, '꪿' => 230, '꫁' => 230, '꫶' => 9, '꯭' => 9, 'ﬞ' => 26, '︠' => 230, '︡' => 230, '︢' => 230, '︣' => 230, '︤' => 230, '︥' => 230, '︦' => 230, '︧' => 220, '︨' => 220, '︩' => 220, '︪' => 220, '︫' => 220, '︬' => 220, '︭' => 220, '︮' => 230, '︯' => 230, '𐇽' => 220, '𐋠' => 220, '𐍶' => 230, '𐍷' => 230, '𐍸' => 230, '𐍹' => 230, '𐍺' => 230, '𐨍' => 220, '𐨏' => 230, '𐨸' => 230, '𐨹' => 1, '𐨺' => 220, '𐨿' => 9, '𐫥' => 230, '𐫦' => 220, '𐴤' => 230, '𐴥' => 230, '𐴦' => 230, '𐴧' => 230, '𐺫' => 230, '𐺬' => 230, '𐽆' => 220, '𐽇' => 220, '𐽈' => 230, '𐽉' => 230, '𐽊' => 230, '𐽋' => 220, '𐽌' => 230, '𐽍' => 220, '𐽎' => 220, '𐽏' => 220, '𐽐' => 220, '𑁆' => 9, '𑁿' => 9, '𑂹' => 9, '𑂺' => 7, '𑄀' => 230, '𑄁' => 230, '𑄂' => 230, '𑄳' => 9, '𑄴' => 9, '𑅳' => 7, '𑇀' => 9, '𑇊' => 7, '𑈵' => 9, '𑈶' => 7, '𑋩' => 7, '𑋪' => 9, '𑌻' => 7, '𑌼' => 7, '𑍍' => 9, '𑍦' => 230, '𑍧' => 230, '𑍨' => 230, '𑍩' => 230, '𑍪' => 230, '𑍫' => 230, '𑍬' => 230, '𑍰' => 230, '𑍱' => 230, '𑍲' => 230, '𑍳' => 230, '𑍴' => 230, '𑑂' => 9, '𑑆' => 7, '𑑞' => 230, '𑓂' => 9, '𑓃' => 7, '𑖿' => 9, '𑗀' => 7, '𑘿' => 9, '𑚶' => 9, '𑚷' => 7, '𑜫' => 9, '𑠹' => 9, '𑠺' => 7, '𑤽' => 9, '𑤾' => 9, '𑥃' => 7, '𑧠' => 9, '𑨴' => 9, '𑩇' => 9, '𑪙' => 9, '𑰿' => 9, '𑵂' => 7, '𑵄' => 9, '𑵅' => 9, '𑶗' => 9, '𖫰' => 1, '𖫱' => 1, '𖫲' => 1, '𖫳' => 1, '𖫴' => 1, '𖬰' => 230, '𖬱' => 230, '𖬲' => 230, '𖬳' => 230, '𖬴' => 230, '𖬵' => 230, '𖬶' => 230, '𖿰' => 6, '𖿱' => 6, '𛲞' => 1, '𝅥' => 216, '𝅦' => 216, '𝅧' => 1, '𝅨' => 1, '𝅩' => 1, '𝅭' => 226, '𝅮' => 216, '𝅯' => 216, '𝅰' => 216, '𝅱' => 216, '𝅲' => 216, '𝅻' => 220, '𝅼' => 220, '𝅽' => 220, '𝅾' => 220, '𝅿' => 220, '𝆀' => 220, '𝆁' => 220, '𝆂' => 220, '𝆅' => 230, '𝆆' => 230, '𝆇' => 230, '𝆈' => 230, '𝆉' => 230, '𝆊' => 220, '𝆋' => 220, '𝆪' => 230, '𝆫' => 230, '𝆬' => 230, '𝆭' => 230, '𝉂' => 230, '𝉃' => 230, '𝉄' => 230, '𞀀' => 230, '𞀁' => 230, '𞀂' => 230, '𞀃' => 230, '𞀄' => 230, '𞀅' => 230, '𞀆' => 230, '𞀈' => 230, '𞀉' => 230, '𞀊' => 230, '𞀋' => 230, '𞀌' => 230, '𞀍' => 230, '𞀎' => 230, '𞀏' => 230, '𞀐' => 230, '𞀑' => 230, '𞀒' => 230, '𞀓' => 230, '𞀔' => 230, '𞀕' => 230, '𞀖' => 230, '𞀗' => 230, '𞀘' => 230, '𞀛' => 230, '𞀜' => 230, '𞀝' => 230, '𞀞' => 230, '𞀟' => 230, '𞀠' => 230, '𞀡' => 230, '𞀣' => 230, '𞀤' => 230, '𞀦' => 230, '𞀧' => 230, '𞀨' => 230, '𞀩' => 230, '𞀪' => 230, '𞄰' => 230, '𞄱' => 230, '𞄲' => 230, '𞄳' => 230, '𞄴' => 230, '𞄵' => 230, '𞄶' => 230, '𞋬' => 230, '𞋭' => 230, '𞋮' => 230, '𞋯' => 230, '𞣐' => 220, '𞣑' => 220, '𞣒' => 220, '𞣓' => 220, '𞣔' => 220, '𞣕' => 220, '𞣖' => 220, '𞥄' => 230, '𞥅' => 230, '𞥆' => 230, '𞥇' => 230, '𞥈' => 230, '𞥉' => 230, '𞥊' => 7); 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '̀' => '̀', '́' => '́', '̓' => '̓', '̈́' => '̈́', 'ʹ' => 'ʹ', ';' => ';', '΅' => '΅', 'Ά' => 'Ά', '·' => '·', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'क़' => 'क़', 'ख़' => 'ख़', 'ग़' => 'ग़', 'ज़' => 'ज़', 'ड़' => 'ड़', 'ढ़' => 'ढ़', 'फ़' => 'फ़', 'य़' => 'य़', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ড়' => 'ড়', 'ঢ়' => 'ঢ়', 'য়' => 'য়', 'ਲ਼' => 'ਲ਼', 'ਸ਼' => 'ਸ਼', 'ਖ਼' => 'ਖ਼', 'ਗ਼' => 'ਗ਼', 'ਜ਼' => 'ਜ਼', 'ਫ਼' => 'ਫ਼', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ଡ଼' => 'ଡ଼', 'ଢ଼' => 'ଢ଼', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'གྷ' => 'གྷ', 'ཌྷ' => 'ཌྷ', 'དྷ' => 'དྷ', 'བྷ' => 'བྷ', 'ཛྷ' => 'ཛྷ', 'ཀྵ' => 'ཀྵ', 'ཱི' => 'ཱི', 'ཱུ' => 'ཱུ', 'ྲྀ' => 'ྲྀ', 'ླྀ' => 'ླྀ', 'ཱྀ' => 'ཱྀ', 'ྒྷ' => 'ྒྷ', 'ྜྷ' => 'ྜྷ', 'ྡྷ' => 'ྡྷ', 'ྦྷ' => 'ྦྷ', 'ྫྷ' => 'ྫྷ', 'ྐྵ' => 'ྐྵ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ά' => 'ά', 'ὲ' => 'ὲ', 'έ' => 'έ', 'ὴ' => 'ὴ', 'ή' => 'ή', 'ὶ' => 'ὶ', 'ί' => 'ί', 'ὸ' => 'ὸ', 'ό' => 'ό', 'ὺ' => 'ὺ', 'ύ' => 'ύ', 'ὼ' => 'ὼ', 'ώ' => 'ώ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'Ά' => 'Ά', 'ᾼ' => 'ᾼ', 'ι' => 'ι', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Έ' => 'Έ', 'Ὴ' => 'Ὴ', 'Ή' => 'Ή', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', 'Ί' => 'Ί', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ύ' => 'Ύ', 'Ῥ' => 'Ῥ', '῭' => '῭', '΅' => '΅', '`' => '`', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ό' => 'Ό', 'Ὼ' => 'Ὼ', 'Ώ' => 'Ώ', 'ῼ' => 'ῼ', '´' => '´', ' ' => ' ', ' ' => ' ', 'Ω' => 'Ω', 'K' => 'K', 'Å' => 'Å', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', '〈' => '〈', '〉' => '〉', '⫝̸' => '⫝̸', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '豈' => '豈', '更' => '更', '車' => '車', '賈' => '賈', '滑' => '滑', '串' => '串', '句' => '句', '龜' => '龜', '龜' => '龜', '契' => '契', '金' => '金', '喇' => '喇', '奈' => '奈', '懶' => '懶', '癩' => '癩', '羅' => '羅', '蘿' => '蘿', '螺' => '螺', '裸' => '裸', '邏' => '邏', '樂' => '樂', '洛' => '洛', '烙' => '烙', '珞' => '珞', '落' => '落', '酪' => '酪', '駱' => '駱', '亂' => '亂', '卵' => '卵', '欄' => '欄', '爛' => '爛', '蘭' => '蘭', '鸞' => '鸞', '嵐' => '嵐', '濫' => '濫', '藍' => '藍', '襤' => '襤', '拉' => '拉', '臘' => '臘', '蠟' => '蠟', '廊' => '廊', '朗' => '朗', '浪' => '浪', '狼' => '狼', '郎' => '郎', '來' => '來', '冷' => '冷', '勞' => '勞', '擄' => '擄', '櫓' => '櫓', '爐' => '爐', '盧' => '盧', '老' => '老', '蘆' => '蘆', '虜' => '虜', '路' => '路', '露' => '露', '魯' => '魯', '鷺' => '鷺', '碌' => '碌', '祿' => '祿', '綠' => '綠', '菉' => '菉', '錄' => '錄', '鹿' => '鹿', '論' => '論', '壟' => '壟', '弄' => '弄', '籠' => '籠', '聾' => '聾', '牢' => '牢', '磊' => '磊', '賂' => '賂', '雷' => '雷', '壘' => '壘', '屢' => '屢', '樓' => '樓', '淚' => '淚', '漏' => '漏', '累' => '累', '縷' => '縷', '陋' => '陋', '勒' => '勒', '肋' => '肋', '凜' => '凜', '凌' => '凌', '稜' => '稜', '綾' => '綾', '菱' => '菱', '陵' => '陵', '讀' => '讀', '拏' => '拏', '樂' => '樂', '諾' => '諾', '丹' => '丹', '寧' => '寧', '怒' => '怒', '率' => '率', '異' => '異', '北' => '北', '磻' => '磻', '便' => '便', '復' => '復', '不' => '不', '泌' => '泌', '數' => '數', '索' => '索', '參' => '參', '塞' => '塞', '省' => '省', '葉' => '葉', '說' => '說', '殺' => '殺', '辰' => '辰', '沈' => '沈', '拾' => '拾', '若' => '若', '掠' => '掠', '略' => '略', '亮' => '亮', '兩' => '兩', '凉' => '凉', '梁' => '梁', '糧' => '糧', '良' => '良', '諒' => '諒', '量' => '量', '勵' => '勵', '呂' => '呂', '女' => '女', '廬' => '廬', '旅' => '旅', '濾' => '濾', '礪' => '礪', '閭' => '閭', '驪' => '驪', '麗' => '麗', '黎' => '黎', '力' => '力', '曆' => '曆', '歷' => '歷', '轢' => '轢', '年' => '年', '憐' => '憐', '戀' => '戀', '撚' => '撚', '漣' => '漣', '煉' => '煉', '璉' => '璉', '秊' => '秊', '練' => '練', '聯' => '聯', '輦' => '輦', '蓮' => '蓮', '連' => '連', '鍊' => '鍊', '列' => '列', '劣' => '劣', '咽' => '咽', '烈' => '烈', '裂' => '裂', '說' => '說', '廉' => '廉', '念' => '念', '捻' => '捻', '殮' => '殮', '簾' => '簾', '獵' => '獵', '令' => '令', '囹' => '囹', '寧' => '寧', '嶺' => '嶺', '怜' => '怜', '玲' => '玲', '瑩' => '瑩', '羚' => '羚', '聆' => '聆', '鈴' => '鈴', '零' => '零', '靈' => '靈', '領' => '領', '例' => '例', '禮' => '禮', '醴' => '醴', '隸' => '隸', '惡' => '惡', '了' => '了', '僚' => '僚', '寮' => '寮', '尿' => '尿', '料' => '料', '樂' => '樂', '燎' => '燎', '療' => '療', '蓼' => '蓼', '遼' => '遼', '龍' => '龍', '暈' => '暈', '阮' => '阮', '劉' => '劉', '杻' => '杻', '柳' => '柳', '流' => '流', '溜' => '溜', '琉' => '琉', '留' => '留', '硫' => '硫', '紐' => '紐', '類' => '類', '六' => '六', '戮' => '戮', '陸' => '陸', '倫' => '倫', '崙' => '崙', '淪' => '淪', '輪' => '輪', '律' => '律', '慄' => '慄', '栗' => '栗', '率' => '率', '隆' => '隆', '利' => '利', '吏' => '吏', '履' => '履', '易' => '易', '李' => '李', '梨' => '梨', '泥' => '泥', '理' => '理', '痢' => '痢', '罹' => '罹', '裏' => '裏', '裡' => '裡', '里' => '里', '離' => '離', '匿' => '匿', '溺' => '溺', '吝' => '吝', '燐' => '燐', '璘' => '璘', '藺' => '藺', '隣' => '隣', '鱗' => '鱗', '麟' => '麟', '林' => '林', '淋' => '淋', '臨' => '臨', '立' => '立', '笠' => '笠', '粒' => '粒', '狀' => '狀', '炙' => '炙', '識' => '識', '什' => '什', '茶' => '茶', '刺' => '刺', '切' => '切', '度' => '度', '拓' => '拓', '糖' => '糖', '宅' => '宅', '洞' => '洞', '暴' => '暴', '輻' => '輻', '行' => '行', '降' => '降', '見' => '見', '廓' => '廓', '兀' => '兀', '嗀' => '嗀', '塚' => '塚', '晴' => '晴', '凞' => '凞', '猪' => '猪', '益' => '益', '礼' => '礼', '神' => '神', '祥' => '祥', '福' => '福', '靖' => '靖', '精' => '精', '羽' => '羽', '蘒' => '蘒', '諸' => '諸', '逸' => '逸', '都' => '都', '飯' => '飯', '飼' => '飼', '館' => '館', '鶴' => '鶴', '郞' => '郞', '隷' => '隷', '侮' => '侮', '僧' => '僧', '免' => '免', '勉' => '勉', '勤' => '勤', '卑' => '卑', '喝' => '喝', '嘆' => '嘆', '器' => '器', '塀' => '塀', '墨' => '墨', '層' => '層', '屮' => '屮', '悔' => '悔', '慨' => '慨', '憎' => '憎', '懲' => '懲', '敏' => '敏', '既' => '既', '暑' => '暑', '梅' => '梅', '海' => '海', '渚' => '渚', '漢' => '漢', '煮' => '煮', '爫' => '爫', '琢' => '琢', '碑' => '碑', '社' => '社', '祉' => '祉', '祈' => '祈', '祐' => '祐', '祖' => '祖', '祝' => '祝', '禍' => '禍', '禎' => '禎', '穀' => '穀', '突' => '突', '節' => '節', '練' => '練', '縉' => '縉', '繁' => '繁', '署' => '署', '者' => '者', '臭' => '臭', '艹' => '艹', '艹' => '艹', '著' => '著', '褐' => '褐', '視' => '視', '謁' => '謁', '謹' => '謹', '賓' => '賓', '贈' => '贈', '辶' => '辶', '逸' => '逸', '難' => '難', '響' => '響', '頻' => '頻', '恵' => '恵', '𤋮' => '𤋮', '舘' => '舘', '並' => '並', '况' => '况', '全' => '全', '侀' => '侀', '充' => '充', '冀' => '冀', '勇' => '勇', '勺' => '勺', '喝' => '喝', '啕' => '啕', '喙' => '喙', '嗢' => '嗢', '塚' => '塚', '墳' => '墳', '奄' => '奄', '奔' => '奔', '婢' => '婢', '嬨' => '嬨', '廒' => '廒', '廙' => '廙', '彩' => '彩', '徭' => '徭', '惘' => '惘', '慎' => '慎', '愈' => '愈', '憎' => '憎', '慠' => '慠', '懲' => '懲', '戴' => '戴', '揄' => '揄', '搜' => '搜', '摒' => '摒', '敖' => '敖', '晴' => '晴', '朗' => '朗', '望' => '望', '杖' => '杖', '歹' => '歹', '殺' => '殺', '流' => '流', '滛' => '滛', '滋' => '滋', '漢' => '漢', '瀞' => '瀞', '煮' => '煮', '瞧' => '瞧', '爵' => '爵', '犯' => '犯', '猪' => '猪', '瑱' => '瑱', '甆' => '甆', '画' => '画', '瘝' => '瘝', '瘟' => '瘟', '益' => '益', '盛' => '盛', '直' => '直', '睊' => '睊', '着' => '着', '磌' => '磌', '窱' => '窱', '節' => '節', '类' => '类', '絛' => '絛', '練' => '練', '缾' => '缾', '者' => '者', '荒' => '荒', '華' => '華', '蝹' => '蝹', '襁' => '襁', '覆' => '覆', '視' => '視', '調' => '調', '諸' => '諸', '請' => '請', '謁' => '謁', '諾' => '諾', '諭' => '諭', '謹' => '謹', '變' => '變', '贈' => '贈', '輸' => '輸', '遲' => '遲', '醙' => '醙', '鉶' => '鉶', '陼' => '陼', '難' => '難', '靖' => '靖', '韛' => '韛', '響' => '響', '頋' => '頋', '頻' => '頻', '鬒' => '鬒', '龜' => '龜', '𢡊' => '𢡊', '𢡄' => '𢡄', '𣏕' => '𣏕', '㮝' => '㮝', '䀘' => '䀘', '䀹' => '䀹', '𥉉' => '𥉉', '𥳐' => '𥳐', '𧻓' => '𧻓', '齃' => '齃', '龎' => '龎', 'יִ' => 'יִ', 'ײַ' => 'ײַ', 'שׁ' => 'שׁ', 'שׂ' => 'שׂ', 'שּׁ' => 'שּׁ', 'שּׂ' => 'שּׂ', 'אַ' => 'אַ', 'אָ' => 'אָ', 'אּ' => 'אּ', 'בּ' => 'בּ', 'גּ' => 'גּ', 'דּ' => 'דּ', 'הּ' => 'הּ', 'וּ' => 'וּ', 'זּ' => 'זּ', 'טּ' => 'טּ', 'יּ' => 'יּ', 'ךּ' => 'ךּ', 'כּ' => 'כּ', 'לּ' => 'לּ', 'מּ' => 'מּ', 'נּ' => 'נּ', 'סּ' => 'סּ', 'ףּ' => 'ףּ', 'פּ' => 'פּ', 'צּ' => 'צּ', 'קּ' => 'קּ', 'רּ' => 'רּ', 'שּ' => 'שּ', 'תּ' => 'תּ', 'וֹ' => 'וֹ', 'בֿ' => 'בֿ', 'כֿ' => 'כֿ', 'פֿ' => 'פֿ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', '𝅗𝅥' => '𝅗𝅥', '𝅘𝅥' => '𝅘𝅥', '𝅘𝅥𝅮' => '𝅘𝅥𝅮', '𝅘𝅥𝅯' => '𝅘𝅥𝅯', '𝅘𝅥𝅰' => '𝅘𝅥𝅰', '𝅘𝅥𝅱' => '𝅘𝅥𝅱', '𝅘𝅥𝅲' => '𝅘𝅥𝅲', '𝆹𝅥' => '𝆹𝅥', '𝆺𝅥' => '𝆺𝅥', '𝆹𝅥𝅮' => '𝆹𝅥𝅮', '𝆺𝅥𝅮' => '𝆺𝅥𝅮', '𝆹𝅥𝅯' => '𝆹𝅥𝅯', '𝆺𝅥𝅯' => '𝆺𝅥𝅯', '丽' => '丽', '丸' => '丸', '乁' => '乁', '𠄢' => '𠄢', '你' => '你', '侮' => '侮', '侻' => '侻', '倂' => '倂', '偺' => '偺', '備' => '備', '僧' => '僧', '像' => '像', '㒞' => '㒞', '𠘺' => '𠘺', '免' => '免', '兔' => '兔', '兤' => '兤', '具' => '具', '𠔜' => '𠔜', '㒹' => '㒹', '內' => '內', '再' => '再', '𠕋' => '𠕋', '冗' => '冗', '冤' => '冤', '仌' => '仌', '冬' => '冬', '况' => '况', '𩇟' => '𩇟', '凵' => '凵', '刃' => '刃', '㓟' => '㓟', '刻' => '刻', '剆' => '剆', '割' => '割', '剷' => '剷', '㔕' => '㔕', '勇' => '勇', '勉' => '勉', '勤' => '勤', '勺' => '勺', '包' => '包', '匆' => '匆', '北' => '北', '卉' => '卉', '卑' => '卑', '博' => '博', '即' => '即', '卽' => '卽', '卿' => '卿', '卿' => '卿', '卿' => '卿', '𠨬' => '𠨬', '灰' => '灰', '及' => '及', '叟' => '叟', '𠭣' => '𠭣', '叫' => '叫', '叱' => '叱', '吆' => '吆', '咞' => '咞', '吸' => '吸', '呈' => '呈', '周' => '周', '咢' => '咢', '哶' => '哶', '唐' => '唐', '啓' => '啓', '啣' => '啣', '善' => '善', '善' => '善', '喙' => '喙', '喫' => '喫', '喳' => '喳', '嗂' => '嗂', '圖' => '圖', '嘆' => '嘆', '圗' => '圗', '噑' => '噑', '噴' => '噴', '切' => '切', '壮' => '壮', '城' => '城', '埴' => '埴', '堍' => '堍', '型' => '型', '堲' => '堲', '報' => '報', '墬' => '墬', '𡓤' => '𡓤', '売' => '売', '壷' => '壷', '夆' => '夆', '多' => '多', '夢' => '夢', '奢' => '奢', '𡚨' => '𡚨', '𡛪' => '𡛪', '姬' => '姬', '娛' => '娛', '娧' => '娧', '姘' => '姘', '婦' => '婦', '㛮' => '㛮', '㛼' => '㛼', '嬈' => '嬈', '嬾' => '嬾', '嬾' => '嬾', '𡧈' => '𡧈', '寃' => '寃', '寘' => '寘', '寧' => '寧', '寳' => '寳', '𡬘' => '𡬘', '寿' => '寿', '将' => '将', '当' => '当', '尢' => '尢', '㞁' => '㞁', '屠' => '屠', '屮' => '屮', '峀' => '峀', '岍' => '岍', '𡷤' => '𡷤', '嵃' => '嵃', '𡷦' => '𡷦', '嵮' => '嵮', '嵫' => '嵫', '嵼' => '嵼', '巡' => '巡', '巢' => '巢', '㠯' => '㠯', '巽' => '巽', '帨' => '帨', '帽' => '帽', '幩' => '幩', '㡢' => '㡢', '𢆃' => '𢆃', '㡼' => '㡼', '庰' => '庰', '庳' => '庳', '庶' => '庶', '廊' => '廊', '𪎒' => '𪎒', '廾' => '廾', '𢌱' => '𢌱', '𢌱' => '𢌱', '舁' => '舁', '弢' => '弢', '弢' => '弢', '㣇' => '㣇', '𣊸' => '𣊸', '𦇚' => '𦇚', '形' => '形', '彫' => '彫', '㣣' => '㣣', '徚' => '徚', '忍' => '忍', '志' => '志', '忹' => '忹', '悁' => '悁', '㤺' => '㤺', '㤜' => '㤜', '悔' => '悔', '𢛔' => '𢛔', '惇' => '惇', '慈' => '慈', '慌' => '慌', '慎' => '慎', '慌' => '慌', '慺' => '慺', '憎' => '憎', '憲' => '憲', '憤' => '憤', '憯' => '憯', '懞' => '懞', '懲' => '懲', '懶' => '懶', '成' => '成', '戛' => '戛', '扝' => '扝', '抱' => '抱', '拔' => '拔', '捐' => '捐', '𢬌' => '𢬌', '挽' => '挽', '拼' => '拼', '捨' => '捨', '掃' => '掃', '揤' => '揤', '𢯱' => '𢯱', '搢' => '搢', '揅' => '揅', '掩' => '掩', '㨮' => '㨮', '摩' => '摩', '摾' => '摾', '撝' => '撝', '摷' => '摷', '㩬' => '㩬', '敏' => '敏', '敬' => '敬', '𣀊' => '𣀊', '旣' => '旣', '書' => '書', '晉' => '晉', '㬙' => '㬙', '暑' => '暑', '㬈' => '㬈', '㫤' => '㫤', '冒' => '冒', '冕' => '冕', '最' => '最', '暜' => '暜', '肭' => '肭', '䏙' => '䏙', '朗' => '朗', '望' => '望', '朡' => '朡', '杞' => '杞', '杓' => '杓', '𣏃' => '𣏃', '㭉' => '㭉', '柺' => '柺', '枅' => '枅', '桒' => '桒', '梅' => '梅', '𣑭' => '𣑭', '梎' => '梎', '栟' => '栟', '椔' => '椔', '㮝' => '㮝', '楂' => '楂', '榣' => '榣', '槪' => '槪', '檨' => '檨', '𣚣' => '𣚣', '櫛' => '櫛', '㰘' => '㰘', '次' => '次', '𣢧' => '𣢧', '歔' => '歔', '㱎' => '㱎', '歲' => '歲', '殟' => '殟', '殺' => '殺', '殻' => '殻', '𣪍' => '𣪍', '𡴋' => '𡴋', '𣫺' => '𣫺', '汎' => '汎', '𣲼' => '𣲼', '沿' => '沿', '泍' => '泍', '汧' => '汧', '洖' => '洖', '派' => '派', '海' => '海', '流' => '流', '浩' => '浩', '浸' => '浸', '涅' => '涅', '𣴞' => '𣴞', '洴' => '洴', '港' => '港', '湮' => '湮', '㴳' => '㴳', '滋' => '滋', '滇' => '滇', '𣻑' => '𣻑', '淹' => '淹', '潮' => '潮', '𣽞' => '𣽞', '𣾎' => '𣾎', '濆' => '濆', '瀹' => '瀹', '瀞' => '瀞', '瀛' => '瀛', '㶖' => '㶖', '灊' => '灊', '災' => '災', '灷' => '灷', '炭' => '炭', '𠔥' => '𠔥', '煅' => '煅', '𤉣' => '𤉣', '熜' => '熜', '𤎫' => '𤎫', '爨' => '爨', '爵' => '爵', '牐' => '牐', '𤘈' => '𤘈', '犀' => '犀', '犕' => '犕', '𤜵' => '𤜵', '𤠔' => '𤠔', '獺' => '獺', '王' => '王', '㺬' => '㺬', '玥' => '玥', '㺸' => '㺸', '㺸' => '㺸', '瑇' => '瑇', '瑜' => '瑜', '瑱' => '瑱', '璅' => '璅', '瓊' => '瓊', '㼛' => '㼛', '甤' => '甤', '𤰶' => '𤰶', '甾' => '甾', '𤲒' => '𤲒', '異' => '異', '𢆟' => '𢆟', '瘐' => '瘐', '𤾡' => '𤾡', '𤾸' => '𤾸', '𥁄' => '𥁄', '㿼' => '㿼', '䀈' => '䀈', '直' => '直', '𥃳' => '𥃳', '𥃲' => '𥃲', '𥄙' => '𥄙', '𥄳' => '𥄳', '眞' => '眞', '真' => '真', '真' => '真', '睊' => '睊', '䀹' => '䀹', '瞋' => '瞋', '䁆' => '䁆', '䂖' => '䂖', '𥐝' => '𥐝', '硎' => '硎', '碌' => '碌', '磌' => '磌', '䃣' => '䃣', '𥘦' => '𥘦', '祖' => '祖', '𥚚' => '𥚚', '𥛅' => '𥛅', '福' => '福', '秫' => '秫', '䄯' => '䄯', '穀' => '穀', '穊' => '穊', '穏' => '穏', '𥥼' => '𥥼', '𥪧' => '𥪧', '𥪧' => '𥪧', '竮' => '竮', '䈂' => '䈂', '𥮫' => '𥮫', '篆' => '篆', '築' => '築', '䈧' => '䈧', '𥲀' => '𥲀', '糒' => '糒', '䊠' => '䊠', '糨' => '糨', '糣' => '糣', '紀' => '紀', '𥾆' => '𥾆', '絣' => '絣', '䌁' => '䌁', '緇' => '緇', '縂' => '縂', '繅' => '繅', '䌴' => '䌴', '𦈨' => '𦈨', '𦉇' => '𦉇', '䍙' => '䍙', '𦋙' => '𦋙', '罺' => '罺', '𦌾' => '𦌾', '羕' => '羕', '翺' => '翺', '者' => '者', '𦓚' => '𦓚', '𦔣' => '𦔣', '聠' => '聠', '𦖨' => '𦖨', '聰' => '聰', '𣍟' => '𣍟', '䏕' => '䏕', '育' => '育', '脃' => '脃', '䐋' => '䐋', '脾' => '脾', '媵' => '媵', '𦞧' => '𦞧', '𦞵' => '𦞵', '𣎓' => '𣎓', '𣎜' => '𣎜', '舁' => '舁', '舄' => '舄', '辞' => '辞', '䑫' => '䑫', '芑' => '芑', '芋' => '芋', '芝' => '芝', '劳' => '劳', '花' => '花', '芳' => '芳', '芽' => '芽', '苦' => '苦', '𦬼' => '𦬼', '若' => '若', '茝' => '茝', '荣' => '荣', '莭' => '莭', '茣' => '茣', '莽' => '莽', '菧' => '菧', '著' => '著', '荓' => '荓', '菊' => '菊', '菌' => '菌', '菜' => '菜', '𦰶' => '𦰶', '𦵫' => '𦵫', '𦳕' => '𦳕', '䔫' => '䔫', '蓱' => '蓱', '蓳' => '蓳', '蔖' => '蔖', '𧏊' => '𧏊', '蕤' => '蕤', '𦼬' => '𦼬', '䕝' => '䕝', '䕡' => '䕡', '𦾱' => '𦾱', '𧃒' => '𧃒', '䕫' => '䕫', '虐' => '虐', '虜' => '虜', '虧' => '虧', '虩' => '虩', '蚩' => '蚩', '蚈' => '蚈', '蜎' => '蜎', '蛢' => '蛢', '蝹' => '蝹', '蜨' => '蜨', '蝫' => '蝫', '螆' => '螆', '䗗' => '䗗', '蟡' => '蟡', '蠁' => '蠁', '䗹' => '䗹', '衠' => '衠', '衣' => '衣', '𧙧' => '𧙧', '裗' => '裗', '裞' => '裞', '䘵' => '䘵', '裺' => '裺', '㒻' => '㒻', '𧢮' => '𧢮', '𧥦' => '𧥦', '䚾' => '䚾', '䛇' => '䛇', '誠' => '誠', '諭' => '諭', '變' => '變', '豕' => '豕', '𧲨' => '𧲨', '貫' => '貫', '賁' => '賁', '贛' => '贛', '起' => '起', '𧼯' => '𧼯', '𠠄' => '𠠄', '跋' => '跋', '趼' => '趼', '跰' => '跰', '𠣞' => '𠣞', '軔' => '軔', '輸' => '輸', '𨗒' => '𨗒', '𨗭' => '𨗭', '邔' => '邔', '郱' => '郱', '鄑' => '鄑', '𨜮' => '𨜮', '鄛' => '鄛', '鈸' => '鈸', '鋗' => '鋗', '鋘' => '鋘', '鉼' => '鉼', '鏹' => '鏹', '鐕' => '鐕', '𨯺' => '𨯺', '開' => '開', '䦕' => '䦕', '閷' => '閷', '𨵷' => '𨵷', '䧦' => '䧦', '雃' => '雃', '嶲' => '嶲', '霣' => '霣', '𩅅' => '𩅅', '𩈚' => '𩈚', '䩮' => '䩮', '䩶' => '䩶', '韠' => '韠', '𩐊' => '𩐊', '䪲' => '䪲', '𩒖' => '𩒖', '頋' => '頋', '頋' => '頋', '頩' => '頩', '𩖶' => '𩖶', '飢' => '飢', '䬳' => '䬳', '餩' => '餩', '馧' => '馧', '駂' => '駂', '駾' => '駾', '䯎' => '䯎', '𩬰' => '𩬰', '鬒' => '鬒', '鱀' => '鱀', '鳽' => '鳽', '䳎' => '䳎', '䳭' => '䳭', '鵧' => '鵧', '𪃎' => '𪃎', '䳸' => '䳸', '𪄅' => '𪄅', '𪈎' => '𪈎', '𪊑' => '𪊑', '麻' => '麻', '䵖' => '䵖', '黹' => '黹', '黾' => '黾', '鼅' => '鼅', '鼏' => '鼏', '鼖' => '鼖', '鼻' => '鼻', '𪘀' => '𪘀'); 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '΅' => '΅', 'Ά' => 'Ά', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ὲ' => 'ὲ', 'ὴ' => 'ὴ', 'ὶ' => 'ὶ', 'ὸ' => 'ὸ', 'ὺ' => 'ὺ', 'ὼ' => 'ὼ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'ᾼ' => 'ᾼ', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Ὴ' => 'Ὴ', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ῥ' => 'Ῥ', '῭' => '῭', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ὼ' => 'Ὼ', 'ῼ' => 'ῼ', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸'); ' ', '¨' => ' ̈', 'ª' => 'a', '¯' => ' ̄', '²' => '2', '³' => '3', '´' => ' ́', 'µ' => 'μ', '¸' => ' ̧', '¹' => '1', 'º' => 'o', '¼' => '1⁄4', '½' => '1⁄2', '¾' => '3⁄4', 'IJ' => 'IJ', 'ij' => 'ij', 'Ŀ' => 'L·', 'ŀ' => 'l·', 'ʼn' => 'ʼn', 'ſ' => 's', 'DŽ' => 'DŽ', 'Dž' => 'Dž', 'dž' => 'dž', 'LJ' => 'LJ', 'Lj' => 'Lj', 'lj' => 'lj', 'NJ' => 'NJ', 'Nj' => 'Nj', 'nj' => 'nj', 'DZ' => 'DZ', 'Dz' => 'Dz', 'dz' => 'dz', 'ʰ' => 'h', 'ʱ' => 'ɦ', 'ʲ' => 'j', 'ʳ' => 'r', 'ʴ' => 'ɹ', 'ʵ' => 'ɻ', 'ʶ' => 'ʁ', 'ʷ' => 'w', 'ʸ' => 'y', '˘' => ' ̆', '˙' => ' ̇', '˚' => ' ̊', '˛' => ' ̨', '˜' => ' ̃', '˝' => ' ̋', 'ˠ' => 'ɣ', 'ˡ' => 'l', 'ˢ' => 's', 'ˣ' => 'x', 'ˤ' => 'ʕ', 'ͺ' => ' ͅ', '΄' => ' ́', '΅' => ' ̈́', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϒ' => 'Υ', 'ϓ' => 'Ύ', 'ϔ' => 'Ϋ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϲ' => 'ς', 'ϴ' => 'Θ', 'ϵ' => 'ε', 'Ϲ' => 'Σ', 'և' => 'եւ', 'ٵ' => 'اٴ', 'ٶ' => 'وٴ', 'ٷ' => 'ۇٴ', 'ٸ' => 'يٴ', 'ำ' => 'ํา', 'ຳ' => 'ໍາ', 'ໜ' => 'ຫນ', 'ໝ' => 'ຫມ', '༌' => '་', 'ཷ' => 'ྲཱྀ', 'ཹ' => 'ླཱྀ', 'ჼ' => 'ნ', 'ᴬ' => 'A', 'ᴭ' => 'Æ', 'ᴮ' => 'B', 'ᴰ' => 'D', 'ᴱ' => 'E', 'ᴲ' => 'Ǝ', 'ᴳ' => 'G', 'ᴴ' => 'H', 'ᴵ' => 'I', 'ᴶ' => 'J', 'ᴷ' => 'K', 'ᴸ' => 'L', 'ᴹ' => 'M', 'ᴺ' => 'N', 'ᴼ' => 'O', 'ᴽ' => 'Ȣ', 'ᴾ' => 'P', 'ᴿ' => 'R', 'ᵀ' => 'T', 'ᵁ' => 'U', 'ᵂ' => 'W', 'ᵃ' => 'a', 'ᵄ' => 'ɐ', 'ᵅ' => 'ɑ', 'ᵆ' => 'ᴂ', 'ᵇ' => 'b', 'ᵈ' => 'd', 'ᵉ' => 'e', 'ᵊ' => 'ə', 'ᵋ' => 'ɛ', 'ᵌ' => 'ɜ', 'ᵍ' => 'g', 'ᵏ' => 'k', 'ᵐ' => 'm', 'ᵑ' => 'ŋ', 'ᵒ' => 'o', 'ᵓ' => 'ɔ', 'ᵔ' => 'ᴖ', 'ᵕ' => 'ᴗ', 'ᵖ' => 'p', 'ᵗ' => 't', 'ᵘ' => 'u', 'ᵙ' => 'ᴝ', 'ᵚ' => 'ɯ', 'ᵛ' => 'v', 'ᵜ' => 'ᴥ', 'ᵝ' => 'β', 'ᵞ' => 'γ', 'ᵟ' => 'δ', 'ᵠ' => 'φ', 'ᵡ' => 'χ', 'ᵢ' => 'i', 'ᵣ' => 'r', 'ᵤ' => 'u', 'ᵥ' => 'v', 'ᵦ' => 'β', 'ᵧ' => 'γ', 'ᵨ' => 'ρ', 'ᵩ' => 'φ', 'ᵪ' => 'χ', 'ᵸ' => 'н', 'ᶛ' => 'ɒ', 'ᶜ' => 'c', 'ᶝ' => 'ɕ', 'ᶞ' => 'ð', 'ᶟ' => 'ɜ', 'ᶠ' => 'f', 'ᶡ' => 'ɟ', 'ᶢ' => 'ɡ', 'ᶣ' => 'ɥ', 'ᶤ' => 'ɨ', 'ᶥ' => 'ɩ', 'ᶦ' => 'ɪ', 'ᶧ' => 'ᵻ', 'ᶨ' => 'ʝ', 'ᶩ' => 'ɭ', 'ᶪ' => 'ᶅ', 'ᶫ' => 'ʟ', 'ᶬ' => 'ɱ', 'ᶭ' => 'ɰ', 'ᶮ' => 'ɲ', 'ᶯ' => 'ɳ', 'ᶰ' => 'ɴ', 'ᶱ' => 'ɵ', 'ᶲ' => 'ɸ', 'ᶳ' => 'ʂ', 'ᶴ' => 'ʃ', 'ᶵ' => 'ƫ', 'ᶶ' => 'ʉ', 'ᶷ' => 'ʊ', 'ᶸ' => 'ᴜ', 'ᶹ' => 'ʋ', 'ᶺ' => 'ʌ', 'ᶻ' => 'z', 'ᶼ' => 'ʐ', 'ᶽ' => 'ʑ', 'ᶾ' => 'ʒ', 'ᶿ' => 'θ', 'ẚ' => 'aʾ', 'ẛ' => 'ṡ', '᾽' => ' ̓', '᾿' => ' ̓', '῀' => ' ͂', '῁' => ' ̈͂', '῍' => ' ̓̀', '῎' => ' ̓́', '῏' => ' ̓͂', '῝' => ' ̔̀', '῞' => ' ̔́', '῟' => ' ̔͂', '῭' => ' ̈̀', '΅' => ' ̈́', '´' => ' ́', '῾' => ' ̔', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', '‑' => '‐', '‗' => ' ̳', '․' => '.', '‥' => '..', '…' => '...', ' ' => ' ', '″' => '′′', '‴' => '′′′', '‶' => '‵‵', '‷' => '‵‵‵', '‼' => '!!', '‾' => ' ̅', '⁇' => '??', '⁈' => '?!', '⁉' => '!?', '⁗' => '′′′′', ' ' => ' ', '⁰' => '0', 'ⁱ' => 'i', '⁴' => '4', '⁵' => '5', '⁶' => '6', '⁷' => '7', '⁸' => '8', '⁹' => '9', '⁺' => '+', '⁻' => '−', '⁼' => '=', '⁽' => '(', '⁾' => ')', 'ⁿ' => 'n', '₀' => '0', '₁' => '1', '₂' => '2', '₃' => '3', '₄' => '4', '₅' => '5', '₆' => '6', '₇' => '7', '₈' => '8', '₉' => '9', '₊' => '+', '₋' => '−', '₌' => '=', '₍' => '(', '₎' => ')', 'ₐ' => 'a', 'ₑ' => 'e', 'ₒ' => 'o', 'ₓ' => 'x', 'ₔ' => 'ə', 'ₕ' => 'h', 'ₖ' => 'k', 'ₗ' => 'l', 'ₘ' => 'm', 'ₙ' => 'n', 'ₚ' => 'p', 'ₛ' => 's', 'ₜ' => 't', '₨' => 'Rs', '℀' => 'a/c', '℁' => 'a/s', 'ℂ' => 'C', '℃' => '°C', '℅' => 'c/o', '℆' => 'c/u', 'ℇ' => 'Ɛ', '℉' => '°F', 'ℊ' => 'g', 'ℋ' => 'H', 'ℌ' => 'H', 'ℍ' => 'H', 'ℎ' => 'h', 'ℏ' => 'ħ', 'ℐ' => 'I', 'ℑ' => 'I', 'ℒ' => 'L', 'ℓ' => 'l', 'ℕ' => 'N', '№' => 'No', 'ℙ' => 'P', 'ℚ' => 'Q', 'ℛ' => 'R', 'ℜ' => 'R', 'ℝ' => 'R', '℠' => 'SM', '℡' => 'TEL', '™' => 'TM', 'ℤ' => 'Z', 'ℨ' => 'Z', 'ℬ' => 'B', 'ℭ' => 'C', 'ℯ' => 'e', 'ℰ' => 'E', 'ℱ' => 'F', 'ℳ' => 'M', 'ℴ' => 'o', 'ℵ' => 'א', 'ℶ' => 'ב', 'ℷ' => 'ג', 'ℸ' => 'ד', 'ℹ' => 'i', '℻' => 'FAX', 'ℼ' => 'π', 'ℽ' => 'γ', 'ℾ' => 'Γ', 'ℿ' => 'Π', '⅀' => '∑', 'ⅅ' => 'D', 'ⅆ' => 'd', 'ⅇ' => 'e', 'ⅈ' => 'i', 'ⅉ' => 'j', '⅐' => '1⁄7', '⅑' => '1⁄9', '⅒' => '1⁄10', '⅓' => '1⁄3', '⅔' => '2⁄3', '⅕' => '1⁄5', '⅖' => '2⁄5', '⅗' => '3⁄5', '⅘' => '4⁄5', '⅙' => '1⁄6', '⅚' => '5⁄6', '⅛' => '1⁄8', '⅜' => '3⁄8', '⅝' => '5⁄8', '⅞' => '7⁄8', '⅟' => '1⁄', 'Ⅰ' => 'I', 'Ⅱ' => 'II', 'Ⅲ' => 'III', 'Ⅳ' => 'IV', 'Ⅴ' => 'V', 'Ⅵ' => 'VI', 'Ⅶ' => 'VII', 'Ⅷ' => 'VIII', 'Ⅸ' => 'IX', 'Ⅹ' => 'X', 'Ⅺ' => 'XI', 'Ⅻ' => 'XII', 'Ⅼ' => 'L', 'Ⅽ' => 'C', 'Ⅾ' => 'D', 'Ⅿ' => 'M', 'ⅰ' => 'i', 'ⅱ' => 'ii', 'ⅲ' => 'iii', 'ⅳ' => 'iv', 'ⅴ' => 'v', 'ⅵ' => 'vi', 'ⅶ' => 'vii', 'ⅷ' => 'viii', 'ⅸ' => 'ix', 'ⅹ' => 'x', 'ⅺ' => 'xi', 'ⅻ' => 'xii', 'ⅼ' => 'l', 'ⅽ' => 'c', 'ⅾ' => 'd', 'ⅿ' => 'm', '↉' => '0⁄3', '∬' => '∫∫', '∭' => '∫∫∫', '∯' => '∮∮', '∰' => '∮∮∮', '①' => '1', '②' => '2', '③' => '3', '④' => '4', '⑤' => '5', '⑥' => '6', '⑦' => '7', '⑧' => '8', '⑨' => '9', '⑩' => '10', '⑪' => '11', '⑫' => '12', '⑬' => '13', '⑭' => '14', '⑮' => '15', '⑯' => '16', '⑰' => '17', '⑱' => '18', '⑲' => '19', '⑳' => '20', '⑴' => '(1)', '⑵' => '(2)', '⑶' => '(3)', '⑷' => '(4)', '⑸' => '(5)', '⑹' => '(6)', '⑺' => '(7)', '⑻' => '(8)', '⑼' => '(9)', '⑽' => '(10)', '⑾' => '(11)', '⑿' => '(12)', '⒀' => '(13)', '⒁' => '(14)', '⒂' => '(15)', '⒃' => '(16)', '⒄' => '(17)', '⒅' => '(18)', '⒆' => '(19)', '⒇' => '(20)', '⒈' => '1.', '⒉' => '2.', '⒊' => '3.', '⒋' => '4.', '⒌' => '5.', '⒍' => '6.', '⒎' => '7.', '⒏' => '8.', '⒐' => '9.', '⒑' => '10.', '⒒' => '11.', '⒓' => '12.', '⒔' => '13.', '⒕' => '14.', '⒖' => '15.', '⒗' => '16.', '⒘' => '17.', '⒙' => '18.', '⒚' => '19.', '⒛' => '20.', '⒜' => '(a)', '⒝' => '(b)', '⒞' => '(c)', '⒟' => '(d)', '⒠' => '(e)', '⒡' => '(f)', '⒢' => '(g)', '⒣' => '(h)', '⒤' => '(i)', '⒥' => '(j)', '⒦' => '(k)', '⒧' => '(l)', '⒨' => '(m)', '⒩' => '(n)', '⒪' => '(o)', '⒫' => '(p)', '⒬' => '(q)', '⒭' => '(r)', '⒮' => '(s)', '⒯' => '(t)', '⒰' => '(u)', '⒱' => '(v)', '⒲' => '(w)', '⒳' => '(x)', '⒴' => '(y)', '⒵' => '(z)', 'Ⓐ' => 'A', 'Ⓑ' => 'B', 'Ⓒ' => 'C', 'Ⓓ' => 'D', 'Ⓔ' => 'E', 'Ⓕ' => 'F', 'Ⓖ' => 'G', 'Ⓗ' => 'H', 'Ⓘ' => 'I', 'Ⓙ' => 'J', 'Ⓚ' => 'K', 'Ⓛ' => 'L', 'Ⓜ' => 'M', 'Ⓝ' => 'N', 'Ⓞ' => 'O', 'Ⓟ' => 'P', 'Ⓠ' => 'Q', 'Ⓡ' => 'R', 'Ⓢ' => 'S', 'Ⓣ' => 'T', 'Ⓤ' => 'U', 'Ⓥ' => 'V', 'Ⓦ' => 'W', 'Ⓧ' => 'X', 'Ⓨ' => 'Y', 'Ⓩ' => 'Z', 'ⓐ' => 'a', 'ⓑ' => 'b', 'ⓒ' => 'c', 'ⓓ' => 'd', 'ⓔ' => 'e', 'ⓕ' => 'f', 'ⓖ' => 'g', 'ⓗ' => 'h', 'ⓘ' => 'i', 'ⓙ' => 'j', 'ⓚ' => 'k', 'ⓛ' => 'l', 'ⓜ' => 'm', 'ⓝ' => 'n', 'ⓞ' => 'o', 'ⓟ' => 'p', 'ⓠ' => 'q', 'ⓡ' => 'r', 'ⓢ' => 's', 'ⓣ' => 't', 'ⓤ' => 'u', 'ⓥ' => 'v', 'ⓦ' => 'w', 'ⓧ' => 'x', 'ⓨ' => 'y', 'ⓩ' => 'z', '⓪' => '0', '⨌' => '∫∫∫∫', '⩴' => '::=', '⩵' => '==', '⩶' => '===', 'ⱼ' => 'j', 'ⱽ' => 'V', 'ⵯ' => 'ⵡ', '⺟' => '母', '⻳' => '龟', '⼀' => '一', '⼁' => '丨', '⼂' => '丶', '⼃' => '丿', '⼄' => '乙', '⼅' => '亅', '⼆' => '二', '⼇' => '亠', '⼈' => '人', '⼉' => '儿', '⼊' => '入', '⼋' => '八', '⼌' => '冂', '⼍' => '冖', '⼎' => '冫', '⼏' => '几', '⼐' => '凵', '⼑' => '刀', '⼒' => '力', '⼓' => '勹', '⼔' => '匕', '⼕' => '匚', '⼖' => '匸', '⼗' => '十', '⼘' => '卜', '⼙' => '卩', '⼚' => '厂', '⼛' => '厶', '⼜' => '又', '⼝' => '口', '⼞' => '囗', '⼟' => '土', '⼠' => '士', '⼡' => '夂', '⼢' => '夊', '⼣' => '夕', '⼤' => '大', '⼥' => '女', '⼦' => '子', '⼧' => '宀', '⼨' => '寸', '⼩' => '小', '⼪' => '尢', '⼫' => '尸', '⼬' => '屮', '⼭' => '山', '⼮' => '巛', '⼯' => '工', '⼰' => '己', '⼱' => '巾', '⼲' => '干', '⼳' => '幺', '⼴' => '广', '⼵' => '廴', '⼶' => '廾', '⼷' => '弋', '⼸' => '弓', '⼹' => '彐', '⼺' => '彡', '⼻' => '彳', '⼼' => '心', '⼽' => '戈', '⼾' => '戶', '⼿' => '手', '⽀' => '支', '⽁' => '攴', '⽂' => '文', '⽃' => '斗', '⽄' => '斤', '⽅' => '方', '⽆' => '无', '⽇' => '日', '⽈' => '曰', '⽉' => '月', '⽊' => '木', '⽋' => '欠', '⽌' => '止', '⽍' => '歹', '⽎' => '殳', '⽏' => '毋', '⽐' => '比', '⽑' => '毛', '⽒' => '氏', '⽓' => '气', '⽔' => '水', '⽕' => '火', '⽖' => '爪', '⽗' => '父', '⽘' => '爻', '⽙' => '爿', '⽚' => '片', '⽛' => '牙', '⽜' => '牛', '⽝' => '犬', '⽞' => '玄', '⽟' => '玉', '⽠' => '瓜', '⽡' => '瓦', '⽢' => '甘', '⽣' => '生', '⽤' => '用', '⽥' => '田', '⽦' => '疋', '⽧' => '疒', '⽨' => '癶', '⽩' => '白', '⽪' => '皮', '⽫' => '皿', '⽬' => '目', '⽭' => '矛', '⽮' => '矢', '⽯' => '石', '⽰' => '示', '⽱' => '禸', '⽲' => '禾', '⽳' => '穴', '⽴' => '立', '⽵' => '竹', '⽶' => '米', '⽷' => '糸', '⽸' => '缶', '⽹' => '网', '⽺' => '羊', '⽻' => '羽', '⽼' => '老', '⽽' => '而', '⽾' => '耒', '⽿' => '耳', '⾀' => '聿', '⾁' => '肉', '⾂' => '臣', '⾃' => '自', '⾄' => '至', '⾅' => '臼', '⾆' => '舌', '⾇' => '舛', '⾈' => '舟', '⾉' => '艮', '⾊' => '色', '⾋' => '艸', '⾌' => '虍', '⾍' => '虫', '⾎' => '血', '⾏' => '行', '⾐' => '衣', '⾑' => '襾', '⾒' => '見', '⾓' => '角', '⾔' => '言', '⾕' => '谷', '⾖' => '豆', '⾗' => '豕', '⾘' => '豸', '⾙' => '貝', '⾚' => '赤', '⾛' => '走', '⾜' => '足', '⾝' => '身', '⾞' => '車', '⾟' => '辛', '⾠' => '辰', '⾡' => '辵', '⾢' => '邑', '⾣' => '酉', '⾤' => '釆', '⾥' => '里', '⾦' => '金', '⾧' => '長', '⾨' => '門', '⾩' => '阜', '⾪' => '隶', '⾫' => '隹', '⾬' => '雨', '⾭' => '靑', '⾮' => '非', '⾯' => '面', '⾰' => '革', '⾱' => '韋', '⾲' => '韭', '⾳' => '音', '⾴' => '頁', '⾵' => '風', '⾶' => '飛', '⾷' => '食', '⾸' => '首', '⾹' => '香', '⾺' => '馬', '⾻' => '骨', '⾼' => '高', '⾽' => '髟', '⾾' => '鬥', '⾿' => '鬯', '⿀' => '鬲', '⿁' => '鬼', '⿂' => '魚', '⿃' => '鳥', '⿄' => '鹵', '⿅' => '鹿', '⿆' => '麥', '⿇' => '麻', '⿈' => '黃', '⿉' => '黍', '⿊' => '黑', '⿋' => '黹', '⿌' => '黽', '⿍' => '鼎', '⿎' => '鼓', '⿏' => '鼠', '⿐' => '鼻', '⿑' => '齊', '⿒' => '齒', '⿓' => '龍', '⿔' => '龜', '⿕' => '龠', ' ' => ' ', '〶' => '〒', '〸' => '十', '〹' => '卄', '〺' => '卅', '゛' => ' ゙', '゜' => ' ゚', 'ゟ' => 'より', 'ヿ' => 'コト', 'ㄱ' => 'ᄀ', 'ㄲ' => 'ᄁ', 'ㄳ' => 'ᆪ', 'ㄴ' => 'ᄂ', 'ㄵ' => 'ᆬ', 'ㄶ' => 'ᆭ', 'ㄷ' => 'ᄃ', 'ㄸ' => 'ᄄ', 'ㄹ' => 'ᄅ', 'ㄺ' => 'ᆰ', 'ㄻ' => 'ᆱ', 'ㄼ' => 'ᆲ', 'ㄽ' => 'ᆳ', 'ㄾ' => 'ᆴ', 'ㄿ' => 'ᆵ', 'ㅀ' => 'ᄚ', 'ㅁ' => 'ᄆ', 'ㅂ' => 'ᄇ', 'ㅃ' => 'ᄈ', 'ㅄ' => 'ᄡ', 'ㅅ' => 'ᄉ', 'ㅆ' => 'ᄊ', 'ㅇ' => 'ᄋ', 'ㅈ' => 'ᄌ', 'ㅉ' => 'ᄍ', 'ㅊ' => 'ᄎ', 'ㅋ' => 'ᄏ', 'ㅌ' => 'ᄐ', 'ㅍ' => 'ᄑ', 'ㅎ' => 'ᄒ', 'ㅏ' => 'ᅡ', 'ㅐ' => 'ᅢ', 'ㅑ' => 'ᅣ', 'ㅒ' => 'ᅤ', 'ㅓ' => 'ᅥ', 'ㅔ' => 'ᅦ', 'ㅕ' => 'ᅧ', 'ㅖ' => 'ᅨ', 'ㅗ' => 'ᅩ', 'ㅘ' => 'ᅪ', 'ㅙ' => 'ᅫ', 'ㅚ' => 'ᅬ', 'ㅛ' => 'ᅭ', 'ㅜ' => 'ᅮ', 'ㅝ' => 'ᅯ', 'ㅞ' => 'ᅰ', 'ㅟ' => 'ᅱ', 'ㅠ' => 'ᅲ', 'ㅡ' => 'ᅳ', 'ㅢ' => 'ᅴ', 'ㅣ' => 'ᅵ', 'ㅤ' => 'ᅠ', 'ㅥ' => 'ᄔ', 'ㅦ' => 'ᄕ', 'ㅧ' => 'ᇇ', 'ㅨ' => 'ᇈ', 'ㅩ' => 'ᇌ', 'ㅪ' => 'ᇎ', 'ㅫ' => 'ᇓ', 'ㅬ' => 'ᇗ', 'ㅭ' => 'ᇙ', 'ㅮ' => 'ᄜ', 'ㅯ' => 'ᇝ', 'ㅰ' => 'ᇟ', 'ㅱ' => 'ᄝ', 'ㅲ' => 'ᄞ', 'ㅳ' => 'ᄠ', 'ㅴ' => 'ᄢ', 'ㅵ' => 'ᄣ', 'ㅶ' => 'ᄧ', 'ㅷ' => 'ᄩ', 'ㅸ' => 'ᄫ', 'ㅹ' => 'ᄬ', 'ㅺ' => 'ᄭ', 'ㅻ' => 'ᄮ', 'ㅼ' => 'ᄯ', 'ㅽ' => 'ᄲ', 'ㅾ' => 'ᄶ', 'ㅿ' => 'ᅀ', 'ㆀ' => 'ᅇ', 'ㆁ' => 'ᅌ', 'ㆂ' => 'ᇱ', 'ㆃ' => 'ᇲ', 'ㆄ' => 'ᅗ', 'ㆅ' => 'ᅘ', 'ㆆ' => 'ᅙ', 'ㆇ' => 'ᆄ', 'ㆈ' => 'ᆅ', 'ㆉ' => 'ᆈ', 'ㆊ' => 'ᆑ', 'ㆋ' => 'ᆒ', 'ㆌ' => 'ᆔ', 'ㆍ' => 'ᆞ', 'ㆎ' => 'ᆡ', '㆒' => '一', '㆓' => '二', '㆔' => '三', '㆕' => '四', '㆖' => '上', '㆗' => '中', '㆘' => '下', '㆙' => '甲', '㆚' => '乙', '㆛' => '丙', '㆜' => '丁', '㆝' => '天', '㆞' => '地', '㆟' => '人', '㈀' => '(ᄀ)', '㈁' => '(ᄂ)', '㈂' => '(ᄃ)', '㈃' => '(ᄅ)', '㈄' => '(ᄆ)', '㈅' => '(ᄇ)', '㈆' => '(ᄉ)', '㈇' => '(ᄋ)', '㈈' => '(ᄌ)', '㈉' => '(ᄎ)', '㈊' => '(ᄏ)', '㈋' => '(ᄐ)', '㈌' => '(ᄑ)', '㈍' => '(ᄒ)', '㈎' => '(가)', '㈏' => '(나)', '㈐' => '(다)', '㈑' => '(라)', '㈒' => '(마)', '㈓' => '(바)', '㈔' => '(사)', '㈕' => '(아)', '㈖' => '(자)', '㈗' => '(차)', '㈘' => '(카)', '㈙' => '(타)', '㈚' => '(파)', '㈛' => '(하)', '㈜' => '(주)', '㈝' => '(오전)', '㈞' => '(오후)', '㈠' => '(一)', '㈡' => '(二)', '㈢' => '(三)', '㈣' => '(四)', '㈤' => '(五)', '㈥' => '(六)', '㈦' => '(七)', '㈧' => '(八)', '㈨' => '(九)', '㈩' => '(十)', '㈪' => '(月)', '㈫' => '(火)', '㈬' => '(水)', '㈭' => '(木)', '㈮' => '(金)', '㈯' => '(土)', '㈰' => '(日)', '㈱' => '(株)', '㈲' => '(有)', '㈳' => '(社)', '㈴' => '(名)', '㈵' => '(特)', '㈶' => '(財)', '㈷' => '(祝)', '㈸' => '(労)', '㈹' => '(代)', '㈺' => '(呼)', '㈻' => '(学)', '㈼' => '(監)', '㈽' => '(企)', '㈾' => '(資)', '㈿' => '(協)', '㉀' => '(祭)', '㉁' => '(休)', '㉂' => '(自)', '㉃' => '(至)', '㉄' => '問', '㉅' => '幼', '㉆' => '文', '㉇' => '箏', '㉐' => 'PTE', '㉑' => '21', '㉒' => '22', '㉓' => '23', '㉔' => '24', '㉕' => '25', '㉖' => '26', '㉗' => '27', '㉘' => '28', '㉙' => '29', '㉚' => '30', '㉛' => '31', '㉜' => '32', '㉝' => '33', '㉞' => '34', '㉟' => '35', '㉠' => 'ᄀ', '㉡' => 'ᄂ', '㉢' => 'ᄃ', '㉣' => 'ᄅ', '㉤' => 'ᄆ', '㉥' => 'ᄇ', '㉦' => 'ᄉ', '㉧' => 'ᄋ', '㉨' => 'ᄌ', '㉩' => 'ᄎ', '㉪' => 'ᄏ', '㉫' => 'ᄐ', '㉬' => 'ᄑ', '㉭' => 'ᄒ', '㉮' => '가', '㉯' => '나', '㉰' => '다', '㉱' => '라', '㉲' => '마', '㉳' => '바', '㉴' => '사', '㉵' => '아', '㉶' => '자', '㉷' => '차', '㉸' => '카', '㉹' => '타', '㉺' => '파', '㉻' => '하', '㉼' => '참고', '㉽' => '주의', '㉾' => '우', '㊀' => '一', '㊁' => '二', '㊂' => '三', '㊃' => '四', '㊄' => '五', '㊅' => '六', '㊆' => '七', '㊇' => '八', '㊈' => '九', '㊉' => '十', '㊊' => '月', '㊋' => '火', '㊌' => '水', '㊍' => '木', '㊎' => '金', '㊏' => '土', '㊐' => '日', '㊑' => '株', '㊒' => '有', '㊓' => '社', '㊔' => '名', '㊕' => '特', '㊖' => '財', '㊗' => '祝', '㊘' => '労', '㊙' => '秘', '㊚' => '男', '㊛' => '女', '㊜' => '適', '㊝' => '優', '㊞' => '印', '㊟' => '注', '㊠' => '項', '㊡' => '休', '㊢' => '写', '㊣' => '正', '㊤' => '上', '㊥' => '中', '㊦' => '下', '㊧' => '左', '㊨' => '右', '㊩' => '医', '㊪' => '宗', '㊫' => '学', '㊬' => '監', '㊭' => '企', '㊮' => '資', '㊯' => '協', '㊰' => '夜', '㊱' => '36', '㊲' => '37', '㊳' => '38', '㊴' => '39', '㊵' => '40', '㊶' => '41', '㊷' => '42', '㊸' => '43', '㊹' => '44', '㊺' => '45', '㊻' => '46', '㊼' => '47', '㊽' => '48', '㊾' => '49', '㊿' => '50', '㋀' => '1月', '㋁' => '2月', '㋂' => '3月', '㋃' => '4月', '㋄' => '5月', '㋅' => '6月', '㋆' => '7月', '㋇' => '8月', '㋈' => '9月', '㋉' => '10月', '㋊' => '11月', '㋋' => '12月', '㋌' => 'Hg', '㋍' => 'erg', '㋎' => 'eV', '㋏' => 'LTD', '㋐' => 'ア', '㋑' => 'イ', '㋒' => 'ウ', '㋓' => 'エ', '㋔' => 'オ', '㋕' => 'カ', '㋖' => 'キ', '㋗' => 'ク', '㋘' => 'ケ', '㋙' => 'コ', '㋚' => 'サ', '㋛' => 'シ', '㋜' => 'ス', '㋝' => 'セ', '㋞' => 'ソ', '㋟' => 'タ', '㋠' => 'チ', '㋡' => 'ツ', '㋢' => 'テ', '㋣' => 'ト', '㋤' => 'ナ', '㋥' => 'ニ', '㋦' => 'ヌ', '㋧' => 'ネ', '㋨' => 'ノ', '㋩' => 'ハ', '㋪' => 'ヒ', '㋫' => 'フ', '㋬' => 'ヘ', '㋭' => 'ホ', '㋮' => 'マ', '㋯' => 'ミ', '㋰' => 'ム', '㋱' => 'メ', '㋲' => 'モ', '㋳' => 'ヤ', '㋴' => 'ユ', '㋵' => 'ヨ', '㋶' => 'ラ', '㋷' => 'リ', '㋸' => 'ル', '㋹' => 'レ', '㋺' => 'ロ', '㋻' => 'ワ', '㋼' => 'ヰ', '㋽' => 'ヱ', '㋾' => 'ヲ', '㋿' => '令和', '㌀' => 'アパート', '㌁' => 'アルファ', '㌂' => 'アンペア', '㌃' => 'アール', '㌄' => 'イニング', '㌅' => 'インチ', '㌆' => 'ウォン', '㌇' => 'エスクード', '㌈' => 'エーカー', '㌉' => 'オンス', '㌊' => 'オーム', '㌋' => 'カイリ', '㌌' => 'カラット', '㌍' => 'カロリー', '㌎' => 'ガロン', '㌏' => 'ガンマ', '㌐' => 'ギガ', '㌑' => 'ギニー', '㌒' => 'キュリー', '㌓' => 'ギルダー', '㌔' => 'キロ', '㌕' => 'キログラム', '㌖' => 'キロメートル', '㌗' => 'キロワット', '㌘' => 'グラム', '㌙' => 'グラムトン', '㌚' => 'クルゼイロ', '㌛' => 'クローネ', '㌜' => 'ケース', '㌝' => 'コルナ', '㌞' => 'コーポ', '㌟' => 'サイクル', '㌠' => 'サンチーム', '㌡' => 'シリング', '㌢' => 'センチ', '㌣' => 'セント', '㌤' => 'ダース', '㌥' => 'デシ', '㌦' => 'ドル', '㌧' => 'トン', '㌨' => 'ナノ', '㌩' => 'ノット', '㌪' => 'ハイツ', '㌫' => 'パーセント', '㌬' => 'パーツ', '㌭' => 'バーレル', '㌮' => 'ピアストル', '㌯' => 'ピクル', '㌰' => 'ピコ', '㌱' => 'ビル', '㌲' => 'ファラッド', '㌳' => 'フィート', '㌴' => 'ブッシェル', '㌵' => 'フラン', '㌶' => 'ヘクタール', '㌷' => 'ペソ', '㌸' => 'ペニヒ', '㌹' => 'ヘルツ', '㌺' => 'ペンス', '㌻' => 'ページ', '㌼' => 'ベータ', '㌽' => 'ポイント', '㌾' => 'ボルト', '㌿' => 'ホン', '㍀' => 'ポンド', '㍁' => 'ホール', '㍂' => 'ホーン', '㍃' => 'マイクロ', '㍄' => 'マイル', '㍅' => 'マッハ', '㍆' => 'マルク', '㍇' => 'マンション', '㍈' => 'ミクロン', '㍉' => 'ミリ', '㍊' => 'ミリバール', '㍋' => 'メガ', '㍌' => 'メガトン', '㍍' => 'メートル', '㍎' => 'ヤード', '㍏' => 'ヤール', '㍐' => 'ユアン', '㍑' => 'リットル', '㍒' => 'リラ', '㍓' => 'ルピー', '㍔' => 'ルーブル', '㍕' => 'レム', '㍖' => 'レントゲン', '㍗' => 'ワット', '㍘' => '0点', '㍙' => '1点', '㍚' => '2点', '㍛' => '3点', '㍜' => '4点', '㍝' => '5点', '㍞' => '6点', '㍟' => '7点', '㍠' => '8点', '㍡' => '9点', '㍢' => '10点', '㍣' => '11点', '㍤' => '12点', '㍥' => '13点', '㍦' => '14点', '㍧' => '15点', '㍨' => '16点', '㍩' => '17点', '㍪' => '18点', '㍫' => '19点', '㍬' => '20点', '㍭' => '21点', '㍮' => '22点', '㍯' => '23点', '㍰' => '24点', '㍱' => 'hPa', '㍲' => 'da', '㍳' => 'AU', '㍴' => 'bar', '㍵' => 'oV', '㍶' => 'pc', '㍷' => 'dm', '㍸' => 'dm2', '㍹' => 'dm3', '㍺' => 'IU', '㍻' => '平成', '㍼' => '昭和', '㍽' => '大正', '㍾' => '明治', '㍿' => '株式会社', '㎀' => 'pA', '㎁' => 'nA', '㎂' => 'μA', '㎃' => 'mA', '㎄' => 'kA', '㎅' => 'KB', '㎆' => 'MB', '㎇' => 'GB', '㎈' => 'cal', '㎉' => 'kcal', '㎊' => 'pF', '㎋' => 'nF', '㎌' => 'μF', '㎍' => 'μg', '㎎' => 'mg', '㎏' => 'kg', '㎐' => 'Hz', '㎑' => 'kHz', '㎒' => 'MHz', '㎓' => 'GHz', '㎔' => 'THz', '㎕' => 'μl', '㎖' => 'ml', '㎗' => 'dl', '㎘' => 'kl', '㎙' => 'fm', '㎚' => 'nm', '㎛' => 'μm', '㎜' => 'mm', '㎝' => 'cm', '㎞' => 'km', '㎟' => 'mm2', '㎠' => 'cm2', '㎡' => 'm2', '㎢' => 'km2', '㎣' => 'mm3', '㎤' => 'cm3', '㎥' => 'm3', '㎦' => 'km3', '㎧' => 'm∕s', '㎨' => 'm∕s2', '㎩' => 'Pa', '㎪' => 'kPa', '㎫' => 'MPa', '㎬' => 'GPa', '㎭' => 'rad', '㎮' => 'rad∕s', '㎯' => 'rad∕s2', '㎰' => 'ps', '㎱' => 'ns', '㎲' => 'μs', '㎳' => 'ms', '㎴' => 'pV', '㎵' => 'nV', '㎶' => 'μV', '㎷' => 'mV', '㎸' => 'kV', '㎹' => 'MV', '㎺' => 'pW', '㎻' => 'nW', '㎼' => 'μW', '㎽' => 'mW', '㎾' => 'kW', '㎿' => 'MW', '㏀' => 'kΩ', '㏁' => 'MΩ', '㏂' => 'a.m.', '㏃' => 'Bq', '㏄' => 'cc', '㏅' => 'cd', '㏆' => 'C∕kg', '㏇' => 'Co.', '㏈' => 'dB', '㏉' => 'Gy', '㏊' => 'ha', '㏋' => 'HP', '㏌' => 'in', '㏍' => 'KK', '㏎' => 'KM', '㏏' => 'kt', '㏐' => 'lm', '㏑' => 'ln', '㏒' => 'log', '㏓' => 'lx', '㏔' => 'mb', '㏕' => 'mil', '㏖' => 'mol', '㏗' => 'PH', '㏘' => 'p.m.', '㏙' => 'PPM', '㏚' => 'PR', '㏛' => 'sr', '㏜' => 'Sv', '㏝' => 'Wb', '㏞' => 'V∕m', '㏟' => 'A∕m', '㏠' => '1日', '㏡' => '2日', '㏢' => '3日', '㏣' => '4日', '㏤' => '5日', '㏥' => '6日', '㏦' => '7日', '㏧' => '8日', '㏨' => '9日', '㏩' => '10日', '㏪' => '11日', '㏫' => '12日', '㏬' => '13日', '㏭' => '14日', '㏮' => '15日', '㏯' => '16日', '㏰' => '17日', '㏱' => '18日', '㏲' => '19日', '㏳' => '20日', '㏴' => '21日', '㏵' => '22日', '㏶' => '23日', '㏷' => '24日', '㏸' => '25日', '㏹' => '26日', '㏺' => '27日', '㏻' => '28日', '㏼' => '29日', '㏽' => '30日', '㏾' => '31日', '㏿' => 'gal', 'ꚜ' => 'ъ', 'ꚝ' => 'ь', 'ꝰ' => 'ꝯ', 'ꟸ' => 'Ħ', 'ꟹ' => 'œ', 'ꭜ' => 'ꜧ', 'ꭝ' => 'ꬷ', 'ꭞ' => 'ɫ', 'ꭟ' => 'ꭒ', 'ꭩ' => 'ʍ', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ', 'ﬠ' => 'ע', 'ﬡ' => 'א', 'ﬢ' => 'ד', 'ﬣ' => 'ה', 'ﬤ' => 'כ', 'ﬥ' => 'ל', 'ﬦ' => 'ם', 'ﬧ' => 'ר', 'ﬨ' => 'ת', '﬩' => '+', 'ﭏ' => 'אל', 'ﭐ' => 'ٱ', 'ﭑ' => 'ٱ', 'ﭒ' => 'ٻ', 'ﭓ' => 'ٻ', 'ﭔ' => 'ٻ', 'ﭕ' => 'ٻ', 'ﭖ' => 'پ', 'ﭗ' => 'پ', 'ﭘ' => 'پ', 'ﭙ' => 'پ', 'ﭚ' => 'ڀ', 'ﭛ' => 'ڀ', 'ﭜ' => 'ڀ', 'ﭝ' => 'ڀ', 'ﭞ' => 'ٺ', 'ﭟ' => 'ٺ', 'ﭠ' => 'ٺ', 'ﭡ' => 'ٺ', 'ﭢ' => 'ٿ', 'ﭣ' => 'ٿ', 'ﭤ' => 'ٿ', 'ﭥ' => 'ٿ', 'ﭦ' => 'ٹ', 'ﭧ' => 'ٹ', 'ﭨ' => 'ٹ', 'ﭩ' => 'ٹ', 'ﭪ' => 'ڤ', 'ﭫ' => 'ڤ', 'ﭬ' => 'ڤ', 'ﭭ' => 'ڤ', 'ﭮ' => 'ڦ', 'ﭯ' => 'ڦ', 'ﭰ' => 'ڦ', 'ﭱ' => 'ڦ', 'ﭲ' => 'ڄ', 'ﭳ' => 'ڄ', 'ﭴ' => 'ڄ', 'ﭵ' => 'ڄ', 'ﭶ' => 'ڃ', 'ﭷ' => 'ڃ', 'ﭸ' => 'ڃ', 'ﭹ' => 'ڃ', 'ﭺ' => 'چ', 'ﭻ' => 'چ', 'ﭼ' => 'چ', 'ﭽ' => 'چ', 'ﭾ' => 'ڇ', 'ﭿ' => 'ڇ', 'ﮀ' => 'ڇ', 'ﮁ' => 'ڇ', 'ﮂ' => 'ڍ', 'ﮃ' => 'ڍ', 'ﮄ' => 'ڌ', 'ﮅ' => 'ڌ', 'ﮆ' => 'ڎ', 'ﮇ' => 'ڎ', 'ﮈ' => 'ڈ', 'ﮉ' => 'ڈ', 'ﮊ' => 'ژ', 'ﮋ' => 'ژ', 'ﮌ' => 'ڑ', 'ﮍ' => 'ڑ', 'ﮎ' => 'ک', 'ﮏ' => 'ک', 'ﮐ' => 'ک', 'ﮑ' => 'ک', 'ﮒ' => 'گ', 'ﮓ' => 'گ', 'ﮔ' => 'گ', 'ﮕ' => 'گ', 'ﮖ' => 'ڳ', 'ﮗ' => 'ڳ', 'ﮘ' => 'ڳ', 'ﮙ' => 'ڳ', 'ﮚ' => 'ڱ', 'ﮛ' => 'ڱ', 'ﮜ' => 'ڱ', 'ﮝ' => 'ڱ', 'ﮞ' => 'ں', 'ﮟ' => 'ں', 'ﮠ' => 'ڻ', 'ﮡ' => 'ڻ', 'ﮢ' => 'ڻ', 'ﮣ' => 'ڻ', 'ﮤ' => 'ۀ', 'ﮥ' => 'ۀ', 'ﮦ' => 'ہ', 'ﮧ' => 'ہ', 'ﮨ' => 'ہ', 'ﮩ' => 'ہ', 'ﮪ' => 'ھ', 'ﮫ' => 'ھ', 'ﮬ' => 'ھ', 'ﮭ' => 'ھ', 'ﮮ' => 'ے', 'ﮯ' => 'ے', 'ﮰ' => 'ۓ', 'ﮱ' => 'ۓ', 'ﯓ' => 'ڭ', 'ﯔ' => 'ڭ', 'ﯕ' => 'ڭ', 'ﯖ' => 'ڭ', 'ﯗ' => 'ۇ', 'ﯘ' => 'ۇ', 'ﯙ' => 'ۆ', 'ﯚ' => 'ۆ', 'ﯛ' => 'ۈ', 'ﯜ' => 'ۈ', 'ﯝ' => 'ۇٴ', 'ﯞ' => 'ۋ', 'ﯟ' => 'ۋ', 'ﯠ' => 'ۅ', 'ﯡ' => 'ۅ', 'ﯢ' => 'ۉ', 'ﯣ' => 'ۉ', 'ﯤ' => 'ې', 'ﯥ' => 'ې', 'ﯦ' => 'ې', 'ﯧ' => 'ې', 'ﯨ' => 'ى', 'ﯩ' => 'ى', 'ﯪ' => 'ئا', 'ﯫ' => 'ئا', 'ﯬ' => 'ئە', 'ﯭ' => 'ئە', 'ﯮ' => 'ئو', 'ﯯ' => 'ئو', 'ﯰ' => 'ئۇ', 'ﯱ' => 'ئۇ', 'ﯲ' => 'ئۆ', 'ﯳ' => 'ئۆ', 'ﯴ' => 'ئۈ', 'ﯵ' => 'ئۈ', 'ﯶ' => 'ئې', 'ﯷ' => 'ئې', 'ﯸ' => 'ئې', 'ﯹ' => 'ئى', 'ﯺ' => 'ئى', 'ﯻ' => 'ئى', 'ﯼ' => 'ی', 'ﯽ' => 'ی', 'ﯾ' => 'ی', 'ﯿ' => 'ی', 'ﰀ' => 'ئج', 'ﰁ' => 'ئح', 'ﰂ' => 'ئم', 'ﰃ' => 'ئى', 'ﰄ' => 'ئي', 'ﰅ' => 'بج', 'ﰆ' => 'بح', 'ﰇ' => 'بخ', 'ﰈ' => 'بم', 'ﰉ' => 'بى', 'ﰊ' => 'بي', 'ﰋ' => 'تج', 'ﰌ' => 'تح', 'ﰍ' => 'تخ', 'ﰎ' => 'تم', 'ﰏ' => 'تى', 'ﰐ' => 'تي', 'ﰑ' => 'ثج', 'ﰒ' => 'ثم', 'ﰓ' => 'ثى', 'ﰔ' => 'ثي', 'ﰕ' => 'جح', 'ﰖ' => 'جم', 'ﰗ' => 'حج', 'ﰘ' => 'حم', 'ﰙ' => 'خج', 'ﰚ' => 'خح', 'ﰛ' => 'خم', 'ﰜ' => 'سج', 'ﰝ' => 'سح', 'ﰞ' => 'سخ', 'ﰟ' => 'سم', 'ﰠ' => 'صح', 'ﰡ' => 'صم', 'ﰢ' => 'ضج', 'ﰣ' => 'ضح', 'ﰤ' => 'ضخ', 'ﰥ' => 'ضم', 'ﰦ' => 'طح', 'ﰧ' => 'طم', 'ﰨ' => 'ظم', 'ﰩ' => 'عج', 'ﰪ' => 'عم', 'ﰫ' => 'غج', 'ﰬ' => 'غم', 'ﰭ' => 'فج', 'ﰮ' => 'فح', 'ﰯ' => 'فخ', 'ﰰ' => 'فم', 'ﰱ' => 'فى', 'ﰲ' => 'في', 'ﰳ' => 'قح', 'ﰴ' => 'قم', 'ﰵ' => 'قى', 'ﰶ' => 'قي', 'ﰷ' => 'كا', 'ﰸ' => 'كج', 'ﰹ' => 'كح', 'ﰺ' => 'كخ', 'ﰻ' => 'كل', 'ﰼ' => 'كم', 'ﰽ' => 'كى', 'ﰾ' => 'كي', 'ﰿ' => 'لج', 'ﱀ' => 'لح', 'ﱁ' => 'لخ', 'ﱂ' => 'لم', 'ﱃ' => 'لى', 'ﱄ' => 'لي', 'ﱅ' => 'مج', 'ﱆ' => 'مح', 'ﱇ' => 'مخ', 'ﱈ' => 'مم', 'ﱉ' => 'مى', 'ﱊ' => 'مي', 'ﱋ' => 'نج', 'ﱌ' => 'نح', 'ﱍ' => 'نخ', 'ﱎ' => 'نم', 'ﱏ' => 'نى', 'ﱐ' => 'ني', 'ﱑ' => 'هج', 'ﱒ' => 'هم', 'ﱓ' => 'هى', 'ﱔ' => 'هي', 'ﱕ' => 'يج', 'ﱖ' => 'يح', 'ﱗ' => 'يخ', 'ﱘ' => 'يم', 'ﱙ' => 'يى', 'ﱚ' => 'يي', 'ﱛ' => 'ذٰ', 'ﱜ' => 'رٰ', 'ﱝ' => 'ىٰ', 'ﱞ' => ' ٌّ', 'ﱟ' => ' ٍّ', 'ﱠ' => ' َّ', 'ﱡ' => ' ُّ', 'ﱢ' => ' ِّ', 'ﱣ' => ' ّٰ', 'ﱤ' => 'ئر', 'ﱥ' => 'ئز', 'ﱦ' => 'ئم', 'ﱧ' => 'ئن', 'ﱨ' => 'ئى', 'ﱩ' => 'ئي', 'ﱪ' => 'بر', 'ﱫ' => 'بز', 'ﱬ' => 'بم', 'ﱭ' => 'بن', 'ﱮ' => 'بى', 'ﱯ' => 'بي', 'ﱰ' => 'تر', 'ﱱ' => 'تز', 'ﱲ' => 'تم', 'ﱳ' => 'تن', 'ﱴ' => 'تى', 'ﱵ' => 'تي', 'ﱶ' => 'ثر', 'ﱷ' => 'ثز', 'ﱸ' => 'ثم', 'ﱹ' => 'ثن', 'ﱺ' => 'ثى', 'ﱻ' => 'ثي', 'ﱼ' => 'فى', 'ﱽ' => 'في', 'ﱾ' => 'قى', 'ﱿ' => 'قي', 'ﲀ' => 'كا', 'ﲁ' => 'كل', 'ﲂ' => 'كم', 'ﲃ' => 'كى', 'ﲄ' => 'كي', 'ﲅ' => 'لم', 'ﲆ' => 'لى', 'ﲇ' => 'لي', 'ﲈ' => 'ما', 'ﲉ' => 'مم', 'ﲊ' => 'نر', 'ﲋ' => 'نز', 'ﲌ' => 'نم', 'ﲍ' => 'نن', 'ﲎ' => 'نى', 'ﲏ' => 'ني', 'ﲐ' => 'ىٰ', 'ﲑ' => 'ير', 'ﲒ' => 'يز', 'ﲓ' => 'يم', 'ﲔ' => 'ين', 'ﲕ' => 'يى', 'ﲖ' => 'يي', 'ﲗ' => 'ئج', 'ﲘ' => 'ئح', 'ﲙ' => 'ئخ', 'ﲚ' => 'ئم', 'ﲛ' => 'ئه', 'ﲜ' => 'بج', 'ﲝ' => 'بح', 'ﲞ' => 'بخ', 'ﲟ' => 'بم', 'ﲠ' => 'به', 'ﲡ' => 'تج', 'ﲢ' => 'تح', 'ﲣ' => 'تخ', 'ﲤ' => 'تم', 'ﲥ' => 'ته', 'ﲦ' => 'ثم', 'ﲧ' => 'جح', 'ﲨ' => 'جم', 'ﲩ' => 'حج', 'ﲪ' => 'حم', 'ﲫ' => 'خج', 'ﲬ' => 'خم', 'ﲭ' => 'سج', 'ﲮ' => 'سح', 'ﲯ' => 'سخ', 'ﲰ' => 'سم', 'ﲱ' => 'صح', 'ﲲ' => 'صخ', 'ﲳ' => 'صم', 'ﲴ' => 'ضج', 'ﲵ' => 'ضح', 'ﲶ' => 'ضخ', 'ﲷ' => 'ضم', 'ﲸ' => 'طح', 'ﲹ' => 'ظم', 'ﲺ' => 'عج', 'ﲻ' => 'عم', 'ﲼ' => 'غج', 'ﲽ' => 'غم', 'ﲾ' => 'فج', 'ﲿ' => 'فح', 'ﳀ' => 'فخ', 'ﳁ' => 'فم', 'ﳂ' => 'قح', 'ﳃ' => 'قم', 'ﳄ' => 'كج', 'ﳅ' => 'كح', 'ﳆ' => 'كخ', 'ﳇ' => 'كل', 'ﳈ' => 'كم', 'ﳉ' => 'لج', 'ﳊ' => 'لح', 'ﳋ' => 'لخ', 'ﳌ' => 'لم', 'ﳍ' => 'له', 'ﳎ' => 'مج', 'ﳏ' => 'مح', 'ﳐ' => 'مخ', 'ﳑ' => 'مم', 'ﳒ' => 'نج', 'ﳓ' => 'نح', 'ﳔ' => 'نخ', 'ﳕ' => 'نم', 'ﳖ' => 'نه', 'ﳗ' => 'هج', 'ﳘ' => 'هم', 'ﳙ' => 'هٰ', 'ﳚ' => 'يج', 'ﳛ' => 'يح', 'ﳜ' => 'يخ', 'ﳝ' => 'يم', 'ﳞ' => 'يه', 'ﳟ' => 'ئم', 'ﳠ' => 'ئه', 'ﳡ' => 'بم', 'ﳢ' => 'به', 'ﳣ' => 'تم', 'ﳤ' => 'ته', 'ﳥ' => 'ثم', 'ﳦ' => 'ثه', 'ﳧ' => 'سم', 'ﳨ' => 'سه', 'ﳩ' => 'شم', 'ﳪ' => 'شه', 'ﳫ' => 'كل', 'ﳬ' => 'كم', 'ﳭ' => 'لم', 'ﳮ' => 'نم', 'ﳯ' => 'نه', 'ﳰ' => 'يم', 'ﳱ' => 'يه', 'ﳲ' => 'ـَّ', 'ﳳ' => 'ـُّ', 'ﳴ' => 'ـِّ', 'ﳵ' => 'طى', 'ﳶ' => 'طي', 'ﳷ' => 'عى', 'ﳸ' => 'عي', 'ﳹ' => 'غى', 'ﳺ' => 'غي', 'ﳻ' => 'سى', 'ﳼ' => 'سي', 'ﳽ' => 'شى', 'ﳾ' => 'شي', 'ﳿ' => 'حى', 'ﴀ' => 'حي', 'ﴁ' => 'جى', 'ﴂ' => 'جي', 'ﴃ' => 'خى', 'ﴄ' => 'خي', 'ﴅ' => 'صى', 'ﴆ' => 'صي', 'ﴇ' => 'ضى', 'ﴈ' => 'ضي', 'ﴉ' => 'شج', 'ﴊ' => 'شح', 'ﴋ' => 'شخ', 'ﴌ' => 'شم', 'ﴍ' => 'شر', 'ﴎ' => 'سر', 'ﴏ' => 'صر', 'ﴐ' => 'ضر', 'ﴑ' => 'طى', 'ﴒ' => 'طي', 'ﴓ' => 'عى', 'ﴔ' => 'عي', 'ﴕ' => 'غى', 'ﴖ' => 'غي', 'ﴗ' => 'سى', 'ﴘ' => 'سي', 'ﴙ' => 'شى', 'ﴚ' => 'شي', 'ﴛ' => 'حى', 'ﴜ' => 'حي', 'ﴝ' => 'جى', 'ﴞ' => 'جي', 'ﴟ' => 'خى', 'ﴠ' => 'خي', 'ﴡ' => 'صى', 'ﴢ' => 'صي', 'ﴣ' => 'ضى', 'ﴤ' => 'ضي', 'ﴥ' => 'شج', 'ﴦ' => 'شح', 'ﴧ' => 'شخ', 'ﴨ' => 'شم', 'ﴩ' => 'شر', 'ﴪ' => 'سر', 'ﴫ' => 'صر', 'ﴬ' => 'ضر', 'ﴭ' => 'شج', 'ﴮ' => 'شح', 'ﴯ' => 'شخ', 'ﴰ' => 'شم', 'ﴱ' => 'سه', 'ﴲ' => 'شه', 'ﴳ' => 'طم', 'ﴴ' => 'سج', 'ﴵ' => 'سح', 'ﴶ' => 'سخ', 'ﴷ' => 'شج', 'ﴸ' => 'شح', 'ﴹ' => 'شخ', 'ﴺ' => 'طم', 'ﴻ' => 'ظم', 'ﴼ' => 'اً', 'ﴽ' => 'اً', 'ﵐ' => 'تجم', 'ﵑ' => 'تحج', 'ﵒ' => 'تحج', 'ﵓ' => 'تحم', 'ﵔ' => 'تخم', 'ﵕ' => 'تمج', 'ﵖ' => 'تمح', 'ﵗ' => 'تمخ', 'ﵘ' => 'جمح', 'ﵙ' => 'جمح', 'ﵚ' => 'حمي', 'ﵛ' => 'حمى', 'ﵜ' => 'سحج', 'ﵝ' => 'سجح', 'ﵞ' => 'سجى', 'ﵟ' => 'سمح', 'ﵠ' => 'سمح', 'ﵡ' => 'سمج', 'ﵢ' => 'سمم', 'ﵣ' => 'سمم', 'ﵤ' => 'صحح', 'ﵥ' => 'صحح', 'ﵦ' => 'صمم', 'ﵧ' => 'شحم', 'ﵨ' => 'شحم', 'ﵩ' => 'شجي', 'ﵪ' => 'شمخ', 'ﵫ' => 'شمخ', 'ﵬ' => 'شمم', 'ﵭ' => 'شمم', 'ﵮ' => 'ضحى', 'ﵯ' => 'ضخم', 'ﵰ' => 'ضخم', 'ﵱ' => 'طمح', 'ﵲ' => 'طمح', 'ﵳ' => 'طمم', 'ﵴ' => 'طمي', 'ﵵ' => 'عجم', 'ﵶ' => 'عمم', 'ﵷ' => 'عمم', 'ﵸ' => 'عمى', 'ﵹ' => 'غمم', 'ﵺ' => 'غمي', 'ﵻ' => 'غمى', 'ﵼ' => 'فخم', 'ﵽ' => 'فخم', 'ﵾ' => 'قمح', 'ﵿ' => 'قمم', 'ﶀ' => 'لحم', 'ﶁ' => 'لحي', 'ﶂ' => 'لحى', 'ﶃ' => 'لجج', 'ﶄ' => 'لجج', 'ﶅ' => 'لخم', 'ﶆ' => 'لخم', 'ﶇ' => 'لمح', 'ﶈ' => 'لمح', 'ﶉ' => 'محج', 'ﶊ' => 'محم', 'ﶋ' => 'محي', 'ﶌ' => 'مجح', 'ﶍ' => 'مجم', 'ﶎ' => 'مخج', 'ﶏ' => 'مخم', 'ﶒ' => 'مجخ', 'ﶓ' => 'همج', 'ﶔ' => 'همم', 'ﶕ' => 'نحم', 'ﶖ' => 'نحى', 'ﶗ' => 'نجم', 'ﶘ' => 'نجم', 'ﶙ' => 'نجى', 'ﶚ' => 'نمي', 'ﶛ' => 'نمى', 'ﶜ' => 'يمم', 'ﶝ' => 'يمم', 'ﶞ' => 'بخي', 'ﶟ' => 'تجي', 'ﶠ' => 'تجى', 'ﶡ' => 'تخي', 'ﶢ' => 'تخى', 'ﶣ' => 'تمي', 'ﶤ' => 'تمى', 'ﶥ' => 'جمي', 'ﶦ' => 'جحى', 'ﶧ' => 'جمى', 'ﶨ' => 'سخى', 'ﶩ' => 'صحي', 'ﶪ' => 'شحي', 'ﶫ' => 'ضحي', 'ﶬ' => 'لجي', 'ﶭ' => 'لمي', 'ﶮ' => 'يحي', 'ﶯ' => 'يجي', 'ﶰ' => 'يمي', 'ﶱ' => 'ممي', 'ﶲ' => 'قمي', 'ﶳ' => 'نحي', 'ﶴ' => 'قمح', 'ﶵ' => 'لحم', 'ﶶ' => 'عمي', 'ﶷ' => 'كمي', 'ﶸ' => 'نجح', 'ﶹ' => 'مخي', 'ﶺ' => 'لجم', 'ﶻ' => 'كمم', 'ﶼ' => 'لجم', 'ﶽ' => 'نجح', 'ﶾ' => 'جحي', 'ﶿ' => 'حجي', 'ﷀ' => 'مجي', 'ﷁ' => 'فمي', 'ﷂ' => 'بحي', 'ﷃ' => 'كمم', 'ﷄ' => 'عجم', 'ﷅ' => 'صمم', 'ﷆ' => 'سخي', 'ﷇ' => 'نجي', 'ﷰ' => 'صلے', 'ﷱ' => 'قلے', 'ﷲ' => 'الله', 'ﷳ' => 'اكبر', 'ﷴ' => 'محمد', 'ﷵ' => 'صلعم', 'ﷶ' => 'رسول', 'ﷷ' => 'عليه', 'ﷸ' => 'وسلم', 'ﷹ' => 'صلى', 'ﷺ' => 'صلى الله عليه وسلم', 'ﷻ' => 'جل جلاله', '﷼' => 'ریال', '︐' => ',', '︑' => '、', '︒' => '。', '︓' => ':', '︔' => ';', '︕' => '!', '︖' => '?', '︗' => '〖', '︘' => '〗', '︙' => '...', '︰' => '..', '︱' => '—', '︲' => '–', '︳' => '_', '︴' => '_', '︵' => '(', '︶' => ')', '︷' => '{', '︸' => '}', '︹' => '〔', '︺' => '〕', '︻' => '【', '︼' => '】', '︽' => '《', '︾' => '》', '︿' => '〈', '﹀' => '〉', '﹁' => '「', '﹂' => '」', '﹃' => '『', '﹄' => '』', '﹇' => '[', '﹈' => ']', '﹉' => ' ̅', '﹊' => ' ̅', '﹋' => ' ̅', '﹌' => ' ̅', '﹍' => '_', '﹎' => '_', '﹏' => '_', '﹐' => ',', '﹑' => '、', '﹒' => '.', '﹔' => ';', '﹕' => ':', '﹖' => '?', '﹗' => '!', '﹘' => '—', '﹙' => '(', '﹚' => ')', '﹛' => '{', '﹜' => '}', '﹝' => '〔', '﹞' => '〕', '﹟' => '#', '﹠' => '&', '﹡' => '*', '﹢' => '+', '﹣' => '-', '﹤' => '<', '﹥' => '>', '﹦' => '=', '﹨' => '\\', '﹩' => '$', '﹪' => '%', '﹫' => '@', 'ﹰ' => ' ً', 'ﹱ' => 'ـً', 'ﹲ' => ' ٌ', 'ﹴ' => ' ٍ', 'ﹶ' => ' َ', 'ﹷ' => 'ـَ', 'ﹸ' => ' ُ', 'ﹹ' => 'ـُ', 'ﹺ' => ' ِ', 'ﹻ' => 'ـِ', 'ﹼ' => ' ّ', 'ﹽ' => 'ـّ', 'ﹾ' => ' ْ', 'ﹿ' => 'ـْ', 'ﺀ' => 'ء', 'ﺁ' => 'آ', 'ﺂ' => 'آ', 'ﺃ' => 'أ', 'ﺄ' => 'أ', 'ﺅ' => 'ؤ', 'ﺆ' => 'ؤ', 'ﺇ' => 'إ', 'ﺈ' => 'إ', 'ﺉ' => 'ئ', 'ﺊ' => 'ئ', 'ﺋ' => 'ئ', 'ﺌ' => 'ئ', 'ﺍ' => 'ا', 'ﺎ' => 'ا', 'ﺏ' => 'ب', 'ﺐ' => 'ب', 'ﺑ' => 'ب', 'ﺒ' => 'ب', 'ﺓ' => 'ة', 'ﺔ' => 'ة', 'ﺕ' => 'ت', 'ﺖ' => 'ت', 'ﺗ' => 'ت', 'ﺘ' => 'ت', 'ﺙ' => 'ث', 'ﺚ' => 'ث', 'ﺛ' => 'ث', 'ﺜ' => 'ث', 'ﺝ' => 'ج', 'ﺞ' => 'ج', 'ﺟ' => 'ج', 'ﺠ' => 'ج', 'ﺡ' => 'ح', 'ﺢ' => 'ح', 'ﺣ' => 'ح', 'ﺤ' => 'ح', 'ﺥ' => 'خ', 'ﺦ' => 'خ', 'ﺧ' => 'خ', 'ﺨ' => 'خ', 'ﺩ' => 'د', 'ﺪ' => 'د', 'ﺫ' => 'ذ', 'ﺬ' => 'ذ', 'ﺭ' => 'ر', 'ﺮ' => 'ر', 'ﺯ' => 'ز', 'ﺰ' => 'ز', 'ﺱ' => 'س', 'ﺲ' => 'س', 'ﺳ' => 'س', 'ﺴ' => 'س', 'ﺵ' => 'ش', 'ﺶ' => 'ش', 'ﺷ' => 'ش', 'ﺸ' => 'ش', 'ﺹ' => 'ص', 'ﺺ' => 'ص', 'ﺻ' => 'ص', 'ﺼ' => 'ص', 'ﺽ' => 'ض', 'ﺾ' => 'ض', 'ﺿ' => 'ض', 'ﻀ' => 'ض', 'ﻁ' => 'ط', 'ﻂ' => 'ط', 'ﻃ' => 'ط', 'ﻄ' => 'ط', 'ﻅ' => 'ظ', 'ﻆ' => 'ظ', 'ﻇ' => 'ظ', 'ﻈ' => 'ظ', 'ﻉ' => 'ع', 'ﻊ' => 'ع', 'ﻋ' => 'ع', 'ﻌ' => 'ع', 'ﻍ' => 'غ', 'ﻎ' => 'غ', 'ﻏ' => 'غ', 'ﻐ' => 'غ', 'ﻑ' => 'ف', 'ﻒ' => 'ف', 'ﻓ' => 'ف', 'ﻔ' => 'ف', 'ﻕ' => 'ق', 'ﻖ' => 'ق', 'ﻗ' => 'ق', 'ﻘ' => 'ق', 'ﻙ' => 'ك', 'ﻚ' => 'ك', 'ﻛ' => 'ك', 'ﻜ' => 'ك', 'ﻝ' => 'ل', 'ﻞ' => 'ل', 'ﻟ' => 'ل', 'ﻠ' => 'ل', 'ﻡ' => 'م', 'ﻢ' => 'م', 'ﻣ' => 'م', 'ﻤ' => 'م', 'ﻥ' => 'ن', 'ﻦ' => 'ن', 'ﻧ' => 'ن', 'ﻨ' => 'ن', 'ﻩ' => 'ه', 'ﻪ' => 'ه', 'ﻫ' => 'ه', 'ﻬ' => 'ه', 'ﻭ' => 'و', 'ﻮ' => 'و', 'ﻯ' => 'ى', 'ﻰ' => 'ى', 'ﻱ' => 'ي', 'ﻲ' => 'ي', 'ﻳ' => 'ي', 'ﻴ' => 'ي', 'ﻵ' => 'لآ', 'ﻶ' => 'لآ', 'ﻷ' => 'لأ', 'ﻸ' => 'لأ', 'ﻹ' => 'لإ', 'ﻺ' => 'لإ', 'ﻻ' => 'لا', 'ﻼ' => 'لا', '!' => '!', '"' => '"', '#' => '#', '$' => '$', '%' => '%', '&' => '&', ''' => '\'', '(' => '(', ')' => ')', '*' => '*', '+' => '+', ',' => ',', '-' => '-', '.' => '.', '/' => '/', '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', ':' => ':', ';' => ';', '<' => '<', '=' => '=', '>' => '>', '?' => '?', '@' => '@', 'A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E', 'F' => 'F', 'G' => 'G', 'H' => 'H', 'I' => 'I', 'J' => 'J', 'K' => 'K', 'L' => 'L', 'M' => 'M', 'N' => 'N', 'O' => 'O', 'P' => 'P', 'Q' => 'Q', 'R' => 'R', 'S' => 'S', 'T' => 'T', 'U' => 'U', 'V' => 'V', 'W' => 'W', 'X' => 'X', 'Y' => 'Y', 'Z' => 'Z', '[' => '[', '\' => '\\', ']' => ']', '^' => '^', '_' => '_', '`' => '`', 'a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd', 'e' => 'e', 'f' => 'f', 'g' => 'g', 'h' => 'h', 'i' => 'i', 'j' => 'j', 'k' => 'k', 'l' => 'l', 'm' => 'm', 'n' => 'n', 'o' => 'o', 'p' => 'p', 'q' => 'q', 'r' => 'r', 's' => 's', 't' => 't', 'u' => 'u', 'v' => 'v', 'w' => 'w', 'x' => 'x', 'y' => 'y', 'z' => 'z', '{' => '{', '|' => '|', '}' => '}', '~' => '~', '⦅' => '⦅', '⦆' => '⦆', '。' => '。', '「' => '「', '」' => '」', '、' => '、', '・' => '・', 'ヲ' => 'ヲ', 'ァ' => 'ァ', 'ィ' => 'ィ', 'ゥ' => 'ゥ', 'ェ' => 'ェ', 'ォ' => 'ォ', 'ャ' => 'ャ', 'ュ' => 'ュ', 'ョ' => 'ョ', 'ッ' => 'ッ', 'ー' => 'ー', 'ア' => 'ア', 'イ' => 'イ', 'ウ' => 'ウ', 'エ' => 'エ', 'オ' => 'オ', 'カ' => 'カ', 'キ' => 'キ', 'ク' => 'ク', 'ケ' => 'ケ', 'コ' => 'コ', 'サ' => 'サ', 'シ' => 'シ', 'ス' => 'ス', 'セ' => 'セ', 'ソ' => 'ソ', 'タ' => 'タ', 'チ' => 'チ', 'ツ' => 'ツ', 'テ' => 'テ', 'ト' => 'ト', 'ナ' => 'ナ', 'ニ' => 'ニ', 'ヌ' => 'ヌ', 'ネ' => 'ネ', 'ノ' => 'ノ', 'ハ' => 'ハ', 'ヒ' => 'ヒ', 'フ' => 'フ', 'ヘ' => 'ヘ', 'ホ' => 'ホ', 'マ' => 'マ', 'ミ' => 'ミ', 'ム' => 'ム', 'メ' => 'メ', 'モ' => 'モ', 'ヤ' => 'ヤ', 'ユ' => 'ユ', 'ヨ' => 'ヨ', 'ラ' => 'ラ', 'リ' => 'リ', 'ル' => 'ル', 'レ' => 'レ', 'ロ' => 'ロ', 'ワ' => 'ワ', 'ン' => 'ン', '゙' => '゙', '゚' => '゚', 'ᅠ' => 'ᅠ', 'ᄀ' => 'ᄀ', 'ᄁ' => 'ᄁ', 'ᆪ' => 'ᆪ', 'ᄂ' => 'ᄂ', 'ᆬ' => 'ᆬ', 'ᆭ' => 'ᆭ', 'ᄃ' => 'ᄃ', 'ᄄ' => 'ᄄ', 'ᄅ' => 'ᄅ', 'ᆰ' => 'ᆰ', 'ᆱ' => 'ᆱ', 'ᆲ' => 'ᆲ', 'ᆳ' => 'ᆳ', 'ᆴ' => 'ᆴ', 'ᆵ' => 'ᆵ', 'ᄚ' => 'ᄚ', 'ᄆ' => 'ᄆ', 'ᄇ' => 'ᄇ', 'ᄈ' => 'ᄈ', 'ᄡ' => 'ᄡ', 'ᄉ' => 'ᄉ', 'ᄊ' => 'ᄊ', 'ᄋ' => 'ᄋ', 'ᄌ' => 'ᄌ', 'ᄍ' => 'ᄍ', 'ᄎ' => 'ᄎ', 'ᄏ' => 'ᄏ', 'ᄐ' => 'ᄐ', 'ᄑ' => 'ᄑ', 'ᄒ' => 'ᄒ', 'ᅡ' => 'ᅡ', 'ᅢ' => 'ᅢ', 'ᅣ' => 'ᅣ', 'ᅤ' => 'ᅤ', 'ᅥ' => 'ᅥ', 'ᅦ' => 'ᅦ', 'ᅧ' => 'ᅧ', 'ᅨ' => 'ᅨ', 'ᅩ' => 'ᅩ', 'ᅪ' => 'ᅪ', 'ᅫ' => 'ᅫ', 'ᅬ' => 'ᅬ', 'ᅭ' => 'ᅭ', 'ᅮ' => 'ᅮ', 'ᅯ' => 'ᅯ', 'ᅰ' => 'ᅰ', 'ᅱ' => 'ᅱ', 'ᅲ' => 'ᅲ', 'ᅳ' => 'ᅳ', 'ᅴ' => 'ᅴ', 'ᅵ' => 'ᅵ', '¢' => '¢', '£' => '£', '¬' => '¬', ' ̄' => ' ̄', '¦' => '¦', '¥' => '¥', '₩' => '₩', '│' => '│', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '■' => '■', '○' => '○', '𝐀' => 'A', '𝐁' => 'B', '𝐂' => 'C', '𝐃' => 'D', '𝐄' => 'E', '𝐅' => 'F', '𝐆' => 'G', '𝐇' => 'H', '𝐈' => 'I', '𝐉' => 'J', '𝐊' => 'K', '𝐋' => 'L', '𝐌' => 'M', '𝐍' => 'N', '𝐎' => 'O', '𝐏' => 'P', '𝐐' => 'Q', '𝐑' => 'R', '𝐒' => 'S', '𝐓' => 'T', '𝐔' => 'U', '𝐕' => 'V', '𝐖' => 'W', '𝐗' => 'X', '𝐘' => 'Y', '𝐙' => 'Z', '𝐚' => 'a', '𝐛' => 'b', '𝐜' => 'c', '𝐝' => 'd', '𝐞' => 'e', '𝐟' => 'f', '𝐠' => 'g', '𝐡' => 'h', '𝐢' => 'i', '𝐣' => 'j', '𝐤' => 'k', '𝐥' => 'l', '𝐦' => 'm', '𝐧' => 'n', '𝐨' => 'o', '𝐩' => 'p', '𝐪' => 'q', '𝐫' => 'r', '𝐬' => 's', '𝐭' => 't', '𝐮' => 'u', '𝐯' => 'v', '𝐰' => 'w', '𝐱' => 'x', '𝐲' => 'y', '𝐳' => 'z', '𝐴' => 'A', '𝐵' => 'B', '𝐶' => 'C', '𝐷' => 'D', '𝐸' => 'E', '𝐹' => 'F', '𝐺' => 'G', '𝐻' => 'H', '𝐼' => 'I', '𝐽' => 'J', '𝐾' => 'K', '𝐿' => 'L', '𝑀' => 'M', '𝑁' => 'N', '𝑂' => 'O', '𝑃' => 'P', '𝑄' => 'Q', '𝑅' => 'R', '𝑆' => 'S', '𝑇' => 'T', '𝑈' => 'U', '𝑉' => 'V', '𝑊' => 'W', '𝑋' => 'X', '𝑌' => 'Y', '𝑍' => 'Z', '𝑎' => 'a', '𝑏' => 'b', '𝑐' => 'c', '𝑑' => 'd', '𝑒' => 'e', '𝑓' => 'f', '𝑔' => 'g', '𝑖' => 'i', '𝑗' => 'j', '𝑘' => 'k', '𝑙' => 'l', '𝑚' => 'm', '𝑛' => 'n', '𝑜' => 'o', '𝑝' => 'p', '𝑞' => 'q', '𝑟' => 'r', '𝑠' => 's', '𝑡' => 't', '𝑢' => 'u', '𝑣' => 'v', '𝑤' => 'w', '𝑥' => 'x', '𝑦' => 'y', '𝑧' => 'z', '𝑨' => 'A', '𝑩' => 'B', '𝑪' => 'C', '𝑫' => 'D', '𝑬' => 'E', '𝑭' => 'F', '𝑮' => 'G', '𝑯' => 'H', '𝑰' => 'I', '𝑱' => 'J', '𝑲' => 'K', '𝑳' => 'L', '𝑴' => 'M', '𝑵' => 'N', '𝑶' => 'O', '𝑷' => 'P', '𝑸' => 'Q', '𝑹' => 'R', '𝑺' => 'S', '𝑻' => 'T', '𝑼' => 'U', '𝑽' => 'V', '𝑾' => 'W', '𝑿' => 'X', '𝒀' => 'Y', '𝒁' => 'Z', '𝒂' => 'a', '𝒃' => 'b', '𝒄' => 'c', '𝒅' => 'd', '𝒆' => 'e', '𝒇' => 'f', '𝒈' => 'g', '𝒉' => 'h', '𝒊' => 'i', '𝒋' => 'j', '𝒌' => 'k', '𝒍' => 'l', '𝒎' => 'm', '𝒏' => 'n', '𝒐' => 'o', '𝒑' => 'p', '𝒒' => 'q', '𝒓' => 'r', '𝒔' => 's', '𝒕' => 't', '𝒖' => 'u', '𝒗' => 'v', '𝒘' => 'w', '𝒙' => 'x', '𝒚' => 'y', '𝒛' => 'z', '𝒜' => 'A', '𝒞' => 'C', '𝒟' => 'D', '𝒢' => 'G', '𝒥' => 'J', '𝒦' => 'K', '𝒩' => 'N', '𝒪' => 'O', '𝒫' => 'P', '𝒬' => 'Q', '𝒮' => 'S', '𝒯' => 'T', '𝒰' => 'U', '𝒱' => 'V', '𝒲' => 'W', '𝒳' => 'X', '𝒴' => 'Y', '𝒵' => 'Z', '𝒶' => 'a', '𝒷' => 'b', '𝒸' => 'c', '𝒹' => 'd', '𝒻' => 'f', '𝒽' => 'h', '𝒾' => 'i', '𝒿' => 'j', '𝓀' => 'k', '𝓁' => 'l', '𝓂' => 'm', '𝓃' => 'n', '𝓅' => 'p', '𝓆' => 'q', '𝓇' => 'r', '𝓈' => 's', '𝓉' => 't', '𝓊' => 'u', '𝓋' => 'v', '𝓌' => 'w', '𝓍' => 'x', '𝓎' => 'y', '𝓏' => 'z', '𝓐' => 'A', '𝓑' => 'B', '𝓒' => 'C', '𝓓' => 'D', '𝓔' => 'E', '𝓕' => 'F', '𝓖' => 'G', '𝓗' => 'H', '𝓘' => 'I', '𝓙' => 'J', '𝓚' => 'K', '𝓛' => 'L', '𝓜' => 'M', '𝓝' => 'N', '𝓞' => 'O', '𝓟' => 'P', '𝓠' => 'Q', '𝓡' => 'R', '𝓢' => 'S', '𝓣' => 'T', '𝓤' => 'U', '𝓥' => 'V', '𝓦' => 'W', '𝓧' => 'X', '𝓨' => 'Y', '𝓩' => 'Z', '𝓪' => 'a', '𝓫' => 'b', '𝓬' => 'c', '𝓭' => 'd', '𝓮' => 'e', '𝓯' => 'f', '𝓰' => 'g', '𝓱' => 'h', '𝓲' => 'i', '𝓳' => 'j', '𝓴' => 'k', '𝓵' => 'l', '𝓶' => 'm', '𝓷' => 'n', '𝓸' => 'o', '𝓹' => 'p', '𝓺' => 'q', '𝓻' => 'r', '𝓼' => 's', '𝓽' => 't', '𝓾' => 'u', '𝓿' => 'v', '𝔀' => 'w', '𝔁' => 'x', '𝔂' => 'y', '𝔃' => 'z', '𝔄' => 'A', '𝔅' => 'B', '𝔇' => 'D', '𝔈' => 'E', '𝔉' => 'F', '𝔊' => 'G', '𝔍' => 'J', '𝔎' => 'K', '𝔏' => 'L', '𝔐' => 'M', '𝔑' => 'N', '𝔒' => 'O', '𝔓' => 'P', '𝔔' => 'Q', '𝔖' => 'S', '𝔗' => 'T', '𝔘' => 'U', '𝔙' => 'V', '𝔚' => 'W', '𝔛' => 'X', '𝔜' => 'Y', '𝔞' => 'a', '𝔟' => 'b', '𝔠' => 'c', '𝔡' => 'd', '𝔢' => 'e', '𝔣' => 'f', '𝔤' => 'g', '𝔥' => 'h', '𝔦' => 'i', '𝔧' => 'j', '𝔨' => 'k', '𝔩' => 'l', '𝔪' => 'm', '𝔫' => 'n', '𝔬' => 'o', '𝔭' => 'p', '𝔮' => 'q', '𝔯' => 'r', '𝔰' => 's', '𝔱' => 't', '𝔲' => 'u', '𝔳' => 'v', '𝔴' => 'w', '𝔵' => 'x', '𝔶' => 'y', '𝔷' => 'z', '𝔸' => 'A', '𝔹' => 'B', '𝔻' => 'D', '𝔼' => 'E', '𝔽' => 'F', '𝔾' => 'G', '𝕀' => 'I', '𝕁' => 'J', '𝕂' => 'K', '𝕃' => 'L', '𝕄' => 'M', '𝕆' => 'O', '𝕊' => 'S', '𝕋' => 'T', '𝕌' => 'U', '𝕍' => 'V', '𝕎' => 'W', '𝕏' => 'X', '𝕐' => 'Y', '𝕒' => 'a', '𝕓' => 'b', '𝕔' => 'c', '𝕕' => 'd', '𝕖' => 'e', '𝕗' => 'f', '𝕘' => 'g', '𝕙' => 'h', '𝕚' => 'i', '𝕛' => 'j', '𝕜' => 'k', '𝕝' => 'l', '𝕞' => 'm', '𝕟' => 'n', '𝕠' => 'o', '𝕡' => 'p', '𝕢' => 'q', '𝕣' => 'r', '𝕤' => 's', '𝕥' => 't', '𝕦' => 'u', '𝕧' => 'v', '𝕨' => 'w', '𝕩' => 'x', '𝕪' => 'y', '𝕫' => 'z', '𝕬' => 'A', '𝕭' => 'B', '𝕮' => 'C', '𝕯' => 'D', '𝕰' => 'E', '𝕱' => 'F', '𝕲' => 'G', '𝕳' => 'H', '𝕴' => 'I', '𝕵' => 'J', '𝕶' => 'K', '𝕷' => 'L', '𝕸' => 'M', '𝕹' => 'N', '𝕺' => 'O', '𝕻' => 'P', '𝕼' => 'Q', '𝕽' => 'R', '𝕾' => 'S', '𝕿' => 'T', '𝖀' => 'U', '𝖁' => 'V', '𝖂' => 'W', '𝖃' => 'X', '𝖄' => 'Y', '𝖅' => 'Z', '𝖆' => 'a', '𝖇' => 'b', '𝖈' => 'c', '𝖉' => 'd', '𝖊' => 'e', '𝖋' => 'f', '𝖌' => 'g', '𝖍' => 'h', '𝖎' => 'i', '𝖏' => 'j', '𝖐' => 'k', '𝖑' => 'l', '𝖒' => 'm', '𝖓' => 'n', '𝖔' => 'o', '𝖕' => 'p', '𝖖' => 'q', '𝖗' => 'r', '𝖘' => 's', '𝖙' => 't', '𝖚' => 'u', '𝖛' => 'v', '𝖜' => 'w', '𝖝' => 'x', '𝖞' => 'y', '𝖟' => 'z', '𝖠' => 'A', '𝖡' => 'B', '𝖢' => 'C', '𝖣' => 'D', '𝖤' => 'E', '𝖥' => 'F', '𝖦' => 'G', '𝖧' => 'H', '𝖨' => 'I', '𝖩' => 'J', '𝖪' => 'K', '𝖫' => 'L', '𝖬' => 'M', '𝖭' => 'N', '𝖮' => 'O', '𝖯' => 'P', '𝖰' => 'Q', '𝖱' => 'R', '𝖲' => 'S', '𝖳' => 'T', '𝖴' => 'U', '𝖵' => 'V', '𝖶' => 'W', '𝖷' => 'X', '𝖸' => 'Y', '𝖹' => 'Z', '𝖺' => 'a', '𝖻' => 'b', '𝖼' => 'c', '𝖽' => 'd', '𝖾' => 'e', '𝖿' => 'f', '𝗀' => 'g', '𝗁' => 'h', '𝗂' => 'i', '𝗃' => 'j', '𝗄' => 'k', '𝗅' => 'l', '𝗆' => 'm', '𝗇' => 'n', '𝗈' => 'o', '𝗉' => 'p', '𝗊' => 'q', '𝗋' => 'r', '𝗌' => 's', '𝗍' => 't', '𝗎' => 'u', '𝗏' => 'v', '𝗐' => 'w', '𝗑' => 'x', '𝗒' => 'y', '𝗓' => 'z', '𝗔' => 'A', '𝗕' => 'B', '𝗖' => 'C', '𝗗' => 'D', '𝗘' => 'E', '𝗙' => 'F', '𝗚' => 'G', '𝗛' => 'H', '𝗜' => 'I', '𝗝' => 'J', '𝗞' => 'K', '𝗟' => 'L', '𝗠' => 'M', '𝗡' => 'N', '𝗢' => 'O', '𝗣' => 'P', '𝗤' => 'Q', '𝗥' => 'R', '𝗦' => 'S', '𝗧' => 'T', '𝗨' => 'U', '𝗩' => 'V', '𝗪' => 'W', '𝗫' => 'X', '𝗬' => 'Y', '𝗭' => 'Z', '𝗮' => 'a', '𝗯' => 'b', '𝗰' => 'c', '𝗱' => 'd', '𝗲' => 'e', '𝗳' => 'f', '𝗴' => 'g', '𝗵' => 'h', '𝗶' => 'i', '𝗷' => 'j', '𝗸' => 'k', '𝗹' => 'l', '𝗺' => 'm', '𝗻' => 'n', '𝗼' => 'o', '𝗽' => 'p', '𝗾' => 'q', '𝗿' => 'r', '𝘀' => 's', '𝘁' => 't', '𝘂' => 'u', '𝘃' => 'v', '𝘄' => 'w', '𝘅' => 'x', '𝘆' => 'y', '𝘇' => 'z', '𝘈' => 'A', '𝘉' => 'B', '𝘊' => 'C', '𝘋' => 'D', '𝘌' => 'E', '𝘍' => 'F', '𝘎' => 'G', '𝘏' => 'H', '𝘐' => 'I', '𝘑' => 'J', '𝘒' => 'K', '𝘓' => 'L', '𝘔' => 'M', '𝘕' => 'N', '𝘖' => 'O', '𝘗' => 'P', '𝘘' => 'Q', '𝘙' => 'R', '𝘚' => 'S', '𝘛' => 'T', '𝘜' => 'U', '𝘝' => 'V', '𝘞' => 'W', '𝘟' => 'X', '𝘠' => 'Y', '𝘡' => 'Z', '𝘢' => 'a', '𝘣' => 'b', '𝘤' => 'c', '𝘥' => 'd', '𝘦' => 'e', '𝘧' => 'f', '𝘨' => 'g', '𝘩' => 'h', '𝘪' => 'i', '𝘫' => 'j', '𝘬' => 'k', '𝘭' => 'l', '𝘮' => 'm', '𝘯' => 'n', '𝘰' => 'o', '𝘱' => 'p', '𝘲' => 'q', '𝘳' => 'r', '𝘴' => 's', '𝘵' => 't', '𝘶' => 'u', '𝘷' => 'v', '𝘸' => 'w', '𝘹' => 'x', '𝘺' => 'y', '𝘻' => 'z', '𝘼' => 'A', '𝘽' => 'B', '𝘾' => 'C', '𝘿' => 'D', '𝙀' => 'E', '𝙁' => 'F', '𝙂' => 'G', '𝙃' => 'H', '𝙄' => 'I', '𝙅' => 'J', '𝙆' => 'K', '𝙇' => 'L', '𝙈' => 'M', '𝙉' => 'N', '𝙊' => 'O', '𝙋' => 'P', '𝙌' => 'Q', '𝙍' => 'R', '𝙎' => 'S', '𝙏' => 'T', '𝙐' => 'U', '𝙑' => 'V', '𝙒' => 'W', '𝙓' => 'X', '𝙔' => 'Y', '𝙕' => 'Z', '𝙖' => 'a', '𝙗' => 'b', '𝙘' => 'c', '𝙙' => 'd', '𝙚' => 'e', '𝙛' => 'f', '𝙜' => 'g', '𝙝' => 'h', '𝙞' => 'i', '𝙟' => 'j', '𝙠' => 'k', '𝙡' => 'l', '𝙢' => 'm', '𝙣' => 'n', '𝙤' => 'o', '𝙥' => 'p', '𝙦' => 'q', '𝙧' => 'r', '𝙨' => 's', '𝙩' => 't', '𝙪' => 'u', '𝙫' => 'v', '𝙬' => 'w', '𝙭' => 'x', '𝙮' => 'y', '𝙯' => 'z', '𝙰' => 'A', '𝙱' => 'B', '𝙲' => 'C', '𝙳' => 'D', '𝙴' => 'E', '𝙵' => 'F', '𝙶' => 'G', '𝙷' => 'H', '𝙸' => 'I', '𝙹' => 'J', '𝙺' => 'K', '𝙻' => 'L', '𝙼' => 'M', '𝙽' => 'N', '𝙾' => 'O', '𝙿' => 'P', '𝚀' => 'Q', '𝚁' => 'R', '𝚂' => 'S', '𝚃' => 'T', '𝚄' => 'U', '𝚅' => 'V', '𝚆' => 'W', '𝚇' => 'X', '𝚈' => 'Y', '𝚉' => 'Z', '𝚊' => 'a', '𝚋' => 'b', '𝚌' => 'c', '𝚍' => 'd', '𝚎' => 'e', '𝚏' => 'f', '𝚐' => 'g', '𝚑' => 'h', '𝚒' => 'i', '𝚓' => 'j', '𝚔' => 'k', '𝚕' => 'l', '𝚖' => 'm', '𝚗' => 'n', '𝚘' => 'o', '𝚙' => 'p', '𝚚' => 'q', '𝚛' => 'r', '𝚜' => 's', '𝚝' => 't', '𝚞' => 'u', '𝚟' => 'v', '𝚠' => 'w', '𝚡' => 'x', '𝚢' => 'y', '𝚣' => 'z', '𝚤' => 'ı', '𝚥' => 'ȷ', '𝚨' => 'Α', '𝚩' => 'Β', '𝚪' => 'Γ', '𝚫' => 'Δ', '𝚬' => 'Ε', '𝚭' => 'Ζ', '𝚮' => 'Η', '𝚯' => 'Θ', '𝚰' => 'Ι', '𝚱' => 'Κ', '𝚲' => 'Λ', '𝚳' => 'Μ', '𝚴' => 'Ν', '𝚵' => 'Ξ', '𝚶' => 'Ο', '𝚷' => 'Π', '𝚸' => 'Ρ', '𝚹' => 'Θ', '𝚺' => 'Σ', '𝚻' => 'Τ', '𝚼' => 'Υ', '𝚽' => 'Φ', '𝚾' => 'Χ', '𝚿' => 'Ψ', '𝛀' => 'Ω', '𝛁' => '∇', '𝛂' => 'α', '𝛃' => 'β', '𝛄' => 'γ', '𝛅' => 'δ', '𝛆' => 'ε', '𝛇' => 'ζ', '𝛈' => 'η', '𝛉' => 'θ', '𝛊' => 'ι', '𝛋' => 'κ', '𝛌' => 'λ', '𝛍' => 'μ', '𝛎' => 'ν', '𝛏' => 'ξ', '𝛐' => 'ο', '𝛑' => 'π', '𝛒' => 'ρ', '𝛓' => 'ς', '𝛔' => 'σ', '𝛕' => 'τ', '𝛖' => 'υ', '𝛗' => 'φ', '𝛘' => 'χ', '𝛙' => 'ψ', '𝛚' => 'ω', '𝛛' => '∂', '𝛜' => 'ε', '𝛝' => 'θ', '𝛞' => 'κ', '𝛟' => 'φ', '𝛠' => 'ρ', '𝛡' => 'π', '𝛢' => 'Α', '𝛣' => 'Β', '𝛤' => 'Γ', '𝛥' => 'Δ', '𝛦' => 'Ε', '𝛧' => 'Ζ', '𝛨' => 'Η', '𝛩' => 'Θ', '𝛪' => 'Ι', '𝛫' => 'Κ', '𝛬' => 'Λ', '𝛭' => 'Μ', '𝛮' => 'Ν', '𝛯' => 'Ξ', '𝛰' => 'Ο', '𝛱' => 'Π', '𝛲' => 'Ρ', '𝛳' => 'Θ', '𝛴' => 'Σ', '𝛵' => 'Τ', '𝛶' => 'Υ', '𝛷' => 'Φ', '𝛸' => 'Χ', '𝛹' => 'Ψ', '𝛺' => 'Ω', '𝛻' => '∇', '𝛼' => 'α', '𝛽' => 'β', '𝛾' => 'γ', '𝛿' => 'δ', '𝜀' => 'ε', '𝜁' => 'ζ', '𝜂' => 'η', '𝜃' => 'θ', '𝜄' => 'ι', '𝜅' => 'κ', '𝜆' => 'λ', '𝜇' => 'μ', '𝜈' => 'ν', '𝜉' => 'ξ', '𝜊' => 'ο', '𝜋' => 'π', '𝜌' => 'ρ', '𝜍' => 'ς', '𝜎' => 'σ', '𝜏' => 'τ', '𝜐' => 'υ', '𝜑' => 'φ', '𝜒' => 'χ', '𝜓' => 'ψ', '𝜔' => 'ω', '𝜕' => '∂', '𝜖' => 'ε', '𝜗' => 'θ', '𝜘' => 'κ', '𝜙' => 'φ', '𝜚' => 'ρ', '𝜛' => 'π', '𝜜' => 'Α', '𝜝' => 'Β', '𝜞' => 'Γ', '𝜟' => 'Δ', '𝜠' => 'Ε', '𝜡' => 'Ζ', '𝜢' => 'Η', '𝜣' => 'Θ', '𝜤' => 'Ι', '𝜥' => 'Κ', '𝜦' => 'Λ', '𝜧' => 'Μ', '𝜨' => 'Ν', '𝜩' => 'Ξ', '𝜪' => 'Ο', '𝜫' => 'Π', '𝜬' => 'Ρ', '𝜭' => 'Θ', '𝜮' => 'Σ', '𝜯' => 'Τ', '𝜰' => 'Υ', '𝜱' => 'Φ', '𝜲' => 'Χ', '𝜳' => 'Ψ', '𝜴' => 'Ω', '𝜵' => '∇', '𝜶' => 'α', '𝜷' => 'β', '𝜸' => 'γ', '𝜹' => 'δ', '𝜺' => 'ε', '𝜻' => 'ζ', '𝜼' => 'η', '𝜽' => 'θ', '𝜾' => 'ι', '𝜿' => 'κ', '𝝀' => 'λ', '𝝁' => 'μ', '𝝂' => 'ν', '𝝃' => 'ξ', '𝝄' => 'ο', '𝝅' => 'π', '𝝆' => 'ρ', '𝝇' => 'ς', '𝝈' => 'σ', '𝝉' => 'τ', '𝝊' => 'υ', '𝝋' => 'φ', '𝝌' => 'χ', '𝝍' => 'ψ', '𝝎' => 'ω', '𝝏' => '∂', '𝝐' => 'ε', '𝝑' => 'θ', '𝝒' => 'κ', '𝝓' => 'φ', '𝝔' => 'ρ', '𝝕' => 'π', '𝝖' => 'Α', '𝝗' => 'Β', '𝝘' => 'Γ', '𝝙' => 'Δ', '𝝚' => 'Ε', '𝝛' => 'Ζ', '𝝜' => 'Η', '𝝝' => 'Θ', '𝝞' => 'Ι', '𝝟' => 'Κ', '𝝠' => 'Λ', '𝝡' => 'Μ', '𝝢' => 'Ν', '𝝣' => 'Ξ', '𝝤' => 'Ο', '𝝥' => 'Π', '𝝦' => 'Ρ', '𝝧' => 'Θ', '𝝨' => 'Σ', '𝝩' => 'Τ', '𝝪' => 'Υ', '𝝫' => 'Φ', '𝝬' => 'Χ', '𝝭' => 'Ψ', '𝝮' => 'Ω', '𝝯' => '∇', '𝝰' => 'α', '𝝱' => 'β', '𝝲' => 'γ', '𝝳' => 'δ', '𝝴' => 'ε', '𝝵' => 'ζ', '𝝶' => 'η', '𝝷' => 'θ', '𝝸' => 'ι', '𝝹' => 'κ', '𝝺' => 'λ', '𝝻' => 'μ', '𝝼' => 'ν', '𝝽' => 'ξ', '𝝾' => 'ο', '𝝿' => 'π', '𝞀' => 'ρ', '𝞁' => 'ς', '𝞂' => 'σ', '𝞃' => 'τ', '𝞄' => 'υ', '𝞅' => 'φ', '𝞆' => 'χ', '𝞇' => 'ψ', '𝞈' => 'ω', '𝞉' => '∂', '𝞊' => 'ε', '𝞋' => 'θ', '𝞌' => 'κ', '𝞍' => 'φ', '𝞎' => 'ρ', '𝞏' => 'π', '𝞐' => 'Α', '𝞑' => 'Β', '𝞒' => 'Γ', '𝞓' => 'Δ', '𝞔' => 'Ε', '𝞕' => 'Ζ', '𝞖' => 'Η', '𝞗' => 'Θ', '𝞘' => 'Ι', '𝞙' => 'Κ', '𝞚' => 'Λ', '𝞛' => 'Μ', '𝞜' => 'Ν', '𝞝' => 'Ξ', '𝞞' => 'Ο', '𝞟' => 'Π', '𝞠' => 'Ρ', '𝞡' => 'Θ', '𝞢' => 'Σ', '𝞣' => 'Τ', '𝞤' => 'Υ', '𝞥' => 'Φ', '𝞦' => 'Χ', '𝞧' => 'Ψ', '𝞨' => 'Ω', '𝞩' => '∇', '𝞪' => 'α', '𝞫' => 'β', '𝞬' => 'γ', '𝞭' => 'δ', '𝞮' => 'ε', '𝞯' => 'ζ', '𝞰' => 'η', '𝞱' => 'θ', '𝞲' => 'ι', '𝞳' => 'κ', '𝞴' => 'λ', '𝞵' => 'μ', '𝞶' => 'ν', '𝞷' => 'ξ', '𝞸' => 'ο', '𝞹' => 'π', '𝞺' => 'ρ', '𝞻' => 'ς', '𝞼' => 'σ', '𝞽' => 'τ', '𝞾' => 'υ', '𝞿' => 'φ', '𝟀' => 'χ', '𝟁' => 'ψ', '𝟂' => 'ω', '𝟃' => '∂', '𝟄' => 'ε', '𝟅' => 'θ', '𝟆' => 'κ', '𝟇' => 'φ', '𝟈' => 'ρ', '𝟉' => 'π', '𝟊' => 'Ϝ', '𝟋' => 'ϝ', '𝟎' => '0', '𝟏' => '1', '𝟐' => '2', '𝟑' => '3', '𝟒' => '4', '𝟓' => '5', '𝟔' => '6', '𝟕' => '7', '𝟖' => '8', '𝟗' => '9', '𝟘' => '0', '𝟙' => '1', '𝟚' => '2', '𝟛' => '3', '𝟜' => '4', '𝟝' => '5', '𝟞' => '6', '𝟟' => '7', '𝟠' => '8', '𝟡' => '9', '𝟢' => '0', '𝟣' => '1', '𝟤' => '2', '𝟥' => '3', '𝟦' => '4', '𝟧' => '5', '𝟨' => '6', '𝟩' => '7', '𝟪' => '8', '𝟫' => '9', '𝟬' => '0', '𝟭' => '1', '𝟮' => '2', '𝟯' => '3', '𝟰' => '4', '𝟱' => '5', '𝟲' => '6', '𝟳' => '7', '𝟴' => '8', '𝟵' => '9', '𝟶' => '0', '𝟷' => '1', '𝟸' => '2', '𝟹' => '3', '𝟺' => '4', '𝟻' => '5', '𝟼' => '6', '𝟽' => '7', '𝟾' => '8', '𝟿' => '9', '𞸀' => 'ا', '𞸁' => 'ب', '𞸂' => 'ج', '𞸃' => 'د', '𞸅' => 'و', '𞸆' => 'ز', '𞸇' => 'ح', '𞸈' => 'ط', '𞸉' => 'ي', '𞸊' => 'ك', '𞸋' => 'ل', '𞸌' => 'م', '𞸍' => 'ن', '𞸎' => 'س', '𞸏' => 'ع', '𞸐' => 'ف', '𞸑' => 'ص', '𞸒' => 'ق', '𞸓' => 'ر', '𞸔' => 'ش', '𞸕' => 'ت', '𞸖' => 'ث', '𞸗' => 'خ', '𞸘' => 'ذ', '𞸙' => 'ض', '𞸚' => 'ظ', '𞸛' => 'غ', '𞸜' => 'ٮ', '𞸝' => 'ں', '𞸞' => 'ڡ', '𞸟' => 'ٯ', '𞸡' => 'ب', '𞸢' => 'ج', '𞸤' => 'ه', '𞸧' => 'ح', '𞸩' => 'ي', '𞸪' => 'ك', '𞸫' => 'ل', '𞸬' => 'م', '𞸭' => 'ن', '𞸮' => 'س', '𞸯' => 'ع', '𞸰' => 'ف', '𞸱' => 'ص', '𞸲' => 'ق', '𞸴' => 'ش', '𞸵' => 'ت', '𞸶' => 'ث', '𞸷' => 'خ', '𞸹' => 'ض', '𞸻' => 'غ', '𞹂' => 'ج', '𞹇' => 'ح', '𞹉' => 'ي', '𞹋' => 'ل', '𞹍' => 'ن', '𞹎' => 'س', '𞹏' => 'ع', '𞹑' => 'ص', '𞹒' => 'ق', '𞹔' => 'ش', '𞹗' => 'خ', '𞹙' => 'ض', '𞹛' => 'غ', '𞹝' => 'ں', '𞹟' => 'ٯ', '𞹡' => 'ب', '𞹢' => 'ج', '𞹤' => 'ه', '𞹧' => 'ح', '𞹨' => 'ط', '𞹩' => 'ي', '𞹪' => 'ك', '𞹬' => 'م', '𞹭' => 'ن', '𞹮' => 'س', '𞹯' => 'ع', '𞹰' => 'ف', '𞹱' => 'ص', '𞹲' => 'ق', '𞹴' => 'ش', '𞹵' => 'ت', '𞹶' => 'ث', '𞹷' => 'خ', '𞹹' => 'ض', '𞹺' => 'ظ', '𞹻' => 'غ', '𞹼' => 'ٮ', '𞹾' => 'ڡ', '𞺀' => 'ا', '𞺁' => 'ب', '𞺂' => 'ج', '𞺃' => 'د', '𞺄' => 'ه', '𞺅' => 'و', '𞺆' => 'ز', '𞺇' => 'ح', '𞺈' => 'ط', '𞺉' => 'ي', '𞺋' => 'ل', '𞺌' => 'م', '𞺍' => 'ن', '𞺎' => 'س', '𞺏' => 'ع', '𞺐' => 'ف', '𞺑' => 'ص', '𞺒' => 'ق', '𞺓' => 'ر', '𞺔' => 'ش', '𞺕' => 'ت', '𞺖' => 'ث', '𞺗' => 'خ', '𞺘' => 'ذ', '𞺙' => 'ض', '𞺚' => 'ظ', '𞺛' => 'غ', '𞺡' => 'ب', '𞺢' => 'ج', '𞺣' => 'د', '𞺥' => 'و', '𞺦' => 'ز', '𞺧' => 'ح', '𞺨' => 'ط', '𞺩' => 'ي', '𞺫' => 'ل', '𞺬' => 'م', '𞺭' => 'ن', '𞺮' => 'س', '𞺯' => 'ع', '𞺰' => 'ف', '𞺱' => 'ص', '𞺲' => 'ق', '𞺳' => 'ر', '𞺴' => 'ش', '𞺵' => 'ت', '𞺶' => 'ث', '𞺷' => 'خ', '𞺸' => 'ذ', '𞺹' => 'ض', '𞺺' => 'ظ', '𞺻' => 'غ', '🄀' => '0.', '🄁' => '0,', '🄂' => '1,', '🄃' => '2,', '🄄' => '3,', '🄅' => '4,', '🄆' => '5,', '🄇' => '6,', '🄈' => '7,', '🄉' => '8,', '🄊' => '9,', '🄐' => '(A)', '🄑' => '(B)', '🄒' => '(C)', '🄓' => '(D)', '🄔' => '(E)', '🄕' => '(F)', '🄖' => '(G)', '🄗' => '(H)', '🄘' => '(I)', '🄙' => '(J)', '🄚' => '(K)', '🄛' => '(L)', '🄜' => '(M)', '🄝' => '(N)', '🄞' => '(O)', '🄟' => '(P)', '🄠' => '(Q)', '🄡' => '(R)', '🄢' => '(S)', '🄣' => '(T)', '🄤' => '(U)', '🄥' => '(V)', '🄦' => '(W)', '🄧' => '(X)', '🄨' => '(Y)', '🄩' => '(Z)', '🄪' => '〔S〕', '🄫' => 'C', '🄬' => 'R', '🄭' => 'CD', '🄮' => 'WZ', '🄰' => 'A', '🄱' => 'B', '🄲' => 'C', '🄳' => 'D', '🄴' => 'E', '🄵' => 'F', '🄶' => 'G', '🄷' => 'H', '🄸' => 'I', '🄹' => 'J', '🄺' => 'K', '🄻' => 'L', '🄼' => 'M', '🄽' => 'N', '🄾' => 'O', '🄿' => 'P', '🅀' => 'Q', '🅁' => 'R', '🅂' => 'S', '🅃' => 'T', '🅄' => 'U', '🅅' => 'V', '🅆' => 'W', '🅇' => 'X', '🅈' => 'Y', '🅉' => 'Z', '🅊' => 'HV', '🅋' => 'MV', '🅌' => 'SD', '🅍' => 'SS', '🅎' => 'PPV', '🅏' => 'WC', '🅪' => 'MC', '🅫' => 'MD', '🅬' => 'MR', '🆐' => 'DJ', '🈀' => 'ほか', '🈁' => 'ココ', '🈂' => 'サ', '🈐' => '手', '🈑' => '字', '🈒' => '双', '🈓' => 'デ', '🈔' => '二', '🈕' => '多', '🈖' => '解', '🈗' => '天', '🈘' => '交', '🈙' => '映', '🈚' => '無', '🈛' => '料', '🈜' => '前', '🈝' => '後', '🈞' => '再', '🈟' => '新', '🈠' => '初', '🈡' => '終', '🈢' => '生', '🈣' => '販', '🈤' => '声', '🈥' => '吹', '🈦' => '演', '🈧' => '投', '🈨' => '捕', '🈩' => '一', '🈪' => '三', '🈫' => '遊', '🈬' => '左', '🈭' => '中', '🈮' => '右', '🈯' => '指', '🈰' => '走', '🈱' => '打', '🈲' => '禁', '🈳' => '空', '🈴' => '合', '🈵' => '満', '🈶' => '有', '🈷' => '月', '🈸' => '申', '🈹' => '割', '🈺' => '営', '🈻' => '配', '🉀' => '〔本〕', '🉁' => '〔三〕', '🉂' => '〔二〕', '🉃' => '〔安〕', '🉄' => '〔点〕', '🉅' => '〔打〕', '🉆' => '〔盗〕', '🉇' => '〔勝〕', '🉈' => '〔敗〕', '🉐' => '得', '🉑' => '可', '🯰' => '0', '🯱' => '1', '🯲' => '2', '🯳' => '3', '🯴' => '4', '🯵' => '5', '🯶' => '6', '🯷' => '7', '🯸' => '8', '🯹' => '9'); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Intl\Normalizer as p; if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; } if (!function_exists('normalizer_is_normalized')) { function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } } if (!function_exists('normalizer_normalize')) { function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } } Symfony Polyfill / Intl: Normalizer =================================== This component provides a fallback implementation for the [`Normalizer`](https://php.net/Normalizer) class provided by the [Intl](https://php.net/intl) extension. More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). { "name": "symfony\/polyfill-intl-normalizer", "type": "library", "description": "Symfony polyfill for intl's Normalizer class and related functions", "keywords": [ "polyfill", "shim", "compatibility", "portable", "intl", "normalizer" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources\/stubs" ] }, "suggest": { "ext-intl": "For best performance" }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\EventDispatcher; use _ContaoManager\Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; /** * Allows providing hooks on domain-specific lifecycles by dispatching events. */ interface EventDispatcherInterface extends PsrEventDispatcherInterface { /** * Dispatches an event to all registered listeners. * * @param object $event The event to pass to the event handlers/listeners * @param string|null $eventName The name of the event to dispatch. If not supplied, * the class of $event should be used instead. * * @return object The passed $event MUST be returned */ public function dispatch(object $event, string $eventName = null) : object; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\EventDispatcher; use _ContaoManager\Psr\EventDispatcher\StoppableEventInterface; /** * Event is the base class for classes containing event data. * * This class contains no event data. It is used by events that do not pass * state information to an event handler when an event is raised. * * You can call the method stopPropagation() to abort the execution of * further listeners in your event listener. * * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Bernhard Schussek * @author Nicolas Grekas */ class Event implements StoppableEventInterface { private $propagationStopped = \false; /** * {@inheritdoc} */ public function isPropagationStopped() : bool { return $this->propagationStopped; } /** * Stops the propagation of the event to further event listeners. * * If multiple event listeners are connected to the same event, no * further event listener will be triggered once any trigger calls * stopPropagation(). */ public function stopPropagation() : void { $this->propagationStopped = \true; } } Copyright (c) 2018-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= The changelog is maintained for all Symfony contracts at the following URL: https://github.com/symfony/contracts/blob/main/CHANGELOG.md Symfony EventDispatcher Contracts ================================= A set of abstractions extracted out of the Symfony components. Can be used to build on semantics that the Symfony components proved useful - and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. { "name": "symfony\/event-dispatcher-contracts", "type": "library", "description": "Generic abstractions related to dispatching event", "keywords": [ "abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "psr\/event-dispatcher": "^1" }, "suggest": { "symfony\/event-dispatcher-implementation": "" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Contracts\\EventDispatcher\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-main": "2.5-dev" }, "thanks": { "name": "symfony\/contracts", "url": "https:\/\/github.com\/symfony\/contracts" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; /** * A sequence of property names or array indices. * * @author Bernhard Schussek * * @extends \Traversable */ interface PropertyPathInterface extends \Traversable { /** * Returns the string representation of the property path. * * @return string */ public function __toString(); /** * Returns the length of the property path, i.e. the number of elements. * * @return int */ public function getLength(); /** * Returns the parent property path. * * The parent property path is the one that contains the same items as * this one except for the last one. * * If this property path only contains one item, null is returned. * * @return self|null */ public function getParent(); /** * Returns the elements of the property path as array. * * @return list */ public function getElements(); /** * Returns the element at the given index in the property path. * * @param int $index The index key * * @return string * * @throws Exception\OutOfBoundsException If the offset is invalid */ public function getElement(int $index); /** * Returns whether the element at the given index is a property. * * @param int $index The index in the property path * * @return bool * * @throws Exception\OutOfBoundsException If the offset is invalid */ public function isProperty(int $index); /** * Returns whether the element at the given index is an array index. * * @param int $index The index in the property path * * @return bool * * @throws Exception\OutOfBoundsException If the offset is invalid */ public function isIndex(int $index); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; /** * Traverses a property path and provides additional methods to find out * information about the current element. * * @author Bernhard Schussek * * @extends \ArrayIterator */ class PropertyPathIterator extends \ArrayIterator implements PropertyPathIteratorInterface { protected $path; public function __construct(PropertyPathInterface $path) { parent::__construct($path->getElements()); $this->path = $path; } /** * {@inheritdoc} */ public function isIndex() { return $this->path->isIndex($this->key()); } /** * {@inheritdoc} */ public function isProperty() { return $this->path->isProperty($this->key()); } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; /** * @author Bernhard Schussek * * @extends \SeekableIterator */ interface PropertyPathIteratorInterface extends \SeekableIterator { /** * Returns whether the current element in the property path is an array * index. * * @return bool */ public function isIndex(); /** * Returns whether the current element in the property path is a property * name. * * @return bool */ public function isProperty(); } CHANGELOG ========= 5.3.0 ----- * deprecate passing a boolean as the second argument of `PropertyAccessor::__construct()`, expecting a combination of bitwise flags instead 5.2.0 ----- * deprecated passing a boolean as the first argument of `PropertyAccessor::__construct()`, expecting a combination of bitwise flags instead * added the ability to disable usage of the magic `__get` & `__set` methods 5.1.0 ----- * Added an `UninitializedPropertyException` * Linking to PropertyInfo extractor to remove a lot of duplicate code 4.4.0 ----- * deprecated passing `null` as `$defaultLifetime` 2nd argument of `PropertyAccessor::createCache()` method, pass `0` instead 4.3.0 ----- * added a `$throwExceptionOnInvalidPropertyPath` argument to the PropertyAccessor constructor. * added `enableExceptionOnInvalidPropertyPath()`, `disableExceptionOnInvalidPropertyPath()` and `isExceptionOnInvalidPropertyPath()` methods to `PropertyAccessorBuilder` 4.0.0 ----- * removed the `StringUtil` class, use `Symfony\Component\Inflector\Inflector` 3.1.0 ----- * deprecated the `StringUtil` class, use `Symfony\Component\Inflector\Inflector` instead 2.7.0 ------ * `UnexpectedTypeException` now expects three constructor arguments: The invalid property value, the `PropertyPathInterface` object and the current index of the property path. 2.5.0 ------ * allowed non alpha numeric characters in second level and deeper object properties names * [BC BREAK] when accessing an index on an object that does not implement ArrayAccess, a NoSuchIndexException is now thrown instead of the semantically wrong NoSuchPropertyException * [BC BREAK] added isReadable() and isWritable() to PropertyAccessorInterface 2.3.0 ------ * added PropertyAccessorBuilder, to enable or disable the support of "__call" * added support for "__call" in the PropertyAccessor (disabled by default) * [BC BREAK] changed PropertyAccessor to continue its search for a property or method even if a non-public match was found. Before, a PropertyAccessDeniedException was thrown in this case. Class PropertyAccessDeniedException was removed now. * deprecated PropertyAccess::getPropertyAccessor * added PropertyAccess::createPropertyAccessor and PropertyAccess::createPropertyAccessorBuilder * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; /** * Writes and reads values to/from an object/array graph. * * @author Bernhard Schussek */ interface PropertyAccessorInterface { /** * Sets the value at the end of the property path of the object graph. * * Example: * * use Symfony\Component\PropertyAccess\PropertyAccess; * * $propertyAccessor = PropertyAccess::createPropertyAccessor(); * * echo $propertyAccessor->setValue($object, 'child.name', 'Fabien'); * // equals echo $object->getChild()->setName('Fabien'); * * This method first tries to find a public setter for each property in the * path. The name of the setter must be the camel-cased property name * prefixed with "set". * * If the setter does not exist, this method tries to find a public * property. The value of the property is then changed. * * If neither is found, an exception is thrown. * * @param object|array $objectOrArray The object or array to modify * @param string|PropertyPathInterface $propertyPath The property path to modify * @param mixed $value The value to set at the end of the property path * * @throws Exception\InvalidArgumentException If the property path is invalid * @throws Exception\AccessException If a property/index does not exist or is not public * @throws Exception\UnexpectedTypeException If a value within the path is neither object nor array */ public function setValue(&$objectOrArray, $propertyPath, $value); /** * Returns the value at the end of the property path of the object graph. * * Example: * * use Symfony\Component\PropertyAccess\PropertyAccess; * * $propertyAccessor = PropertyAccess::createPropertyAccessor(); * * echo $propertyAccessor->getValue($object, 'child.name'); * // equals echo $object->getChild()->getName(); * * This method first tries to find a public getter for each property in the * path. The name of the getter must be the camel-cased property name * prefixed with "get", "is", or "has". * * If the getter does not exist, this method tries to find a public * property. The value of the property is then returned. * * If none of them are found, an exception is thrown. * * @param object|array $objectOrArray The object or array to traverse * @param string|PropertyPathInterface $propertyPath The property path to read * * @return mixed * * @throws Exception\InvalidArgumentException If the property path is invalid * @throws Exception\AccessException If a property/index does not exist or is not public * @throws Exception\UnexpectedTypeException If a value within the path is neither object * nor array */ public function getValue($objectOrArray, $propertyPath); /** * Returns whether a value can be written at a given property path. * * Whenever this method returns true, {@link setValue()} is guaranteed not * to throw an exception when called with the same arguments. * * @param object|array $objectOrArray The object or array to check * @param string|PropertyPathInterface $propertyPath The property path to check * * @return bool * * @throws Exception\InvalidArgumentException If the property path is invalid */ public function isWritable($objectOrArray, $propertyPath); /** * Returns whether a property path can be read from an object graph. * * Whenever this method returns true, {@link getValue()} is guaranteed not * to throw an exception when called with the same arguments. * * @param object|array $objectOrArray The object or array to check * @param string|PropertyPathInterface $propertyPath The property path to check * * @return bool * * @throws Exception\InvalidArgumentException If the property path is invalid */ public function isReadable($objectOrArray, $propertyPath); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\OutOfBoundsException; /** * Default implementation of {@link PropertyPathInterface}. * * @author Bernhard Schussek * * @implements \IteratorAggregate */ class PropertyPath implements \IteratorAggregate, PropertyPathInterface { /** * Character used for separating between plural and singular of an element. */ public const SINGULAR_SEPARATOR = '|'; /** * The elements of the property path. * * @var list */ private $elements = []; /** * The number of elements in the property path. * * @var int */ private $length; /** * Contains a Boolean for each property in $elements denoting whether this * element is an index. It is a property otherwise. * * @var array */ private $isIndex = []; /** * String representation of the path. * * @var string */ private $pathAsString; /** * Constructs a property path from a string. * * @param PropertyPath|string $propertyPath The property path as string or instance * * @throws InvalidArgumentException If the given path is not a string * @throws InvalidPropertyPathException If the syntax of the property path is not valid */ public function __construct($propertyPath) { // Can be used as copy constructor if ($propertyPath instanceof self) { /* @var PropertyPath $propertyPath */ $this->elements = $propertyPath->elements; $this->length = $propertyPath->length; $this->isIndex = $propertyPath->isIndex; $this->pathAsString = $propertyPath->pathAsString; return; } if (!\is_string($propertyPath)) { throw new InvalidArgumentException(\sprintf('The property path constructor needs a string or an instance of "Symfony\\Component\\PropertyAccess\\PropertyPath". Got: "%s".', \get_debug_type($propertyPath))); } if ('' === $propertyPath) { throw new InvalidPropertyPathException('The property path should not be empty.'); } $this->pathAsString = $propertyPath; $position = 0; $remaining = $propertyPath; // first element is evaluated differently - no leading dot for properties $pattern = '/^(([^\\.\\[]++)|\\[([^\\]]++)\\])(.*)/'; while (\preg_match($pattern, $remaining, $matches)) { if ('' !== $matches[2]) { $element = $matches[2]; $this->isIndex[] = \false; } else { $element = $matches[3]; $this->isIndex[] = \true; } $this->elements[] = $element; $position += \strlen($matches[1]); $remaining = $matches[4]; $pattern = '/^(\\.([^\\.|\\[]++)|\\[([^\\]]++)\\])(.*)/'; } if ('' !== $remaining) { throw new InvalidPropertyPathException(\sprintf('Could not parse property path "%s". Unexpected token "%s" at position %d.', $propertyPath, $remaining[0], $position)); } $this->length = \count($this->elements); } /** * {@inheritdoc} */ public function __toString() { return $this->pathAsString; } /** * {@inheritdoc} */ public function getLength() { return $this->length; } /** * {@inheritdoc} */ public function getParent() { if ($this->length <= 1) { return null; } $parent = clone $this; --$parent->length; $parent->pathAsString = \substr($parent->pathAsString, 0, \max(\strrpos($parent->pathAsString, '.'), \strrpos($parent->pathAsString, '['))); \array_pop($parent->elements); \array_pop($parent->isIndex); return $parent; } /** * Returns a new iterator for this path. * * @return PropertyPathIteratorInterface */ #[\ReturnTypeWillChange] public function getIterator() { return new PropertyPathIterator($this); } /** * {@inheritdoc} */ public function getElements() { return $this->elements; } /** * {@inheritdoc} */ public function getElement(int $index) { if (!isset($this->elements[$index])) { throw new OutOfBoundsException(\sprintf('The index "%s" is not within the property path.', $index)); } return $this->elements[$index]; } /** * {@inheritdoc} */ public function isProperty(int $index) { if (!isset($this->isIndex[$index])) { throw new OutOfBoundsException(\sprintf('The index "%s" is not within the property path.', $index)); } return !$this->isIndex[$index]; } /** * {@inheritdoc} */ public function isIndex(int $index) { if (!isset($this->isIndex[$index])) { throw new OutOfBoundsException(\sprintf('The index "%s" is not within the property path.', $index)); } return $this->isIndex[$index]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; /** * A configurable builder to create a PropertyAccessor. * * @author Jérémie Augustin */ class PropertyAccessorBuilder { /** @var int */ private $magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET; private $throwExceptionOnInvalidIndex = \false; private $throwExceptionOnInvalidPropertyPath = \true; /** * @var CacheItemPoolInterface|null */ private $cacheItemPool; /** * @var PropertyReadInfoExtractorInterface|null */ private $readInfoExtractor; /** * @var PropertyWriteInfoExtractorInterface|null */ private $writeInfoExtractor; /** * Enables the use of all magic methods by the PropertyAccessor. * * @return $this */ public function enableMagicMethods() : self { $this->magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET | PropertyAccessor::MAGIC_CALL; return $this; } /** * Disable the use of all magic methods by the PropertyAccessor. * * @return $this */ public function disableMagicMethods() : self { $this->magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS; return $this; } /** * Enables the use of "__call" by the PropertyAccessor. * * @return $this */ public function enableMagicCall() { $this->magicMethods |= PropertyAccessor::MAGIC_CALL; return $this; } /** * Enables the use of "__get" by the PropertyAccessor. */ public function enableMagicGet() : self { $this->magicMethods |= PropertyAccessor::MAGIC_GET; return $this; } /** * Enables the use of "__set" by the PropertyAccessor. * * @return $this */ public function enableMagicSet() : self { $this->magicMethods |= PropertyAccessor::MAGIC_SET; return $this; } /** * Disables the use of "__call" by the PropertyAccessor. * * @return $this */ public function disableMagicCall() { $this->magicMethods &= ~PropertyAccessor::MAGIC_CALL; return $this; } /** * Disables the use of "__get" by the PropertyAccessor. * * @return $this */ public function disableMagicGet() : self { $this->magicMethods &= ~PropertyAccessor::MAGIC_GET; return $this; } /** * Disables the use of "__set" by the PropertyAccessor. * * @return $this */ public function disableMagicSet() : self { $this->magicMethods &= ~PropertyAccessor::MAGIC_SET; return $this; } /** * @return bool whether the use of "__call" by the PropertyAccessor is enabled */ public function isMagicCallEnabled() { return (bool) ($this->magicMethods & PropertyAccessor::MAGIC_CALL); } /** * @return bool whether the use of "__get" by the PropertyAccessor is enabled */ public function isMagicGetEnabled() : bool { return $this->magicMethods & PropertyAccessor::MAGIC_GET; } /** * @return bool whether the use of "__set" by the PropertyAccessor is enabled */ public function isMagicSetEnabled() : bool { return $this->magicMethods & PropertyAccessor::MAGIC_SET; } /** * Enables exceptions when reading a non-existing index. * * This has no influence on writing non-existing indices with PropertyAccessorInterface::setValue() * which are always created on-the-fly. * * @return $this */ public function enableExceptionOnInvalidIndex() { $this->throwExceptionOnInvalidIndex = \true; return $this; } /** * Disables exceptions when reading a non-existing index. * * Instead, null is returned when calling PropertyAccessorInterface::getValue() on a non-existing index. * * @return $this */ public function disableExceptionOnInvalidIndex() { $this->throwExceptionOnInvalidIndex = \false; return $this; } /** * @return bool whether an exception is thrown or null is returned when reading a non-existing index */ public function isExceptionOnInvalidIndexEnabled() { return $this->throwExceptionOnInvalidIndex; } /** * Enables exceptions when reading a non-existing property. * * This has no influence on writing non-existing indices with PropertyAccessorInterface::setValue() * which are always created on-the-fly. * * @return $this */ public function enableExceptionOnInvalidPropertyPath() { $this->throwExceptionOnInvalidPropertyPath = \true; return $this; } /** * Disables exceptions when reading a non-existing index. * * Instead, null is returned when calling PropertyAccessorInterface::getValue() on a non-existing index. * * @return $this */ public function disableExceptionOnInvalidPropertyPath() { $this->throwExceptionOnInvalidPropertyPath = \false; return $this; } /** * @return bool whether an exception is thrown or null is returned when reading a non-existing property */ public function isExceptionOnInvalidPropertyPath() { return $this->throwExceptionOnInvalidPropertyPath; } /** * Sets a cache system. * * @return $this */ public function setCacheItemPool(?CacheItemPoolInterface $cacheItemPool = null) { $this->cacheItemPool = $cacheItemPool; return $this; } /** * Gets the used cache system. * * @return CacheItemPoolInterface|null */ public function getCacheItemPool() { return $this->cacheItemPool; } /** * @return $this */ public function setReadInfoExtractor(?PropertyReadInfoExtractorInterface $readInfoExtractor) { $this->readInfoExtractor = $readInfoExtractor; return $this; } public function getReadInfoExtractor() : ?PropertyReadInfoExtractorInterface { return $this->readInfoExtractor; } /** * @return $this */ public function setWriteInfoExtractor(?PropertyWriteInfoExtractorInterface $writeInfoExtractor) { $this->writeInfoExtractor = $writeInfoExtractor; return $this; } public function getWriteInfoExtractor() : ?PropertyWriteInfoExtractorInterface { return $this->writeInfoExtractor; } /** * Builds and returns a new PropertyAccessor object. * * @return PropertyAccessorInterface */ public function getPropertyAccessor() { $throw = PropertyAccessor::DO_NOT_THROW; if ($this->throwExceptionOnInvalidIndex) { $throw |= PropertyAccessor::THROW_ON_INVALID_INDEX; } if ($this->throwExceptionOnInvalidPropertyPath) { $throw |= PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH; } return new PropertyAccessor($this->magicMethods, $throw, $this->cacheItemPool, $this->readInfoExtractor, $this->writeInfoExtractor); } } PropertyAccess Component ======================== The PropertyAccess component provides functions to read and write from/to an object or array using a simple string notation. Resources --------- * [Documentation](https://symfony.com/doc/current/components/property_access.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Psr\Log\NullLogger; use _ContaoManager\Symfony\Component\Cache\Adapter\AdapterInterface; use _ContaoManager\Symfony\Component\Cache\Adapter\ApcuAdapter; use _ContaoManager\Symfony\Component\Cache\Adapter\NullAdapter; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\AccessException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use _ContaoManager\Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfo; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfo; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; /** * Default implementation of {@link PropertyAccessorInterface}. * * @author Bernhard Schussek * @author Kévin Dunglas * @author Nicolas Grekas */ class PropertyAccessor implements PropertyAccessorInterface { /** @var int Allow none of the magic methods */ public const DISALLOW_MAGIC_METHODS = ReflectionExtractor::DISALLOW_MAGIC_METHODS; /** @var int Allow magic __get methods */ public const MAGIC_GET = ReflectionExtractor::ALLOW_MAGIC_GET; /** @var int Allow magic __set methods */ public const MAGIC_SET = ReflectionExtractor::ALLOW_MAGIC_SET; /** @var int Allow magic __call methods */ public const MAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL; public const DO_NOT_THROW = 0; public const THROW_ON_INVALID_INDEX = 1; public const THROW_ON_INVALID_PROPERTY_PATH = 2; private const VALUE = 0; private const REF = 1; private const IS_REF_CHAINED = 2; private const CACHE_PREFIX_READ = 'r'; private const CACHE_PREFIX_WRITE = 'w'; private const CACHE_PREFIX_PROPERTY_PATH = 'p'; private $magicMethodsFlags; private $ignoreInvalidIndices; private $ignoreInvalidProperty; /** * @var CacheItemPoolInterface */ private $cacheItemPool; private $propertyPathCache = []; /** * @var PropertyReadInfoExtractorInterface */ private $readInfoExtractor; /** * @var PropertyWriteInfoExtractorInterface */ private $writeInfoExtractor; private $readPropertyCache = []; private $writePropertyCache = []; private const RESULT_PROTO = [self::VALUE => null]; /** * Should not be used by application code. Use * {@link PropertyAccess::createPropertyAccessor()} instead. * * @param int $magicMethods A bitwise combination of the MAGIC_* constants * to specify the allowed magic methods (__get, __set, __call) * or self::DISALLOW_MAGIC_METHODS for none * @param int $throw A bitwise combination of the THROW_* constants * to specify when exceptions should be thrown * @param PropertyReadInfoExtractorInterface $readInfoExtractor * @param PropertyWriteInfoExtractorInterface $writeInfoExtractor */ public function __construct($magicMethods = self::MAGIC_GET | self::MAGIC_SET, $throw = self::THROW_ON_INVALID_PROPERTY_PATH, ?CacheItemPoolInterface $cacheItemPool = null, $readInfoExtractor = null, $writeInfoExtractor = null) { if (\is_bool($magicMethods)) { \trigger_deprecation('symfony/property-access', '5.2', 'Passing a boolean as the first argument to "%s()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).', __METHOD__); $magicMethods = ($magicMethods ? self::MAGIC_CALL : 0) | self::MAGIC_GET | self::MAGIC_SET; } elseif (!\is_int($magicMethods)) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be an integer, "%s" given.', __METHOD__, \get_debug_type($readInfoExtractor))); } if (\is_bool($throw)) { \trigger_deprecation('symfony/property-access', '5.3', 'Passing a boolean as the second argument to "%s()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).', __METHOD__); $throw = $throw ? self::THROW_ON_INVALID_INDEX : self::DO_NOT_THROW; if (!\is_bool($readInfoExtractor)) { $throw |= self::THROW_ON_INVALID_PROPERTY_PATH; } } if (\is_bool($readInfoExtractor)) { \trigger_deprecation('symfony/property-access', '5.3', 'Passing a boolean as the fourth argument to "%s()" is deprecated. Pass a combination of bitwise flags as the second argument instead (i.e an integer).', __METHOD__); if ($readInfoExtractor) { $throw |= self::THROW_ON_INVALID_PROPERTY_PATH; } $readInfoExtractor = $writeInfoExtractor; $writeInfoExtractor = 4 < \func_num_args() ? \func_get_arg(4) : null; } if (null !== $readInfoExtractor && !$readInfoExtractor instanceof PropertyReadInfoExtractorInterface) { throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be null or an instance of "%s", "%s" given.', __METHOD__, PropertyReadInfoExtractorInterface::class, \get_debug_type($readInfoExtractor))); } if (null !== $writeInfoExtractor && !$writeInfoExtractor instanceof PropertyWriteInfoExtractorInterface) { throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be null or an instance of "%s", "%s" given.', __METHOD__, PropertyWriteInfoExtractorInterface::class, \get_debug_type($writeInfoExtractor))); } $this->magicMethodsFlags = $magicMethods; $this->ignoreInvalidIndices = 0 === ($throw & self::THROW_ON_INVALID_INDEX); $this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value $this->ignoreInvalidProperty = 0 === ($throw & self::THROW_ON_INVALID_PROPERTY_PATH); $this->readInfoExtractor = $readInfoExtractor ?? new ReflectionExtractor([], null, null, \false); $this->writeInfoExtractor = $writeInfoExtractor ?? new ReflectionExtractor(['set'], null, null, \false); } /** * {@inheritdoc} */ public function getValue($objectOrArray, $propertyPath) { $zval = [self::VALUE => $objectOrArray]; if (\is_object($objectOrArray) && \false === \strpbrk((string) $propertyPath, '.[')) { return $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty)[self::VALUE]; } $propertyPath = $this->getPropertyPath($propertyPath); $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices); return $propertyValues[\count($propertyValues) - 1][self::VALUE]; } /** * {@inheritdoc} */ public function setValue(&$objectOrArray, $propertyPath, $value) { if (\is_object($objectOrArray) && \false === \strpbrk((string) $propertyPath, '.[')) { $zval = [self::VALUE => $objectOrArray]; try { $this->writeProperty($zval, $propertyPath, $value); return; } catch (\TypeError $e) { self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e); // It wasn't thrown in this class so rethrow it throw $e; } } $propertyPath = $this->getPropertyPath($propertyPath); $zval = [self::VALUE => $objectOrArray, self::REF => &$objectOrArray]; $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1); $overwrite = \true; try { for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) { $zval = $propertyValues[$i]; unset($propertyValues[$i]); // You only need set value for current element if: // 1. it's the parent of the last index element // OR // 2. its child is not passed by reference // // This may avoid unnecessary value setting process for array elements. // For example: // '[a][b][c]' => 'old-value' // If you want to change its value to 'new-value', // you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]' if ($overwrite) { $property = $propertyPath->getElement($i); if ($propertyPath->isIndex($i)) { if ($overwrite = !isset($zval[self::REF])) { $ref =& $zval[self::REF]; $ref = $zval[self::VALUE]; } $this->writeIndex($zval, $property, $value); if ($overwrite) { $zval[self::VALUE] = $zval[self::REF]; } } else { $this->writeProperty($zval, $property, $value); } // if current element is an object // OR // if current element's reference chain is not broken - current element // as well as all its ancients in the property path are all passed by reference, // then there is no need to continue the value setting process if (\is_object($zval[self::VALUE]) || isset($zval[self::IS_REF_CHAINED])) { break; } } $value = $zval[self::VALUE]; } } catch (\TypeError $e) { self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e); // It wasn't thrown in this class so rethrow it throw $e; } } private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, ?\Throwable $previous = null) : void { if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) { return; } if (\PHP_VERSION_ID < 80000) { if (\preg_match('/^Typed property \\S+::\\$\\S+ must be (\\S+), (\\S+) used$/', $message, $matches)) { [, $expectedType, $actualType] = $matches; throw new InvalidArgumentException(\sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous); } if (!\str_starts_with($message, 'Argument ')) { return; } $pos = \strpos($message, $delim = 'must be of the type ') ?: (\strpos($message, $delim = 'must be an instance of ') ?: \strpos($message, $delim = 'must implement interface ')); $pos += \strlen($delim); $j = \strpos($message, ',', $pos); $type = \substr($message, 2 + $j, \strpos($message, ' given', $j) - $j - 2); $message = \substr($message, $pos, $j - $pos); throw new InvalidArgumentException(\sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath), 0, $previous); } if (\preg_match('/^\\S+::\\S+\\(\\): Argument #\\d+ \\(\\$\\S+\\) must be of type (\\S+), (\\S+) given/', $message, $matches)) { [, $expectedType, $actualType] = $matches; throw new InvalidArgumentException(\sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous); } if (\preg_match('/^Cannot assign (\\S+) to property \\S+::\\$\\S+ of type (\\S+)$/', $message, $matches)) { [, $actualType, $expectedType] = $matches; throw new InvalidArgumentException(\sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous); } } /** * {@inheritdoc} */ public function isReadable($objectOrArray, $propertyPath) { if (!$propertyPath instanceof PropertyPathInterface) { $propertyPath = new PropertyPath($propertyPath); } try { $zval = [self::VALUE => $objectOrArray]; $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices); return \true; } catch (AccessException $e) { return \false; } catch (UnexpectedTypeException $e) { return \false; } } /** * {@inheritdoc} */ public function isWritable($objectOrArray, $propertyPath) { $propertyPath = $this->getPropertyPath($propertyPath); try { $zval = [self::VALUE => $objectOrArray]; $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1); for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) { $zval = $propertyValues[$i]; unset($propertyValues[$i]); if ($propertyPath->isIndex($i)) { if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) { return \false; } } elseif (!\is_object($zval[self::VALUE]) || !$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) { return \false; } if (\is_object($zval[self::VALUE])) { return \true; } } return \true; } catch (AccessException $e) { return \false; } catch (UnexpectedTypeException $e) { return \false; } } /** * Reads the path from an object up to a given path index. * * @throws UnexpectedTypeException if a value within the path is neither object nor array * @throws NoSuchIndexException If a non-existing index is accessed */ private function readPropertiesUntil(array $zval, PropertyPathInterface $propertyPath, int $lastIndex, bool $ignoreInvalidIndices = \true) : array { if (!\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) { throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, 0); } // Add the root object to the list $propertyValues = [$zval]; for ($i = 0; $i < $lastIndex; ++$i) { $property = $propertyPath->getElement($i); $isIndex = $propertyPath->isIndex($i); if ($isIndex) { // Create missing nested arrays on demand if ($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property) || \is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property, $zval[self::VALUE])) { if (!$ignoreInvalidIndices) { if (!\is_array($zval[self::VALUE])) { if (!$zval[self::VALUE] instanceof \Traversable) { throw new NoSuchIndexException(\sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath)); } $zval[self::VALUE] = \iterator_to_array($zval[self::VALUE]); } throw new NoSuchIndexException(\sprintf('Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".', $property, (string) $propertyPath, \print_r(\array_keys($zval[self::VALUE]), \true))); } if ($i + 1 < $propertyPath->getLength()) { if (isset($zval[self::REF])) { $zval[self::VALUE][$property] = []; $zval[self::REF] = $zval[self::VALUE]; } else { $zval[self::VALUE] = [$property => []]; } } } $zval = $this->readIndex($zval, $property); } else { $zval = $this->readProperty($zval, $property, $this->ignoreInvalidProperty); } // the final value of the path must not be validated if ($i + 1 < $propertyPath->getLength() && !\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) { throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1); } if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) { // Set the IS_REF_CHAINED flag to true if: // current property is passed by reference and // it is the first element in the property path or // the IS_REF_CHAINED flag of its parent element is true // Basically, this flag is true only when the reference chain from the top element to current element is not broken $zval[self::IS_REF_CHAINED] = \true; } $propertyValues[] = $zval; } return $propertyValues; } /** * Reads a key from an array-like structure. * * @param string|int $index The key to read * * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array */ private function readIndex(array $zval, $index) : array { if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) { throw new NoSuchIndexException(\sprintf('Cannot read index "%s" from object of type "%s" because it doesn\'t implement \\ArrayAccess.', $index, \get_debug_type($zval[self::VALUE]))); } $result = self::RESULT_PROTO; if (isset($zval[self::VALUE][$index])) { $result[self::VALUE] = $zval[self::VALUE][$index]; if (!isset($zval[self::REF])) { // Save creating references when doing read-only lookups } elseif (\is_array($zval[self::VALUE])) { $result[self::REF] =& $zval[self::REF][$index]; } elseif (\is_object($result[self::VALUE])) { $result[self::REF] = $result[self::VALUE]; } } return $result; } /** * Reads the value of a property from an object. * * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public */ private function readProperty(array $zval, string $property, bool $ignoreInvalidProperty = \false) : array { if (!\is_object($zval[self::VALUE])) { throw new NoSuchPropertyException(\sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.', $property)); } $result = self::RESULT_PROTO; $object = $zval[self::VALUE]; $class = \get_class($object); $access = $this->getReadInfo($class, $property); if (null !== $access) { $name = $access->getName(); $type = $access->getType(); try { if (PropertyReadInfo::TYPE_METHOD === $type) { try { $result[self::VALUE] = $object->{$name}(); } catch (\TypeError $e) { [$trace] = $e->getTrace(); // handle uninitialized properties in PHP >= 7 if (__FILE__ === ($trace['file'] ?? null) && $name === $trace['function'] && $object instanceof $trace['class'] && \preg_match('/Return value (?:of .*::\\w+\\(\\) )?must be of (?:the )?type (\\w+), null returned$/', $e->getMessage(), $matches)) { throw new UninitializedPropertyException(\sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', \get_debug_type($object), $name, $matches[1]), 0, $e); } throw $e; } } elseif (PropertyReadInfo::TYPE_PROPERTY === $type) { if ($access->canBeReference() && !isset($object->{$name}) && !\array_key_exists($name, (array) $object) && (\PHP_VERSION_ID < 70400 || !(new \ReflectionProperty($class, $name))->hasType())) { throw new UninitializedPropertyException(\sprintf('The property "%s::$%s" is not initialized.', $class, $name)); } $result[self::VALUE] = $object->{$name}; if (isset($zval[self::REF]) && $access->canBeReference()) { $result[self::REF] =& $object->{$name}; } } } catch (\Error $e) { // handle uninitialized properties in PHP >= 7.4 if (\PHP_VERSION_ID >= 70400 && \preg_match('/^Typed property ([\\w\\\\@]+)::\\$(\\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) { $r = new \ReflectionProperty(\str_contains($matches[1], '@anonymous') ? $class : $matches[1], $matches[2]); $type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type; throw new UninitializedPropertyException(\sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $matches[1], $r->getName(), $type), 0, $e); } throw $e; } } elseif (\property_exists($object, $property) && \array_key_exists($property, (array) $object)) { $result[self::VALUE] = $object->{$property}; if (isset($zval[self::REF])) { $result[self::REF] =& $object->{$property}; } } elseif (!$ignoreInvalidProperty) { throw new NoSuchPropertyException(\sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class)); } // Objects are always passed around by reference if (isset($zval[self::REF]) && \is_object($result[self::VALUE])) { $result[self::REF] = $result[self::VALUE]; } return $result; } /** * Guesses how to read the property value. */ private function getReadInfo(string $class, string $property) : ?PropertyReadInfo { $key = \str_replace('\\', '.', $class) . '..' . $property; if (isset($this->readPropertyCache[$key])) { return $this->readPropertyCache[$key]; } if ($this->cacheItemPool) { $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ . \rawurlencode($key)); if ($item->isHit()) { return $this->readPropertyCache[$key] = $item->get(); } } $accessor = $this->readInfoExtractor->getReadInfo($class, $property, ['enable_getter_setter_extraction' => \true, 'enable_magic_methods_extraction' => $this->magicMethodsFlags, 'enable_constructor_extraction' => \false]); if (isset($item)) { $this->cacheItemPool->save($item->set($accessor)); } return $this->readPropertyCache[$key] = $accessor; } /** * Sets the value of an index in a given array-accessible value. * * @param string|int $index The index to write at * @param mixed $value The value to write * * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array */ private function writeIndex(array $zval, $index, $value) { if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) { throw new NoSuchIndexException(\sprintf('Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \\ArrayAccess.', $index, \get_debug_type($zval[self::VALUE]))); } $zval[self::REF][$index] = $value; } /** * Sets the value of a property in the given object. * * @param mixed $value The value to write * * @throws NoSuchPropertyException if the property does not exist or is not public */ private function writeProperty(array $zval, string $property, $value) { if (!\is_object($zval[self::VALUE])) { throw new NoSuchPropertyException(\sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?', $property)); } $object = $zval[self::VALUE]; $class = \get_class($object); $mutator = $this->getWriteInfo($class, $property, $value); if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) { $type = $mutator->getType(); if (PropertyWriteInfo::TYPE_METHOD === $type) { $object->{$mutator->getName()}($value); } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) { $object->{$mutator->getName()} = $value; } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) { $this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo()); } } elseif ($object instanceof \stdClass && \property_exists($object, $property)) { $object->{$property} = $value; } elseif (!$this->ignoreInvalidProperty) { if ($mutator->hasErrors()) { throw new NoSuchPropertyException(\implode('. ', $mutator->getErrors()) . '.'); } throw new NoSuchPropertyException(\sprintf('Could not determine access type for property "%s" in class "%s".', $property, \get_debug_type($object))); } } /** * Adjusts a collection-valued property by calling add*() and remove*() methods. */ private function writeCollection(array $zval, string $property, iterable $collection, PropertyWriteInfo $addMethod, PropertyWriteInfo $removeMethod) { // At this point the add and remove methods have been found $previousValue = $this->readProperty($zval, $property); $previousValue = $previousValue[self::VALUE]; $removeMethodName = $removeMethod->getName(); $addMethodName = $addMethod->getName(); if ($previousValue instanceof \Traversable) { $previousValue = \iterator_to_array($previousValue); } if ($previousValue && \is_array($previousValue)) { if (\is_object($collection)) { $collection = \iterator_to_array($collection); } foreach ($previousValue as $key => $item) { if (!\in_array($item, $collection, \true)) { unset($previousValue[$key]); $zval[self::VALUE]->{$removeMethodName}($item); } } } else { $previousValue = \false; } foreach ($collection as $item) { if (!$previousValue || !\in_array($item, $previousValue, \true)) { $zval[self::VALUE]->{$addMethodName}($item); } } } private function getWriteInfo(string $class, string $property, $value) : PropertyWriteInfo { $useAdderAndRemover = \is_iterable($value); $key = \str_replace('\\', '.', $class) . '..' . $property . '..' . (int) $useAdderAndRemover; if (isset($this->writePropertyCache[$key])) { return $this->writePropertyCache[$key]; } if ($this->cacheItemPool) { $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE . \rawurlencode($key)); if ($item->isHit()) { return $this->writePropertyCache[$key] = $item->get(); } } $mutator = $this->writeInfoExtractor->getWriteInfo($class, $property, ['enable_getter_setter_extraction' => \true, 'enable_magic_methods_extraction' => $this->magicMethodsFlags, 'enable_constructor_extraction' => \false, 'enable_adder_remover_extraction' => $useAdderAndRemover]); if (isset($item)) { $this->cacheItemPool->save($item->set($mutator)); } return $this->writePropertyCache[$key] = $mutator; } /** * Returns whether a property is writable in the given object. */ private function isPropertyWritable(object $object, string $property) : bool { $mutatorForArray = $this->getWriteInfo(\get_class($object), $property, []); if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || $object instanceof \stdClass && \property_exists($object, $property)) { return \true; } $mutator = $this->getWriteInfo(\get_class($object), $property, ''); return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || $object instanceof \stdClass && \property_exists($object, $property); } /** * Gets a PropertyPath instance and caches it. * * @param string|PropertyPath $propertyPath */ private function getPropertyPath($propertyPath) : PropertyPath { if ($propertyPath instanceof PropertyPathInterface) { // Don't call the copy constructor has it is not needed here return $propertyPath; } if (isset($this->propertyPathCache[$propertyPath])) { return $this->propertyPathCache[$propertyPath]; } if ($this->cacheItemPool) { $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH . \rawurlencode($propertyPath)); if ($item->isHit()) { return $this->propertyPathCache[$propertyPath] = $item->get(); } } $propertyPathInstance = new PropertyPath($propertyPath); if (isset($item)) { $item->set($propertyPathInstance); $this->cacheItemPool->save($item); } return $this->propertyPathCache[$propertyPath] = $propertyPathInstance; } /** * Creates the APCu adapter if applicable. * * @return AdapterInterface * * @throws \LogicException When the Cache Component isn't available */ public static function createCache(string $namespace, int $defaultLifetime, string $version, ?LoggerInterface $logger = null) { if (!\class_exists(ApcuAdapter::class)) { throw new \LogicException(\sprintf('The Symfony Cache component must be installed to use "%s()".', __METHOD__)); } if (!ApcuAdapter::isSupported()) { return new NullAdapter(); } $apcu = new ApcuAdapter($namespace, $defaultLifetime / 5, $version); if ('cli' === \PHP_SAPI && !\filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { $apcu->setLogger(new NullLogger()); } elseif (null !== $logger) { $apcu->setLogger($logger); } return $apcu; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; /** * Entry point of the PropertyAccess component. * * @author Bernhard Schussek */ final class PropertyAccess { /** * Creates a property accessor with the default configuration. */ public static function createPropertyAccessor() : PropertyAccessor { return self::createPropertyAccessorBuilder()->getPropertyAccessor(); } public static function createPropertyAccessorBuilder() : PropertyAccessorBuilder { return new PropertyAccessorBuilder(); } /** * This class cannot be instantiated. */ private function __construct() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess; use _ContaoManager\Symfony\Component\PropertyAccess\Exception\OutOfBoundsException; /** * @author Bernhard Schussek */ class PropertyPathBuilder { private $elements = []; private $isIndex = []; /** * Creates a new property path builder. * * @param PropertyPathInterface|string|null $path The path to initially store * in the builder. Optional. */ public function __construct($path = null) { if (null !== $path) { $this->append($path); } } /** * Appends a (sub-) path to the current path. * * @param PropertyPathInterface|string $path The path to append * @param int $offset The offset where the appended * piece starts in $path * @param int $length The length of the appended piece * If 0, the full path is appended */ public function append($path, int $offset = 0, int $length = 0) { if (\is_string($path)) { $path = new PropertyPath($path); } if (0 === $length) { $end = $path->getLength(); } else { $end = $offset + $length; } for (; $offset < $end; ++$offset) { $this->elements[] = $path->getElement($offset); $this->isIndex[] = $path->isIndex($offset); } } /** * Appends an index element to the current path. */ public function appendIndex(string $name) { $this->elements[] = $name; $this->isIndex[] = \true; } /** * Appends a property element to the current path. */ public function appendProperty(string $name) { $this->elements[] = $name; $this->isIndex[] = \false; } /** * Removes elements from the current path. * * @throws OutOfBoundsException if offset is invalid */ public function remove(int $offset, int $length = 1) { if (!isset($this->elements[$offset])) { throw new OutOfBoundsException(\sprintf('The offset "%s" is not within the property path.', $offset)); } $this->resize($offset, $length, 0); } /** * Replaces a sub-path by a different (sub-) path. * * @param int $offset The offset at which to replace * @param int $length The length of the piece to replace * @param PropertyPathInterface|string $path The path to insert * @param int $pathOffset The offset where the inserted piece * starts in $path * @param int $pathLength The length of the inserted piece * If 0, the full path is inserted * * @throws OutOfBoundsException If the offset is invalid */ public function replace(int $offset, int $length, $path, int $pathOffset = 0, int $pathLength = 0) { if (\is_string($path)) { $path = new PropertyPath($path); } if ($offset < 0 && \abs($offset) <= $this->getLength()) { $offset = $this->getLength() + $offset; } elseif (!isset($this->elements[$offset])) { throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path'); } if (0 === $pathLength) { $pathLength = $path->getLength() - $pathOffset; } $this->resize($offset, $length, $pathLength); for ($i = 0; $i < $pathLength; ++$i) { $this->elements[$offset + $i] = $path->getElement($pathOffset + $i); $this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i); } \ksort($this->elements); } /** * Replaces a property element by an index element. * * @throws OutOfBoundsException If the offset is invalid */ public function replaceByIndex(int $offset, ?string $name = null) { if (!isset($this->elements[$offset])) { throw new OutOfBoundsException(\sprintf('The offset "%s" is not within the property path.', $offset)); } if (null !== $name) { $this->elements[$offset] = $name; } $this->isIndex[$offset] = \true; } /** * Replaces an index element by a property element. * * @throws OutOfBoundsException If the offset is invalid */ public function replaceByProperty(int $offset, ?string $name = null) { if (!isset($this->elements[$offset])) { throw new OutOfBoundsException(\sprintf('The offset "%s" is not within the property path.', $offset)); } if (null !== $name) { $this->elements[$offset] = $name; } $this->isIndex[$offset] = \false; } /** * Returns the length of the current path. * * @return int */ public function getLength() { return \count($this->elements); } /** * Returns the current property path. * * @return PropertyPathInterface|null */ public function getPropertyPath() { $pathAsString = $this->__toString(); return '' !== $pathAsString ? new PropertyPath($pathAsString) : null; } /** * Returns the current property path as string. * * @return string */ public function __toString() { $string = ''; foreach ($this->elements as $offset => $element) { if ($this->isIndex[$offset]) { $element = '[' . $element . ']'; } elseif ('' !== $string) { $string .= '.'; } $string .= $element; } return $string; } /** * Resizes the path so that a chunk of length $cutLength is * removed at $offset and another chunk of length $insertionLength * can be inserted. */ private function resize(int $offset, int $cutLength, int $insertionLength) { // Nothing else to do in this case if ($insertionLength === $cutLength) { return; } $length = \count($this->elements); if ($cutLength > $insertionLength) { // More elements should be removed than inserted $diff = $cutLength - $insertionLength; $newLength = $length - $diff; // Shift elements to the left (left-to-right until the new end) // Max allowed offset to be shifted is such that // $offset + $diff < $length (otherwise invalid index access) // i.e. $offset < $length - $diff = $newLength for ($i = $offset; $i < $newLength; ++$i) { $this->elements[$i] = $this->elements[$i + $diff]; $this->isIndex[$i] = $this->isIndex[$i + $diff]; } // All remaining elements should be removed $this->elements = \array_slice($this->elements, 0, $i); $this->isIndex = \array_slice($this->isIndex, 0, $i); } else { $diff = $insertionLength - $cutLength; $newLength = $length + $diff; $indexAfterInsertion = $offset + $insertionLength; // $diff <= $insertionLength // $indexAfterInsertion >= $insertionLength // => $diff <= $indexAfterInsertion // In each of the following loops, $i >= $diff must hold, // otherwise ($i - $diff) becomes negative. // Shift old elements to the right to make up space for the // inserted elements. This needs to be done left-to-right in // order to preserve an ascending array index order // Since $i = max($length, $indexAfterInsertion) and $indexAfterInsertion >= $diff, // $i >= $diff is guaranteed. for ($i = \max($length, $indexAfterInsertion); $i < $newLength; ++$i) { $this->elements[$i] = $this->elements[$i - $diff]; $this->isIndex[$i] = $this->isIndex[$i - $diff]; } // Shift remaining elements to the right. Do this right-to-left // so we don't overwrite elements before copying them // The last written index is the immediate index after the inserted // string, because the indices before that will be overwritten // anyway. // Since $i >= $indexAfterInsertion and $indexAfterInsertion >= $diff, // $i >= $diff is guaranteed. for ($i = $length - 1; $i >= $indexAfterInsertion; --$i) { $this->elements[$i] = $this->elements[$i - $diff]; $this->isIndex[$i] = $this->isIndex[$i - $diff]; } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Thrown when a property path is not available. * * @author Stéphane Escandell */ class AccessException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Thrown when a property is not initialized. * * @author Jules Pietri */ class UninitializedPropertyException extends AccessException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Marker interface for the PropertyAccess component. * * @author Bernhard Schussek */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Base OutOfBoundsException for the PropertyAccess component. * * @author Bernhard Schussek */ class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Base RuntimeException for the PropertyAccess component. * * @author Bernhard Schussek */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Base InvalidArgumentException for the PropertyAccess component. * * @author Bernhard Schussek */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Thrown when a property path is malformed. * * @author Bernhard Schussek */ class InvalidPropertyPathException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyPathInterface; /** * Thrown when a value does not match an expected type. * * @author Bernhard Schussek */ class UnexpectedTypeException extends RuntimeException { /** * @param mixed $value The unexpected value found while traversing property path * @param int $pathIndex The property path index when the unexpected value was found */ public function __construct($value, PropertyPathInterface $path, int $pathIndex) { $message = \sprintf('PropertyAccessor requires a graph of objects or arrays to operate on, ' . 'but it found type "%s" while trying to traverse path "%s" at property "%s".', \gettype($value), (string) $path, $path->getElement($pathIndex)); parent::__construct($message); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Thrown when an index cannot be found. * * @author Stéphane Escandell */ class NoSuchIndexException extends AccessException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyAccess\Exception; /** * Thrown when a property cannot be found. * * @author Bernhard Schussek */ class NoSuchPropertyException extends AccessException { } { "name": "symfony\/property-access", "type": "library", "description": "Provides functions to read and write from\/to an object or array using a simple string notation", "keywords": [ "property", "index", "access", "object", "array", "extraction", "injection", "reflection", "property-path" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16", "symfony\/property-info": "^5.2|^6.0" }, "require-dev": { "symfony\/cache": "^4.4|^5.0|^6.0" }, "suggest": { "psr\/cache-implementation": "To cache access methods." }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\PropertyAccess\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder; /** * Extends \SplFileInfo to support relative paths. * * @author Fabien Potencier */ class SplFileInfo extends \SplFileInfo { private $relativePath; private $relativePathname; /** * @param string $file The file name * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ public function __construct(string $file, string $relativePath, string $relativePathname) { parent::__construct($file); $this->relativePath = $relativePath; $this->relativePathname = $relativePathname; } /** * Returns the relative path. * * This path does not contain the file name. * * @return string */ public function getRelativePath() { return $this->relativePath; } /** * Returns the relative path name. * * This path contains the file name. * * @return string */ public function getRelativePathname() { return $this->relativePathname; } public function getFilenameWithoutExtension() : string { $filename = $this->getFilename(); return \pathinfo($filename, \PATHINFO_FILENAME); } /** * Returns the contents of the file. * * @return string * * @throws \RuntimeException */ public function getContents() { \set_error_handler(function ($type, $msg) use(&$error) { $error = $msg; }); try { $content = \file_get_contents($this->getPathname()); } finally { \restore_error_handler(); } if (\false === $content) { throw new \RuntimeException($error); } return $content; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Comparator; /** * NumberComparator compiles a simple comparison to an anonymous * subroutine, which you can call with a value to be tested again. * * Now this would be very pointless, if NumberCompare didn't understand * magnitudes. * * The target value may use magnitudes of kilobytes (k, ki), * megabytes (m, mi), or gigabytes (g, gi). Those suffixed * with an i use the appropriate 2**n version in accordance with the * IEC standard: http://physics.nist.gov/cuu/Units/binary.html * * Based on the Perl Number::Compare module. * * @author Fabien Potencier PHP port * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp * * @see http://physics.nist.gov/cuu/Units/binary.html */ class NumberComparator extends Comparator { /** * @param string|null $test A comparison string or null * * @throws \InvalidArgumentException If the test is not understood */ public function __construct(?string $test) { if (null === $test || !\preg_match('#^\\s*(==|!=|[<>]=?)?\\s*([0-9\\.]+)\\s*([kmg]i?)?\\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); } $target = $matches[2]; if (!\is_numeric($target)) { throw new \InvalidArgumentException(\sprintf('Invalid number "%s".', $target)); } if (isset($matches[3])) { // magnitude switch (\strtolower($matches[3])) { case 'k': $target *= 1000; break; case 'ki': $target *= 1024; break; case 'm': $target *= 1000000; break; case 'mi': $target *= 1024 * 1024; break; case 'g': $target *= 1000000000; break; case 'gi': $target *= 1024 * 1024 * 1024; break; } } parent::__construct($target, $matches[1] ?: '=='); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Comparator; /** * @author Fabien Potencier */ class Comparator { private $target; private $operator = '=='; public function __construct(?string $target = null, string $operator = '==') { if (null === $target) { \trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__); } $this->target = $target; $this->doSetOperator($operator); } /** * Gets the target value. * * @return string */ public function getTarget() { if (null === $this->target) { \trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); } return $this->target; } /** * @deprecated set the target via the constructor instead */ public function setTarget(string $target) { \trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__); $this->target = $target; } /** * Gets the comparison operator. * * @return string */ public function getOperator() { return $this->operator; } /** * Sets the comparison operator. * * @throws \InvalidArgumentException * * @deprecated set the operator via the constructor instead */ public function setOperator(string $operator) { \trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__); $this->doSetOperator('' === $operator ? '==' : $operator); } /** * Tests against the target. * * @param mixed $test A test value * * @return bool */ public function test($test) { if (null === $this->target) { \trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); } switch ($this->operator) { case '>': return $test > $this->target; case '>=': return $test >= $this->target; case '<': return $test < $this->target; case '<=': return $test <= $this->target; case '!=': return $test != $this->target; } return $test == $this->target; } private function doSetOperator(string $operator) : void { if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { throw new \InvalidArgumentException(\sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Comparator; /** * DateCompare compiles date comparisons. * * @author Fabien Potencier */ class DateComparator extends Comparator { /** * @param string $test A comparison string * * @throws \InvalidArgumentException If the test is not understood */ public function __construct(string $test) { if (!\preg_match('#^\\s*(==|!=|[<>]=?|after|since|before|until)?\\s*(.+?)\\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a date test.', $test)); } try { $date = new \DateTime($matches[2]); $target = $date->format('U'); } catch (\Exception $e) { throw new \InvalidArgumentException(\sprintf('"%s" is not a valid date.', $matches[2])); } $operator = $matches[1] ?? '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } if ('until' === $operator || 'before' === $operator) { $operator = '<'; } parent::__construct($target, $operator); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder; use _ContaoManager\Symfony\Component\Finder\Comparator\DateComparator; use _ContaoManager\Symfony\Component\Finder\Comparator\NumberComparator; use _ContaoManager\Symfony\Component\Finder\Exception\DirectoryNotFoundException; use _ContaoManager\Symfony\Component\Finder\Iterator\CustomFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\DateRangeFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\FilecontentFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\FilenameFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\LazyIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; use _ContaoManager\Symfony\Component\Finder\Iterator\SortableIterator; /** * Finder allows to build rules to find files and directories. * * It is a thin wrapper around several specialized iterator classes. * * All rules may be invoked several times. * * All methods return the current Finder object to allow chaining: * * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); * * @author Fabien Potencier * * @implements \IteratorAggregate */ class Finder implements \IteratorAggregate, \Countable { public const IGNORE_VCS_FILES = 1; public const IGNORE_DOT_FILES = 2; public const IGNORE_VCS_IGNORED_FILES = 4; private $mode = 0; private $names = []; private $notNames = []; private $exclude = []; private $filters = []; private $depths = []; private $sizes = []; private $followLinks = \false; private $reverseSorting = \false; private $sort = \false; private $ignore = 0; private $dirs = []; private $dates = []; private $iterators = []; private $contains = []; private $notContains = []; private $paths = []; private $notPaths = []; private $ignoreUnreadableDirs = \false; private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; public function __construct() { $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; } /** * Creates a new Finder. * * @return static */ public static function create() { return new static(); } /** * Restricts the matching to directories only. * * @return $this */ public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } /** * Restricts the matching to files only. * * @return $this */ public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } /** * Adds tests for the directory depth. * * Usage: * * $finder->depth('> 1') // the Finder will start matching at level 1. * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. * $finder->depth(['>= 1', '< 3']) * * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels * * @return $this * * @see DepthRangeFilterIterator * @see NumberComparator */ public function depth($levels) { foreach ((array) $levels as $level) { $this->depths[] = new Comparator\NumberComparator($level); } return $this; } /** * Adds tests for file dates (last modified). * * The date must be something that strtotime() is able to parse: * * $finder->date('since yesterday'); * $finder->date('until 2 days ago'); * $finder->date('> now - 2 hours'); * $finder->date('>= 2005-10-15'); * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); * * @param string|string[] $dates A date range string or an array of date ranges * * @return $this * * @see strtotime * @see DateRangeFilterIterator * @see DateComparator */ public function date($dates) { foreach ((array) $dates as $date) { $this->dates[] = new Comparator\DateComparator($date); } return $this; } /** * Adds rules that files must match. * * You can use patterns (delimited with / sign), globs or simple strings. * * $finder->name('/\.php$/') * $finder->name('*.php') // same as above, without dot files * $finder->name('test.php') * $finder->name(['test.py', 'test.php']) * * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ public function name($patterns) { $this->names = \array_merge($this->names, (array) $patterns); return $this; } /** * Adds rules that files must not match. * * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ public function notName($patterns) { $this->notNames = \array_merge($this->notNames, (array) $patterns); return $this; } /** * Adds tests that file contents must match. * * Strings or PCRE patterns can be used: * * $finder->contains('Lorem ipsum') * $finder->contains('/Lorem ipsum/i') * $finder->contains(['dolor', '/ipsum/i']) * * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns * * @return $this * * @see FilecontentFilterIterator */ public function contains($patterns) { $this->contains = \array_merge($this->contains, (array) $patterns); return $this; } /** * Adds tests that file contents must not match. * * Strings or PCRE patterns can be used: * * $finder->notContains('Lorem ipsum') * $finder->notContains('/Lorem ipsum/i') * $finder->notContains(['lorem', '/dolor/i']) * * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns * * @return $this * * @see FilecontentFilterIterator */ public function notContains($patterns) { $this->notContains = \array_merge($this->notContains, (array) $patterns); return $this; } /** * Adds rules that filenames must match. * * You can use patterns (delimited with / sign) or simple strings. * * $finder->path('some/special/dir') * $finder->path('/some\/special\/dir/') // same as above * $finder->path(['some dir', 'another/dir']) * * Use only / as dirname separator. * * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ public function path($patterns) { $this->paths = \array_merge($this->paths, (array) $patterns); return $this; } /** * Adds rules that filenames must not match. * * You can use patterns (delimited with / sign) or simple strings. * * $finder->notPath('some/special/dir') * $finder->notPath('/some\/special\/dir/') // same as above * $finder->notPath(['some/file.txt', 'another/file.log']) * * Use only / as dirname separator. * * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ public function notPath($patterns) { $this->notPaths = \array_merge($this->notPaths, (array) $patterns); return $this; } /** * Adds tests for file sizes. * * $finder->size('> 10K'); * $finder->size('<= 1Ki'); * $finder->size(4); * $finder->size(['> 10K', '< 20K']) * * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges * * @return $this * * @see SizeRangeFilterIterator * @see NumberComparator */ public function size($sizes) { foreach ((array) $sizes as $size) { $this->sizes[] = new Comparator\NumberComparator($size); } return $this; } /** * Excludes directories. * * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: * * $finder->in(__DIR__)->exclude('ruby'); * * @param string|array $dirs A directory path or an array of directories * * @return $this * * @see ExcludeDirectoryFilterIterator */ public function exclude($dirs) { $this->exclude = \array_merge($this->exclude, (array) $dirs); return $this; } /** * Excludes "hidden" directories and files (starting with a dot). * * This option is enabled by default. * * @return $this * * @see ExcludeDirectoryFilterIterator */ public function ignoreDotFiles(bool $ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } /** * Forces the finder to ignore version control directories. * * This option is enabled by default. * * @return $this * * @see ExcludeDirectoryFilterIterator */ public function ignoreVCS(bool $ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } return $this; } /** * Forces Finder to obey .gitignore and ignore files based on rules listed there. * * This option is disabled by default. * * @return $this */ public function ignoreVCSIgnored(bool $ignoreVCSIgnored) { if ($ignoreVCSIgnored) { $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; } return $this; } /** * Adds VCS patterns. * * @see ignoreVCS() * * @param string|string[] $pattern VCS patterns to ignore */ public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = \array_unique(self::$vcsPatterns); } /** * Sorts files and directories by an anonymous function. * * The anonymous function receives two \SplFileInfo instances to compare. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sort(\Closure $closure) { $this->sort = $closure; return $this; } /** * Sorts files and directories by name. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByName(bool $useNaturalSort = \false) { $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } /** * Sorts files and directories by type (directories before files), then by name. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } /** * Sorts files and directories by the last accessed time. * * This is the time that the file was last accessed, read or written to. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } /** * Reverses the sorting. * * @return $this */ public function reverseSorting() { $this->reverseSorting = \true; return $this; } /** * Sorts files and directories by the last inode changed time. * * This is the time that the inode information was last modified (permissions, owner, group or other metadata). * * On Windows, since inode is not available, changed time is actually the file creation time. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } /** * Sorts files and directories by the last modified time. * * This is the last time the actual contents of the file were last modified. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return $this * * @see SortableIterator */ public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } /** * Filters the iterator with an anonymous function. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * * @return $this * * @see CustomFilterIterator */ public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } /** * Forces the following of symlinks. * * @return $this */ public function followLinks() { $this->followLinks = \true; return $this; } /** * Tells finder to ignore unreadable directories. * * By default, scanning unreadable directories content throws an AccessDeniedException. * * @return $this */ public function ignoreUnreadableDirs(bool $ignore = \true) { $this->ignoreUnreadableDirs = $ignore; return $this; } /** * Searches files and directories which match defined rules. * * @param string|string[] $dirs A directory path or an array of directories * * @return $this * * @throws DirectoryNotFoundException if one of the directories does not exist */ public function in($dirs) { $resolvedDirs = []; foreach ((array) $dirs as $dir) { if (\is_dir($dir)) { $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = \glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { \sort($glob); $resolvedDirs[] = \array_map([$this, 'normalizeDir'], $glob); } else { throw new DirectoryNotFoundException(\sprintf('The "%s" directory does not exist.', $dir)); } } $this->dirs = \array_merge($this->dirs, ...$resolvedDirs); return $this; } /** * Returns an Iterator for the current Finder configuration. * * This method implements the IteratorAggregate interface. * * @return \Iterator * * @throws \LogicException if the in() method has not been called */ #[\ReturnTypeWillChange] public function getIterator() { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); } if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { $iterator = $this->searchInDirectory($this->dirs[0]); if ($this->sort || $this->reverseSorting) { $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; } $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { $iterator->append(new \IteratorIterator(new LazyIterator(function () use($dir) { return $this->searchInDirectory($dir); }))); } foreach ($this->iterators as $it) { $iterator->append($it); } if ($this->sort || $this->reverseSorting) { $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; } /** * Appends an existing set of files/directories to the finder. * * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * * @return $this * * @throws \InvalidArgumentException when the given argument is not iterable */ public function append(iterable $iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; } elseif (\is_iterable($iterator)) { $it = new \ArrayIterator(); foreach ($iterator as $file) { $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); $it[$file->getPathname()] = $file; } $this->iterators[] = $it; } else { throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); } return $this; } /** * Check if any results were found. * * @return bool */ public function hasResults() { foreach ($this->getIterator() as $_) { return \true; } return \false; } /** * Counts all the results collected by the iterators. * * @return int */ #[\ReturnTypeWillChange] public function count() { return \iterator_count($this->getIterator()); } private function searchInDirectory(string $dir) : \Iterator { $exclude = $this->exclude; $notPaths = $this->notPaths; if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $exclude = \array_merge($exclude, self::$vcsPatterns); } if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { $notPaths[] = '#(^|/)\\..+(/|$)#'; } $minDepth = 0; $maxDepth = \PHP_INT_MAX; foreach ($this->depths as $comparator) { switch ($comparator->getOperator()) { case '>': $minDepth = $comparator->getTarget() + 1; break; case '>=': $minDepth = $comparator->getTarget(); break; case '<': $maxDepth = $comparator->getTarget() - 1; break; case '<=': $maxDepth = $comparator->getTarget(); break; default: $minDepth = $maxDepth = $comparator->getTarget(); } } $flags = \RecursiveDirectoryIterator::SKIP_DOTS; if ($this->followLinks) { $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; } $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); if ($exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); } $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); } if ($this->mode) { $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); } if ($this->names || $this->notNames) { $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); } if ($this->contains || $this->notContains) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->sizes) { $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); } if ($this->dates) { $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); } if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if ($this->paths || $notPaths) { $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); } if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir); } return $iterator; } /** * Normalizes given directory names by removing trailing slashes. * * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper */ private function normalizeDir(string $dir) : string { if ('/' === $dir) { return $dir; } $dir = \rtrim($dir, '/' . \DIRECTORY_SEPARATOR); if (\preg_match('#^(ssh2\\.)?s?ftp://#', $dir)) { $dir .= '/'; } return $dir; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4.0 ----- * Deprecate `Comparator::setTarget()` and `Comparator::setOperator()` * Add a constructor to `Comparator` that allows setting target and operator * Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified * Add recursive .gitignore files support 5.0.0 ----- * added `$useNaturalSort` argument to `Finder::sortByName()` 4.3.0 ----- * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore 4.2.0 ----- * added $useNaturalSort option to Finder::sortByName() method * the `Finder::sortByName()` method will have a new `$useNaturalSort` argument in version 5.0, not defining it is deprecated * added `Finder::reverseSorting()` to reverse the sorting 4.0.0 ----- * removed `ExceptionInterface` * removed `Symfony\Component\Finder\Iterator\FilterIterator` 3.4.0 ----- * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` * added Finder::hasResults() method to check if any results were found 3.3.0 ----- * added double-star matching to Glob::toRegex() 3.0.0 ----- * removed deprecated classes 2.8.0 ----- * deprecated adapters and related classes 2.5.0 ----- * added support for GLOB_BRACE in the paths passed to Finder::in() 2.3.0 ----- * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception 2.2.0 ----- * added Finder::path() and Finder::notPath() methods * added finder adapters to improve performance on specific platforms * added support for wildcard characters (glob patterns) in the paths passed to Finder::in() 2.1.0 ----- * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and Finder::sortByModifiedTime() * added Countable to Finder * added support for an array of directories as an argument to Finder::exclude() * added searching based on the file content via Finder::contains() and Finder::notContains() * added support for the != operator in the Comparator * [BC BREAK] filter expressions (used for file name and content) are no more considered as regexps but glob patterns when they are enclosed in '*' or '?' * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder; /** * Glob matches globbing patterns against text. * * if match_glob("foo.*", "foo.bar") echo "matched\n"; * * // prints foo.bar and foo.baz * $regex = glob_to_regex("foo.*"); * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) * { * if (/$regex/) echo "matched: $car\n"; * } * * Glob implements glob(3) style matching that can be used to match * against text, rather than fetching names from a filesystem. * * Based on the Perl Text::Glob module. * * @author Fabien Potencier PHP port * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp */ class Glob { /** * Returns a regexp which is the equivalent of the glob pattern. * * @return string */ public static function toRegex(string $glob, bool $strictLeadingDot = \true, bool $strictWildcardSlash = \true, string $delimiter = '#') { $firstByte = \true; $escaping = \false; $inCurlies = 0; $regex = ''; $sizeGlob = \strlen($glob); for ($i = 0; $i < $sizeGlob; ++$i) { $car = $glob[$i]; if ($firstByte && $strictLeadingDot && '.' !== $car) { $regex .= '(?=[^\\.])'; } $firstByte = '/' === $car; if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1] . $glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { $car = '[^/]++/'; if (!isset($glob[$i + 3])) { $car .= '?'; } if ($strictLeadingDot) { $car = '(?=[^\\.])' . $car; } $car = '/(?:' . $car . ')*'; $i += 2 + isset($glob[$i + 3]); if ('/' === $delimiter) { $car = \str_replace('/', '\\/', $car); } } if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { $regex .= "\\{$car}"; } elseif ('*' === $car) { $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); } elseif ('?' === $car) { $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); } elseif ('{' === $car) { $regex .= $escaping ? '\\{' : '('; if (!$escaping) { ++$inCurlies; } } elseif ('}' === $car && $inCurlies) { $regex .= $escaping ? '}' : ')'; if (!$escaping) { --$inCurlies; } } elseif (',' === $car && $inCurlies) { $regex .= $escaping ? ',' : '|'; } elseif ('\\' === $car) { if ($escaping) { $regex .= '\\\\'; $escaping = \false; } else { $escaping = \true; } continue; } else { $regex .= $car; } $escaping = \false; } return $delimiter . '^' . $regex . '$' . $delimiter; } } Finder Component ================ The Finder component finds files and directories via an intuitive fluent interface. Resources --------- * [Documentation](https://symfony.com/doc/current/components/finder.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * @author Jérémy Derussé * * @internal */ class LazyIterator implements \IteratorAggregate { private $iteratorFactory; public function __construct(callable $iteratorFactory) { $this->iteratorFactory = $iteratorFactory; } public function getIterator() : \Traversable { yield from ($this->iteratorFactory)(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; use _ContaoManager\Symfony\Component\Finder\Gitignore; final class VcsIgnoredFilterIterator extends \FilterIterator { /** * @var string */ private $baseDir; /** * @var array */ private $gitignoreFilesCache = []; /** * @var array */ private $ignoredPathsCache = []; public function __construct(\Iterator $iterator, string $baseDir) { $this->baseDir = $this->normalizePath($baseDir); parent::__construct($iterator); } public function accept() : bool { $file = $this->current(); $fileRealPath = $this->normalizePath($file->getRealPath()); return !$this->isIgnored($fileRealPath); } private function isIgnored(string $fileRealPath) : bool { if (\is_dir($fileRealPath) && !\str_ends_with($fileRealPath, '/')) { $fileRealPath .= '/'; } if (isset($this->ignoredPathsCache[$fileRealPath])) { return $this->ignoredPathsCache[$fileRealPath]; } $ignored = \false; foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { // rules in ignored directories are ignored, no need to check further. break; } $fileRelativePath = \substr($fileRealPath, \strlen($parentDirectory) + 1); if (null === ($regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore"))) { continue; } [$exclusionRegex, $inclusionRegex] = $regexps; if (\preg_match($exclusionRegex, $fileRelativePath)) { $ignored = \true; continue; } if (\preg_match($inclusionRegex, $fileRelativePath)) { $ignored = \false; } } return $this->ignoredPathsCache[$fileRealPath] = $ignored; } /** * @return list */ private function parentsDirectoryDownward(string $fileRealPath) : array { $parentDirectories = []; $parentDirectory = $fileRealPath; while (\true) { $newParentDirectory = \dirname($parentDirectory); // dirname('/') = '/' if ($newParentDirectory === $parentDirectory) { break; } $parentDirectory = $newParentDirectory; if (0 !== \strpos($parentDirectory, $this->baseDir)) { break; } $parentDirectories[] = $parentDirectory; } return \array_reverse($parentDirectories); } /** * @return array{0: string, 1: string}|null */ private function readGitignoreFile(string $path) : ?array { if (\array_key_exists($path, $this->gitignoreFilesCache)) { return $this->gitignoreFilesCache[$path]; } if (!\file_exists($path)) { return $this->gitignoreFilesCache[$path] = null; } if (!\is_file($path) || !\is_readable($path)) { throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable."); } $gitignoreFileContent = \file_get_contents($path); return $this->gitignoreFilesCache[$path] = [Gitignore::toRegex($gitignoreFileContent), Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent)]; } private function normalizePath(string $path) : string { if ('\\' === \DIRECTORY_SEPARATOR) { return \str_replace('\\', '/', $path); } return $path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author Włodzimierz Gajda * * @extends MultiplePcreFilterIterator */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { if (!$this->matchRegexps && !$this->noMatchRegexps) { return \true; } $fileinfo = $this->current(); if ($fileinfo->isDir() || !$fileinfo->isReadable()) { return \false; } $content = $fileinfo->getContents(); if (!$content) { return \false; } return $this->isAccepted($content); } /** * Converts string to regexp if necessary. * * @param string $str Pattern: string or regexp * * @return string */ protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/' . \preg_quote($str, '/') . '/'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). * * @author Fabien Potencier * * @template-covariant TKey * @template-covariant TValue * * @extends \FilterIterator */ abstract class MultiplePcreFilterIterator extends \FilterIterator { protected $matchRegexps = []; protected $noMatchRegexps = []; /** * @param \Iterator $iterator The Iterator to filter * @param string[] $matchPatterns An array of patterns that need to match * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { foreach ($matchPatterns as $pattern) { $this->matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } /** * Checks whether the string is accepted by the regex filters. * * If there is no regexps defined in the class, this method will accept the string. * Such case can be handled by child classes before calling the method if they want to * apply a different behavior. * * @return bool */ protected function isAccepted(string $string) { // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { if (\preg_match($regex, $string)) { return \false; } } // should at least match one rule if ($this->matchRegexps) { foreach ($this->matchRegexps as $regex) { if (\preg_match($regex, $string)) { return \true; } } return \false; } // If there is no match rules, the file is accepted return \true; } /** * Checks whether the string is a regex. * * @return bool */ protected function isRegex(string $str) { $availableModifiers = 'imsxuADU'; if (\PHP_VERSION_ID >= 80200) { $availableModifiers .= 'n'; } if (\preg_match('/^(.{3,}?)[' . $availableModifiers . ']*$/', $str, $m)) { $start = \substr($m[1], 0, 1); $end = \substr($m[1], -1); if ($start === $end) { return !\preg_match('/[*?[:alnum:] \\\\]/', $start); } foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { if ($start === $delimiters[0] && $end === $delimiters[1]) { return \true; } } } return \false; } /** * Converts string into regexp. * * @return string */ protected abstract function toRegex(string $str); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; use _ContaoManager\Symfony\Component\Finder\Comparator\NumberComparator; /** * SizeRangeFilterIterator filters out files that are not in the given size range. * * @author Fabien Potencier * * @extends \FilterIterator */ class SizeRangeFilterIterator extends \FilterIterator { private $comparators = []; /** * @param \Iterator $iterator * @param NumberComparator[] $comparators */ public function __construct(\Iterator $iterator, array $comparators) { $this->comparators = $comparators; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { return \true; } $filesize = $fileinfo->getSize(); foreach ($this->comparators as $compare) { if (!$compare->test($filesize)) { return \false; } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; use _ContaoManager\Symfony\Component\Finder\Glob; /** * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). * * @author Fabien Potencier * * @extends MultiplePcreFilterIterator */ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { return $this->isAccepted($this->current()->getFilename()); } /** * Converts glob to regexp. * * PCRE patterns are left unchanged. * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp * * @return string */ protected function toRegex(string $str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; use _ContaoManager\Symfony\Component\Finder\Comparator\DateComparator; /** * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). * * @author Fabien Potencier * * @extends \FilterIterator */ class DateRangeFilterIterator extends \FilterIterator { private $comparators = []; /** * @param \Iterator $iterator * @param DateComparator[] $comparators */ public function __construct(\Iterator $iterator, array $comparators) { $this->comparators = $comparators; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); if (!\file_exists($fileinfo->getPathname())) { return \false; } $filedate = $fileinfo->getMTime(); foreach ($this->comparators as $compare) { if (!$compare->test($filedate)) { return \false; } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * CustomFilterIterator filters files by applying anonymous functions. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * * @author Fabien Potencier * * @extends \FilterIterator */ class CustomFilterIterator extends \FilterIterator { private $filters = []; /** * @param \Iterator $iterator The Iterator to filter * @param callable[] $filters An array of PHP callbacks * * @throws \InvalidArgumentException */ public function __construct(\Iterator $iterator, array $filters) { foreach ($filters as $filter) { if (!\is_callable($filter)) { throw new \InvalidArgumentException('Invalid PHP callback.'); } } $this->filters = $filters; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (\false === $filter($fileinfo)) { return \false; } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; use _ContaoManager\Symfony\Component\Finder\Exception\AccessDeniedException; use _ContaoManager\Symfony\Component\Finder\SplFileInfo; /** * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { /** * @var bool */ private $ignoreUnreadableDirs; /** * @var bool */ private $ignoreFirstRewind = \true; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations private $rootPath; private $subPath; private $directorySeparator = '/'; /** * @throws \RuntimeException */ public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = \false) { if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { throw new \RuntimeException('This iterator only support returning current as fileinfo.'); } parent::__construct($path, $flags); $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = $path; if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = \DIRECTORY_SEPARATOR; } } /** * Return an instance of SplFileInfo with support for relative paths. * * @return SplFileInfo */ #[\ReturnTypeWillChange] public function current() { // the logic here avoids redoing the same work in all iterations if (null === ($subPathname = $this->subPath)) { $subPathname = $this->subPath = $this->getSubPath(); } if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } $subPathname .= $this->getFilename(); if ('/' !== ($basePath = $this->rootPath)) { $basePath .= $this->directorySeparator; } return new SplFileInfo($basePath . $subPathname, $this->subPath, $subPathname); } /** * @param bool $allowLinks * * @return bool */ #[\ReturnTypeWillChange] public function hasChildren($allowLinks = \false) { $hasChildren = parent::hasChildren($allowLinks); if (!$hasChildren || !$this->ignoreUnreadableDirs) { return $hasChildren; } try { parent::getChildren(); return \true; } catch (\UnexpectedValueException $e) { // If directory is unreadable and finder is set to ignore it, skip children return \false; } } /** * @return \RecursiveDirectoryIterator * * @throws AccessDeniedException */ #[\ReturnTypeWillChange] public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; // performance optimization to avoid redoing the same work in all children $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } /** * @return void */ #[\ReturnTypeWillChange] public function next() { $this->ignoreFirstRewind = \false; parent::next(); } /** * @return void */ #[\ReturnTypeWillChange] public function rewind() { // some streams like FTP are not rewindable, ignore the first rewind after creation, // as newly created DirectoryIterator does not need to be rewound if ($this->ignoreFirstRewind) { $this->ignoreFirstRewind = \false; return; } parent::rewind(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier * * @extends \FilterIterator * * @implements \RecursiveIterator */ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { private $iterator; private $isRecursive; private $excludedDirs = []; private $excludedPattern; /** * @param \Iterator $iterator The Iterator to filter * @param string[] $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { $this->iterator = $iterator; $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = []; foreach ($directories as $directory) { $directory = \rtrim($directory, '/'); if (!$this->isRecursive || \str_contains($directory, '/')) { $patterns[] = \preg_quote($directory, '#'); } else { $this->excludedDirs[$directory] = \true; } } if ($patterns) { $this->excludedPattern = '#(?:^|/)(?:' . \implode('|', $patterns) . ')(?:/|$)#'; } parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return \false; } if ($this->excludedPattern) { $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); $path = \str_replace('\\', '/', $path); return !\preg_match($this->excludedPattern, $path); } return \true; } /** * @return bool */ #[\ReturnTypeWillChange] public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } /** * @return self */ #[\ReturnTypeWillChange] public function getChildren() { $children = new self($this->iterator->getChildren(), []); $children->excludedDirs = $this->excludedDirs; $children->excludedPattern = $this->excludedPattern; return $children; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * SortableIterator applies a sort on a given Iterator. * * @author Fabien Potencier * * @implements \IteratorAggregate */ class SortableIterator implements \IteratorAggregate { public const SORT_BY_NONE = 0; public const SORT_BY_NAME = 1; public const SORT_BY_TYPE = 2; public const SORT_BY_ACCESSED_TIME = 3; public const SORT_BY_CHANGED_TIME = 4; public const SORT_BY_MODIFIED_TIME = 5; public const SORT_BY_NAME_NATURAL = 6; private $iterator; private $sort; /** * @param \Traversable $iterator * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) * * @throws \InvalidArgumentException */ public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = \false) { $this->iterator = $iterator; $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use($order) { return $order * \strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_NAME_NATURAL === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use($order) { return $order * \strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use($order) { if ($a->isDir() && $b->isFile()) { return -$order; } elseif ($a->isFile() && $b->isDir()) { return $order; } return $order * \strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use($order) { return $order * ($a->getATime() - $b->getATime()); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use($order) { return $order * ($a->getCTime() - $b->getCTime()); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use($order) { return $order * ($a->getMTime() - $b->getMTime()); }; } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use($sort) { return -$sort($a, $b); } : $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } /** * @return \Traversable */ #[\ReturnTypeWillChange] public function getIterator() { if (1 === $this->sort) { return $this->iterator; } $array = \iterator_to_array($this->iterator, \true); if (-1 === $this->sort) { $array = \array_reverse($array); } else { \uasort($array, $this->sort); } return new \ArrayIterator($array); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * DepthRangeFilterIterator limits the directory depth. * * @author Fabien Potencier * * @template-covariant TKey * @template-covariant TValue * * @extends \FilterIterator */ class DepthRangeFilterIterator extends \FilterIterator { private $minDepth = 0; /** * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter * @param int $minDepth The min depth * @param int $maxDepth The max depth */ public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) { $this->minDepth = $minDepth; $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * FileTypeFilterIterator only keeps files, directories, or both. * * @author Fabien Potencier * * @extends \FilterIterator */ class FileTypeFilterIterator extends \FilterIterator { public const ONLY_FILES = 1; public const ONLY_DIRECTORIES = 2; private $mode; /** * @param \Iterator $iterator The Iterator to filter * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, int $mode) { $this->mode = $mode; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { return \false; } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { return \false; } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Iterator; /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author Włodzimierz Gajda * * @extends MultiplePcreFilterIterator */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool */ #[\ReturnTypeWillChange] public function accept() { $filename = $this->current()->getRelativePathname(); if ('\\' === \DIRECTORY_SEPARATOR) { $filename = \str_replace('\\', '/', $filename); } return $this->isAccepted($filename); } /** * Converts strings to regexp. * * PCRE patterns are left unchanged. * * Default conversion: * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' * * Use only / as directory separator (on Windows also). * * @param string $str Pattern: regexp or dirname * * @return string */ protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/' . \preg_quote($str, '/') . '/'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder; /** * Gitignore matches against text. * * @author Michael Voříšek * @author Ahmed Abdou */ class Gitignore { /** * Returns a regexp which is the equivalent of the gitignore pattern. * * Format specification: https://git-scm.com/docs/gitignore#_pattern_format */ public static function toRegex(string $gitignoreFileContent) : string { return self::buildRegex($gitignoreFileContent, \false); } public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent) : string { return self::buildRegex($gitignoreFileContent, \true); } private static function buildRegex(string $gitignoreFileContent, bool $inverted) : string { $gitignoreFileContent = \preg_replace('~(? * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Exception; /** * @author Jean-François Simon */ class AccessDeniedException extends \UnexpectedValueException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Finder\Exception; /** * @author Andreas Erhard */ class DirectoryNotFoundException extends \InvalidArgumentException { } { "name": "symfony\/finder", "type": "library", "description": "Finds files and directories via an intuitive fluent interface", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; use _ContaoManager\Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; use _ContaoManager\Symfony\Component\VarDumper\Dumper\DataDumperInterface; use _ContaoManager\Symfony\Component\VarDumper\Dumper\HtmlDumper; use _ContaoManager\Symfony\Component\VarDumper\Server\Connection; /** * @author Nicolas Grekas * * @final */ class DumpDataCollector extends DataCollector implements DataDumperInterface { private $stopwatch; private $fileLinkFormat; private $dataCount = 0; private $isCollected = \true; private $clonesCount = 0; private $clonesIndex = 0; private $rootRefs; private $charset; private $requestStack; private $dumper; private $sourceContextProvider; /** * @param string|FileLinkFormatter|null $fileLinkFormat * @param DataDumperInterface|Connection|null $dumper */ public function __construct(?Stopwatch $stopwatch = null, $fileLinkFormat = null, ?string $charset = null, ?RequestStack $requestStack = null, $dumper = null) { $fileLinkFormat = ($fileLinkFormat ?: \ini_get('xdebug.file_link_format')) ?: \get_cfg_var('xdebug.file_link_format'); $this->stopwatch = $stopwatch; $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter && \false === $fileLinkFormat->format('', 0) ? \false : $fileLinkFormat; $this->charset = (($charset ?: \ini_get('php.output_encoding')) ?: \ini_get('default_charset')) ?: 'UTF-8'; $this->requestStack = $requestStack; $this->dumper = $dumper; // All clones share these properties by reference: $this->rootRefs = [&$this->data, &$this->dataCount, &$this->isCollected, &$this->clonesCount]; $this->sourceContextProvider = $dumper instanceof Connection && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset); } public function __clone() { $this->clonesIndex = ++$this->clonesCount; } public function dump(Data $data) { if ($this->stopwatch) { $this->stopwatch->start('dump'); } ['name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt] = $this->sourceContextProvider->getContext(); if ($this->dumper instanceof Connection) { if (!$this->dumper->write($data)) { $this->isCollected = \false; } } elseif ($this->dumper) { $this->doDump($this->dumper, $data, $name, $file, $line); } else { $this->isCollected = \false; } if (!$this->dataCount) { $this->data = []; } $this->data[] = \compact('data', 'name', 'file', 'line', 'fileExcerpt'); ++$this->dataCount; if ($this->stopwatch) { $this->stopwatch->stop('dump'); } } public function collect(Request $request, Response $response, ?\Throwable $exception = null) { if (!$this->dataCount) { $this->data = []; } // Sub-requests and programmatic calls stay in the collected profile. if ($this->dumper || $this->requestStack && $this->requestStack->getMainRequest() !== $request || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { return; } // In all other conditions that remove the web debug toolbar, dumps are written on the output. if (!$this->requestStack || !$response->headers->has('X-Debug-Token') || $response->isRedirection() || $response->headers->has('Content-Type') && !\str_contains($response->headers->get('Content-Type') ?? '', 'html') || 'html' !== $request->getRequestFormat() || \false === \strripos($response->getContent(), '')) { if ($response->headers->has('Content-Type') && \str_contains($response->headers->get('Content-Type') ?? '', 'html')) { $dumper = new HtmlDumper('php://output', $this->charset); $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { $dumper = new CliDumper('php://output', $this->charset); if (\method_exists($dumper, 'setDisplayOptions')) { $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } } foreach ($this->data as $dump) { $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']); } } } public function reset() { if ($this->stopwatch) { $this->stopwatch->reset(); } $this->data = []; $this->dataCount = 0; $this->isCollected = \true; $this->clonesCount = 0; $this->clonesIndex = 0; } /** * @internal */ public function __sleep() : array { if (!$this->dataCount) { $this->data = []; } if ($this->clonesCount !== $this->clonesIndex) { return []; } $this->data[] = $this->fileLinkFormat; $this->data[] = $this->charset; $this->dataCount = 0; $this->isCollected = \true; return parent::__sleep(); } /** * @internal */ public function __wakeup() { parent::__wakeup(); $charset = \array_pop($this->data); $fileLinkFormat = \array_pop($this->data); $this->dataCount = \count($this->data); foreach ($this->data as $dump) { if (!\is_string($dump['name']) || !\is_string($dump['file']) || !\is_int($dump['line'])) { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } } self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null); } public function getDumpsCount() : int { return $this->dataCount; } public function getDumps(string $format, int $maxDepthLimit = -1, int $maxItemsPerDepth = -1) : array { $data = \fopen('php://memory', 'r+'); if ('html' === $format) { $dumper = new HtmlDumper($data, $this->charset); $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { throw new \InvalidArgumentException(\sprintf('Invalid dump format: "%s".', $format)); } $dumps = []; if (!$this->dataCount) { return $this->data = []; } foreach ($this->data as $dump) { $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); $dump['data'] = \stream_get_contents($data, -1, 0); \ftruncate($data, 0); \rewind($data); $dumps[] = $dump; } return $dumps; } public function getName() : string { return 'dump'; } public function __destruct() { if (0 === $this->clonesCount-- && !$this->isCollected && $this->dataCount) { $this->clonesCount = 0; $this->isCollected = \true; $h = \headers_list(); $i = \count($h); \array_unshift($h, 'Content-Type: ' . \ini_get('default_mimetype')); while (0 !== \stripos($h[$i], 'Content-Type:')) { --$i; } if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) && \stripos($h[$i], 'html')) { $dumper = new HtmlDumper('php://output', $this->charset); $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { $dumper = new CliDumper('php://output', $this->charset); if (\method_exists($dumper, 'setDisplayOptions')) { $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } } foreach ($this->data as $i => $dump) { $this->data[$i] = null; $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']); } $this->data = []; $this->dataCount = 0; } } private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line) { if ($dumper instanceof CliDumper) { $contextDumper = function ($name, $file, $line, $fmt) { if ($this instanceof HtmlDumper) { if ($file) { $s = $this->style('meta', '%s'); $f = \strip_tags($this->style('', $file)); $name = \strip_tags($this->style('', $name)); if ($fmt && ($link = \is_string($fmt) ? \strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line))) { $name = \sprintf('' . $s . '', \strip_tags($this->style('', $link)), $f, $name); } else { $name = \sprintf('' . $s . '', $f, $name); } } else { $name = $this->style('meta', $name); } $this->line = $name . ' on line ' . $this->style('meta', $line) . ':'; } else { $this->line = $this->style('meta', $name) . ' on line ' . $this->style('meta', $line) . ':'; } $this->dumpLine(0); }; $contextDumper = $contextDumper->bindTo($dumper, $dumper); $contextDumper($name, $file, $line, $this->fileLinkFormat); } else { $cloner = new VarCloner(); $dumper->dump($cloner->cloneVar($name . ' on line ' . $line . ':')); } $dumper->dump($data); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * @author Fabien Potencier * * @final */ class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface { public function __construct() { $this->reset(); } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { $this->updateMemoryUsage(); } /** * {@inheritdoc} */ public function reset() { $this->data = ['memory' => 0, 'memory_limit' => $this->convertToBytes(\ini_get('memory_limit'))]; } /** * {@inheritdoc} */ public function lateCollect() { $this->updateMemoryUsage(); } public function getMemory() : int { return $this->data['memory']; } /** * @return int|float */ public function getMemoryLimit() { return $this->data['memory_limit']; } public function updateMemoryUsage() { $this->data['memory'] = \memory_get_peak_usage(\true); } /** * {@inheritdoc} */ public function getName() : string { return 'memory'; } /** * @return int|float */ private function convertToBytes(string $memoryLimit) { if ('-1' === $memoryLimit) { return -1; } $memoryLimit = \strtolower($memoryLimit); $max = \strtolower(\ltrim($memoryLimit, '+')); if (\str_starts_with($max, '0x')) { $max = \intval($max, 16); } elseif (\str_starts_with($max, '0')) { $max = \intval($max, 8); } else { $max = (int) $max; } switch (\substr($memoryLimit, -1)) { case 't': $max *= 1024; // no break case 'g': $max *= 1024; // no break case 'm': $max *= 1024; // no break case 'k': $max *= 1024; } return $max; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; /** * LateDataCollectorInterface. * * @author Fabien Potencier */ interface LateDataCollectorInterface { /** * Collects data as late as possible. */ public function lateCollect(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\RedirectResponse; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerEvent; /** * @author Fabien Potencier */ class RouterDataCollector extends DataCollector { /** * @var \SplObjectStorage */ protected $controllers; public function __construct() { $this->reset(); } /** * {@inheritdoc} * * @final */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { if ($response instanceof RedirectResponse) { $this->data['redirect'] = \true; $this->data['url'] = $response->getTargetUrl(); if ($this->controllers->contains($request)) { $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); } } unset($this->controllers[$request]); } public function reset() { $this->controllers = new \SplObjectStorage(); $this->data = ['redirect' => \false, 'url' => null, 'route' => null]; } protected function guessRoute(Request $request, $controller) { return 'n/a'; } /** * Remembers the controller associated to each request. */ public function onKernelController(ControllerEvent $event) { $this->controllers[$event->getRequest()] = $event->getController(); } /** * @return bool Whether this request will result in a redirect */ public function getRedirect() { return $this->data['redirect']; } /** * @return string|null */ public function getTargetUrl() { return $this->data['url']; } /** * @return string|null */ public function getTargetRoute() { return $this->data['route']; } /** * {@inheritdoc} */ public function getName() { return 'router'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; use _ContaoManager\Symfony\Component\Stopwatch\StopwatchEvent; /** * @author Fabien Potencier * * @final */ class TimeDataCollector extends DataCollector implements LateDataCollectorInterface { private $kernel; private $stopwatch; public function __construct(?KernelInterface $kernel = null, ?Stopwatch $stopwatch = null) { $this->kernel = $kernel; $this->stopwatch = $stopwatch; $this->data = ['events' => [], 'stopwatch_installed' => \false, 'start_time' => 0]; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { if (null !== $this->kernel) { $startTime = $this->kernel->getStartTime(); } else { $startTime = $request->server->get('REQUEST_TIME_FLOAT'); } $this->data = ['token' => $request->attributes->get('_stopwatch_token'), 'start_time' => $startTime * 1000, 'events' => [], 'stopwatch_installed' => \class_exists(Stopwatch::class, \false)]; } /** * {@inheritdoc} */ public function reset() { $this->data = ['events' => [], 'stopwatch_installed' => \false, 'start_time' => 0]; if (null !== $this->stopwatch) { $this->stopwatch->reset(); } } /** * {@inheritdoc} */ public function lateCollect() { if (null !== $this->stopwatch && isset($this->data['token'])) { $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); } unset($this->data['token']); } /** * @param StopwatchEvent[] $events The request events */ public function setEvents(array $events) { foreach ($events as $event) { $event->ensureStopped(); } $this->data['events'] = $events; } /** * @return StopwatchEvent[] */ public function getEvents() : array { return $this->data['events']; } /** * Gets the request elapsed time. */ public function getDuration() : float { if (!isset($this->data['events']['__section__'])) { return 0; } $lastEvent = $this->data['events']['__section__']; return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); } /** * Gets the initialization time. * * This is the time spent until the beginning of the request handling. */ public function getInitTime() : float { if (!isset($this->data['events']['__section__'])) { return 0; } return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); } public function getStartTime() : float { return $this->data['start_time']; } public function isStopwatchInstalled() : bool { return $this->data['stopwatch_installed']; } /** * {@inheritdoc} */ public function getName() : string { return 'time'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * DataCollectorInterface. * * @author Fabien Potencier */ interface DataCollectorInterface extends ResetInterface { /** * Collects data for the given Request and Response. */ public function collect(Request $request, Response $response, ?\Throwable $exception = null); /** * Returns the name of the collector. * * @return string */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\ParameterBag; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; /** * @author Fabien Potencier * * @final */ class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface { /** * @var \SplObjectStorage */ private $controllers; private $sessionUsages = []; private $requestStack; public function __construct(?RequestStack $requestStack = null) { $this->controllers = new \SplObjectStorage(); $this->requestStack = $requestStack; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { // attributes are serialized and as they can be anything, they need to be converted to strings. $attributes = []; $route = ''; foreach ($request->attributes->all() as $key => $value) { if ('_route' === $key) { $route = \is_object($value) ? $value->getPath() : $value; $attributes[$key] = $route; } else { $attributes[$key] = $value; } } $content = $request->getContent(); $sessionMetadata = []; $sessionAttributes = []; $flashes = []; if ($request->hasSession()) { $session = $request->getSession(); if ($session->isStarted()) { $sessionMetadata['Created'] = \date(\DATE_RFC822, $session->getMetadataBag()->getCreated()); $sessionMetadata['Last used'] = \date(\DATE_RFC822, $session->getMetadataBag()->getLastUsed()); $sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime(); $sessionAttributes = $session->all(); $flashes = $session->getFlashBag()->peekAll(); } } $statusCode = $response->getStatusCode(); $responseCookies = []; foreach ($response->headers->getCookies() as $cookie) { $responseCookies[$cookie->getName()] = $cookie; } $dotenvVars = []; foreach (\explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '') as $name) { if ('' !== $name && isset($_ENV[$name])) { $dotenvVars[$name] = $_ENV[$name]; } } $this->data = ['method' => $request->getMethod(), 'format' => $request->getRequestFormat(), 'content_type' => $response->headers->get('Content-Type', 'text/html'), 'status_text' => Response::$statusTexts[$statusCode] ?? '', 'status_code' => $statusCode, 'request_query' => $request->query->all(), 'request_request' => $request->request->all(), 'request_files' => $request->files->all(), 'request_headers' => $request->headers->all(), 'request_server' => $request->server->all(), 'request_cookies' => $request->cookies->all(), 'request_attributes' => $attributes, 'route' => $route, 'response_headers' => $response->headers->all(), 'response_cookies' => $responseCookies, 'session_metadata' => $sessionMetadata, 'session_attributes' => $sessionAttributes, 'session_usages' => \array_values($this->sessionUsages), 'stateless_check' => $this->requestStack && ($mainRequest = $this->requestStack->getMainRequest()) && $mainRequest->attributes->get('_stateless', \false), 'flashes' => $flashes, 'path_info' => $request->getPathInfo(), 'controller' => 'n/a', 'locale' => $request->getLocale(), 'dotenv_vars' => $dotenvVars]; if (isset($this->data['request_headers']['php-auth-pw'])) { $this->data['request_headers']['php-auth-pw'] = '******'; } if (isset($this->data['request_server']['PHP_AUTH_PW'])) { $this->data['request_server']['PHP_AUTH_PW'] = '******'; } if (isset($this->data['request_request']['_password'])) { $encodedPassword = \rawurlencode($this->data['request_request']['_password']); $content = \str_replace('_password=' . $encodedPassword, '_password=******', $content); $this->data['request_request']['_password'] = '******'; } $this->data['content'] = $content; foreach ($this->data as $key => $value) { if (!\is_array($value)) { continue; } if ('request_headers' === $key || 'response_headers' === $key) { $this->data[$key] = \array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); } } if (isset($this->controllers[$request])) { $this->data['controller'] = $this->parseController($this->controllers[$request]); unset($this->controllers[$request]); } if ($request->attributes->has('_redirected') && ($redirectCookie = $request->cookies->get('sf_redirect'))) { $this->data['redirect'] = \json_decode($redirectCookie, \true); $response->headers->clearCookie('sf_redirect'); } if ($response->isRedirect()) { $response->headers->setCookie(new Cookie('sf_redirect', \json_encode(['token' => $response->headers->get('x-debug-token'), 'route' => $request->attributes->get('_route', 'n/a'), 'method' => $request->getMethod(), 'controller' => $this->parseController($request->attributes->get('_controller')), 'status_code' => $statusCode, 'status_text' => Response::$statusTexts[$statusCode]]), 0, '/', null, $request->isSecure(), \true, \false, 'lax')); } $this->data['identifier'] = $this->data['route'] ?: (\is_array($this->data['controller']) ? $this->data['controller']['class'] . '::' . $this->data['controller']['method'] . '()' : $this->data['controller']); if ($response->headers->has('x-previous-debug-token')) { $this->data['forward_token'] = $response->headers->get('x-previous-debug-token'); } } public function lateCollect() { $this->data = $this->cloneVar($this->data); } public function reset() { $this->data = []; $this->controllers = new \SplObjectStorage(); $this->sessionUsages = []; } public function getMethod() { return $this->data['method']; } public function getPathInfo() { return $this->data['path_info']; } public function getRequestRequest() { return new ParameterBag($this->data['request_request']->getValue()); } public function getRequestQuery() { return new ParameterBag($this->data['request_query']->getValue()); } public function getRequestFiles() { return new ParameterBag($this->data['request_files']->getValue()); } public function getRequestHeaders() { return new ParameterBag($this->data['request_headers']->getValue()); } public function getRequestServer(bool $raw = \false) { return new ParameterBag($this->data['request_server']->getValue($raw)); } public function getRequestCookies(bool $raw = \false) { return new ParameterBag($this->data['request_cookies']->getValue($raw)); } public function getRequestAttributes() { return new ParameterBag($this->data['request_attributes']->getValue()); } public function getResponseHeaders() { return new ParameterBag($this->data['response_headers']->getValue()); } public function getResponseCookies() { return new ParameterBag($this->data['response_cookies']->getValue()); } public function getSessionMetadata() { return $this->data['session_metadata']->getValue(); } public function getSessionAttributes() { return $this->data['session_attributes']->getValue(); } public function getStatelessCheck() { return $this->data['stateless_check']; } public function getSessionUsages() { return $this->data['session_usages']; } public function getFlashes() { return $this->data['flashes']->getValue(); } public function getContent() { return $this->data['content']; } public function isJsonRequest() { return 1 === \preg_match('{^application/(?:\\w+\\++)*json$}i', $this->data['request_headers']['content-type']); } public function getPrettyJson() { $decoded = \json_decode($this->getContent()); return \JSON_ERROR_NONE === \json_last_error() ? \json_encode($decoded, \JSON_PRETTY_PRINT) : null; } public function getContentType() { return $this->data['content_type']; } public function getStatusText() { return $this->data['status_text']; } public function getStatusCode() { return $this->data['status_code']; } public function getFormat() { return $this->data['format']; } public function getLocale() { return $this->data['locale']; } public function getDotenvVars() { return new ParameterBag($this->data['dotenv_vars']->getValue()); } /** * Gets the route name. * * The _route request attributes is automatically set by the Router Matcher. */ public function getRoute() : string { return $this->data['route']; } public function getIdentifier() { return $this->data['identifier']; } /** * Gets the route parameters. * * The _route_params request attributes is automatically set by the RouterListener. */ public function getRouteParams() : array { return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : []; } /** * Gets the parsed controller. * * @return array|string|Data The controller as a string or array of data * with keys 'class', 'method', 'file' and 'line' */ public function getController() { return $this->data['controller']; } /** * Gets the previous request attributes. * * @return array|Data|false A legacy array of data from the previous redirection response * or false otherwise */ public function getRedirect() { return $this->data['redirect'] ?? \false; } public function getForwardToken() { return $this->data['forward_token'] ?? null; } public function onKernelController(ControllerEvent $event) { $this->controllers[$event->getRequest()] = $event->getController(); } public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } if ($event->getRequest()->cookies->has('sf_redirect')) { $event->getRequest()->attributes->set('_redirected', \true); } } public static function getSubscribedEvents() : array { return [KernelEvents::CONTROLLER => 'onKernelController', KernelEvents::RESPONSE => 'onKernelResponse']; } /** * {@inheritdoc} */ public function getName() : string { return 'request'; } public function collectSessionUsage() : void { $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); $traceEndIndex = \count($trace) - 1; for ($i = $traceEndIndex; $i > 0; --$i) { if (null !== ($class = $trace[$i]['class'] ?? null) && (\is_subclass_of($class, SessionInterface::class) || \is_subclass_of($class, SessionBagInterface::class))) { $traceEndIndex = $i; break; } } if (\count($trace) - 1 === $traceEndIndex) { return; } // Remove part of the backtrace that belongs to session only \array_splice($trace, 0, $traceEndIndex); // Merge identical backtraces generated by internal call reports $name = \sprintf('%s:%s', $trace[1]['class'] ?? $trace[0]['file'], $trace[0]['line']); if (!\array_key_exists($name, $this->sessionUsages)) { $this->sessionUsages[$name] = ['name' => $name, 'file' => $trace[0]['file'], 'line' => $trace[0]['line'], 'trace' => $trace]; } } /** * @param string|object|array|null $controller The controller to parse * * @return array|string An array of controller data or a simple string */ private function parseController($controller) { if (\is_string($controller) && \str_contains($controller, '::')) { $controller = \explode('::', $controller); } if (\is_array($controller)) { try { $r = new \ReflectionMethod($controller[0], $controller[1]); return ['class' => \is_object($controller[0]) ? \get_debug_type($controller[0]) : $controller[0], 'method' => $controller[1], 'file' => $r->getFileName(), 'line' => $r->getStartLine()]; } catch (\ReflectionException $e) { if (\is_callable($controller)) { // using __call or __callStatic return ['class' => \is_object($controller[0]) ? \get_debug_type($controller[0]) : $controller[0], 'method' => $controller[1], 'file' => 'n/a', 'line' => 'n/a']; } } } if ($controller instanceof \Closure) { $r = new \ReflectionFunction($controller); $controller = ['class' => $r->getName(), 'method' => null, 'file' => $r->getFileName(), 'line' => $r->getStartLine()]; if (\str_contains($r->name, '{closure}')) { return $controller; } $controller['method'] = $r->name; if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $controller['class'] = $class->name; } else { return $r->name; } return $controller; } if (\is_object($controller)) { $r = new \ReflectionClass($controller); return ['class' => $r->getName(), 'method' => null, 'file' => $r->getFileName(), 'line' => $r->getStartLine()]; } return \is_string($controller) ? $controller : 'n/a'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * @author Bart van den Burg * * @final */ class AjaxDataCollector extends DataCollector { public function collect(Request $request, Response $response, ?\Throwable $exception = null) { // all collecting is done client side } public function reset() { // all collecting is done client side } public function getName() : string { return 'ajax'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\VarDumper\Caster\CutStub; use _ContaoManager\Symfony\Component\VarDumper\Caster\ReflectionCaster; use _ContaoManager\Symfony\Component\VarDumper\Cloner\ClonerInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Stub; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; /** * DataCollector. * * Children of this class must store the collected data in the data property. * * @author Fabien Potencier * @author Bernhard Schussek */ abstract class DataCollector implements DataCollectorInterface { /** * @var array|Data */ protected $data = []; /** * @var ClonerInterface */ private $cloner; /** * Converts the variable into a serializable Data instance. * * This array can be displayed in the template using * the VarDumper component. * * @param mixed $var * * @return Data */ protected function cloneVar($var) { if ($var instanceof Data) { return $var; } if (null === $this->cloner) { $this->cloner = new VarCloner(); $this->cloner->setMaxItems(-1); $this->cloner->addCasters($this->getCasters()); } return $this->cloner->cloneVar($var); } /** * @return callable[] The casters to add to the cloner */ protected function getCasters() { $casters = ['*' => function ($v, array $a, Stub $s, $isNested) { if (!$v instanceof Stub) { foreach ($a as $k => $v) { if (\is_object($v) && !$v instanceof \DateTimeInterface && !$v instanceof Stub) { $a[$k] = new CutStub($v); } } } return $a; }] + ReflectionCaster::UNSET_CLOSURE_FILE_INFO; return $casters; } /** * @return array */ public function __sleep() { return ['data']; } public function __wakeup() { } /** * @internal to prevent implementing \Serializable */ protected final function serialize() { } /** * @internal to prevent implementing \Serializable */ protected final function unserialize($data) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Log\DebugLoggerInterface; /** * @author Fabien Potencier * * @final */ class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface { private $logger; private $containerPathPrefix; private $currentRequest; private $requestStack; private $processedLogs; public function __construct(?object $logger = null, ?string $containerPathPrefix = null, ?RequestStack $requestStack = null) { if (null !== $logger && $logger instanceof DebugLoggerInterface) { $this->logger = $logger; } $this->containerPathPrefix = $containerPathPrefix; $this->requestStack = $requestStack; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; } /** * {@inheritdoc} */ public function reset() { if ($this->logger instanceof DebugLoggerInterface) { $this->logger->clear(); } $this->data = []; } /** * {@inheritdoc} */ public function lateCollect() { if (null !== $this->logger) { $containerDeprecationLogs = $this->getContainerDeprecationLogs(); $this->data = $this->computeErrorsCount($containerDeprecationLogs); // get compiler logs later (only when they are needed) to improve performance $this->data['compiler_logs'] = []; $this->data['compiler_logs_filepath'] = $this->containerPathPrefix . 'Compiler.log'; $this->data['logs'] = $this->sanitizeLogs(\array_merge($this->logger->getLogs($this->currentRequest), $containerDeprecationLogs)); $this->data = $this->cloneVar($this->data); } $this->currentRequest = null; } public function getLogs() { return $this->data['logs'] ?? []; } public function getProcessedLogs() { if (null !== $this->processedLogs) { return $this->processedLogs; } $rawLogs = $this->getLogs(); if ([] === $rawLogs) { return $this->processedLogs = $rawLogs; } $logs = []; foreach ($this->getLogs()->getValue() as $rawLog) { $rawLogData = $rawLog->getValue(); if ($rawLogData['priority']->getValue() > 300) { $logType = 'error'; } elseif (isset($rawLogData['scream']) && \false === $rawLogData['scream']->getValue()) { $logType = 'deprecation'; } elseif (isset($rawLogData['scream']) && \true === $rawLogData['scream']->getValue()) { $logType = 'silenced'; } else { $logType = 'regular'; } $logs[] = ['type' => $logType, 'errorCount' => $rawLog['errorCount'] ?? 1, 'timestamp' => $rawLogData['timestamp_rfc3339']->getValue(), 'priority' => $rawLogData['priority']->getValue(), 'priorityName' => $rawLogData['priorityName']->getValue(), 'channel' => $rawLogData['channel']->getValue(), 'message' => $rawLogData['message'], 'context' => $rawLogData['context']]; } // sort logs from oldest to newest \usort($logs, static function ($logA, $logB) { return $logA['timestamp'] <=> $logB['timestamp']; }); return $this->processedLogs = $logs; } public function getFilters() { $filters = ['channel' => [], 'priority' => ['Debug' => 100, 'Info' => 200, 'Notice' => 250, 'Warning' => 300, 'Error' => 400, 'Critical' => 500, 'Alert' => 550, 'Emergency' => 600]]; $allChannels = []; foreach ($this->getProcessedLogs() as $log) { if ('' === \trim($log['channel'] ?? '')) { continue; } $allChannels[] = $log['channel']; } $channels = \array_unique($allChannels); \sort($channels); $filters['channel'] = $channels; return $filters; } public function getPriorities() { return $this->data['priorities'] ?? []; } public function countErrors() { return $this->data['error_count'] ?? 0; } public function countDeprecations() { return $this->data['deprecation_count'] ?? 0; } public function countWarnings() { return $this->data['warning_count'] ?? 0; } public function countScreams() { return $this->data['scream_count'] ?? 0; } public function getCompilerLogs() { return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null)); } /** * {@inheritdoc} */ public function getName() : string { return 'logger'; } private function getContainerDeprecationLogs() : array { if (null === $this->containerPathPrefix || !\is_file($file = $this->containerPathPrefix . 'Deprecations.log')) { return []; } if ('' === ($logContent = \trim(\file_get_contents($file)))) { return []; } $bootTime = \filemtime($file); $logs = []; foreach (\unserialize($logContent) as $log) { $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])]; $log['timestamp'] = $bootTime; $log['timestamp_rfc3339'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED); $log['priority'] = 100; $log['priorityName'] = 'DEBUG'; $log['channel'] = null; $log['scream'] = \false; unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); $logs[] = $log; } return $logs; } private function getContainerCompilerLogs(?string $compilerLogsFilepath = null) : array { if (!$compilerLogsFilepath || !\is_file($compilerLogsFilepath)) { return []; } $logs = []; foreach (\file($compilerLogsFilepath, \FILE_IGNORE_NEW_LINES) as $log) { $log = \explode(': ', $log, 2); if (!isset($log[1]) || !\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+(?:\\\\[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+)++$/', $log[0])) { $log = ['Unknown Compiler Pass', \implode(': ', $log)]; } $logs[$log[0]][] = ['message' => $log[1]]; } return $logs; } private function sanitizeLogs(array $logs) { $sanitizedLogs = []; $silencedLogs = []; foreach ($logs as $log) { if (!$this->isSilencedOrDeprecationErrorLog($log)) { $sanitizedLogs[] = $log; continue; } $message = '_' . $log['message']; $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { if (isset($silencedLogs[$h = \spl_object_hash($exception)])) { continue; } $silencedLogs[$h] = \true; if (!isset($sanitizedLogs[$message])) { $sanitizedLogs[$message] = $log + ['errorCount' => 0, 'scream' => \true]; } $sanitizedLogs[$message]['errorCount'] += $exception->count; continue; } $errorId = \md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\x00{$message}", \true); if (isset($sanitizedLogs[$errorId])) { ++$sanitizedLogs[$errorId]['errorCount']; } else { $log += ['errorCount' => 1, 'scream' => \false]; $sanitizedLogs[$errorId] = $log; } } return \array_values($sanitizedLogs); } private function isSilencedOrDeprecationErrorLog(array $log) : bool { if (!isset($log['context']['exception'])) { return \false; } $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { return \true; } if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [\E_DEPRECATED, \E_USER_DEPRECATED], \true)) { return \true; } return \false; } private function computeErrorsCount(array $containerDeprecationLogs) : array { $silencedLogs = []; $count = ['error_count' => $this->logger->countErrors($this->currentRequest), 'deprecation_count' => 0, 'warning_count' => 0, 'scream_count' => 0, 'priorities' => []]; foreach ($this->logger->getLogs($this->currentRequest) as $log) { if (isset($count['priorities'][$log['priority']])) { ++$count['priorities'][$log['priority']]['count']; } else { $count['priorities'][$log['priority']] = ['count' => 1, 'name' => $log['priorityName']]; } if ('WARNING' === $log['priorityName']) { ++$count['warning_count']; } if ($this->isSilencedOrDeprecationErrorLog($log)) { $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { if (isset($silencedLogs[$h = \spl_object_hash($exception)])) { continue; } $silencedLogs[$h] = \true; $count['scream_count'] += $exception->count; } else { ++$count['deprecation_count']; } } } foreach ($containerDeprecationLogs as $deprecationLog) { $count['deprecation_count'] += $deprecationLog['context']['exception']->count; } \ksort($count['priorities']); return $count; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; use _ContaoManager\Symfony\Component\VarDumper\Caster\ClassStub; /** * @author Fabien Potencier * * @final */ class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface { /** * @var KernelInterface */ private $kernel; /** * Sets the Kernel associated with this Request. */ public function setKernel(?KernelInterface $kernel = null) { $this->kernel = $kernel; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { $eom = \DateTime::createFromFormat('d/m/Y', '01/' . Kernel::END_OF_MAINTENANCE); $eol = \DateTime::createFromFormat('d/m/Y', '01/' . Kernel::END_OF_LIFE); $this->data = ['token' => $response->headers->get('X-Debug-Token'), 'symfony_version' => Kernel::VERSION, 'symfony_minor_version' => \sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION), 'symfony_lts' => 4 === Kernel::MINOR_VERSION, 'symfony_state' => $this->determineSymfonyState(), 'symfony_eom' => $eom->format('F Y'), 'symfony_eol' => $eol->format('F Y'), 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', 'php_version' => \PHP_VERSION, 'php_architecture' => \PHP_INT_SIZE * 8, 'php_intl_locale' => \class_exists(\Locale::class, \false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', 'php_timezone' => \date_default_timezone_get(), 'xdebug_enabled' => \extension_loaded('xdebug'), 'apcu_enabled' => \extension_loaded('apcu') && \filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN), 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN), 'bundles' => [], 'sapi_name' => \PHP_SAPI]; if (isset($this->kernel)) { foreach ($this->kernel->getBundles() as $name => $bundle) { $this->data['bundles'][$name] = new ClassStub(\get_class($bundle)); } } if (\preg_match('~^(\\d+(?:\\.\\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) { $this->data['php_version'] = $matches[1]; $this->data['php_version_extra'] = $matches[2]; } } /** * {@inheritdoc} */ public function reset() { $this->data = []; } public function lateCollect() { $this->data = $this->cloneVar($this->data); } /** * Gets the token. */ public function getToken() : ?string { return $this->data['token']; } /** * Gets the Symfony version. */ public function getSymfonyVersion() : string { return $this->data['symfony_version']; } /** * Returns the state of the current Symfony release. * * @return string One of: unknown, dev, stable, eom, eol */ public function getSymfonyState() : string { return $this->data['symfony_state']; } /** * Returns the minor Symfony version used (without patch numbers of extra * suffix like "RC", "beta", etc.). */ public function getSymfonyMinorVersion() : string { return $this->data['symfony_minor_version']; } /** * Returns if the current Symfony version is a Long-Term Support one. */ public function isSymfonyLts() : bool { return $this->data['symfony_lts']; } /** * Returns the human readable date when this Symfony version ends its * maintenance period. */ public function getSymfonyEom() : string { return $this->data['symfony_eom']; } /** * Returns the human readable date when this Symfony version reaches its * "end of life" and won't receive bugs or security fixes. */ public function getSymfonyEol() : string { return $this->data['symfony_eol']; } /** * Gets the PHP version. */ public function getPhpVersion() : string { return $this->data['php_version']; } /** * Gets the PHP version extra part. */ public function getPhpVersionExtra() : ?string { return $this->data['php_version_extra'] ?? null; } /** * @return int The PHP architecture as number of bits (e.g. 32 or 64) */ public function getPhpArchitecture() : int { return $this->data['php_architecture']; } public function getPhpIntlLocale() : string { return $this->data['php_intl_locale']; } public function getPhpTimezone() : string { return $this->data['php_timezone']; } /** * Gets the environment. */ public function getEnv() : string { return $this->data['env']; } /** * Returns true if the debug is enabled. * * @return bool|string true if debug is enabled, false otherwise or a string if no kernel was set */ public function isDebug() { return $this->data['debug']; } /** * Returns true if the XDebug is enabled. */ public function hasXDebug() : bool { return $this->data['xdebug_enabled']; } /** * Returns true if APCu is enabled. */ public function hasApcu() : bool { return $this->data['apcu_enabled']; } /** * Returns true if Zend OPcache is enabled. */ public function hasZendOpcache() : bool { return $this->data['zend_opcache_enabled']; } public function getBundles() { return $this->data['bundles']; } /** * Gets the PHP SAPI name. */ public function getSapiName() : string { return $this->data['sapi_name']; } /** * {@inheritdoc} */ public function getName() : string { return 'config'; } /** * Tries to retrieve information about the current Symfony version. * * @return string One of: dev, stable, eom, eol */ private function determineSymfonyState() : string { $now = new \DateTime(); $eom = \DateTime::createFromFormat('d/m/Y', '01/' . Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); $eol = \DateTime::createFromFormat('d/m/Y', '01/' . Kernel::END_OF_LIFE)->modify('last day of this month'); if ($now > $eol) { $versionState = 'eol'; } elseif ($now > $eom) { $versionState = 'eom'; } elseif ('' !== Kernel::EXTRA_VERSION) { $versionState = 'dev'; } else { $versionState = 'stable'; } return $versionState; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\VarDumper\Cloner\Data; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * @author Fabien Potencier * * @final */ class EventDataCollector extends DataCollector implements LateDataCollectorInterface { protected $dispatcher; private $requestStack; private $currentRequest; public function __construct(?EventDispatcherInterface $dispatcher = null, ?RequestStack $requestStack = null) { $this->dispatcher = $dispatcher; $this->requestStack = $requestStack; } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; $this->data = ['called_listeners' => [], 'not_called_listeners' => [], 'orphaned_events' => []]; } public function reset() { $this->data = []; if ($this->dispatcher instanceof ResetInterface) { $this->dispatcher->reset(); } } public function lateCollect() { if ($this->dispatcher instanceof TraceableEventDispatcher) { $this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest)); $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest)); $this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest)); } $this->data = $this->cloneVar($this->data); } /** * @param array $listeners An array of called listeners * * @see TraceableEventDispatcher */ public function setCalledListeners(array $listeners) { $this->data['called_listeners'] = $listeners; } /** * @see TraceableEventDispatcher * * @return array|Data */ public function getCalledListeners() { return $this->data['called_listeners']; } /** * @see TraceableEventDispatcher */ public function setNotCalledListeners(array $listeners) { $this->data['not_called_listeners'] = $listeners; } /** * @see TraceableEventDispatcher * * @return array|Data */ public function getNotCalledListeners() { return $this->data['not_called_listeners']; } /** * @param array $events An array of orphaned events * * @see TraceableEventDispatcher */ public function setOrphanedEvents(array $events) { $this->data['orphaned_events'] = $events; } /** * @see TraceableEventDispatcher * * @return array|Data */ public function getOrphanedEvents() { return $this->data['orphaned_events']; } /** * {@inheritdoc} */ public function getName() : string { return 'events'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DataCollector; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\FlattenException; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * @author Fabien Potencier * * @final */ class ExceptionDataCollector extends DataCollector { /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { if (null !== $exception) { $this->data = ['exception' => FlattenException::createFromThrowable($exception)]; } } /** * {@inheritdoc} */ public function reset() { $this->data = []; } public function hasException() : bool { return isset($this->data['exception']); } /** * @return \Exception|FlattenException */ public function getException() { return $this->data['exception']; } public function getMessage() : string { return $this->data['exception']->getMessage(); } public function getCode() : int { return $this->data['exception']->getCode(); } public function getStatusCode() : int { return $this->data['exception']->getStatusCode(); } public function getTrace() : array { return $this->data['exception']->getTrace(); } /** * {@inheritdoc} */ public function getName() : string { return 'exception'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Attribute; /** * Service tag to autoconfigure controllers. */ #[\Attribute(\Attribute::TARGET_CLASS)] class AsController { public function __construct() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Attribute; \trigger_deprecation('symfony/http-kernel', '5.3', 'The "%s" interface is deprecated.', ArgumentInterface::class); /** * Marker interface for controller argument attributes. * * @deprecated since Symfony 5.3 */ interface ArgumentInterface { } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata; /** * Builds method argument data. * * @author Iltar van der Berg */ interface ArgumentMetadataFactoryInterface { /** * @param string|object|array $controller The controller to resolve the arguments for * * @return ArgumentMetadata[] */ public function createArgumentMetadata($controller); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata; use _ContaoManager\Symfony\Component\HttpKernel\Attribute\ArgumentInterface; /** * Responsible for storing metadata of an argument. * * @author Iltar van der Berg */ class ArgumentMetadata { public const IS_INSTANCEOF = 2; private $name; private $type; private $isVariadic; private $hasDefaultValue; private $defaultValue; private $isNullable; private $attributes; /** * @param object[] $attributes */ public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = \false, $attributes = []) { $this->name = $name; $this->type = $type; $this->isVariadic = $isVariadic; $this->hasDefaultValue = $hasDefaultValue; $this->defaultValue = $defaultValue; $this->isNullable = $isNullable || null === $type || $hasDefaultValue && null === $defaultValue; if (null === $attributes || $attributes instanceof ArgumentInterface) { \trigger_deprecation('symfony/http-kernel', '5.3', 'The "%s" constructor expects an array of PHP attributes as last argument, %s given.', __CLASS__, \get_debug_type($attributes)); $attributes = $attributes ? [$attributes] : []; } $this->attributes = $attributes; } /** * Returns the name as given in PHP, $foo would yield "foo". * * @return string */ public function getName() { return $this->name; } /** * Returns the type of the argument. * * The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+. * * @return string|null */ public function getType() { return $this->type; } /** * Returns whether the argument is defined as "...$variadic". * * @return bool */ public function isVariadic() { return $this->isVariadic; } /** * Returns whether the argument has a default value. * * Implies whether an argument is optional. * * @return bool */ public function hasDefaultValue() { return $this->hasDefaultValue; } /** * Returns whether the argument accepts null values. * * @return bool */ public function isNullable() { return $this->isNullable; } /** * Returns the default value of the argument. * * @return mixed * * @throws \LogicException if no default value is present; {@see self::hasDefaultValue()} */ public function getDefaultValue() { if (!$this->hasDefaultValue) { throw new \LogicException(\sprintf('Argument $%s does not have a default value. Use "%s::hasDefaultValue()" to avoid this exception.', $this->name, __CLASS__)); } return $this->defaultValue; } /** * Returns the attribute (if any) that was set on the argument. */ public function getAttribute() : ?ArgumentInterface { \trigger_deprecation('symfony/http-kernel', '5.3', 'Method "%s()" is deprecated, use "getAttributes()" instead.', __METHOD__); if (!$this->attributes) { return null; } return $this->attributes[0] instanceof ArgumentInterface ? $this->attributes[0] : null; } /** * @return object[] */ public function getAttributes(?string $name = null, int $flags = 0) : array { if (!$name) { return $this->attributes; } $attributes = []; if ($flags & self::IS_INSTANCEOF) { foreach ($this->attributes as $attribute) { if ($attribute instanceof $name) { $attributes[] = $attribute; } } } else { foreach ($this->attributes as $attribute) { if (\get_class($attribute) === $name) { $attributes[] = $attribute; } } } return $attributes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata; /** * Builds {@see ArgumentMetadata} objects based on the given Controller. * * @author Iltar van der Berg */ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface { /** * {@inheritdoc} */ public function createArgumentMetadata($controller) : array { $arguments = []; if (\is_array($controller)) { $reflection = new \ReflectionMethod($controller[0], $controller[1]); $class = $reflection->class; } elseif (\is_object($controller) && !$controller instanceof \Closure) { $reflection = new \ReflectionMethod($controller, '__invoke'); $class = $reflection->class; } else { $reflection = new \ReflectionFunction($controller); if ($class = \str_contains($reflection->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $reflection->getClosureCalledClass() : $reflection->getClosureScopeClass())) { $class = $class->name; } } foreach ($reflection->getParameters() as $param) { $attributes = []; if (\PHP_VERSION_ID >= 80000) { foreach ($param->getAttributes() as $reflectionAttribute) { if (\class_exists($reflectionAttribute->getName())) { $attributes[] = $reflectionAttribute->newInstance(); } } } $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $class), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes); } return $arguments; } /** * Returns an associated type to the given parameter if available. */ private function getType(\ReflectionParameter $parameter, ?string $class) : ?string { if (!($type = $parameter->getType())) { return null; } $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; if (null !== $class) { switch (\strtolower($name)) { case 'self': return $class; case 'parent': return \get_parent_class($class) ?: null; } } return $name; } } CHANGELOG ========= 5.4 --- * Add the ability to enable the profiler using a request query parameter, body parameter or attribute * Deprecate `AbstractTestSessionListener` and `TestSessionListener`, use `AbstractSessionListener` and `SessionListener` instead * Deprecate the `fileLinkFormat` parameter of `DebugHandlersListener` * Add support for configuring log level, and status code by exception class * Allow ignoring "kernel.reset" methods that don't exist with "on_invalid" attribute 5.3 --- * Deprecate `ArgumentInterface` * Add `ArgumentMetadata::getAttributes()` * Deprecate `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead * Mark the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal * Deprecate returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()` * Deprecate `HttpKernelInterface::MASTER_REQUEST` and add `HttpKernelInterface::MAIN_REQUEST` as replacement * Deprecate `KernelEvent::isMasterRequest()` and add `isMainRequest()` as replacement * Add `#[AsController]` attribute for declaring standalone controllers on PHP 8 * Add `FragmentUriGeneratorInterface` and `FragmentUriGenerator` to generate the URI of a fragment 5.2.0 ----- * added session usage * made the public `http_cache` service handle requests when available * allowed enabling trusted hosts and proxies using new `kernel.trusted_hosts`, `kernel.trusted_proxies` and `kernel.trusted_headers` parameters * content of request parameter `_password` is now also hidden in the request profiler raw content section * Allowed adding attributes on controller arguments that will be passed to argument resolvers. * kernels implementing the `ExtensionInterface` will now be auto-registered to the container * added parameter `kernel.runtime_environment`, defined as `%env(default:kernel.environment:APP_RUNTIME_ENV)%` * do not set a default `Accept` HTTP header when using `HttpKernelBrowser` 5.1.0 ----- * allowed to use a specific logger channel for deprecations * made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+; not returning an array is deprecated * made kernels implementing `WarmableInterface` be part of the cache warmup stage * deprecated support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead * allowed using public aliases to reference controllers * added session usage reporting when the `_stateless` attribute of the request is set to `true` * added `AbstractSessionListener::onSessionUsage()` to report when the session is used while a request is stateless 5.0.0 ----- * removed support for getting the container from a non-booted kernel * removed the first and second constructor argument of `ConfigDataCollector` * removed `ConfigDataCollector::getApplicationName()` * removed `ConfigDataCollector::getApplicationVersion()` * removed support for `Symfony\Component\Templating\EngineInterface` in `HIncludeFragmentRenderer`, use a `Twig\Environment` only * removed `TranslatorListener` in favor of `LocaleAwareListener` * removed `getRootDir()` and `getName()` from `Kernel` and `KernelInterface` * removed `FilterControllerArgumentsEvent`, use `ControllerArgumentsEvent` instead * removed `FilterControllerEvent`, use `ControllerEvent` instead * removed `FilterResponseEvent`, use `ResponseEvent` instead * removed `GetResponseEvent`, use `RequestEvent` instead * removed `GetResponseForControllerResultEvent`, use `ViewEvent` instead * removed `GetResponseForExceptionEvent`, use `ExceptionEvent` instead * removed `PostResponseEvent`, use `TerminateEvent` instead * removed `SaveSessionListener` in favor of `AbstractSessionListener` * removed `Client`, use `HttpKernelBrowser` instead * added method `getProjectDir()` to `KernelInterface` * removed methods `serialize` and `unserialize` from `DataCollector`, store the serialized state in the data property instead * made `ProfilerStorageInterface` internal * removed the second and third argument of `KernelInterface::locateResource` * removed the second and third argument of `FileLocator::__construct` * removed loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as fallback directories. * removed class `ExceptionListener`, use `ErrorListener` instead 4.4.0 ----- * The `DebugHandlersListener` class has been marked as `final` * Added new Bundle directory convention consistent with standard skeletons * Deprecated the second and third argument of `KernelInterface::locateResource` * Deprecated the second and third argument of `FileLocator::__construct` * Deprecated loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as fallback directories. Resources like service definitions are usually loaded relative to the current directory or with a glob pattern. The fallback directories have never been advocated so you likely do not use those in any app based on the SF Standard or Flex edition. * Marked all dispatched event classes as `@final` * Added `ErrorController` to enable the preview and error rendering mechanism * Getting the container from a non-booted kernel is deprecated. * Marked the `AjaxDataCollector`, `ConfigDataCollector`, `EventDataCollector`, `ExceptionDataCollector`, `LoggerDataCollector`, `MemoryDataCollector`, `RequestDataCollector` and `TimeDataCollector` classes as `@final`. * Marked the `RouterDataCollector::collect()` method as `@final`. * The `DataCollectorInterface::collect()` and `Profiler::collect()` methods third parameter signature will be `\Throwable $exception = null` instead of `\Exception $exception = null` in Symfony 5.0. * Deprecated methods `ExceptionEvent::get/setException()`, use `get/setThrowable()` instead * Deprecated class `ExceptionListener`, use `ErrorListener` instead 4.3.0 ----- * renamed `Client` to `HttpKernelBrowser` * `KernelInterface` doesn't extend `Serializable` anymore * deprecated the `Kernel::serialize()` and `unserialize()` methods * increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener` * made `Symfony\Component\HttpKernel\EventListener\LocaleListener` set the default locale early * deprecated `TranslatorListener` in favor of `LocaleAwareListener` * added the registration of all `LocaleAwareInterface` implementations into the `LocaleAwareListener` * made `FileLinkFormatter` final and not implement `Serializable` anymore * the base `DataCollector` doesn't implement `Serializable` anymore, you should store all the serialized state in the data property instead * `DumpDataCollector` has been marked as `final` * added an event listener to prevent search engines from indexing applications in debug mode. * renamed `FilterControllerArgumentsEvent` to `ControllerArgumentsEvent` * renamed `FilterControllerEvent` to `ControllerEvent` * renamed `FilterResponseEvent` to `ResponseEvent` * renamed `GetResponseEvent` to `RequestEvent` * renamed `GetResponseForControllerResultEvent` to `ViewEvent` * renamed `GetResponseForExceptionEvent` to `ExceptionEvent` * renamed `PostResponseEvent` to `TerminateEvent` * added `HttpClientKernel` for handling requests with an `HttpClientInterface` instance * added `trace_header` and `trace_level` configuration options to `HttpCache` 4.2.0 ----- * deprecated `KernelInterface::getRootDir()` and the `kernel.root_dir` parameter * deprecated `KernelInterface::getName()` and the `kernel.name` parameter * deprecated the first and second constructor argument of `ConfigDataCollector` * deprecated `ConfigDataCollector::getApplicationName()` * deprecated `ConfigDataCollector::getApplicationVersion()` 4.1.0 ----- * added orphaned events support to `EventDataCollector` * `ExceptionListener` now logs exceptions at priority `0` (previously logged at `-128`) * Added support for using `service::method` to reference controllers, making it consistent with other cases. It is recommended over the `service:action` syntax with a single colon, which will be deprecated in the future. * Added the ability to profile individual argument value resolvers via the `Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver` 4.0.0 ----- * removed the `DataCollector::varToString()` method, use `DataCollector::cloneVar()` instead * using the `DataCollector::cloneVar()` method requires the VarDumper component * removed the `ValueExporter` class * removed `ControllerResolverInterface::getArguments()` * removed `TraceableControllerResolver::getArguments()` * removed `ControllerResolver::getArguments()` and the ability to resolve arguments * removed the `argument_resolver` service dependency from the `debug.controller_resolver` * removed `LazyLoadingFragmentHandler::addRendererService()` * removed `Psr6CacheClearer::addPool()` * removed `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` * removed `Kernel::loadClassCache()`, `Kernel::doLoadClassCache()`, `Kernel::setClassCache()`, and `Kernel::getEnvParameters()` * support for the `X-Status-Code` when handling exceptions in the `HttpKernel` has been dropped, use the `HttpKernel::allowCustomResponseCode()` method instead * removed convention-based commands registration * removed the `ChainCacheClearer::add()` method * removed the `CacheaWarmerAggregate::add()` and `setWarmers()` methods * made `CacheWarmerAggregate` and `ChainCacheClearer` classes final 3.4.0 ----- * added a minimalist PSR-3 `Logger` class that writes in `stderr` * made kernels implementing `CompilerPassInterface` able to process the container * deprecated bundle inheritance * added `RebootableInterface` and implemented it in `Kernel` * deprecated commands auto registration * deprecated `EnvParametersResource` * added `Symfony\Component\HttpKernel\Client::catchExceptions()` * deprecated the `ChainCacheClearer::add()` method * deprecated the `CacheaWarmerAggregate::add()` and `setWarmers()` methods * made `CacheWarmerAggregate` and `ChainCacheClearer` classes final * added the possibility to reset the profiler to its initial state * deprecated data collectors without a `reset()` method * deprecated implementing `DebugLoggerInterface` without a `clear()` method 3.3.0 ----- * added `kernel.project_dir` and `Kernel::getProjectDir()` * deprecated `kernel.root_dir` and `Kernel::getRootDir()` * deprecated `Kernel::getEnvParameters()` * deprecated the special `SYMFONY__` environment variables * added the possibility to change the query string parameter used by `UriSigner` * deprecated `LazyLoadingFragmentHandler::addRendererService()` * deprecated `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` * deprecated `Psr6CacheClearer::addPool()` 3.2.0 ----- * deprecated `DataCollector::varToString()`, use `cloneVar()` instead * changed surrogate capability name in `AbstractSurrogate::addSurrogateCapability` to 'symfony' * Added `ControllerArgumentValueResolverPass` 3.1.0 ----- * deprecated passing objects as URI attributes to the ESI and SSI renderers * deprecated `ControllerResolver::getArguments()` * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel` * added `Symfony\Component\HttpKernel\Controller\ArgumentResolver` * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getMethod()` * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getRedirect()` * added the `kernel.controller_arguments` event, triggered after controller arguments have been resolved 3.0.0 ----- * removed `Symfony\Component\HttpKernel\Kernel::init()` * removed `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle()` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle()` * removed `Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher::setProfiler()` * removed `Symfony\Component\HttpKernel\EventListener\FragmentListener::getLocalIpAddresses()` * removed `Symfony\Component\HttpKernel\EventListener\LocaleListener::setRequest()` * removed `Symfony\Component\HttpKernel\EventListener\RouterListener::setRequest()` * removed `Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest()` * removed `Symfony\Component\HttpKernel\Fragment\FragmentHandler::setRequest()` * removed `Symfony\Component\HttpKernel\HttpCache\Esi::hasSurrogateEsiCapability()` * removed `Symfony\Component\HttpKernel\HttpCache\Esi::addSurrogateEsiCapability()` * removed `Symfony\Component\HttpKernel\HttpCache\Esi::needsEsiParsing()` * removed `Symfony\Component\HttpKernel\HttpCache\HttpCache::getEsi()` * removed `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` * removed `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass` * removed `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` * removed `Symfony\Component\HttpKernel\EventListener\EsiListener` * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategy` * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategyInterface` * removed `Symfony\Component\HttpKernel\Log\LoggerInterface` * removed `Symfony\Component\HttpKernel\Log\NullLogger` * removed `Symfony\Component\HttpKernel\Profiler::import()` * removed `Symfony\Component\HttpKernel\Profiler::export()` 2.8.0 ----- * deprecated `Profiler::import` and `Profiler::export` 2.7.0 ----- * added the HTTP status code to profiles 2.6.0 ----- * deprecated `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener`, use `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` instead * deprecated unused method `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle` 2.5.0 ----- * deprecated `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass`, use `Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass` instead 2.4.0 ----- * added event listeners for the session * added the KernelEvents::FINISH_REQUEST event 2.3.0 ----- * [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, `Symfony\Component\HttpKernel\Exception\FatalErrorException` and `Symfony\Component\HttpKernel\Exception\FlattenException` * deprecated `Symfony\Component\HttpKernel\Kernel::init()` * added the possibility to specify an id an extra attributes to hinclude tags * added the collect of data if a controller is a Closure in the Request collector * pass exceptions from the ExceptionListener to the logger using the logging context to allow for more detailed messages 2.2.0 ----- * [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default) * added Symfony\Component\HttpKernel\EventListener\FragmentListener * added Symfony\Component\HttpKernel\UriSigner * added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface) * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel * added ControllerReference to create reference of Controllers (used in the FragmentRenderer class) * [BC BREAK] renamed TimeDataCollector::getTotalTime() to TimeDataCollector::getDuration() * updated the MemoryDataCollector to include the memory used in the kernel.terminate event listeners * moved the Stopwatch classes to a new component * added TraceableControllerResolver * added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher) * added support for WinCache opcode cache in ConfigDataCollector 2.1.0 ----- * [BC BREAK] the charset is now configured via the Kernel::getCharset() method * [BC BREAK] the current locale for the user is not stored anymore in the session * added the HTTP method to the profiler storage * updated all listeners to implement EventSubscriberInterface * added TimeDataCollector * added ContainerAwareTraceableEventDispatcher * moved TraceableEventDispatcherInterface to the EventDispatcher component * added RouterListener, LocaleListener, and StreamedResponseListener * added CacheClearerInterface (and ChainCacheClearer) * added a kernel.terminate event (via TerminableInterface and PostResponseEvent) * added a Stopwatch class * added WarmableInterface * improved extensibility between bundles * added profiler storages for Memcache(d), File-based, MongoDB, Redis * moved Filesystem class to its own component * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Config; use _ContaoManager\Symfony\Component\Config\FileLocator as BaseFileLocator; use _ContaoManager\Symfony\Component\HttpKernel\KernelInterface; /** * FileLocator uses the KernelInterface to locate resources in bundles. * * @author Fabien Potencier */ class FileLocator extends BaseFileLocator { private $kernel; public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; parent::__construct(); } /** * {@inheritdoc} */ public function locate(string $file, ?string $currentPath = null, bool $first = \true) { if (isset($file[0]) && '@' === $file[0]) { $resource = $this->kernel->locateResource($file); return $first ? $resource : [$resource]; } return parent::locate($file, $currentPath, $first); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\HttpClient\HttpClient; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpFoundation\ResponseHeaderBag; use _ContaoManager\Symfony\Component\Mime\Part\AbstractPart; use _ContaoManager\Symfony\Component\Mime\Part\DataPart; use _ContaoManager\Symfony\Component\Mime\Part\Multipart\FormDataPart; use _ContaoManager\Symfony\Component\Mime\Part\TextPart; use _ContaoManager\Symfony\Contracts\HttpClient\HttpClientInterface; // Help opcache.preload discover always-needed symbols \class_exists(ResponseHeaderBag::class); /** * An implementation of a Symfony HTTP kernel using a "real" HTTP client. * * @author Fabien Potencier */ final class HttpClientKernel implements HttpKernelInterface { private $client; public function __construct(?HttpClientInterface $client = null) { if (null === $client && !\class_exists(HttpClient::class)) { throw new \LogicException(\sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); } $this->client = $client ?? HttpClient::create(); } public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = \true) : Response { $headers = $this->getHeaders($request); $body = ''; if (null !== ($part = $this->getBody($request))) { $headers = \array_merge($headers, $part->getPreparedHeaders()->toArray()); $body = $part->bodyToIterable(); } $response = $this->client->request($request->getMethod(), $request->getUri(), ['headers' => $headers, 'body' => $body] + $request->attributes->get('http_client_options', [])); $response = new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch)); $response->headers->remove('X-Body-File'); $response->headers->remove('X-Body-Eval'); $response->headers->remove('X-Content-Digest'); $response->headers = new class($response->headers->all()) extends ResponseHeaderBag { protected function computeCacheControlValue() : string { return $this->getCacheControlHeader(); // preserve the original value } }; return $response; } private function getBody(Request $request) : ?AbstractPart { if (\in_array($request->getMethod(), ['GET', 'HEAD'])) { return null; } if (!\class_exists(AbstractPart::class)) { throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".'); } if ($content = $request->getContent()) { return new TextPart($content, 'utf-8', 'plain', '8bit'); } $fields = $request->request->all(); foreach ($request->files->all() as $name => $file) { $fields[$name] = DataPart::fromPath($file->getPathname(), $file->getClientOriginalName(), $file->getClientMimeType()); } return new FormDataPart($fields); } private function getHeaders(Request $request) : array { $headers = []; foreach ($request->headers as $key => $value) { $headers[$key] = $value; } $cookies = []; foreach ($request->cookies->all() as $name => $value) { $cookies[] = $name . '=' . $value; } if ($cookies) { $headers['cookie'] = \implode('; ', $cookies); } return $headers; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Terminable extends the Kernel request/response cycle with dispatching a post * response event after sending the response and before shutting down the kernel. * * @author Jordi Boggiano * @author Pierre Minnieur */ interface TerminableInterface { /** * Terminates a request/response cycle. * * Should be called after sending the response and before shutting down the kernel. */ public function terminate(Request $request, Response $response); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheClearer; /** * CacheClearerInterface. * * @author Dustin Dobervich */ interface CacheClearerInterface { /** * Clears any caches necessary. */ public function clear(string $cacheDir); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheClearer; /** * ChainCacheClearer. * * @author Dustin Dobervich * * @final */ class ChainCacheClearer implements CacheClearerInterface { private $clearers; /** * @param iterable $clearers */ public function __construct(iterable $clearers = []) { $this->clearers = $clearers; } /** * {@inheritdoc} */ public function clear(string $cacheDir) { foreach ($this->clearers as $clearer) { $clearer->clear($cacheDir); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheClearer; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; /** * @author Nicolas Grekas */ class Psr6CacheClearer implements CacheClearerInterface { private $pools = []; /** * @param array $pools */ public function __construct(array $pools = []) { $this->pools = $pools; } /** * @return bool */ public function hasPool(string $name) { return isset($this->pools[$name]); } /** * @return CacheItemPoolInterface * * @throws \InvalidArgumentException If the cache pool with the given name does not exist */ public function getPool(string $name) { if (!$this->hasPool($name)) { throw new \InvalidArgumentException(\sprintf('Cache pool not found: "%s".', $name)); } return $this->pools[$name]; } /** * @return bool * * @throws \InvalidArgumentException If the cache pool with the given name does not exist */ public function clearPool(string $name) { if (!isset($this->pools[$name])) { throw new \InvalidArgumentException(\sprintf('Cache pool not found: "%s".', $name)); } return $this->pools[$name]->clear(); } /** * {@inheritdoc} */ public function clear(string $cacheDir) { foreach ($this->pools as $pool) { $pool->clear(); } } } Welcome to Symfony!

    You're seeing this page because you haven't configured any homepage URL and debug mode is enabled.

    Welcome to Symfony

    Your application is now ready and you can start working on it.

    * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\TerminateEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ViewEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use _ContaoManager\Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use _ContaoManager\Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; // Help opcache.preload discover always-needed symbols \class_exists(ControllerArgumentsEvent::class); \class_exists(ControllerEvent::class); \class_exists(ExceptionEvent::class); \class_exists(FinishRequestEvent::class); \class_exists(RequestEvent::class); \class_exists(ResponseEvent::class); \class_exists(TerminateEvent::class); \class_exists(ViewEvent::class); \class_exists(KernelEvents::class); /** * HttpKernel notifies events to convert a Request object to a Response one. * * @author Fabien Potencier */ class HttpKernel implements HttpKernelInterface, TerminableInterface { protected $dispatcher; protected $resolver; protected $requestStack; private $argumentResolver; public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, ?RequestStack $requestStack = null, ?ArgumentResolverInterface $argumentResolver = null) { $this->dispatcher = $dispatcher; $this->resolver = $resolver; $this->requestStack = $requestStack ?? new RequestStack(); $this->argumentResolver = $argumentResolver ?? new ArgumentResolver(); } /** * {@inheritdoc} */ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = \true) { $request->headers->set('X-Php-Ob-Level', (string) \ob_get_level()); $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Exception $e) { if ($e instanceof RequestExceptionInterface) { $e = new BadRequestHttpException($e->getMessage(), $e); } if (\false === $catch) { $this->finishRequest($request, $type); throw $e; } return $this->handleThrowable($e, $request, $type); } finally { $this->requestStack->pop(); } } /** * {@inheritdoc} */ public function terminate(Request $request, Response $response) { $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE); } /** * @internal */ public function terminateWithException(\Throwable $exception, ?Request $request = null) { if (!($request = $request ?: $this->requestStack->getMainRequest())) { throw $exception; } if ($pop = $request !== $this->requestStack->getMainRequest()) { $this->requestStack->push($request); } try { $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST); } finally { if ($pop) { $this->requestStack->pop(); } } $response->sendHeaders(); $response->sendContent(); $this->terminate($request, $response); } /** * Handles a request to convert it to a response. * * Exceptions are not caught. * * @throws \LogicException If one of the listener does not behave as expected * @throws NotFoundHttpException When controller cannot be found */ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST) : Response { // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (\false === ($controller = $this->resolver->getController($request))) { throw new NotFoundHttpException(\sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); } $event = new ControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller); $event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); $arguments = $event->getArguments(); // call controller $response = $controller(...$arguments); // view if (!$response instanceof Response) { $event = new ViewEvent($this, $request, $type, $response); $this->dispatcher->dispatch($event, KernelEvents::VIEW); if ($event->hasResponse()) { $response = $event->getResponse(); } else { $msg = \sprintf('The controller must return a "Symfony\\Component\\HttpFoundation\\Response" object but it returned %s.', $this->varToString($response)); // the user may have forgotten to return something if (null === $response) { $msg .= ' Did you forget to add a return statement somewhere in your controller?'; } throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); } } return $this->filterResponse($response, $request, $type); } /** * Filters a response object. * * @throws \RuntimeException if the passed object is not a Response instance */ private function filterResponse(Response $response, Request $request, int $type) : Response { $event = new ResponseEvent($this, $request, $type, $response); $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); $this->finishRequest($request, $type); return $event->getResponse(); } /** * Publishes the finish request event, then pop the request from the stack. * * Note that the order of the operations is important here, otherwise * operations such as {@link RequestStack::getParentRequest()} can lead to * weird results. */ private function finishRequest(Request $request, int $type) { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); } /** * Handles a throwable by trying to convert it to a Response. * * @throws \Exception */ private function handleThrowable(\Throwable $e, Request $request, int $type) : Response { $event = new ExceptionEvent($this, $request, $type, $e); $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); // a listener might have replaced the exception $e = $event->getThrowable(); if (!$event->hasResponse()) { $this->finishRequest($request, $type); throw $e; } $response = $event->getResponse(); // the developer asked for a specific status code if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { // ensure that we actually have an error response if ($e instanceof HttpExceptionInterface) { // keep the HTTP status code and headers $response->setStatusCode($e->getStatusCode()); $response->headers->add($e->getHeaders()); } else { $response->setStatusCode(500); } } try { return $this->filterResponse($response, $request, $type); } catch (\Exception $e) { return $response; } } /** * Returns a human-readable string for the specified variable. */ private function varToString($var) : string { if (\is_object($var)) { return \sprintf('an object of type %s', \get_class($var)); } if (\is_array($var)) { $a = []; foreach ($var as $k => $v) { $a[] = \sprintf('%s => ...', $k); } return \sprintf('an array ([%s])', \mb_substr(\implode(', ', $a), 0, 255)); } if (\is_resource($var)) { return \sprintf('a resource (%s)', \get_resource_type($var)); } if (null === $var) { return 'null'; } if (\false === $var) { return 'a boolean value (false)'; } if (\true === $var) { return 'a boolean value (true)'; } if (\is_string($var)) { return \sprintf('a string ("%s%s")', \mb_substr($var, 0, 255), \mb_strlen($var) > 255 ? '...' : ''); } if (\is_numeric($var)) { return \sprintf('a number (%s)', (string) $var); } return (string) $var; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer; /** * Aggregates several cache warmers into a single one. * * @author Fabien Potencier * * @final */ class CacheWarmerAggregate implements CacheWarmerInterface { private $warmers; private $debug; private $deprecationLogsFilepath; private $optionalsEnabled = \false; private $onlyOptionalsEnabled = \false; /** * @param iterable $warmers */ public function __construct(iterable $warmers = [], bool $debug = \false, ?string $deprecationLogsFilepath = null) { $this->warmers = $warmers; $this->debug = $debug; $this->deprecationLogsFilepath = $deprecationLogsFilepath; } public function enableOptionalWarmers() { $this->optionalsEnabled = \true; } public function enableOnlyOptionalWarmers() { $this->onlyOptionalsEnabled = $this->optionalsEnabled = \true; } /** * {@inheritdoc} */ public function warmUp(string $cacheDir) : array { if ($collectDeprecations = $this->debug && !\defined('_ContaoManager\\PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; $previousHandler = \set_error_handler(function ($type, $message, $file, $line) use(&$collectedLogs, &$previousHandler) { if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) { return $previousHandler ? $previousHandler($type, $message, $file, $line) : \false; } if (isset($collectedLogs[$message])) { ++$collectedLogs[$message]['count']; return null; } $backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); // Clean the trace by removing first frames added by the error handler itself. for ($i = 0; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { $backtrace = \array_slice($backtrace, 1 + $i); break; } } $collectedLogs[$message] = ['type' => $type, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $backtrace, 'count' => 1]; return null; }); } $preload = []; try { foreach ($this->warmers as $warmer) { if (!$this->optionalsEnabled && $warmer->isOptional()) { continue; } if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) { continue; } $preload[] = \array_values((array) $warmer->warmUp($cacheDir)); } } finally { if ($collectDeprecations) { \restore_error_handler(); if (\is_file($this->deprecationLogsFilepath)) { $previousLogs = \unserialize(\file_get_contents($this->deprecationLogsFilepath)); if (\is_array($previousLogs)) { $collectedLogs = \array_merge($previousLogs, $collectedLogs); } } \file_put_contents($this->deprecationLogsFilepath, \serialize(\array_values($collectedLogs))); } } return \array_values(\array_unique(\array_merge([], ...$preload))); } /** * {@inheritdoc} */ public function isOptional() : bool { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer; /** * Abstract cache warmer that knows how to write a file to the cache. * * @author Fabien Potencier */ abstract class CacheWarmer implements CacheWarmerInterface { protected function writeCacheFile(string $file, $content) { $tmpFile = @\tempnam(\dirname($file), \basename($file)); if (\false !== @\file_put_contents($tmpFile, $content) && @\rename($tmpFile, $file)) { @\chmod($file, 0666 & ~\umask()); return; } throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $file)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer; /** * Interface for classes that support warming their cache. * * @author Fabien Potencier */ interface WarmableInterface { /** * Warms up the cache. * * @return string[] A list of classes or files to preload on PHP 7.4+ */ public function warmUp(string $cacheDir); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer; /** * Interface for classes able to warm up the cache. * * @author Fabien Potencier */ interface CacheWarmerInterface extends WarmableInterface { /** * Checks whether this warmer is optional or not. * * Optional warmers can be ignored on certain conditions. * * A warmer should return true if the cache can be * generated incrementally and on-demand. * * @return bool */ public function isOptional(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Bundle; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerAwareTrait; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; /** * An implementation of BundleInterface that adds a few conventions for DependencyInjection extensions. * * @author Fabien Potencier */ abstract class Bundle implements BundleInterface { use ContainerAwareTrait; protected $name; protected $extension; protected $path; private $namespace; /** * {@inheritdoc} */ public function boot() { } /** * {@inheritdoc} */ public function shutdown() { } /** * {@inheritdoc} * * This method can be overridden to register compilation passes, * other extensions, ... */ public function build(ContainerBuilder $container) { } /** * Returns the bundle's container extension. * * @return ExtensionInterface|null * * @throws \LogicException */ public function getContainerExtension() { if (null === $this->extension) { $extension = $this->createContainerExtension(); if (null !== $extension) { if (!$extension instanceof ExtensionInterface) { throw new \LogicException(\sprintf('Extension "%s" must implement Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface.', \get_debug_type($extension))); } // check naming convention $basename = \preg_replace('/Bundle$/', '', $this->getName()); $expectedAlias = Container::underscore($basename); if ($expectedAlias != $extension->getAlias()) { throw new \LogicException(\sprintf('Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', $expectedAlias, $extension->getAlias())); } $this->extension = $extension; } else { $this->extension = \false; } } return $this->extension ?: null; } /** * {@inheritdoc} */ public function getNamespace() { if (null === $this->namespace) { $this->parseClassName(); } return $this->namespace; } /** * {@inheritdoc} */ public function getPath() { if (null === $this->path) { $reflected = new \ReflectionObject($this); $this->path = \dirname($reflected->getFileName()); } return $this->path; } /** * Returns the bundle name (the class short name). */ public final function getName() : string { if (null === $this->name) { $this->parseClassName(); } return $this->name; } public function registerCommands(Application $application) { } /** * Returns the bundle's container extension class. * * @return string */ protected function getContainerExtensionClass() { $basename = \preg_replace('/Bundle$/', '', $this->getName()); return $this->getNamespace() . '\\DependencyInjection\\' . $basename . 'Extension'; } /** * Creates the bundle's container extension. * * @return ExtensionInterface|null */ protected function createContainerExtension() { return \class_exists($class = $this->getContainerExtensionClass()) ? new $class() : null; } private function parseClassName() { $pos = \strrpos(static::class, '\\'); $this->namespace = \false === $pos ? '' : \substr(static::class, 0, $pos); if (null === $this->name) { $this->name = \false === $pos ? static::class : \substr(static::class, $pos + 1); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Bundle; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerAwareInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; /** * BundleInterface. * * @author Fabien Potencier */ interface BundleInterface extends ContainerAwareInterface { /** * Boots the Bundle. */ public function boot(); /** * Shutdowns the Bundle. */ public function shutdown(); /** * Builds the bundle. * * It is only ever called once when the cache is empty. */ public function build(ContainerBuilder $container); /** * Returns the container extension that should be implicitly loaded. * * @return ExtensionInterface|null */ public function getContainerExtension(); /** * Returns the bundle name (the class short name). * * @return string */ public function getName(); /** * Gets the Bundle namespace. * * @return string */ public function getNamespace(); /** * Gets the Bundle directory path. * * The path should always be returned as a Unix path (with /). * * @return string */ public function getPath(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\BundleInterface; /** * The Kernel is the heart of the Symfony system. * * It manages an environment made of application kernel and bundles. * * @method string getBuildDir() Returns the build directory - not implementing it is deprecated since Symfony 5.2. * This directory should be used to store build artifacts, and can be read-only at runtime. * Caches written at runtime should be stored in the "cache directory" ({@see KernelInterface::getCacheDir()}). * * @author Fabien Potencier */ interface KernelInterface extends HttpKernelInterface { /** * Returns an array of bundles to register. * * @return iterable */ public function registerBundles(); /** * Loads the container configuration. */ public function registerContainerConfiguration(LoaderInterface $loader); /** * Boots the current kernel. */ public function boot(); /** * Shutdowns the kernel. * * This method is mainly useful when doing functional testing. */ public function shutdown(); /** * Gets the registered bundle instances. * * @return array */ public function getBundles(); /** * Returns a bundle. * * @return BundleInterface * * @throws \InvalidArgumentException when the bundle is not enabled */ public function getBundle(string $name); /** * Returns the file path for a given bundle resource. * * A Resource can be a file or a directory. * * The resource name must follow the following pattern: * * "@BundleName/path/to/a/file.something" * * where BundleName is the name of the bundle * and the remaining part is the relative path in the bundle. * * @return string * * @throws \InvalidArgumentException if the file cannot be found or the name is not valid * @throws \RuntimeException if the name contains invalid/unsafe characters */ public function locateResource(string $name); /** * Gets the environment. * * @return string */ public function getEnvironment(); /** * Checks if debug mode is enabled. * * @return bool */ public function isDebug(); /** * Gets the project dir (path of the project's composer file). * * @return string */ public function getProjectDir(); /** * Gets the current container. * * @return ContainerInterface */ public function getContainer(); /** * Gets the request start time (not available if debug is disabled). * * @return float */ public function getStartTime(); /** * Gets the cache directory. * * Since Symfony 5.2, the cache directory should be used for caches that are written at runtime. * For caches and artifacts that can be warmed at compile-time and deployed as read-only, * use the new "build directory" returned by the {@see getBuildDir()} method. * * @return string */ public function getCacheDir(); /** * Gets the log directory. * * @return string */ public function getLogDir(); /** * Gets the charset of the application. * * @return string */ public function getCharset(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; /** * Responsible for resolving the arguments passed to an action. * * @author Iltar van der Berg */ final class ArgumentResolver implements ArgumentResolverInterface { private $argumentMetadataFactory; private $argumentValueResolvers; /** * @param iterable $argumentValueResolvers */ public function __construct(?ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = []) { $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); } /** * {@inheritdoc} */ public function getArguments(Request $request, callable $controller) : array { $arguments = []; foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) { foreach ($this->argumentValueResolvers as $resolver) { if (!$resolver->supports($request, $metadata)) { continue; } $resolved = $resolver->resolve($request, $metadata); $atLeastOne = \false; foreach ($resolved as $append) { $atLeastOne = \true; $arguments[] = $append; } if (!$atLeastOne) { throw new \InvalidArgumentException(\sprintf('"%s::resolve()" must yield at least one value.', \get_debug_type($resolver))); } // continue to the next controller argument continue 2; } $representative = $controller; if (\is_array($representative)) { $representative = \sprintf('%s::%s()', \get_class($representative[0]), $representative[1]); } elseif (\is_object($representative)) { $representative = \get_class($representative); } throw new \RuntimeException(\sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); } return $arguments; } /** * @return iterable */ public static function getDefaultArgumentValueResolvers() : iterable { return [new RequestAttributeValueResolver(), new RequestValueResolver(), new SessionValueResolver(), new DefaultValueResolver(), new VariadicValueResolver()]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; /** * Acts as a marker and a data holder for a Controller. * * Some methods in Symfony accept both a URI (as a string) or a controller as * an argument. In the latter case, instead of passing an array representing * the controller, you can use an instance of this class. * * @author Fabien Potencier * * @see FragmentRendererInterface */ class ControllerReference { public $controller; public $attributes = []; public $query = []; /** * @param string $controller The controller name * @param array $attributes An array of parameters to add to the Request attributes * @param array $query An array of parameters to add to the Request query string */ public function __construct(string $controller, array $attributes = [], array $query = []) { $this->controller = $controller; $this->attributes = $attributes; $this->query = $query; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Renders error or exception pages from a given FlattenException. * * @author Yonel Ceruto * @author Matthias Pigulla */ class ErrorController { private $kernel; private $controller; private $errorRenderer; public function __construct(HttpKernelInterface $kernel, $controller, ErrorRendererInterface $errorRenderer) { $this->kernel = $kernel; $this->controller = $controller; $this->errorRenderer = $errorRenderer; } public function __invoke(\Throwable $exception) : Response { $exception = $this->errorRenderer->render($exception); return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders()); } public function preview(Request $request, int $code) : Response { /* * This Request mimics the parameters set by * \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with * the additional "showException" flag. */ $subRequest = $request->duplicate(null, null, ['_controller' => $this->controller, 'exception' => new HttpException($code, 'This is a sample exception.'), 'logger' => null, 'showException' => \false]); return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Container; /** * A controller resolver searching for a controller in a psr-11 container when using the "service::method" notation. * * @author Fabien Potencier * @author Maxime Steinhausser */ class ContainerControllerResolver extends ControllerResolver { protected $container; public function __construct(ContainerInterface $container, ?LoggerInterface $logger = null) { $this->container = $container; parent::__construct($logger); } protected function createController(string $controller) { if (1 === \substr_count($controller, ':')) { $controller = \str_replace(':', '::', $controller); \trigger_deprecation('symfony/http-kernel', '5.1', 'Referencing controllers with a single colon is deprecated. Use "%s" instead.', $controller); } return parent::createController($controller); } /** * {@inheritdoc} */ protected function instantiateController(string $class) { $class = \ltrim($class, '\\'); if ($this->container->has($class)) { return $this->container->get($class); } try { return parent::instantiateController($class); } catch (\Error $e) { } $this->throwExceptionIfControllerWasRemoved($class, $e); if ($e instanceof \ArgumentCountError) { throw new \InvalidArgumentException(\sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e); } throw new \InvalidArgumentException(\sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e); } private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous) { if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) { throw new \InvalidArgumentException(\sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Yields a variadic argument's values from the request attributes. * * @author Iltar van der Berg */ final class VariadicValueResolver implements ArgumentValueResolverInterface { /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { return $argument->isVariadic() && $request->attributes->has($argument->getName()); } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { $values = $request->attributes->get($argument->getName()); if (!\is_array($values)) { throw new \InvalidArgumentException(\sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), \get_debug_type($values))); } yield from $values; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Yields a service keyed by _controller and argument name. * * @author Nicolas Grekas */ final class ServiceValueResolver implements ArgumentValueResolverInterface { private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { $controller = $request->attributes->get('_controller'); if (\is_array($controller) && \is_callable($controller, \true) && \is_string($controller[0])) { $controller = $controller[0] . '::' . $controller[1]; } elseif (!\is_string($controller) || '' === $controller) { return \false; } if ('\\' === $controller[0]) { $controller = \ltrim($controller, '\\'); } if (!$this->container->has($controller) && \false !== ($i = \strrpos($controller, ':'))) { $controller = \substr($controller, 0, $i) . \strtolower(\substr($controller, $i)); } return $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { if (\is_array($controller = $request->attributes->get('_controller'))) { $controller = $controller[0] . '::' . $controller[1]; } if ('\\' === $controller[0]) { $controller = \ltrim($controller, '\\'); } if (!$this->container->has($controller)) { $i = \strrpos($controller, ':'); $controller = \substr($controller, 0, $i) . \strtolower(\substr($controller, $i)); } try { (yield $this->container->get($controller)->get($argument->getName())); } catch (RuntimeException $e) { $what = \sprintf('argument $%s of "%s()"', $argument->getName(), $controller); $message = \preg_replace('/service "\\.service_locator\\.[^"]++"/', $what, $e->getMessage()); if ($e->getMessage() === $message) { $message = \sprintf('Cannot resolve %s: %s', $what, $message); } $r = new \ReflectionProperty($e, 'message'); $r->setAccessible(\true); $r->setValue($e, $message); throw $e; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Yields the default value defined in the action signature when no value has been given. * * @author Iltar van der Berg */ final class DefaultValueResolver implements ArgumentValueResolverInterface { /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { return $argument->hasDefaultValue() || null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic(); } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { (yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Yields the same instance as the request object passed along. * * @author Iltar van der Berg */ final class RequestValueResolver implements ArgumentValueResolverInterface { /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { return Request::class === $argument->getType() || \is_subclass_of($argument->getType(), Request::class); } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { (yield $request); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Yields the Session. * * @author Iltar van der Berg */ final class SessionValueResolver implements ArgumentValueResolverInterface { /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { if (!$request->hasSession()) { return \false; } $type = $argument->getType(); if (SessionInterface::class !== $type && !\is_subclass_of($type, SessionInterface::class)) { return \false; } return $request->getSession() instanceof $type; } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { (yield $request->getSession()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Yields a non-variadic argument's value from the request attributes. * * @author Iltar van der Berg */ final class RequestAttributeValueResolver implements ArgumentValueResolverInterface { /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { return !$argument->isVariadic() && $request->attributes->has($argument->getName()); } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { (yield $request->attributes->get($argument->getName())); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Provides an intuitive error message when controller fails because it is not registered as a service. * * @author Simeon Kolev */ final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface { private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { $controller = $request->attributes->get('_controller'); if (\is_array($controller) && \is_callable($controller, \true) && \is_string($controller[0])) { $controller = $controller[0] . '::' . $controller[1]; } elseif (!\is_string($controller) || '' === $controller) { return \false; } if ('\\' === $controller[0]) { $controller = \ltrim($controller, '\\'); } if (!$this->container->has($controller) && \false !== ($i = \strrpos($controller, ':'))) { $controller = \substr($controller, 0, $i) . \strtolower(\substr($controller, $i)); } return \false === $this->container->has($controller); } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { if (\is_array($controller = $request->attributes->get('_controller'))) { $controller = $controller[0] . '::' . $controller[1]; } if ('\\' === $controller[0]) { $controller = \ltrim($controller, '\\'); } if (!$this->container->has($controller)) { $controller = \false !== ($i = \strrpos($controller, ':')) ? \substr($controller, 0, $i) . \strtolower(\substr($controller, $i)) : $controller . '::__invoke'; } $what = \sprintf('argument $%s of "%s()"', $argument->getName(), $controller); $message = \sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what); throw new RuntimeException($message); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; /** * Provides timing information via the stopwatch. * * @author Iltar van der Berg */ final class TraceableValueResolver implements ArgumentValueResolverInterface { private $inner; private $stopwatch; public function __construct(ArgumentValueResolverInterface $inner, Stopwatch $stopwatch) { $this->inner = $inner; $this->stopwatch = $stopwatch; } /** * {@inheritdoc} */ public function supports(Request $request, ArgumentMetadata $argument) : bool { $method = \get_class($this->inner) . '::' . __FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); $return = $this->inner->supports($request, $argument); $this->stopwatch->stop($method); return $return; } /** * {@inheritdoc} */ public function resolve(Request $request, ArgumentMetadata $argument) : iterable { $method = \get_class($this->inner) . '::' . __FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); yield from $this->inner->resolve($request, $argument); $this->stopwatch->stop($method); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * An ArgumentResolverInterface instance knows how to determine the * arguments for a specific action. * * @author Fabien Potencier */ interface ArgumentResolverInterface { /** * Returns the arguments to pass to the controller. * * @return array * * @throws \RuntimeException When no value could be provided for a required argument */ public function getArguments(Request $request, callable $controller); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * A ControllerResolverInterface implementation knows how to determine the * controller to execute based on a Request object. * * A Controller can be any valid PHP callable. * * @author Fabien Potencier */ interface ControllerResolverInterface { /** * Returns the Controller instance associated with a Request. * * As several resolvers can exist for a single application, a resolver must * return false when it is not able to determine the controller. * * The resolver must only throw an exception when it should be able to load a * controller but cannot because of some errors made by the developer. * * @return callable|false A PHP callable representing the Controller, * or false if this resolver is not able to determine the controller * * @throws \LogicException If a controller was found based on the request but it is not callable */ public function getController(Request $request); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; /** * @author Fabien Potencier */ class TraceableArgumentResolver implements ArgumentResolverInterface { private $resolver; private $stopwatch; public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) { $this->resolver = $resolver; $this->stopwatch = $stopwatch; } /** * {@inheritdoc} */ public function getArguments(Request $request, callable $controller) { $e = $this->stopwatch->start('controller.get_arguments'); try { return $this->resolver->getArguments($request, $controller); } finally { $e->stop(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * This implementation uses the '_controller' request attribute to determine * the controller to execute. * * @author Fabien Potencier * @author Tobias Schultze */ class ControllerResolver implements ControllerResolverInterface { private $logger; public function __construct(?LoggerInterface $logger = null) { $this->logger = $logger; } /** * {@inheritdoc} */ public function getController(Request $request) { if (!($controller = $request->attributes->get('_controller'))) { if (null !== $this->logger) { $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.'); } return \false; } if (\is_array($controller)) { if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) { try { $controller[0] = $this->instantiateController($controller[0]); } catch (\Error|\LogicException $e) { try { // We cannot just check is_callable but have to use reflection because a non-static method // can still be called statically in PHP but we don't want that. This is deprecated in PHP 7, so we // could simplify this with PHP 8. if ((new \ReflectionMethod($controller[0], $controller[1]))->isStatic()) { return $controller; } } catch (\ReflectionException $reflectionException) { throw $e; } throw $e; } } if (!\is_callable($controller)) { throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $this->getControllerError($controller)); } return $controller; } if (\is_object($controller)) { if (!\is_callable($controller)) { throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $this->getControllerError($controller)); } return $controller; } if (\function_exists($controller)) { return $controller; } try { $callable = $this->createController($controller); } catch (\InvalidArgumentException $e) { throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $e->getMessage(), 0, $e); } if (!\is_callable($callable)) { throw new \InvalidArgumentException(\sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()) . $this->getControllerError($callable)); } return $callable; } /** * Returns a callable for the given controller. * * @return callable * * @throws \InvalidArgumentException When the controller cannot be created */ protected function createController(string $controller) { if (!\str_contains($controller, '::')) { $controller = $this->instantiateController($controller); if (!\is_callable($controller)) { throw new \InvalidArgumentException($this->getControllerError($controller)); } return $controller; } [$class, $method] = \explode('::', $controller, 2); try { $controller = [$this->instantiateController($class), $method]; } catch (\Error|\LogicException $e) { try { if ((new \ReflectionMethod($class, $method))->isStatic()) { return $class . '::' . $method; } } catch (\ReflectionException $reflectionException) { throw $e; } throw $e; } if (!\is_callable($controller)) { throw new \InvalidArgumentException($this->getControllerError($controller)); } return $controller; } /** * Returns an instantiated controller. * * @return object */ protected function instantiateController(string $class) { return new $class(); } private function getControllerError($callable) : string { if (\is_string($callable)) { if (\str_contains($callable, '::')) { $callable = \explode('::', $callable, 2); } else { return \sprintf('Function "%s" does not exist.', $callable); } } if (\is_object($callable)) { $availableMethods = $this->getClassMethodsWithoutMagicMethods($callable); $alternativeMsg = $availableMethods ? \sprintf(' or use one of the available methods: "%s"', \implode('", "', $availableMethods)) : ''; return \sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.', \get_debug_type($callable), $alternativeMsg); } if (!\is_array($callable)) { return \sprintf('Invalid type for controller given, expected string, array or object, got "%s".', \get_debug_type($callable)); } if (!isset($callable[0]) || !isset($callable[1]) || 2 !== \count($callable)) { return 'Invalid array callable, expected [controller, method].'; } [$controller, $method] = $callable; if (\is_string($controller) && !\class_exists($controller)) { return \sprintf('Class "%s" does not exist.', $controller); } $className = \is_object($controller) ? \get_debug_type($controller) : $controller; if (\method_exists($controller, $method)) { return \sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className); } $collection = $this->getClassMethodsWithoutMagicMethods($controller); $alternatives = []; foreach ($collection as $item) { $lev = \levenshtein($method, $item); if ($lev <= \strlen($method) / 3 || \str_contains($item, $method)) { $alternatives[] = $item; } } \asort($alternatives); $message = \sprintf('Expected method "%s" on class "%s"', $method, $className); if (\count($alternatives) > 0) { $message .= \sprintf(', did you mean "%s"?', \implode('", "', $alternatives)); } else { $message .= \sprintf('. Available methods: "%s".', \implode('", "', $collection)); } return $message; } private function getClassMethodsWithoutMagicMethods($classOrObject) : array { $methods = \get_class_methods($classOrObject); return \array_filter($methods, function (string $method) { return 0 !== \strncmp($method, '__', 2); }); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; /** * @author Fabien Potencier */ class TraceableControllerResolver implements ControllerResolverInterface { private $resolver; private $stopwatch; public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) { $this->resolver = $resolver; $this->stopwatch = $stopwatch; } /** * {@inheritdoc} */ public function getController(Request $request) { $e = $this->stopwatch->start('controller.get_callable'); try { return $this->resolver->getController($request); } finally { $e->stop(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Controller; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * Responsible for resolving the value of an argument based on its metadata. * * @author Iltar van der Berg */ interface ArgumentValueResolverInterface { /** * Whether this resolver can resolve the value for the given ArgumentMetadata. * * @return bool */ public function supports(Request $request, ArgumentMetadata $argument); /** * Returns the possible value(s). * * @return iterable */ public function resolve(Request $request, ArgumentMetadata $argument); } HttpKernel Component ==================== The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. It's flexible enough to create full-stack frameworks, micro-frameworks or advanced CMS systems like Drupal. Sponsor ------- The HttpKernel component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and fix your projects. We provide a wide range of professional services including development, consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. We are a worker cooperative! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/http_kernel.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://les-tilleuls.coop [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; /** * Interface implemented by rendering strategies able to generate an URL for a fragment. * * @author Kévin Dunglas */ interface FragmentUriGeneratorInterface { /** * Generates a fragment URI for a given controller. * * @param bool $absolute Whether to generate an absolute URL or not * @param bool $strict Whether to allow non-scalar attributes or not * @param bool $sign Whether to sign the URL or not */ public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = \false, bool $strict = \true, bool $sign = \true) : string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\SubRequestHandler; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. * * @author Fabien Potencier */ class InlineFragmentRenderer extends RoutableFragmentRenderer { private $kernel; private $dispatcher; public function __construct(HttpKernelInterface $kernel, ?EventDispatcherInterface $dispatcher = null) { $this->kernel = $kernel; $this->dispatcher = $dispatcher; } /** * {@inheritdoc} * * Additional available options: * * * alt: an alternative URI to render in case of an error */ public function render($uri, Request $request, array $options = []) { $reference = null; if ($uri instanceof ControllerReference) { $reference = $uri; // Remove attributes from the generated URI because if not, the Symfony // routing system will use them to populate the Request attributes. We don't // want that as we want to preserve objects (so we manually set Request attributes // below instead) $attributes = $reference->attributes; $reference->attributes = []; // The request format and locale might have been overridden by the user foreach (['_format', '_locale'] as $key) { if (isset($attributes[$key])) { $reference->attributes[$key] = $attributes[$key]; } } $uri = $this->generateFragmentUri($uri, $request, \false, \false); $reference->attributes = \array_merge($attributes, $reference->attributes); } $subRequest = $this->createSubRequest($uri, $request); // override Request attributes as they can be objects (which are not supported by the generated URI) if (null !== $reference) { $subRequest->attributes->add($reference->attributes); } $level = \ob_get_level(); try { return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, \false); } catch (\Exception $e) { // we dispatch the exception event to trigger the logging // the response that comes back is ignored if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { $event = new ExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); } // let's clean up the output buffers that were created by the sub-request Response::closeOutputBuffers($level, \false); if (isset($options['alt'])) { $alt = $options['alt']; unset($options['alt']); return $this->render($alt, $request, $options); } if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { throw $e; } return new Response(); } } protected function createSubRequest(string $uri, Request $request) { $cookies = $request->cookies->all(); $server = $request->server->all(); unset($server['HTTP_IF_MODIFIED_SINCE']); unset($server['HTTP_IF_NONE_MATCH']); $subRequest = Request::create($uri, 'get', [], $cookies, [], $server); if ($request->headers->has('Surrogate-Capability')) { $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); } static $setSession; if (null === $setSession) { $setSession = \Closure::bind(static function ($subRequest, $request) { $subRequest->session = $request->session; }, null, Request::class); } $setSession($subRequest, $request); if ($request->get('_format')) { $subRequest->attributes->set('_format', $request->get('_format')); } if ($request->getDefaultLocale() !== $request->getLocale()) { $subRequest->setLocale($request->getLocale()); } return $subRequest; } /** * {@inheritdoc} */ public function getName() { return 'inline'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; /** * Implements the SSI rendering strategy. * * @author Sebastian Krebs */ class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer { /** * {@inheritdoc} */ public function getName() { return 'ssi'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; use _ContaoManager\Symfony\Component\HttpKernel\UriSigner; /** * Implements Surrogate rendering strategy. * * @author Fabien Potencier */ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer { private $surrogate; private $inlineStrategy; private $signer; /** * The "fallback" strategy when surrogate is not available should always be an * instance of InlineFragmentRenderer. * * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported */ public function __construct(?SurrogateInterface $surrogate, FragmentRendererInterface $inlineStrategy, ?UriSigner $signer = null) { $this->surrogate = $surrogate; $this->inlineStrategy = $inlineStrategy; $this->signer = $signer; } /** * {@inheritdoc} * * Note that if the current Request has no surrogate capability, this method * falls back to use the inline rendering strategy. * * Additional available options: * * * alt: an alternative URI to render in case of an error * * comment: a comment to add when returning the surrogate tag * * Note, that not all surrogate strategies support all options. For now * 'alt' and 'comment' are only supported by ESI. * * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface */ public function render($uri, Request $request, array $options = []) { if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { throw new \InvalidArgumentException('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is not supported. Use a different rendering strategy or pass scalar values.'); } return $this->inlineStrategy->render($uri, $request, $options); } if ($uri instanceof ControllerReference) { $uri = $this->generateSignedFragmentUri($uri, $request); } $alt = $options['alt'] ?? null; if ($alt instanceof ControllerReference) { $alt = $this->generateSignedFragmentUri($alt, $request); } $tag = $this->surrogate->renderIncludeTag($uri, $alt, $options['ignore_errors'] ?? \false, $options['comment'] ?? ''); return new Response($tag); } private function generateSignedFragmentUri(ControllerReference $uri, Request $request) : string { return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); } private function containsNonScalars(array $values) : bool { foreach ($values as $value) { if (\is_scalar($value) || null === $value) { continue; } if (!\is_array($value) || $this->containsNonScalars($value)) { return \true; } } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; /** * Interface implemented by all rendering strategies. * * @author Fabien Potencier */ interface FragmentRendererInterface { /** * Renders a URI and returns the Response content. * * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * * @return Response */ public function render($uri, Request $request, array $options = []); /** * Gets the name of the strategy. * * @return string */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; use _ContaoManager\Symfony\Component\HttpKernel\UriSigner; /** * Generates a fragment URI. * * @author Kévin Dunglas * @author Fabien Potencier */ final class FragmentUriGenerator implements FragmentUriGeneratorInterface { private $fragmentPath; private $signer; private $requestStack; public function __construct(string $fragmentPath, ?UriSigner $signer = null, ?RequestStack $requestStack = null) { $this->fragmentPath = $fragmentPath; $this->signer = $signer; $this->requestStack = $requestStack; } /** * {@inheritDoc} */ public function generate(ControllerReference $controller, ?Request $request = null, bool $absolute = \false, bool $strict = \true, bool $sign = \true) : string { if (null === $request && (null === $this->requestStack || null === ($request = $this->requestStack->getCurrentRequest()))) { throw new \LogicException('Generating a fragment URL can only be done when handling a Request.'); } if ($sign && null === $this->signer) { throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); } if ($strict) { $this->checkNonScalar($controller->attributes); } // We need to forward the current _format and _locale values as we don't have // a proper routing pattern to do the job for us. // This makes things inconsistent if you switch from rendering a controller // to rendering a route if the route pattern does not contain the special // _format and _locale placeholders. if (!isset($controller->attributes['_format'])) { $controller->attributes['_format'] = $request->getRequestFormat(); } if (!isset($controller->attributes['_locale'])) { $controller->attributes['_locale'] = $request->getLocale(); } $controller->attributes['_controller'] = $controller->controller; $controller->query['_path'] = \http_build_query($controller->attributes, '', '&'); $path = $this->fragmentPath . '?' . \http_build_query($controller->query, '', '&'); // we need to sign the absolute URI, but want to return the path only. $fragmentUri = $sign || $absolute ? $request->getUriForPath($path) : $request->getBaseUrl() . $path; if (!$sign) { return $fragmentUri; } $fragmentUri = $this->signer->sign($fragmentUri); return $absolute ? $fragmentUri : \substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); } private function checkNonScalar(array $values) : void { foreach ($values as $key => $value) { if (\is_array($value)) { $this->checkNonScalar($value); } elseif (!\is_scalar($value) && null !== $value) { throw new \LogicException(\sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; /** * Implements the ESI rendering strategy. * * @author Fabien Potencier */ class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer { /** * {@inheritdoc} */ public function getName() { return 'esi'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; use _ContaoManager\Symfony\Component\HttpKernel\UriSigner; use _ContaoManager\Twig\Environment; /** * Implements the Hinclude rendering strategy. * * @author Fabien Potencier */ class HIncludeFragmentRenderer extends RoutableFragmentRenderer { private $globalDefaultTemplate; private $signer; private $twig; private $charset; /** * @param string|null $globalDefaultTemplate The global default content (it can be a template name or the content) */ public function __construct(?Environment $twig = null, ?UriSigner $signer = null, ?string $globalDefaultTemplate = null, string $charset = 'utf-8') { $this->twig = $twig; $this->globalDefaultTemplate = $globalDefaultTemplate; $this->signer = $signer; $this->charset = $charset; } /** * Checks if a templating engine has been set. * * @return bool */ public function hasTemplating() { return null !== $this->twig; } /** * {@inheritdoc} * * Additional available options: * * * default: The default content (it can be a template name or the content) * * id: An optional hx:include tag id attribute * * attributes: An optional array of hx:include tag attributes */ public function render($uri, Request $request, array $options = []) { if ($uri instanceof ControllerReference) { $uri = (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); } // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. $uri = \str_replace('&', '&', $uri); $template = $options['default'] ?? $this->globalDefaultTemplate; if (null !== $this->twig && $template && $this->twig->getLoader()->exists($template)) { $content = $this->twig->render($template); } else { $content = $template; } $attributes = isset($options['attributes']) && \is_array($options['attributes']) ? $options['attributes'] : []; if (isset($options['id']) && $options['id']) { $attributes['id'] = $options['id']; } $renderedAttributes = ''; if (\count($attributes) > 0) { $flags = \ENT_QUOTES | \ENT_SUBSTITUTE; foreach ($attributes as $attribute => $value) { $renderedAttributes .= \sprintf(' %s="%s"', \htmlspecialchars($attribute, $flags, $this->charset, \false), \htmlspecialchars($value, $flags, $this->charset, \false)); } } return new Response(\sprintf('%s', $uri, $renderedAttributes, $content)); } /** * {@inheritdoc} */ public function getName() { return 'hinclude'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpFoundation\StreamedResponse; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; /** * Renders a URI that represents a resource fragment. * * This class handles the rendering of resource fragments that are included into * a main resource. The handling of the rendering is managed by specialized renderers. * * @author Fabien Potencier * * @see FragmentRendererInterface */ class FragmentHandler { private $debug; private $renderers = []; private $requestStack; /** * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances * @param bool $debug Whether the debug mode is enabled or not */ public function __construct(RequestStack $requestStack, array $renderers = [], bool $debug = \false) { $this->requestStack = $requestStack; foreach ($renderers as $renderer) { $this->addRenderer($renderer); } $this->debug = $debug; } /** * Adds a renderer. */ public function addRenderer(FragmentRendererInterface $renderer) { $this->renderers[$renderer->getName()] = $renderer; } /** * Renders a URI and returns the Response content. * * Available options: * * * ignore_errors: true to return an empty string in case of an error * * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * * @return string|null * * @throws \InvalidArgumentException when the renderer does not exist * @throws \LogicException when no main request is being handled */ public function render($uri, string $renderer = 'inline', array $options = []) { if (!isset($options['ignore_errors'])) { $options['ignore_errors'] = !$this->debug; } if (!isset($this->renderers[$renderer])) { throw new \InvalidArgumentException(\sprintf('The "%s" renderer does not exist.', $renderer)); } if (!($request = $this->requestStack->getCurrentRequest())) { throw new \LogicException('Rendering a fragment can only be done when handling a Request.'); } return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options)); } /** * Delivers the Response as a string. * * When the Response is a StreamedResponse, the content is streamed immediately * instead of being returned. * * @return string|null The Response content or null when the Response is streamed * * @throws \RuntimeException when the Response is not successful */ protected function deliver(Response $response) { if (!$response->isSuccessful()) { $responseStatusCode = $response->getStatusCode(); throw new \RuntimeException(\sprintf('Error when rendering "%s" (Status code is %d).', $this->requestStack->getCurrentRequest()->getUri(), $responseStatusCode), 0, new HttpException($responseStatusCode)); } if (!$response instanceof StreamedResponse) { return $response->getContent(); } $response->sendContent(); return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Fragment; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ControllerReference; use _ContaoManager\Symfony\Component\HttpKernel\EventListener\FragmentListener; /** * Adds the possibility to generate a fragment URI for a given Controller. * * @author Fabien Potencier */ abstract class RoutableFragmentRenderer implements FragmentRendererInterface { /** * @internal */ protected $fragmentPath = '/_fragment'; /** * Sets the fragment path that triggers the fragment listener. * * @see FragmentListener */ public function setFragmentPath(string $path) { $this->fragmentPath = $path; } /** * Generates a fragment URI for a given controller. * * @param bool $absolute Whether to generate an absolute URL or not * @param bool $strict Whether to allow non-scalar attributes or not * * @return string */ protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = \false, bool $strict = \true) { return (new FragmentUriGenerator($this->fragmentPath))->generate($reference, $request, $absolute, $strict, \false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\TerminateEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ViewEvent; /** * Contains all events thrown in the HttpKernel component. * * @author Bernhard Schussek */ final class KernelEvents { /** * The REQUEST event occurs at the very beginning of request * dispatching. * * This event allows you to create a response for a request before any * other code in the framework is executed. * * @Event("Symfony\Component\HttpKernel\Event\RequestEvent") */ public const REQUEST = 'kernel.request'; /** * The EXCEPTION event occurs when an uncaught exception appears. * * This event allows you to create a response for a thrown exception or * to modify the thrown exception. * * @Event("Symfony\Component\HttpKernel\Event\ExceptionEvent") */ public const EXCEPTION = 'kernel.exception'; /** * The CONTROLLER event occurs once a controller was found for * handling a request. * * This event allows you to change the controller that will handle the * request. * * @Event("Symfony\Component\HttpKernel\Event\ControllerEvent") */ public const CONTROLLER = 'kernel.controller'; /** * The CONTROLLER_ARGUMENTS event occurs once controller arguments have been resolved. * * This event allows you to change the arguments that will be passed to * the controller. * * @Event("Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent") */ public const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; /** * The VIEW event occurs when the return value of a controller * is not a Response instance. * * This event allows you to create a response for the return value of the * controller. * * @Event("Symfony\Component\HttpKernel\Event\ViewEvent") */ public const VIEW = 'kernel.view'; /** * The RESPONSE event occurs once a response was created for * replying to a request. * * This event allows you to modify or replace the response that will be * replied. * * @Event("Symfony\Component\HttpKernel\Event\ResponseEvent") */ public const RESPONSE = 'kernel.response'; /** * The FINISH_REQUEST event occurs when a response was generated for a request. * * This event allows you to reset the global and environmental state of * the application, when it was changed during the request. * * @Event("Symfony\Component\HttpKernel\Event\FinishRequestEvent") */ public const FINISH_REQUEST = 'kernel.finish_request'; /** * The TERMINATE event occurs once a response was sent. * * This event allows you to run expensive post-response jobs. * * @Event("Symfony\Component\HttpKernel\Event\TerminateEvent") */ public const TERMINATE = 'kernel.terminate'; /** * Event aliases. * * These aliases can be consumed by RegisterListenersPass. */ public const ALIASES = [ControllerArgumentsEvent::class => self::CONTROLLER_ARGUMENTS, ControllerEvent::class => self::CONTROLLER, ResponseEvent::class => self::RESPONSE, FinishRequestEvent::class => self::FINISH_REQUEST, RequestEvent::class => self::REQUEST, ViewEvent::class => self::VIEW, ExceptionEvent::class => self::EXCEPTION, TerminateEvent::class => self::TERMINATE]; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Esi implements the ESI capabilities to Request and Response instances. * * For more information, read the following W3C notes: * * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang) * * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch) * * @author Fabien Potencier */ class Esi extends AbstractSurrogate { public function getName() { return 'esi'; } /** * {@inheritdoc} */ public function addSurrogateControl(Response $response) { if (\str_contains($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); } } /** * {@inheritdoc} */ public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = \true, string $comment = '') { $html = \sprintf('', $uri, $ignoreErrors ? ' onerror="continue"' : '', $alt ? \sprintf(' alt="%s"', $alt) : ''); if (!empty($comment)) { return \sprintf("\n%s", $comment, $html); } return $html; } /** * {@inheritdoc} */ public function process(Request $request, Response $response) { $type = $response->headers->get('Content-Type'); if (empty($type)) { $type = 'text/html'; } $parts = \explode(';', $type); if (!\in_array($parts[0], $this->contentTypes)) { return $response; } // we don't use a proper XML parser here as we can have ESI tags in a plain text response $content = $response->getContent(); $content = \preg_replace('#.*?#s', '', $content); $content = \preg_replace('#]+>#s', '', $content); $boundary = self::generateBodyEvalBoundary(); $chunks = \preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); $i = 1; while (isset($chunks[$i])) { $options = []; \preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER); foreach ($matches as $set) { $options[$set[1]] = $set[2]; } if (!isset($options['src'])) { throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); } $chunks[$i] = $boundary . $options['src'] . "\n" . ($options['alt'] ?? '') . "\n" . ('continue' === ($options['onerror'] ?? '')) . "\n"; $i += 2; } $content = $boundary . \implode('', $chunks) . $boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'ESI'); // remove ESI/1.0 from the Surrogate-Control header $this->removeFromControl($response); return $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /* * This code is partially based on the Rack-Cache library by Ryan Tomayko, * which is released under the MIT license. * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\TerminableInterface; /** * Cache provides HTTP caching. * * @author Fabien Potencier */ class HttpCache implements HttpKernelInterface, TerminableInterface { public const BODY_EVAL_BOUNDARY_LENGTH = 24; private $kernel; private $store; private $request; private $surrogate; private $surrogateCacheStrategy; private $options = []; private $traces = []; /** * Constructor. * * The available options are: * * * debug If true, exceptions are thrown when things go wrong. Otherwise, the cache * will try to carry on and deliver a meaningful response. * * * trace_level May be one of 'none', 'short' and 'full'. For 'short', a concise trace of the * main request will be added as an HTTP header. 'full' will add traces for all * requests (including ESI subrequests). (default: 'full' if in debug; 'none' otherwise) * * * trace_header Header name to use for traces. (default: X-Symfony-Cache) * * * default_ttl The number of seconds that a cache entry should be considered * fresh when no explicit freshness information is provided in * a response. Explicit Cache-Control or Expires headers * override this value. (default: 0) * * * private_headers Set of request headers that trigger "private" cache-control behavior * on responses that don't explicitly state whether the response is * public or private via a Cache-Control directive. (default: Authorization and Cookie) * * * allow_reload Specifies whether the client can force a cache reload by including a * Cache-Control "no-cache" directive in the request. Set it to ``true`` * for compliance with RFC 2616. (default: false) * * * allow_revalidate Specifies whether the client can force a cache revalidate by including * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` * for compliance with RFC 2616. (default: false) * * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the * Response TTL precision is a second) during which the cache can immediately return * a stale response while it revalidates it in the background (default: 2). * This setting is overridden by the stale-while-revalidate HTTP Cache-Control * extension (see RFC 5861). * * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which * the cache can serve a stale response when an error is encountered (default: 60). * This setting is overridden by the stale-if-error HTTP Cache-Control extension * (see RFC 5861). */ public function __construct(HttpKernelInterface $kernel, StoreInterface $store, ?SurrogateInterface $surrogate = null, array $options = []) { $this->store = $store; $this->kernel = $kernel; $this->surrogate = $surrogate; // needed in case there is a fatal error because the backend is too slow to respond \register_shutdown_function([$this->store, 'cleanup']); $this->options = \array_merge(['debug' => \false, 'default_ttl' => 0, 'private_headers' => ['Authorization', 'Cookie'], 'allow_reload' => \false, 'allow_revalidate' => \false, 'stale_while_revalidate' => 2, 'stale_if_error' => 60, 'trace_level' => 'none', 'trace_header' => 'X-Symfony-Cache'], $options); if (!isset($options['trace_level'])) { $this->options['trace_level'] = $this->options['debug'] ? 'full' : 'none'; } } /** * Gets the current store. * * @return StoreInterface */ public function getStore() { return $this->store; } /** * Returns an array of events that took place during processing of the last request. * * @return array */ public function getTraces() { return $this->traces; } private function addTraces(Response $response) { $traceString = null; if ('full' === $this->options['trace_level']) { $traceString = $this->getLog(); } if ('short' === $this->options['trace_level'] && ($masterId = \array_key_first($this->traces))) { $traceString = \implode('/', $this->traces[$masterId]); } if (null !== $traceString) { $response->headers->add([$this->options['trace_header'] => $traceString]); } } /** * Returns a log message for the events of the last request processing. * * @return string */ public function getLog() { $log = []; foreach ($this->traces as $request => $traces) { $log[] = \sprintf('%s: %s', $request, \implode(', ', $traces)); } return \implode('; ', $log); } /** * Gets the Request instance associated with the main request. * * @return Request */ public function getRequest() { return $this->request; } /** * Gets the Kernel instance. * * @return HttpKernelInterface */ public function getKernel() { return $this->kernel; } /** * Gets the Surrogate instance. * * @return SurrogateInterface * * @throws \LogicException */ public function getSurrogate() { return $this->surrogate; } /** * {@inheritdoc} */ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = \true) { // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism if (HttpKernelInterface::MAIN_REQUEST === $type) { $this->traces = []; // Keep a clone of the original request for surrogates so they can access it. // We must clone here to get a separate instance because the application will modify the request during // the application flow (we know it always does because we do ourselves by setting REMOTE_ADDR to 127.0.0.1 // and adding the X-Forwarded-For header, see HttpCache::forward()). $this->request = clone $request; if (null !== $this->surrogate) { $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); } } $this->traces[$this->getTraceKey($request)] = []; if (!$request->isMethodSafe()) { $response = $this->invalidate($request, $catch); } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { $response = $this->pass($request, $catch); } elseif ($this->options['allow_reload'] && $request->isNoCache()) { /* If allow_reload is configured and the client requests "Cache-Control: no-cache", reload the cache by fetching a fresh response and caching it (if possible). */ $this->record($request, 'reload'); $response = $this->fetch($request, $catch); } else { $response = $this->lookup($request, $catch); } $this->restoreResponseBody($request, $response); if (HttpKernelInterface::MAIN_REQUEST === $type) { $this->addTraces($response); } if (null !== $this->surrogate) { if (HttpKernelInterface::MAIN_REQUEST === $type) { $this->surrogateCacheStrategy->update($response); } else { $this->surrogateCacheStrategy->add($response); } } $response->prepare($request); $response->isNotModified($request); return $response; } /** * {@inheritdoc} */ public function terminate(Request $request, Response $response) { if ($this->getKernel() instanceof TerminableInterface) { $this->getKernel()->terminate($request, $response); } } /** * Forwards the Request to the backend without storing the Response in the cache. * * @param bool $catch Whether to process exceptions * * @return Response */ protected function pass(Request $request, bool $catch = \false) { $this->record($request, 'pass'); return $this->forward($request, $catch); } /** * Invalidates non-safe methods (like POST, PUT, and DELETE). * * @param bool $catch Whether to process exceptions * * @return Response * * @throws \Exception * * @see RFC2616 13.10 */ protected function invalidate(Request $request, bool $catch = \false) { $response = $this->pass($request, $catch); // invalidate only when the response is successful if ($response->isSuccessful() || $response->isRedirect()) { try { $this->store->invalidate($request); // As per the RFC, invalidate Location and Content-Location URLs if present foreach (['Location', 'Content-Location'] as $header) { if ($uri = $response->headers->get($header)) { $subRequest = Request::create($uri, 'get', [], [], [], $request->server->all()); $this->store->invalidate($subRequest); } } $this->record($request, 'invalidate'); } catch (\Exception $e) { $this->record($request, 'invalidate-failed'); if ($this->options['debug']) { throw $e; } } } return $response; } /** * Lookups a Response from the cache for the given Request. * * When a matching cache entry is found and is fresh, it uses it as the * response without forwarding any request to the backend. When a matching * cache entry is found but is stale, it attempts to "validate" the entry with * the backend using conditional GET. When no matching cache entry is found, * it triggers "miss" processing. * * @param bool $catch Whether to process exceptions * * @return Response * * @throws \Exception */ protected function lookup(Request $request, bool $catch = \false) { try { $entry = $this->store->lookup($request); } catch (\Exception $e) { $this->record($request, 'lookup-failed'); if ($this->options['debug']) { throw $e; } return $this->pass($request, $catch); } if (null === $entry) { $this->record($request, 'miss'); return $this->fetch($request, $catch); } if (!$this->isFreshEnough($request, $entry)) { $this->record($request, 'stale'); return $this->validate($request, $entry, $catch); } if ($entry->headers->hasCacheControlDirective('no-cache')) { return $this->validate($request, $entry, $catch); } $this->record($request, 'fresh'); $entry->headers->set('Age', $entry->getAge()); return $entry; } /** * Validates that a cache entry is fresh. * * The original request is used as a template for a conditional * GET request with the backend. * * @param bool $catch Whether to process exceptions * * @return Response */ protected function validate(Request $request, Response $entry, bool $catch = \false) { $subRequest = clone $request; // send no head requests because we want content if ('HEAD' === $request->getMethod()) { $subRequest->setMethod('GET'); } // add our cached last-modified validator if ($entry->headers->has('Last-Modified')) { $subRequest->headers->set('If-Modified-Since', $entry->headers->get('Last-Modified')); } // Add our cached etag validator to the environment. // We keep the etags from the client to handle the case when the client // has a different private valid entry which is not cached here. $cachedEtags = $entry->getEtag() ? [$entry->getEtag()] : []; $requestEtags = $request->getETags(); if ($etags = \array_unique(\array_merge($cachedEtags, $requestEtags))) { $subRequest->headers->set('If-None-Match', \implode(', ', $etags)); } $response = $this->forward($subRequest, $catch, $entry); if (304 == $response->getStatusCode()) { $this->record($request, 'valid'); // return the response and not the cache entry if the response is valid but not cached $etag = $response->getEtag(); if ($etag && \in_array($etag, $requestEtags) && !\in_array($etag, $cachedEtags)) { return $response; } $entry = clone $entry; $entry->headers->remove('Date'); foreach (['Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified'] as $name) { if ($response->headers->has($name)) { $entry->headers->set($name, $response->headers->get($name)); } } $response = $entry; } else { $this->record($request, 'invalid'); } if ($response->isCacheable()) { $this->store($request, $response); } return $response; } /** * Unconditionally fetches a fresh response from the backend and * stores it in the cache if is cacheable. * * @param bool $catch Whether to process exceptions * * @return Response */ protected function fetch(Request $request, bool $catch = \false) { $subRequest = clone $request; // send no head requests because we want content if ('HEAD' === $request->getMethod()) { $subRequest->setMethod('GET'); } // avoid that the backend sends no content $subRequest->headers->remove('If-Modified-Since'); $subRequest->headers->remove('If-None-Match'); $response = $this->forward($subRequest, $catch); if ($response->isCacheable()) { $this->store($request, $response); } return $response; } /** * Forwards the Request to the backend and returns the Response. * * All backend requests (cache passes, fetches, cache validations) * run through this method. * * @param bool $catch Whether to catch exceptions or not * @param Response|null $entry A Response instance (the stale entry if present, null otherwise) * * @return Response */ protected function forward(Request $request, bool $catch = \false, ?Response $entry = null) { if ($this->surrogate) { $this->surrogate->addSurrogateCapability($request); } // always a "master" request (as the real master request can be in cache) $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST, $catch); /* * Support stale-if-error given on Responses or as a config option. * RFC 7234 summarizes in Section 4.2.4 (but also mentions with the individual * Cache-Control directives) that * * A cache MUST NOT generate a stale response if it is prohibited by an * explicit in-protocol directive (e.g., by a "no-store" or "no-cache" * cache directive, a "must-revalidate" cache-response-directive, or an * applicable "s-maxage" or "proxy-revalidate" cache-response-directive; * see Section 5.2.2). * * https://tools.ietf.org/html/rfc7234#section-4.2.4 * * We deviate from this in one detail, namely that we *do* serve entries in the * stale-if-error case even if they have a `s-maxage` Cache-Control directive. */ if (null !== $entry && \in_array($response->getStatusCode(), [500, 502, 503, 504]) && !$entry->headers->hasCacheControlDirective('no-cache') && !$entry->mustRevalidate()) { if (null === ($age = $entry->headers->getCacheControlDirective('stale-if-error'))) { $age = $this->options['stale_if_error']; } /* * stale-if-error gives the (extra) time that the Response may be used *after* it has become stale. * So we compare the time the $entry has been sitting in the cache already with the * time it was fresh plus the allowed grace period. */ if ($entry->getAge() <= $entry->getMaxAge() + $age) { $this->record($request, 'stale-if-error'); return $entry; } } /* RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate clock MUST NOT send a "Date" header, although it MUST send one in most other cases except for 1xx or 5xx responses where it MAY do so. Anyway, a client that received a message without a "Date" header MUST add it. */ if (!$response->headers->has('Date')) { $response->setDate(\DateTime::createFromFormat('U', \time())); } $this->processResponseBody($request, $response); if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { $response->setPrivate(); } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { $response->setTtl($this->options['default_ttl']); } return $response; } /** * Checks whether the cache entry is "fresh enough" to satisfy the Request. * * @return bool */ protected function isFreshEnough(Request $request, Response $entry) { if (!$entry->isFresh()) { return $this->lock($request, $entry); } if ($this->options['allow_revalidate'] && null !== ($maxAge = $request->headers->getCacheControlDirective('max-age'))) { return $maxAge > 0 && $maxAge >= $entry->getAge(); } return \true; } /** * Locks a Request during the call to the backend. * * @return bool true if the cache entry can be returned even if it is staled, false otherwise */ protected function lock(Request $request, Response $entry) { // try to acquire a lock to call the backend $lock = $this->store->lock($request); if (\true === $lock) { // we have the lock, call the backend return \false; } // there is already another process calling the backend // May we serve a stale response? if ($this->mayServeStaleWhileRevalidate($entry)) { $this->record($request, 'stale-while-revalidate'); return \true; } // wait for the lock to be released if ($this->waitForLock($request)) { // replace the current entry with the fresh one $new = $this->lookup($request); $entry->headers = $new->headers; $entry->setContent($new->getContent()); $entry->setStatusCode($new->getStatusCode()); $entry->setProtocolVersion($new->getProtocolVersion()); foreach ($new->headers->getCookies() as $cookie) { $entry->headers->setCookie($cookie); } } else { // backend is slow as hell, send a 503 response (to avoid the dog pile effect) $entry->setStatusCode(503); $entry->setContent('503 Service Unavailable'); $entry->headers->set('Retry-After', 10); } return \true; } /** * Writes the Response to the cache. * * @throws \Exception */ protected function store(Request $request, Response $response) { try { $this->store->write($request, $response); $this->record($request, 'store'); $response->headers->set('Age', $response->getAge()); } catch (\Exception $e) { $this->record($request, 'store-failed'); if ($this->options['debug']) { throw $e; } } // now that the response is cached, release the lock $this->store->unlock($request); } /** * Restores the Response body. */ private function restoreResponseBody(Request $request, Response $response) { if ($response->headers->has('X-Body-Eval')) { \assert(self::BODY_EVAL_BOUNDARY_LENGTH === 24); \ob_start(); $content = $response->getContent(); $boundary = \substr($content, 0, 24); $j = \strpos($content, $boundary, 24); echo \substr($content, 24, $j - 24); $i = $j + 24; while (\false !== ($j = \strpos($content, $boundary, $i))) { [$uri, $alt, $ignoreErrors, $part] = \explode("\n", \substr($content, $i, $j - $i), 4); $i = $j + 24; echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); echo $part; } $response->setContent(\ob_get_clean()); $response->headers->remove('X-Body-Eval'); if (!$response->headers->has('Transfer-Encoding')) { $response->headers->set('Content-Length', \strlen($response->getContent())); } } elseif ($response->headers->has('X-Body-File')) { // Response does not include possibly dynamic content (ESI, SSI), so we need // not handle the content for HEAD requests if (!$request->isMethod('HEAD')) { $response->setContent(\file_get_contents($response->headers->get('X-Body-File'))); } } else { return; } $response->headers->remove('X-Body-File'); } protected function processResponseBody(Request $request, Response $response) { if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { $this->surrogate->process($request, $response); } } /** * Checks if the Request includes authorization or other sensitive information * that should cause the Response to be considered private by default. */ private function isPrivateRequest(Request $request) : bool { foreach ($this->options['private_headers'] as $key) { $key = \strtolower(\str_replace('HTTP_', '', $key)); if ('cookie' === $key) { if (\count($request->cookies->all())) { return \true; } } elseif ($request->headers->has($key)) { return \true; } } return \false; } /** * Records that an event took place. */ private function record(Request $request, string $event) { $this->traces[$this->getTraceKey($request)][] = $event; } /** * Calculates the key we use in the "trace" array for a given request. */ private function getTraceKey(Request $request) : string { $path = $request->getPathInfo(); if ($qs = $request->getQueryString()) { $path .= '?' . $qs; } return $request->getMethod() . ' ' . $path; } /** * Checks whether the given (cached) response may be served as "stale" when a revalidation * is currently in progress. */ private function mayServeStaleWhileRevalidate(Response $entry) : bool { $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); if (null === $timeout) { $timeout = $this->options['stale_while_revalidate']; } $age = $entry->getAge(); $maxAge = $entry->getMaxAge() ?? 0; $ttl = $maxAge - $age; return \abs($ttl) < $timeout; } /** * Waits for the store to release a locked entry. */ private function waitForLock(Request $request) : bool { $wait = 0; while ($this->store->isLocked($request) && $wait < 100) { \usleep(50000); ++$wait; } return $wait < 100; } } * * This code is partially based on the Rack-Cache library by Ryan Tomayko, * which is released under the MIT license. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Store implements all the logic for storing cache metadata (Request and Response headers). * * @author Fabien Potencier */ class Store implements StoreInterface { protected $root; /** @var \SplObjectStorage */ private $keyCache; /** @var array */ private $locks = []; private $options; /** * Constructor. * * The available options are: * * * private_headers Set of response headers that should not be stored * when a response is cached. (default: Set-Cookie) * * @throws \RuntimeException */ public function __construct(string $root, array $options = []) { $this->root = $root; if (!\is_dir($this->root) && !@\mkdir($this->root, 0777, \true) && !\is_dir($this->root)) { throw new \RuntimeException(\sprintf('Unable to create the store directory (%s).', $this->root)); } $this->keyCache = new \SplObjectStorage(); $this->options = \array_merge(['private_headers' => ['Set-Cookie']], $options); } /** * Cleanups storage. */ public function cleanup() { // unlock everything foreach ($this->locks as $lock) { \flock($lock, \LOCK_UN); \fclose($lock); } $this->locks = []; } /** * Tries to lock the cache for a given Request, without blocking. * * @return bool|string true if the lock is acquired, the path to the current lock otherwise */ public function lock(Request $request) { $key = $this->getCacheKey($request); if (!isset($this->locks[$key])) { $path = $this->getPath($key); if (!\is_dir(\dirname($path)) && \false === @\mkdir(\dirname($path), 0777, \true) && !\is_dir(\dirname($path))) { return $path; } $h = \fopen($path, 'c'); if (!\flock($h, \LOCK_EX | \LOCK_NB)) { \fclose($h); return $path; } $this->locks[$key] = $h; } return \true; } /** * Releases the lock for the given Request. * * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise */ public function unlock(Request $request) { $key = $this->getCacheKey($request); if (isset($this->locks[$key])) { \flock($this->locks[$key], \LOCK_UN); \fclose($this->locks[$key]); unset($this->locks[$key]); return \true; } return \false; } public function isLocked(Request $request) { $key = $this->getCacheKey($request); if (isset($this->locks[$key])) { return \true; // shortcut if lock held by this process } if (!\is_file($path = $this->getPath($key))) { return \false; } $h = \fopen($path, 'r'); \flock($h, \LOCK_EX | \LOCK_NB, $wouldBlock); \flock($h, \LOCK_UN); // release the lock we just acquired \fclose($h); return (bool) $wouldBlock; } /** * Locates a cached Response for the Request provided. * * @return Response|null */ public function lookup(Request $request) { $key = $this->getCacheKey($request); if (!($entries = $this->getMetadata($key))) { return null; } // find a cached entry that matches the request. $match = null; foreach ($entries as $entry) { if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? \implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { $match = $entry; break; } } if (null === $match) { return null; } $headers = $match[1]; if (\file_exists($path = $this->getPath($headers['x-content-digest'][0]))) { return $this->restoreResponse($headers, $path); } // TODO the metaStore referenced an entity that doesn't exist in // the entityStore. We definitely want to return nil but we should // also purge the entry from the meta-store when this is detected. return null; } /** * Writes a cache entry to the store for the given Request and Response. * * Existing entries are read and any that match the response are removed. This * method calls write with the new list of cache entries. * * @return string * * @throws \RuntimeException */ public function write(Request $request, Response $response) { $key = $this->getCacheKey($request); $storedEnv = $this->persistRequest($request); if ($response->headers->has('X-Body-File')) { // Assume the response came from disk, but at least perform some safeguard checks if (!$response->headers->has('X-Content-Digest')) { throw new \RuntimeException('A restored response must have the X-Content-Digest header.'); } $digest = $response->headers->get('X-Content-Digest'); if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) { throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.'); } // Everything seems ok, omit writing content to disk } else { $digest = $this->generateContentDigest($response); $response->headers->set('X-Content-Digest', $digest); if (!$this->save($digest, $response->getContent(), \false)) { throw new \RuntimeException('Unable to store the entity.'); } if (!$response->headers->has('Transfer-Encoding')) { $response->headers->set('Content-Length', \strlen($response->getContent())); } } // read existing cache entries, remove non-varying, and add this one to the list $entries = []; $vary = $response->headers->get('vary'); foreach ($this->getMetadata($key) as $entry) { if (!isset($entry[1]['vary'][0])) { $entry[1]['vary'] = ['']; } if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) { $entries[] = $entry; } } $headers = $this->persistResponse($response); unset($headers['age']); foreach ($this->options['private_headers'] as $h) { unset($headers[\strtolower($h)]); } \array_unshift($entries, [$storedEnv, $headers]); if (!$this->save($key, \serialize($entries))) { throw new \RuntimeException('Unable to store the metadata.'); } return $key; } /** * Returns content digest for $response. * * @return string */ protected function generateContentDigest(Response $response) { return 'en' . \hash('sha256', $response->getContent()); } /** * Invalidates all cache entries that match the request. * * @throws \RuntimeException */ public function invalidate(Request $request) { $modified = \false; $key = $this->getCacheKey($request); $entries = []; foreach ($this->getMetadata($key) as $entry) { $response = $this->restoreResponse($entry[1]); if ($response->isFresh()) { $response->expire(); $modified = \true; $entries[] = [$entry[0], $this->persistResponse($response)]; } else { $entries[] = $entry; } } if ($modified && !$this->save($key, \serialize($entries))) { throw new \RuntimeException('Unable to store the metadata.'); } } /** * Determines whether two Request HTTP header sets are non-varying based on * the vary response header value provided. * * @param string|null $vary A Response vary header * @param array $env1 A Request HTTP header array * @param array $env2 A Request HTTP header array */ private function requestsMatch(?string $vary, array $env1, array $env2) : bool { if (empty($vary)) { return \true; } foreach (\preg_split('/[\\s,]+/', $vary) as $header) { $key = \str_replace('_', '-', \strtolower($header)); $v1 = $env1[$key] ?? null; $v2 = $env2[$key] ?? null; if ($v1 !== $v2) { return \false; } } return \true; } /** * Gets all data associated with the given key. * * Use this method only if you know what you are doing. */ private function getMetadata(string $key) : array { if (!($entries = $this->load($key))) { return []; } return \unserialize($entries) ?: []; } /** * Purges data for the given URL. * * This method purges both the HTTP and the HTTPS version of the cache entry. * * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise */ public function purge(string $url) { $http = \preg_replace('#^https:#', 'http:', $url); $https = \preg_replace('#^http:#', 'https:', $url); $purgedHttp = $this->doPurge($http); $purgedHttps = $this->doPurge($https); return $purgedHttp || $purgedHttps; } /** * Purges data for the given URL. */ private function doPurge(string $url) : bool { $key = $this->getCacheKey(Request::create($url)); if (isset($this->locks[$key])) { \flock($this->locks[$key], \LOCK_UN); \fclose($this->locks[$key]); unset($this->locks[$key]); } if (\is_file($path = $this->getPath($key))) { \unlink($path); return \true; } return \false; } /** * Loads data for the given key. */ private function load(string $key) : ?string { $path = $this->getPath($key); return \is_file($path) && \false !== ($contents = @\file_get_contents($path)) ? $contents : null; } /** * Save data for the given key. */ private function save(string $key, string $data, bool $overwrite = \true) : bool { $path = $this->getPath($key); if (!$overwrite && \file_exists($path)) { return \true; } if (isset($this->locks[$key])) { $fp = $this->locks[$key]; @\ftruncate($fp, 0); @\fseek($fp, 0); $len = @\fwrite($fp, $data); if (\strlen($data) !== $len) { @\ftruncate($fp, 0); return \false; } } else { if (!\is_dir(\dirname($path)) && \false === @\mkdir(\dirname($path), 0777, \true) && !\is_dir(\dirname($path))) { return \false; } $tmpFile = \tempnam(\dirname($path), \basename($path)); if (\false === ($fp = @\fopen($tmpFile, 'w'))) { @\unlink($tmpFile); return \false; } @\fwrite($fp, $data); @\fclose($fp); if ($data != \file_get_contents($tmpFile)) { @\unlink($tmpFile); return \false; } if (\false === @\rename($tmpFile, $path)) { @\unlink($tmpFile); return \false; } } @\chmod($path, 0666 & ~\umask()); return \true; } public function getPath(string $key) { return $this->root . \DIRECTORY_SEPARATOR . \substr($key, 0, 2) . \DIRECTORY_SEPARATOR . \substr($key, 2, 2) . \DIRECTORY_SEPARATOR . \substr($key, 4, 2) . \DIRECTORY_SEPARATOR . \substr($key, 6); } /** * Generates a cache key for the given Request. * * This method should return a key that must only depend on a * normalized version of the request URI. * * If the same URI can have more than one representation, based on some * headers, use a Vary header to indicate them, and each representation will * be stored independently under the same cache key. * * @return string */ protected function generateCacheKey(Request $request) { return 'md' . \hash('sha256', $request->getUri()); } /** * Returns a cache key for the given Request. */ private function getCacheKey(Request $request) : string { if (isset($this->keyCache[$request])) { return $this->keyCache[$request]; } return $this->keyCache[$request] = $this->generateCacheKey($request); } /** * Persists the Request HTTP headers. */ private function persistRequest(Request $request) : array { return $request->headers->all(); } /** * Persists the Response HTTP headers. */ private function persistResponse(Response $response) : array { $headers = $response->headers->all(); $headers['X-Status'] = [$response->getStatusCode()]; return $headers; } /** * Restores a Response from the HTTP headers and body. */ private function restoreResponse(array $headers, ?string $path = null) : ?Response { $status = $headers['X-Status'][0]; unset($headers['X-Status']); $content = null; if (null !== $path) { $headers['X-Body-File'] = [$path]; unset($headers['x-body-file']); if ($headers['X-Body-Eval'] ?? $headers['x-body-eval'] ?? \false) { $content = \file_get_contents($path); \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === 24); if (48 > \strlen($content) || \substr($content, -24) !== \substr($content, 0, 24)) { return null; } } } return new Response($content, $status, $headers); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; interface SurrogateInterface { /** * Returns surrogate name. * * @return string */ public function getName(); /** * Returns a new cache strategy instance. * * @return ResponseCacheStrategyInterface */ public function createCacheStrategy(); /** * Checks that at least one surrogate has Surrogate capability. * * @return bool */ public function hasSurrogateCapability(Request $request); /** * Adds Surrogate-capability to the given Request. */ public function addSurrogateCapability(Request $request); /** * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. * * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. */ public function addSurrogateControl(Response $response); /** * Checks that the Response needs to be parsed for Surrogate tags. * * @return bool */ public function needsParsing(Response $response); /** * Renders a Surrogate tag. * * @param string|null $alt An alternate URI * @param string $comment A comment to add as an esi:include tag * * @return string */ public function renderIncludeTag(string $uri, ?string $alt = null, bool $ignoreErrors = \true, string $comment = ''); /** * Replaces a Response Surrogate tags with the included resource content. * * @return Response */ public function process(Request $request, Response $response); /** * Handles a Surrogate from the cache. * * @param string $alt An alternative URI * * @return string * * @throws \RuntimeException * @throws \Exception */ public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors); } * * This code is partially based on the Rack-Cache library by Ryan Tomayko, * which is released under the MIT license. * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * ResponseCacheStrategyInterface implementations know how to compute the * Response cache HTTP header based on the different response cache headers. * * @author Fabien Potencier */ interface ResponseCacheStrategyInterface { /** * Adds a Response. */ public function add(Response $response); /** * Updates the Response HTTP headers based on the embedded Responses. */ public function update(Response $response); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * ResponseCacheStrategy knows how to compute the Response cache HTTP header * based on the different response cache headers. * * This implementation changes the main response TTL to the smallest TTL received * or force validation if one of the surrogates has validation cache strategy. * * @author Fabien Potencier */ class ResponseCacheStrategy implements ResponseCacheStrategyInterface { /** * Cache-Control headers that are sent to the final response if they appear in ANY of the responses. */ private const OVERRIDE_DIRECTIVES = ['private', 'no-cache', 'no-store', 'no-transform', 'must-revalidate', 'proxy-revalidate']; /** * Cache-Control headers that are sent to the final response if they appear in ALL of the responses. */ private const INHERIT_DIRECTIVES = ['public', 'immutable']; private $embeddedResponses = 0; private $isNotCacheableResponseEmbedded = \false; private $age = 0; private $flagDirectives = ['no-cache' => null, 'no-store' => null, 'no-transform' => null, 'must-revalidate' => null, 'proxy-revalidate' => null, 'public' => null, 'private' => null, 'immutable' => null]; private $ageDirectives = ['max-age' => null, 's-maxage' => null, 'expires' => null]; /** * {@inheritdoc} */ public function add(Response $response) { ++$this->embeddedResponses; foreach (self::OVERRIDE_DIRECTIVES as $directive) { if ($response->headers->hasCacheControlDirective($directive)) { $this->flagDirectives[$directive] = \true; } } foreach (self::INHERIT_DIRECTIVES as $directive) { if (\false !== $this->flagDirectives[$directive]) { $this->flagDirectives[$directive] = $response->headers->hasCacheControlDirective($directive); } } $age = $response->getAge(); $this->age = \max($this->age, $age); if ($this->willMakeFinalResponseUncacheable($response)) { $this->isNotCacheableResponseEmbedded = \true; return; } $isHeuristicallyCacheable = $response->headers->hasCacheControlDirective('public'); $maxAge = $response->headers->hasCacheControlDirective('max-age') ? (int) $response->headers->getCacheControlDirective('max-age') : null; $this->storeRelativeAgeDirective('max-age', $maxAge, $age, $isHeuristicallyCacheable); $sharedMaxAge = $response->headers->hasCacheControlDirective('s-maxage') ? (int) $response->headers->getCacheControlDirective('s-maxage') : $maxAge; $this->storeRelativeAgeDirective('s-maxage', $sharedMaxAge, $age, $isHeuristicallyCacheable); $expires = $response->getExpires(); $expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null; $this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0, $isHeuristicallyCacheable); } /** * {@inheritdoc} */ public function update(Response $response) { // if we have no embedded Response, do nothing if (0 === $this->embeddedResponses) { return; } // Remove validation related headers of the master response, // because some of the response content comes from at least // one embedded response (which likely has a different caching strategy). $response->setEtag(null); $response->setLastModified(null); $this->add($response); $response->headers->set('Age', $this->age); if ($this->isNotCacheableResponseEmbedded) { if ($this->flagDirectives['no-store']) { $response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate'); } else { $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); } return; } $flags = \array_filter($this->flagDirectives); if (isset($flags['must-revalidate'])) { $flags['no-cache'] = \true; } $response->headers->set('Cache-Control', \implode(', ', \array_keys($flags))); $maxAge = null; if (\is_numeric($this->ageDirectives['max-age'])) { $maxAge = $this->ageDirectives['max-age'] + $this->age; $response->headers->addCacheControlDirective('max-age', $maxAge); } if (\is_numeric($this->ageDirectives['s-maxage'])) { $sMaxage = $this->ageDirectives['s-maxage'] + $this->age; if ($maxAge !== $sMaxage) { $response->headers->addCacheControlDirective('s-maxage', $sMaxage); } } if (\is_numeric($this->ageDirectives['expires'])) { $date = clone $response->getDate(); $date = $date->modify('+' . ($this->ageDirectives['expires'] + $this->age) . ' seconds'); $response->setExpires($date); } } /** * RFC2616, Section 13.4. * * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 */ private function willMakeFinalResponseUncacheable(Response $response) : bool { // RFC2616: A response received with a status code of 200, 203, 300, 301 or 410 // MAY be stored by a cache […] unless a cache-control directive prohibits caching. if ($response->headers->hasCacheControlDirective('no-cache') || $response->headers->getCacheControlDirective('no-store')) { return \true; } // Last-Modified and Etag headers cannot be merged, they render the response uncacheable // by default (except if the response also has max-age etc.). if (\in_array($response->getStatusCode(), [200, 203, 300, 301, 410]) && null === $response->getLastModified() && null === $response->getEtag()) { return \false; } // RFC2616: A response received with any other status code (e.g. status codes 302 and 307) // MUST NOT be returned in a reply to a subsequent request unless there are // cache-control directives or another header(s) that explicitly allow it. $cacheControl = ['max-age', 's-maxage', 'must-revalidate', 'proxy-revalidate', 'public', 'private']; foreach ($cacheControl as $key) { if ($response->headers->hasCacheControlDirective($key)) { return \false; } } if ($response->headers->has('Expires')) { return \false; } return \true; } /** * Store lowest max-age/s-maxage/expires for the final response. * * The response might have been stored in cache a while ago. To keep things comparable, * we have to subtract the age so that the value is normalized for an age of 0. * * If the value is lower than the currently stored value, we update the value, to keep a rolling * minimal value of each instruction. * * If the value is NULL and the isHeuristicallyCacheable parameter is false, the directive will * not be set on the final response. In this case, not all responses had the directive set and no * value can be found that satisfies the requirements of all responses. The directive will be dropped * from the final response. * * If the isHeuristicallyCacheable parameter is true, however, the current response has been marked * as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve * as an upper bound. In this case, we can proceed and possibly keep the directive on the final response. */ private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable) { if (null === $value) { if ($isHeuristicallyCacheable) { /* * See https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2 * This particular response does not require maximum lifetime; heuristics might be applied. * Other responses, however, might have more stringent requirements on maximum lifetime. * So, return early here so that the final response can have the more limiting value set. */ return; } $this->ageDirectives[$directive] = \false; } if (\false !== $this->ageDirectives[$directive]) { $value -= $age; $this->ageDirectives[$directive] = null !== $this->ageDirectives[$directive] ? \min($this->ageDirectives[$directive], $value) : $value; } } } * * This code is partially based on the Rack-Cache library by Ryan Tomayko, * which is released under the MIT license. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Interface implemented by HTTP cache stores. * * @author Fabien Potencier */ interface StoreInterface { /** * Locates a cached Response for the Request provided. * * @return Response|null */ public function lookup(Request $request); /** * Writes a cache entry to the store for the given Request and Response. * * Existing entries are read and any that match the response are removed. This * method calls write with the new list of cache entries. * * @return string The key under which the response is stored */ public function write(Request $request, Response $response); /** * Invalidates all cache entries that match the request. */ public function invalidate(Request $request); /** * Locks the cache for a given Request. * * @return bool|string true if the lock is acquired, the path to the current lock otherwise */ public function lock(Request $request); /** * Releases the lock for the given Request. * * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise */ public function unlock(Request $request); /** * Returns whether or not a lock exists. * * @return bool true if lock exists, false otherwise */ public function isLocked(Request $request); /** * Purges data for the given URL. * * @return bool true if the URL exists and has been purged, false otherwise */ public function purge(string $url); /** * Cleanups storage. */ public function cleanup(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Ssi implements the SSI capabilities to Request and Response instances. * * @author Sebastian Krebs */ class Ssi extends AbstractSurrogate { /** * {@inheritdoc} */ public function getName() { return 'ssi'; } /** * {@inheritdoc} */ public function addSurrogateControl(Response $response) { if (\str_contains($response->getContent(), '', $uri); } /** * {@inheritdoc} */ public function process(Request $request, Response $response) { $type = $response->headers->get('Content-Type'); if (empty($type)) { $type = 'text/html'; } $parts = \explode(';', $type); if (!\in_array($parts[0], $this->contentTypes)) { return $response; } // we don't use a proper XML parser here as we can have SSI tags in a plain text response $content = $response->getContent(); $boundary = self::generateBodyEvalBoundary(); $chunks = \preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); $i = 1; while (isset($chunks[$i])) { $options = []; \preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER); foreach ($matches as $set) { $options[$set[1]] = $set[2]; } if (!isset($options['virtual'])) { throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); } $chunks[$i] = $boundary . $options['virtual'] . "\n\n\n"; $i += 2; } $content = $boundary . \implode('', $chunks) . $boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'SSI'); // remove SSI/1.0 from the Surrogate-Control header $this->removeFromControl($response); return $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Abstract class implementing Surrogate capabilities to Request and Response instances. * * @author Fabien Potencier * @author Robin Chalas */ abstract class AbstractSurrogate implements SurrogateInterface { protected $contentTypes; protected $phpEscapeMap = [['', '', '', '']]; /** * @param array $contentTypes An array of content-type that should be parsed for Surrogate information * (default: text/html, text/xml, application/xhtml+xml, and application/xml) */ public function __construct(array $contentTypes = ['text/html', 'text/xml', 'application/xhtml+xml', 'application/xml']) { $this->contentTypes = $contentTypes; } /** * Returns a new cache strategy instance. * * @return ResponseCacheStrategyInterface */ public function createCacheStrategy() { return new ResponseCacheStrategy(); } /** * {@inheritdoc} */ public function hasSurrogateCapability(Request $request) { if (null === ($value = $request->headers->get('Surrogate-Capability'))) { return \false; } return \str_contains($value, \sprintf('%s/1.0', \strtoupper($this->getName()))); } /** * {@inheritdoc} */ public function addSurrogateCapability(Request $request) { $current = $request->headers->get('Surrogate-Capability'); $new = \sprintf('symfony="%s/1.0"', \strtoupper($this->getName())); $request->headers->set('Surrogate-Capability', $current ? $current . ', ' . $new : $new); } /** * {@inheritdoc} */ public function needsParsing(Response $response) { if (!($control = $response->headers->get('Surrogate-Control'))) { return \false; } $pattern = \sprintf('#content="[^"]*%s/1.0[^"]*"#', \strtoupper($this->getName())); return (bool) \preg_match($pattern, $control); } /** * {@inheritdoc} */ public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors) { $subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all()); try { $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, \true); if (!$response->isSuccessful() && Response::HTTP_NOT_MODIFIED !== $response->getStatusCode()) { throw new \RuntimeException(\sprintf('Error when rendering "%s" (Status code is %d).', $subRequest->getUri(), $response->getStatusCode())); } return $response->getContent(); } catch (\Exception $e) { if ($alt) { return $this->handle($cache, $alt, '', $ignoreErrors); } if (!$ignoreErrors) { throw $e; } } return ''; } /** * Remove the Surrogate from the Surrogate-Control header. */ protected function removeFromControl(Response $response) { if (!$response->headers->has('Surrogate-Control')) { return; } $value = $response->headers->get('Surrogate-Control'); $upperName = \strtoupper($this->getName()); if (\sprintf('content="%s/1.0"', $upperName) == $value) { $response->headers->remove('Surrogate-Control'); } elseif (\preg_match(\sprintf('#,\\s*content="%s/1.0"#', $upperName), $value)) { $response->headers->set('Surrogate-Control', \preg_replace(\sprintf('#,\\s*content="%s/1.0"#', $upperName), '', $value)); } elseif (\preg_match(\sprintf('#content="%s/1.0",\\s*#', $upperName), $value)) { $response->headers->set('Surrogate-Control', \preg_replace(\sprintf('#content="%s/1.0",\\s*#', $upperName), '', $value)); } } protected static function generateBodyEvalBoundary() : string { static $cookie; $cookie = \hash('md5', $cookie ?? ($cookie = \random_bytes(16)), \true); $boundary = \base64_encode($cookie); \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === \strlen($boundary)); return $boundary; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\HttpCache; use _ContaoManager\Symfony\Component\HttpFoundation\IpUtils; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * @author Nicolas Grekas * * @internal */ class SubRequestHandler { public static function handle(HttpKernelInterface $kernel, Request $request, int $type, bool $catch) : Response { // save global state related to trusted headers and proxies $trustedProxies = Request::getTrustedProxies(); $trustedHeaderSet = Request::getTrustedHeaderSet(); // remove untrusted values $remoteAddr = $request->server->get('REMOTE_ADDR'); if (!$remoteAddr || !IpUtils::checkIp($remoteAddr, $trustedProxies)) { $trustedHeaders = ['FORWARDED' => $trustedHeaderSet & Request::HEADER_FORWARDED, 'X_FORWARDED_FOR' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_FOR, 'X_FORWARDED_HOST' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_HOST, 'X_FORWARDED_PROTO' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PROTO, 'X_FORWARDED_PORT' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PORT, 'X_FORWARDED_PREFIX' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PREFIX]; foreach (\array_filter($trustedHeaders) as $name => $key) { $request->headers->remove($name); $request->server->remove('HTTP_' . $name); } } // compute trusted values, taking any trusted proxies into account $trustedIps = []; $trustedValues = []; foreach (\array_reverse($request->getClientIps()) as $ip) { $trustedIps[] = $ip; $trustedValues[] = \sprintf('for="%s"', $ip); } if ($ip !== $remoteAddr) { $trustedIps[] = $remoteAddr; $trustedValues[] = \sprintf('for="%s"', $remoteAddr); } // set trusted values, reusing as much as possible the global trusted settings if (Request::HEADER_FORWARDED & $trustedHeaderSet) { $trustedValues[0] .= \sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme()); $request->headers->set('Forwarded', $v = \implode(', ', $trustedValues)); $request->server->set('HTTP_FORWARDED', $v); } if (Request::HEADER_X_FORWARDED_FOR & $trustedHeaderSet) { $request->headers->set('X-Forwarded-For', $v = \implode(', ', $trustedIps)); $request->server->set('HTTP_X_FORWARDED_FOR', $v); } elseif (!(Request::HEADER_FORWARDED & $trustedHeaderSet)) { Request::setTrustedProxies($trustedProxies, $trustedHeaderSet | Request::HEADER_X_FORWARDED_FOR); $request->headers->set('X-Forwarded-For', $v = \implode(', ', $trustedIps)); $request->server->set('HTTP_X_FORWARDED_FOR', $v); } // fix the client IP address by setting it to 127.0.0.1, // which is the core responsibility of this method $request->server->set('REMOTE_ADDR', '127.0.0.1'); // ensure 127.0.0.1 is set as trusted proxy if (!IpUtils::checkIp('127.0.0.1', $trustedProxies)) { Request::setTrustedProxies(\array_merge($trustedProxies, ['127.0.0.1']), Request::getTrustedHeaderSet()); } try { return $kernel->handle($request, $type, $catch); } finally { // restore global state Request::setTrustedProxies($trustedProxies, $trustedHeaderSet); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * HttpKernelInterface handles a Request to convert it to a Response. * * @author Fabien Potencier */ interface HttpKernelInterface { public const MAIN_REQUEST = 1; public const SUB_REQUEST = 2; /** * @deprecated since symfony/http-kernel 5.3, use MAIN_REQUEST instead. * To ease the migration, this constant won't be removed until Symfony 7.0. */ public const MASTER_REQUEST = self::MAIN_REQUEST; /** * Handles a Request to convert it to a Response. * * When $catch is true, the implementation must catch all exceptions * and do its best to convert them to a Response instance. * * @param int $type The type of the request * (one of HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST) * @param bool $catch Whether to catch exceptions or not * * @return Response * * @throws \Exception When an Exception occurs during processing */ public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = \true); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Log; use _ContaoManager\Psr\Log\AbstractLogger; use _ContaoManager\Psr\Log\InvalidArgumentException; use _ContaoManager\Psr\Log\LogLevel; /** * Minimalist PSR-3 logger designed to write in stderr or any other stream. * * @author Kévin Dunglas */ class Logger extends AbstractLogger { private const LEVELS = [LogLevel::DEBUG => 0, LogLevel::INFO => 1, LogLevel::NOTICE => 2, LogLevel::WARNING => 3, LogLevel::ERROR => 4, LogLevel::CRITICAL => 5, LogLevel::ALERT => 6, LogLevel::EMERGENCY => 7]; private $minLevelIndex; private $formatter; /** @var resource|null */ private $handle; /** * @param string|resource|null $output */ public function __construct(?string $minLevel = null, $output = null, ?callable $formatter = null) { if (null === $minLevel) { $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { switch ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) { case -1: $minLevel = LogLevel::ERROR; break; case 1: $minLevel = LogLevel::NOTICE; break; case 2: $minLevel = LogLevel::INFO; break; case 3: $minLevel = LogLevel::DEBUG; break; } } } if (!isset(self::LEVELS[$minLevel])) { throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $minLevel)); } $this->minLevelIndex = self::LEVELS[$minLevel]; $this->formatter = $formatter ?: [$this, 'format']; if ($output && \false === ($this->handle = \is_resource($output) ? $output : @\fopen($output, 'a'))) { throw new InvalidArgumentException(\sprintf('Unable to open "%s".', $output)); } } /** * {@inheritdoc} * * @return void */ public function log($level, $message, array $context = []) { if (!isset(self::LEVELS[$level])) { throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $level)); } if (self::LEVELS[$level] < $this->minLevelIndex) { return; } $formatter = $this->formatter; if ($this->handle) { @\fwrite($this->handle, $formatter($level, $message, $context) . \PHP_EOL); } else { \error_log($formatter($level, $message, $context, \false)); } } private function format(string $level, string $message, array $context, bool $prefixDate = \true) : string { if (\str_contains($message, '{')) { $replacements = []; foreach ($context as $key => $val) { if (null === $val || \is_scalar($val) || \is_object($val) && \method_exists($val, '__toString')) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); } elseif (\is_object($val)) { $replacements["{{$key}}"] = '[object ' . \get_class($val) . ']'; } else { $replacements["{{$key}}"] = '[' . \gettype($val) . ']'; } } $message = \strtr($message, $replacements); } $log = \sprintf('[%s] %s', $level, $message); if ($prefixDate) { $log = \date(\DateTime::RFC3339) . ' ' . $log; } return $log; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Log; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * DebugLoggerInterface. * * @author Fabien Potencier */ interface DebugLoggerInterface { /** * Returns an array of logs. * * A log is an array with the following mandatory keys: * timestamp, message, priority, and priorityName. * It can also have an optional context key containing an array. * * @return array */ public function getLogs(?Request $request = null); /** * Returns the number of errors. * * @return int */ public function countErrors(?Request $request = null); /** * Removes all log records. */ public function clear(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Profiler; /** * Storage for profiler using files. * * @author Alexandre Salomé */ class FileProfilerStorage implements ProfilerStorageInterface { /** * Folder where profiler data are stored. * * @var string */ private $folder; /** * Constructs the file storage using a "dsn-like" path. * * Example : "file:/path/to/the/storage/folder" * * @throws \RuntimeException */ public function __construct(string $dsn) { if (!\str_starts_with($dsn, 'file:')) { throw new \RuntimeException(\sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); } $this->folder = \substr($dsn, 5); if (!\is_dir($this->folder) && \false === @\mkdir($this->folder, 0777, \true) && !\is_dir($this->folder)) { throw new \RuntimeException(\sprintf('Unable to create the storage directory (%s).', $this->folder)); } } /** * {@inheritdoc} */ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null) : array { $file = $this->getIndexFilename(); if (!\file_exists($file)) { return []; } $file = \fopen($file, 'r'); \fseek($file, 0, \SEEK_END); $result = []; while (\count($result) < $limit && ($line = $this->readLineFromFile($file))) { $values = \str_getcsv($line); if (7 !== \count($values)) { // skip invalid lines continue; } [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values; $csvTime = (int) $csvTime; if ($ip && !\str_contains($csvIp, $ip) || $url && !\str_contains($csvUrl, $url) || $method && !\str_contains($csvMethod, $method) || $statusCode && !\str_contains($csvStatusCode, $statusCode)) { continue; } if (!empty($start) && $csvTime < $start) { continue; } if (!empty($end) && $csvTime > $end) { continue; } $result[$csvToken] = ['token' => $csvToken, 'ip' => $csvIp, 'method' => $csvMethod, 'url' => $csvUrl, 'time' => $csvTime, 'parent' => $csvParent, 'status_code' => $csvStatusCode]; } \fclose($file); return \array_values($result); } /** * {@inheritdoc} */ public function purge() { $flags = \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); foreach ($iterator as $file) { if (\is_file($file)) { \unlink($file); } else { \rmdir($file); } } } /** * {@inheritdoc} */ public function read(string $token) : ?Profile { return $this->doRead($token); } /** * {@inheritdoc} * * @throws \RuntimeException */ public function write(Profile $profile) : bool { $file = $this->getFilename($profile->getToken()); $profileIndexed = \is_file($file); if (!$profileIndexed) { // Create directory $dir = \dirname($file); if (!\is_dir($dir) && \false === @\mkdir($dir, 0777, \true) && !\is_dir($dir)) { throw new \RuntimeException(\sprintf('Unable to create the storage directory (%s).', $dir)); } } $profileToken = $profile->getToken(); // when there are errors in sub-requests, the parent and/or children tokens // may equal the profile token, resulting in infinite loops $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; $childrenToken = \array_filter(\array_map(function (Profile $p) use($profileToken) { return $profileToken !== $p->getToken() ? $p->getToken() : null; }, $profile->getChildren())); // Store profile $data = ['token' => $profileToken, 'parent' => $parentToken, 'children' => $childrenToken, 'data' => $profile->getCollectors(), 'ip' => $profile->getIp(), 'method' => $profile->getMethod(), 'url' => $profile->getUrl(), 'time' => $profile->getTime(), 'status_code' => $profile->getStatusCode()]; $data = \serialize($data); if (\function_exists('gzencode')) { $data = \gzencode($data, 3); } if (\false === \file_put_contents($file, $data, \LOCK_EX)) { return \false; } if (!$profileIndexed) { // Add to index if (\false === ($file = \fopen($this->getIndexFilename(), 'a'))) { return \false; } \fputcsv($file, [$profile->getToken(), $profile->getIp(), $profile->getMethod(), $profile->getUrl(), $profile->getTime(), $profile->getParentToken(), $profile->getStatusCode()]); \fclose($file); } return \true; } /** * Gets filename to store data, associated to the token. * * @return string */ protected function getFilename(string $token) { // Uses 4 last characters, because first are mostly the same. $folderA = \substr($token, -2, 2); $folderB = \substr($token, -4, 2); return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token; } /** * Gets the index filename. * * @return string */ protected function getIndexFilename() { return $this->folder . '/index.csv'; } /** * Reads a line in the file, backward. * * This function automatically skips the empty lines and do not include the line return in result value. * * @param resource $file The file resource, with the pointer placed at the end of the line to read * * @return mixed */ protected function readLineFromFile($file) { $line = ''; $position = \ftell($file); if (0 === $position) { return null; } while (\true) { $chunkSize = \min($position, 1024); $position -= $chunkSize; \fseek($file, $position); if (0 === $chunkSize) { // bof reached break; } $buffer = \fread($file, $chunkSize); if (\false === ($upTo = \strrpos($buffer, "\n"))) { $line = $buffer . $line; continue; } $position += $upTo; $line = \substr($buffer, $upTo + 1) . $line; \fseek($file, \max(0, $position), \SEEK_SET); if ('' !== $line) { break; } } return '' === $line ? null : $line; } protected function createProfileFromData(string $token, array $data, ?Profile $parent = null) { $profile = new Profile($token); $profile->setIp($data['ip']); $profile->setMethod($data['method']); $profile->setUrl($data['url']); $profile->setTime($data['time']); $profile->setStatusCode($data['status_code']); $profile->setCollectors($data['data']); if (!$parent && $data['parent']) { $parent = $this->read($data['parent']); } if ($parent) { $profile->setParent($parent); } foreach ($data['children'] as $token) { if (null !== ($childProfile = $this->doRead($token, $profile))) { $profile->addChild($childProfile); } } return $profile; } private function doRead($token, ?Profile $profile = null) : ?Profile { if (!$token || !\file_exists($file = $this->getFilename($token))) { return null; } $h = \fopen($file, 'r'); \flock($h, \LOCK_SH); $data = \stream_get_contents($h); \flock($h, \LOCK_UN); \fclose($h); if (\function_exists('gzdecode')) { $data = @\gzdecode($data) ?: $data; } if (!($data = \unserialize($data))) { return null; } return $this->createProfileFromData($token, $data, $profile); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Profiler; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; /** * Profile. * * @author Fabien Potencier */ class Profile { private $token; /** * @var DataCollectorInterface[] */ private $collectors = []; private $ip; private $method; private $url; private $time; private $statusCode; /** * @var Profile */ private $parent; /** * @var Profile[] */ private $children = []; public function __construct(string $token) { $this->token = $token; } public function setToken(string $token) { $this->token = $token; } /** * Gets the token. * * @return string */ public function getToken() { return $this->token; } /** * Sets the parent token. */ public function setParent(self $parent) { $this->parent = $parent; } /** * Returns the parent profile. * * @return self|null */ public function getParent() { return $this->parent; } /** * Returns the parent token. * * @return string|null */ public function getParentToken() { return $this->parent ? $this->parent->getToken() : null; } /** * Returns the IP. * * @return string|null */ public function getIp() { return $this->ip; } public function setIp(?string $ip) { $this->ip = $ip; } /** * Returns the request method. * * @return string|null */ public function getMethod() { return $this->method; } public function setMethod(string $method) { $this->method = $method; } /** * Returns the URL. * * @return string|null */ public function getUrl() { return $this->url; } public function setUrl(?string $url) { $this->url = $url; } /** * @return int */ public function getTime() { return $this->time ?? 0; } public function setTime(int $time) { $this->time = $time; } public function setStatusCode(int $statusCode) { $this->statusCode = $statusCode; } /** * @return int|null */ public function getStatusCode() { return $this->statusCode; } /** * Finds children profilers. * * @return self[] */ public function getChildren() { return $this->children; } /** * Sets children profiler. * * @param Profile[] $children */ public function setChildren(array $children) { $this->children = []; foreach ($children as $child) { $this->addChild($child); } } /** * Adds the child token. */ public function addChild(self $child) { $this->children[] = $child; $child->setParent($this); } public function getChildByToken(string $token) : ?self { foreach ($this->children as $child) { if ($token === $child->getToken()) { return $child; } } return null; } /** * Gets a Collector by name. * * @return DataCollectorInterface * * @throws \InvalidArgumentException if the collector does not exist */ public function getCollector(string $name) { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(\sprintf('Collector "%s" does not exist.', $name)); } return $this->collectors[$name]; } /** * Gets the Collectors associated with this profile. * * @return DataCollectorInterface[] */ public function getCollectors() { return $this->collectors; } /** * Sets the Collectors associated with this profile. * * @param DataCollectorInterface[] $collectors */ public function setCollectors(array $collectors) { $this->collectors = []; foreach ($collectors as $collector) { $this->addCollector($collector); } } /** * Adds a Collector. */ public function addCollector(DataCollectorInterface $collector) { $this->collectors[$collector->getName()] = $collector; } /** * @return bool */ public function hasCollector(string $name) { return isset($this->collectors[$name]); } /** * @return array */ public function __sleep() { return ['token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Profiler; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use _ContaoManager\Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Profiler. * * @author Fabien Potencier */ class Profiler implements ResetInterface { private $storage; /** * @var DataCollectorInterface[] */ private $collectors = []; private $logger; private $initiallyEnabled = \true; private $enabled = \true; public function __construct(ProfilerStorageInterface $storage, ?LoggerInterface $logger = null, bool $enable = \true) { $this->storage = $storage; $this->logger = $logger; $this->initiallyEnabled = $this->enabled = $enable; } /** * Disables the profiler. */ public function disable() { $this->enabled = \false; } /** * Enables the profiler. */ public function enable() { $this->enabled = \true; } /** * Loads the Profile for the given Response. * * @return Profile|null */ public function loadProfileFromResponse(Response $response) { if (!($token = $response->headers->get('X-Debug-Token'))) { return null; } return $this->loadProfile($token); } /** * Loads the Profile for the given token. * * @return Profile|null */ public function loadProfile(string $token) { return $this->storage->read($token); } /** * Saves a Profile. * * @return bool */ public function saveProfile(Profile $profile) { // late collect foreach ($profile->getCollectors() as $collector) { if ($collector instanceof LateDataCollectorInterface) { $collector->lateCollect(); } } if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => \get_class($this->storage)]); } return $ret; } /** * Purges all data from the storage. */ public function purge() { $this->storage->purge(); } /** * Finds profiler tokens for the given criteria. * * @param int|null $limit The maximum number of tokens to return * @param string|null $start The start date to search from * @param string|null $end The end date to search to * * @return array * * @see https://php.net/datetime.formats for the supported date/time formats */ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?string $start, ?string $end, ?string $statusCode = null) { return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); } /** * Collects data for the given Response. * * @return Profile|null */ public function collect(Request $request, Response $response, ?\Throwable $exception = null) { if (\false === $this->enabled) { return null; } $profile = new Profile(\substr(\hash('sha256', \uniqid(\mt_rand(), \true)), 0, 6)); $profile->setTime(\time()); $profile->setUrl($request->getUri()); $profile->setMethod($request->getMethod()); $profile->setStatusCode($response->getStatusCode()); try { $profile->setIp($request->getClientIp()); } catch (ConflictingHeadersException $e) { $profile->setIp('Unknown'); } if ($prevToken = $response->headers->get('X-Debug-Token')) { $response->headers->set('X-Previous-Debug-Token', $prevToken); } $response->headers->set('X-Debug-Token', $profile->getToken()); foreach ($this->collectors as $collector) { $collector->collect($request, $response, $exception); // we need to clone for sub-requests $profile->addCollector(clone $collector); } return $profile; } public function reset() { foreach ($this->collectors as $collector) { $collector->reset(); } $this->enabled = $this->initiallyEnabled; } /** * Gets the Collectors associated with this profiler. * * @return array */ public function all() { return $this->collectors; } /** * Sets the Collectors associated with this profiler. * * @param DataCollectorInterface[] $collectors An array of collectors */ public function set(array $collectors = []) { $this->collectors = []; foreach ($collectors as $collector) { $this->add($collector); } } /** * Adds a Collector. */ public function add(DataCollectorInterface $collector) { $this->collectors[$collector->getName()] = $collector; } /** * Returns true if a Collector for the given name exists. * * @param string $name A collector name * * @return bool */ public function has(string $name) { return isset($this->collectors[$name]); } /** * Gets a Collector by name. * * @param string $name A collector name * * @return DataCollectorInterface * * @throws \InvalidArgumentException if the collector does not exist */ public function get(string $name) { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(\sprintf('Collector "%s" does not exist.', $name)); } return $this->collectors[$name]; } private function getTimestamp(?string $value) : ?int { if (null === $value || '' === $value) { return null; } try { $value = new \DateTime(\is_numeric($value) ? '@' . $value : $value); } catch (\Exception $e) { return null; } return $value->getTimestamp(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Profiler; /** * ProfilerStorageInterface. * * This interface exists for historical reasons. The only supported * implementation is FileProfilerStorage. * * As the profiler must only be used on non-production servers, the file storage * is more than enough and no other implementations will ever be supported. * * @internal * * @author Fabien Potencier */ interface ProfilerStorageInterface { /** * Finds profiler tokens for the given criteria. * * @param int|null $limit The maximum number of tokens to return * @param int|null $start The start date to search from * @param int|null $end The end date to search to */ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null) : array; /** * Reads data associated with the given token. * * The method returns false if the token does not exist in the storage. */ public function read(string $token) : ?Profile; /** * Saves a Profile. */ public function write(Profile $profile) : bool; /** * Purges all data from the database. */ public function purge(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; /** * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. * * @author Fabien Potencier */ class FragmentRendererPass implements CompilerPassInterface { private $handlerService; private $rendererTag; public function __construct(string $handlerService = 'fragment.handler', string $rendererTag = 'kernel.fragment_renderer') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->handlerService = $handlerService; $this->rendererTag = $rendererTag; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->handlerService)) { return; } $definition = $container->getDefinition($this->handlerService); $renderers = []; foreach ($container->findTaggedServiceIds($this->rendererTag, \true) as $id => $tags) { $def = $container->getDefinition($id); $class = $container->getParameterBag()->resolveValue($def->getClass()); if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(FragmentRendererInterface::class)) { throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class)); } foreach ($tags as $tag) { $renderers[$tag['alias']] = new Reference($id); } } $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Resets provided services. * * @author Alexander M. Turek * @author Nicolas Grekas * * @internal */ class ServicesResetter implements ResetInterface { private $resettableServices; private $resetMethods; /** * @param \Traversable $resettableServices * @param array $resetMethods */ public function __construct(\Traversable $resettableServices, array $resetMethods) { $this->resettableServices = $resettableServices; $this->resetMethods = $resetMethods; } public function reset() { foreach ($this->resettableServices as $id => $service) { foreach ((array) $this->resetMethods[$id] as $resetMethod) { if ('?' === $resetMethod[0] && !\method_exists($service, $resetMethod = \substr($resetMethod, 1))) { continue; } $service->{$resetMethod}(); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Register all services that have the "kernel.locale_aware" tag into the listener. * * @author Pierre Bobiet */ class RegisterLocaleAwareServicesPass implements CompilerPassInterface { private $listenerServiceId; private $localeAwareTag; public function __construct(string $listenerServiceId = 'locale_aware_listener', string $localeAwareTag = 'kernel.locale_aware') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->listenerServiceId = $listenerServiceId; $this->localeAwareTag = $localeAwareTag; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->listenerServiceId)) { return; } $services = []; foreach ($container->findTaggedServiceIds($this->localeAwareTag) as $id => $tags) { $services[] = new Reference($id); } if (!$services) { $container->removeDefinition($this->listenerServiceId); return; } $container->getDefinition($this->listenerServiceId)->setArgument(0, new IteratorArgument($services)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Alexander M. Turek */ class ResettableServicePass implements CompilerPassInterface { private $tagName; public function __construct(string $tagName = 'kernel.reset') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->tagName = $tagName; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->has('services_resetter')) { return; } $services = $methods = []; foreach ($container->findTaggedServiceIds($this->tagName, \true) as $id => $tags) { $services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE); foreach ($tags as $attributes) { if (!isset($attributes['method'])) { throw new RuntimeException(\sprintf('Tag "%s" requires the "method" attribute to be set.', $this->tagName)); } if (!isset($methods[$id])) { $methods[$id] = []; } if ('ignore' === ($attributes['on_invalid'] ?? null)) { $attributes['method'] = '?' . $attributes['method']; } $methods[$id][] = $attributes['method']; } } if (!$services) { $container->removeAlias('services_resetter'); $container->removeDefinition('services_resetter'); return; } $container->findDefinition('services_resetter')->setArgument(0, new IteratorArgument($services))->setArgument(1, $methods); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Ensures certain extensions are always loaded. * * @author Kris Wallsmith */ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass { private $extensions; /** * @param string[] $extensions */ public function __construct(array $extensions) { $this->extensions = $extensions; } public function process(ContainerBuilder $container) { foreach ($this->extensions as $extension) { if (!\count($container->getExtensionConfig($extension))) { $container->loadFromExtension($extension, []); } } parent::process($container); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use Composer\Autoload\ClassLoader; use _ContaoManager\Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\ErrorHandler\DebugClassLoader; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; /** * Sets the classes to compile in the cache for the container. * * @author Fabien Potencier */ class AddAnnotatedClassesToCachePass implements CompilerPassInterface { private $kernel; public function __construct(Kernel $kernel) { $this->kernel = $kernel; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $annotatedClasses = []; foreach ($container->getExtensions() as $extension) { if ($extension instanceof Extension) { $annotatedClasses[] = $extension->getAnnotatedClassesToCompile(); } } $annotatedClasses = \array_merge($this->kernel->getAnnotatedClassesToCompile(), ...$annotatedClasses); $existingClasses = $this->getClassesInComposerClassMaps(); $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); } /** * Expands the given class patterns using a list of existing classes. * * @param array $patterns The class patterns to expand * @param array $classes The existing classes to match against the patterns */ private function expandClasses(array $patterns, array $classes) : array { $expanded = []; // Explicit classes declared in the patterns are returned directly foreach ($patterns as $key => $pattern) { if (!\str_ends_with($pattern, '\\') && !\str_contains($pattern, '*')) { unset($patterns[$key]); $expanded[] = \ltrim($pattern, '\\'); } } // Match patterns with the classes list $regexps = $this->patternsToRegexps($patterns); foreach ($classes as $class) { $class = \ltrim($class, '\\'); if ($this->matchAnyRegexps($class, $regexps)) { $expanded[] = $class; } } return \array_unique($expanded); } private function getClassesInComposerClassMaps() : array { $classes = []; foreach (\spl_autoload_functions() as $function) { if (!\is_array($function)) { continue; } if ($function[0] instanceof DebugClassLoader || $function[0] instanceof LegacyDebugClassLoader) { $function = $function[0]->getClassLoader(); } if (\is_array($function) && $function[0] instanceof ClassLoader) { $classes += \array_filter($function[0]->getClassMap()); } } return \array_keys($classes); } private function patternsToRegexps(array $patterns) : array { $regexps = []; foreach ($patterns as $pattern) { // Escape user input $regex = \preg_quote(\ltrim($pattern, '\\')); // Wildcards * and ** $regex = \strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']); // If this class does not end by a slash, anchor the end if ('\\' !== \substr($regex, -1)) { $regex .= '$'; } $regexps[] = '{^\\\\' . $regex . '}'; } return $regexps; } private function matchAnyRegexps(string $class, array $regexps) : bool { $isTest = \str_contains($class, 'Test'); foreach ($regexps as $regex) { if ($isTest && !\str_contains($regex, 'Test')) { continue; } if (\preg_match($regex, '\\' . $class)) { return \true; } } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * This extension sub-class provides first-class integration with the * Config/Definition Component. * * You can use this as base class if * * a) you use the Config/Definition component for configuration, * b) your configuration class is named "Configuration", and * c) the configuration class resides in the DependencyInjection sub-folder. * * @author Johannes M. Schmitt */ abstract class ConfigurableExtension extends Extension { /** * {@inheritdoc} */ public final function load(array $configs, ContainerBuilder $container) { $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); } /** * Configures the passed container according to the merged configuration. */ protected abstract function loadInternal(array $mergedConfig, ContainerBuilder $container); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\HttpKernel\Log\Logger; /** * Registers the default logger if necessary. * * @author Kévin Dunglas */ class LoggerPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $container->setAlias(LoggerInterface::class, 'logger')->setPublic(\false); if ($container->has('logger')) { return; } $container->register('logger', Logger::class)->setPublic(\false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; /** * Allow adding classes to the class cache. * * @author Fabien Potencier */ abstract class Extension extends BaseExtension { private $annotatedClasses = []; /** * Gets the annotated classes to cache. * * @return array */ public function getAnnotatedClassesToCompile() { return $this->annotatedClasses; } /** * Adds annotated classes to the class cache. * * @param array $annotatedClasses An array of class patterns */ public function addAnnotatedClassesToCompile(array $annotatedClasses) { $this->annotatedClasses = \array_merge($this->annotatedClasses, $annotatedClasses); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; use _ContaoManager\Symfony\Component\Stopwatch\Stopwatch; /** * Gathers and configures the argument value resolvers. * * @author Iltar van der Berg */ class ControllerArgumentValueResolverPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; private $argumentResolverService; private $argumentValueResolverTag; private $traceableResolverStopwatch; public function __construct(string $argumentResolverService = 'argument_resolver', string $argumentValueResolverTag = 'controller.argument_value_resolver', string $traceableResolverStopwatch = 'debug.stopwatch') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->argumentResolverService = $argumentResolverService; $this->argumentValueResolverTag = $argumentValueResolverTag; $this->traceableResolverStopwatch = $traceableResolverStopwatch; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->argumentResolverService)) { return; } $resolvers = $this->findAndSortTaggedServices($this->argumentValueResolverTag, $container); if ($container->getParameter('kernel.debug') && \class_exists(Stopwatch::class) && $container->has($this->traceableResolverStopwatch)) { foreach ($resolvers as $resolverReference) { $id = (string) $resolverReference; $container->register("debug.{$id}", TraceableValueResolver::class)->setDecoratedService($id)->setArguments([new Reference("debug.{$id}.inner"), new Reference($this->traceableResolverStopwatch)]); } } $container->getDefinition($this->argumentResolverService)->replaceArgument(1, new IteratorArgument($resolvers)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\Target; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerAwareInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Creates the service-locators required by ServiceValueResolver. * * @author Nicolas Grekas */ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface { private $resolverServiceId; private $controllerTag; private $controllerLocator; private $notTaggedControllerResolverServiceId; public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments', string $controllerLocator = 'argument_resolver.controller_locator', string $notTaggedControllerResolverServiceId = 'argument_resolver.not_tagged_controller') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->resolverServiceId = $resolverServiceId; $this->controllerTag = $controllerTag; $this->controllerLocator = $controllerLocator; $this->notTaggedControllerResolverServiceId = $notTaggedControllerResolverServiceId; } public function process(ContainerBuilder $container) { if (\false === $container->hasDefinition($this->resolverServiceId) && \false === $container->hasDefinition($this->notTaggedControllerResolverServiceId)) { return; } $parameterBag = $container->getParameterBag(); $controllers = []; $publicAliases = []; foreach ($container->getAliases() as $id => $alias) { if ($alias->isPublic() && !$alias->isPrivate()) { $publicAliases[(string) $alias][] = $id; } } foreach ($container->findTaggedServiceIds($this->controllerTag, \true) as $id => $tags) { $def = $container->getDefinition($id); $def->setPublic(\true); $class = $def->getClass(); $autowire = $def->isAutowired(); $bindings = $def->getBindings(); // resolve service class, taking parent definitions into account while ($def instanceof ChildDefinition) { $def = $container->findDefinition($def->getParent()); $class = $class ?: $def->getClass(); $bindings += $def->getBindings(); } $class = $parameterBag->resolveValue($class); if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } $isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || \is_subclass_of($class, AbstractController::class); // get regular public methods $methods = []; $arguments = []; foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) { if ('setContainer' === $r->name && $isContainerAware) { continue; } if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) { $methods[\strtolower($r->name)] = [$r, $r->getParameters()]; } } // validate and collect explicit per-actions and per-arguments service references foreach ($tags as $attributes) { if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) { $autowire = \true; continue; } foreach (['action', 'argument', 'id'] as $k) { if (!isset($attributes[$k][0])) { throw new InvalidArgumentException(\sprintf('Missing "%s" attribute on tag "%s" %s for service "%s".', $k, $this->controllerTag, \json_encode($attributes, \JSON_UNESCAPED_UNICODE), $id)); } } if (!isset($methods[$action = \strtolower($attributes['action'])])) { throw new InvalidArgumentException(\sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class)); } [$r, $parameters] = $methods[$action]; $found = \false; foreach ($parameters as $p) { if ($attributes['argument'] === $p->name) { if (!isset($arguments[$r->name][$p->name])) { $arguments[$r->name][$p->name] = $attributes['id']; } $found = \true; break; } } if (!$found) { throw new InvalidArgumentException(\sprintf('Invalid "%s" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $this->controllerTag, $id, $r->name, $attributes['argument'], $class)); } } foreach ($methods as [$r, $parameters]) { /** @var \ReflectionMethod $r */ // create a per-method map of argument-names to service/type-references $args = []; foreach ($parameters as $p) { /** @var \ReflectionParameter $p */ $type = \ltrim($target = (string) ProxyHelper::getTypeHint($r, $p), '\\'); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; if (isset($arguments[$r->name][$p->name])) { $target = $arguments[$r->name][$p->name]; if ('?' !== $target[0]) { $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; } elseif ('' === ($target = (string) \substr($target, 1))) { throw new InvalidArgumentException(\sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id)); } elseif ($p->allowsNull() && !$p->isOptional()) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } } elseif (isset($bindings[$bindingName = $type . ' $' . ($name = Target::parseName($p))]) || isset($bindings[$bindingName = '$' . $name]) || isset($bindings[$bindingName = $type])) { $binding = $bindings[$bindingName]; [$bindingValue, $bindingId, , $bindingType, $bindingFile] = $binding->getValues(); $binding->setValues([$bindingValue, $bindingId, \true, $bindingType, $bindingFile]); if (!$bindingValue instanceof Reference) { $args[$p->name] = new Reference('.value.' . $container->hash($bindingValue)); $container->register((string) $args[$p->name], 'mixed')->setFactory('current')->addArgument([$bindingValue]); } else { $args[$p->name] = $bindingValue; } continue; } elseif (!$type || !$autowire || '\\' !== $target[0]) { continue; } elseif (\is_subclass_of($type, \UnitEnum::class)) { // do not attempt to register enum typed arguments if not already present in bindings continue; } elseif (!$p->allowsNull()) { $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; } if (Request::class === $type || SessionInterface::class === $type || Response::class === $type) { continue; } if ($type && !$p->isOptional() && !$p->allowsNull() && !\class_exists($type) && !\interface_exists($type, \false)) { $message = \sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); // see if the type-hint lives in the same namespace as the controller if (0 === \strncmp($type, $class, \strrpos($class, '\\'))) { $message .= ' Did you forget to add a use statement?'; } $container->register($erroredId = '.errored.' . $container->hash($message), $type)->addError($message); $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE); } else { $target = \ltrim($target, '\\'); $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior); } } // register the maps as a per-method service-locators if ($args) { $controllers[$id . '::' . $r->name] = ServiceLocatorTagPass::register($container, $args); foreach ($publicAliases[$id] ?? [] as $alias) { $controllers[$alias . '::' . $r->name] = clone $controllers[$id . '::' . $r->name]; } } } } $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers); if ($container->hasDefinition($this->resolverServiceId)) { $container->getDefinition($this->resolverServiceId)->replaceArgument(0, $controllerLocatorRef); } if ($container->hasDefinition($this->notTaggedControllerResolverServiceId)) { $container->getDefinition($this->notTaggedControllerResolverServiceId)->replaceArgument(0, $controllerLocatorRef); } $container->setAlias($this->controllerLocator, (string) $controllerLocatorRef); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Removes empty service-locators registered for ServiceValueResolver. * * @author Nicolas Grekas */ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface { private $controllerLocator; public function __construct(string $controllerLocator = 'argument_resolver.controller_locator') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->controllerLocator = $controllerLocator; } public function process(ContainerBuilder $container) { $controllerLocator = $container->findDefinition($this->controllerLocator); $controllers = $controllerLocator->getArgument(0); foreach ($controllers as $controller => $argumentRef) { $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); if (!$argumentLocator->getArgument(0)) { // remove empty argument locators $reason = \sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller); } else { // any methods listed for call-at-instantiation cannot be actions $reason = \false; [$id, $action] = \explode('::', $controller); if ($container->hasAlias($id)) { continue; } $controllerDef = $container->getDefinition($id); foreach ($controllerDef->getMethodCalls() as [$method]) { if (0 === \strcasecmp($action, $method)) { $reason = \sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); break; } } if (!$reason) { // see Symfony\Component\HttpKernel\Controller\ContainerControllerResolver $controllers[$id . ':' . $action] = $argumentRef; if ('__invoke' === $action) { $controllers[$id] = $argumentRef; } continue; } } unset($controllers[$controller]); $container->log($this, $reason); } $controllerLocator->replaceArgument(0, $controllers); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Fragment\FragmentHandler; /** * Lazily loads fragment renderers from the dependency injection container. * * @author Fabien Potencier */ class LazyLoadingFragmentHandler extends FragmentHandler { private $container; /** * @var array */ private $initialized = []; public function __construct(ContainerInterface $container, RequestStack $requestStack, bool $debug = \false) { $this->container = $container; parent::__construct($requestStack, [], $debug); } /** * {@inheritdoc} */ public function render($uri, string $renderer = 'inline', array $options = []) { if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { $this->addRenderer($this->container->get($renderer)); $this->initialized[$renderer] = \true; } return parent::render($uri, $renderer, $options); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey * * @see http://tools.ietf.org/html/rfc6585 */ class TooManyRequestsHttpException extends HttpException { /** * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int|null $code The internal exception code */ public function __construct($retryAfter = null, ?string $message = '', ?\Throwable $previous = null, ?int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } if (null === $code) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } if ($retryAfter) { $headers['Retry-After'] = $retryAfter; } parent::__construct(429, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class ConflictHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(409, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * HttpException. * * @author Kris Wallsmith */ class HttpException extends \RuntimeException implements HttpExceptionInterface { private $statusCode; private $headers; public function __construct(int $statusCode, ?string $message = '', ?\Throwable $previous = null, array $headers = [], ?int $code = 0) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } if (null === $code) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } $this->statusCode = $statusCode; $this->headers = $headers; parent::__construct($message, $code, $previous); } public function getStatusCode() { return $this->statusCode; } public function getHeaders() { return $this->headers; } /** * Set response headers. * * @param array $headers Response headers */ public function setHeaders(array $headers) { $this->headers = $headers; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class UnauthorizedHttpException extends HttpException { /** * @param string $challenge WWW-Authenticate challenge string * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int|null $code The internal exception code */ public function __construct(string $challenge, ?string $message = '', ?\Throwable $previous = null, ?int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } if (null === $code) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } $headers['WWW-Authenticate'] = $challenge; parent::__construct(401, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; class InvalidMetadataException extends \LogicException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class LengthRequiredHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(411, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Steve Hutchins */ class UnprocessableEntityHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(422, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class PreconditionFailedHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(412, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class GoneHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(410, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class UnsupportedMediaTypeHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(415, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Mathias Arlaud */ class UnexpectedSessionUsageException extends \LogicException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class ServiceUnavailableHttpException extends HttpException { /** * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int|null $code The internal exception code */ public function __construct($retryAfter = null, ?string $message = '', ?\Throwable $previous = null, ?int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } if (null === $code) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } if ($retryAfter) { $headers['Retry-After'] = $retryAfter; } parent::__construct(503, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * Interface for HTTP error exceptions. * * @author Kris Wallsmith */ interface HttpExceptionInterface extends \Throwable { /** * Returns the status code. * * @return int */ public function getStatusCode(); /** * Returns response headers. * * @return array */ public function getHeaders(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Fabien Potencier * @author Christophe Coevoet */ class AccessDeniedHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(403, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class BadRequestHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(400, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Grégoire Pineau */ class ControllerDoesNotReturnResponseException extends \LogicException { public function __construct(string $message, callable $controller, string $file, int $line) { parent::__construct($message); if (!($controllerDefinition = $this->parseControllerDefinition($controller))) { return; } $this->file = $controllerDefinition['file']; $this->line = $controllerDefinition['line']; $r = new \ReflectionProperty(\Exception::class, 'trace'); $r->setAccessible(\true); $r->setValue($this, \array_merge([['line' => $line, 'file' => $file]], $this->getTrace())); } private function parseControllerDefinition(callable $controller) : ?array { if (\is_string($controller) && \str_contains($controller, '::')) { $controller = \explode('::', $controller); } if (\is_array($controller)) { try { $r = new \ReflectionMethod($controller[0], $controller[1]); return ['file' => $r->getFileName(), 'line' => $r->getEndLine()]; } catch (\ReflectionException $e) { return null; } } if ($controller instanceof \Closure) { $r = new \ReflectionFunction($controller); return ['file' => $r->getFileName(), 'line' => $r->getEndLine()]; } if (\is_object($controller)) { $r = new \ReflectionClass($controller); try { $line = $r->getMethod('__invoke')->getEndLine(); } catch (\ReflectionException $e) { $line = $r->getEndLine(); } return ['file' => $r->getFileName(), 'line' => $line]; } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey */ class NotAcceptableHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(406, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Ben Ramsey * * @see http://tools.ietf.org/html/rfc6585 */ class PreconditionRequiredHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(428, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Fabien Potencier */ class NotFoundHttpException extends HttpException { /** * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int $code The internal exception code */ public function __construct(?string $message = '', ?\Throwable $previous = null, int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } parent::__construct(404, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Exception; /** * @author Kris Wallsmith */ class MethodNotAllowedHttpException extends HttpException { /** * @param string[] $allow An array of allowed methods * @param string|null $message The internal exception message * @param \Throwable|null $previous The previous exception * @param int|null $code The internal exception code */ public function __construct(array $allow, ?string $message = '', ?\Throwable $previous = null, ?int $code = 0, array $headers = []) { if (null === $message) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } if (null === $code) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); $code = 0; } $headers['Allow'] = \strtoupper(\implode(', ', $allow)); parent::__construct(405, $message, $previous, $headers, $code); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Allows filtering of a controller callable. * * You can call getController() to retrieve the current controller. With * setController() you can set a new controller that is used in the processing * of the request. * * Controllers should be callables. * * @author Bernhard Schussek */ final class ControllerEvent extends KernelEvent { private $controller; public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType) { parent::__construct($kernel, $request, $requestType); $this->setController($controller); } public function getController() : callable { return $this->controller; } public function setController(callable $controller) : void { $this->controller = $controller; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Allows to create a response for a thrown exception. * * Call setResponse() to set the response that will be returned for the * current request. The propagation of this event is stopped as soon as a * response is set. * * You can also call setThrowable() to replace the thrown exception. This * exception will be thrown if no response is set during processing of this * event. * * @author Bernhard Schussek */ final class ExceptionEvent extends RequestEvent { private $throwable; /** * @var bool */ private $allowCustomResponseCode = \false; public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, \Throwable $e) { parent::__construct($kernel, $request, $requestType); $this->setThrowable($e); } public function getThrowable() : \Throwable { return $this->throwable; } /** * Replaces the thrown exception. * * This exception will be thrown if no response is set in the event. */ public function setThrowable(\Throwable $exception) : void { $this->throwable = $exception; } /** * Mark the event as allowing a custom response code. */ public function allowCustomResponseCode() : void { $this->allowCustomResponseCode = \true; } /** * Returns true if the event allows a custom response code. */ public function isAllowingCustomResponseCode() : bool { return $this->allowCustomResponseCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; /** * Triggered whenever a request is fully processed. * * @author Benjamin Eberlei */ final class FinishRequestEvent extends KernelEvent { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Allows to filter a Response object. * * You can call getResponse() to retrieve the current response. With * setResponse() you can set a new response that will be returned to the * browser. * * @author Bernhard Schussek */ final class ResponseEvent extends KernelEvent { private $response; public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, Response $response) { parent::__construct($kernel, $request, $requestType); $this->setResponse($response); } public function getResponse() : Response { return $this->response; } public function setResponse(Response $response) : void { $this->response = $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Allows to execute logic after a response was sent. * * Since it's only triggered on main requests, the `getRequestType()` method * will always return the value of `HttpKernelInterface::MAIN_REQUEST`. * * @author Jordi Boggiano */ final class TerminateEvent extends KernelEvent { private $response; public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) { parent::__construct($kernel, $request, HttpKernelInterface::MAIN_REQUEST); $this->response = $response; } public function getResponse() : Response { return $this->response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Allows filtering of controller arguments. * * You can call getController() to retrieve the controller and getArguments * to retrieve the current arguments. With setArguments() you can replace * arguments that are used to call the controller. * * Arguments set in the event must be compatible with the signature of the * controller. * * @author Christophe Coevoet */ final class ControllerArgumentsEvent extends KernelEvent { private $controller; private $arguments; public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, ?int $requestType) { parent::__construct($kernel, $request, $requestType); $this->controller = $controller; $this->arguments = $arguments; } public function getController() : callable { return $this->controller; } public function setController(callable $controller) { $this->controller = $controller; } public function getArguments() : array { return $this->arguments; } public function setArguments(array $arguments) { $this->arguments = $arguments; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * Base class for events thrown in the HttpKernel component. * * @author Bernhard Schussek */ class KernelEvent extends Event { private $kernel; private $request; private $requestType; /** * @param int $requestType The request type the kernel is currently processing; one of * HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST */ public function __construct(HttpKernelInterface $kernel, Request $request, ?int $requestType) { $this->kernel = $kernel; $this->request = $request; $this->requestType = $requestType; } /** * Returns the kernel in which this event was thrown. * * @return HttpKernelInterface */ public function getKernel() { return $this->kernel; } /** * Returns the request the kernel is currently processing. * * @return Request */ public function getRequest() { return $this->request; } /** * Returns the request type the kernel is currently processing. * * @return int One of HttpKernelInterface::MAIN_REQUEST and * HttpKernelInterface::SUB_REQUEST */ public function getRequestType() { return $this->requestType; } /** * Checks if this is the main request. */ public function isMainRequest() : bool { return HttpKernelInterface::MAIN_REQUEST === $this->requestType; } /** * Checks if this is a master request. * * @return bool * * @deprecated since symfony/http-kernel 5.3, use isMainRequest() instead */ public function isMasterRequest() { \trigger_deprecation('symfony/http-kernel', '5.3', '"%s()" is deprecated, use "isMainRequest()" instead.', __METHOD__); return $this->isMainRequest(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Allows to create a response for a request. * * Call setResponse() to set the response that will be returned for the * current request. The propagation of this event is stopped as soon as a * response is set. * * @author Bernhard Schussek */ class RequestEvent extends KernelEvent { private $response; /** * Returns the response object. * * @return Response|null */ public function getResponse() { return $this->response; } /** * Sets a response and stops event propagation. */ public function setResponse(Response $response) { $this->response = $response; $this->stopPropagation(); } /** * Returns whether a response was set. * * @return bool */ public function hasResponse() { return null !== $this->response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Event; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; /** * Allows to create a response for the return value of a controller. * * Call setResponse() to set the response that will be returned for the * current request. The propagation of this event is stopped as soon as a * response is set. * * @author Bernhard Schussek */ final class ViewEvent extends RequestEvent { /** * The return value of the controller. * * @var mixed */ private $controllerResult; public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, $controllerResult) { parent::__construct($kernel, $request, $requestType); $this->controllerResult = $controllerResult; } /** * Returns the return value of the controller. * * @return mixed */ public function getControllerResult() { return $this->controllerResult; } /** * Assigns the return value of the controller. * * @param mixed $controllerResult The controller return value */ public function setControllerResult($controllerResult) : void { $this->controllerResult = $controllerResult; } } { "name": "symfony\/http-kernel", "type": "library", "description": "Provides a structured process for converting a Request into a Response", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/error-handler": "^4.4|^5.0|^6.0", "symfony\/event-dispatcher": "^5.0|^6.0", "symfony\/http-foundation": "^5.4.21|^6.2.7", "symfony\/polyfill-ctype": "^1.8", "symfony\/polyfill-php73": "^1.9", "symfony\/polyfill-php80": "^1.16", "psr\/log": "^1|^2" }, "require-dev": { "symfony\/browser-kit": "^5.4|^6.0", "symfony\/config": "^5.0|^6.0", "symfony\/console": "^4.4|^5.0|^6.0", "symfony\/css-selector": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^5.3|^6.0", "symfony\/dom-crawler": "^4.4|^5.0|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/finder": "^4.4|^5.0|^6.0", "symfony\/http-client-contracts": "^1.1|^2|^3", "symfony\/process": "^4.4|^5.0|^6.0", "symfony\/routing": "^4.4|^5.0|^6.0", "symfony\/stopwatch": "^4.4|^5.0|^6.0", "symfony\/translation": "^4.4|^5.0|^6.0", "symfony\/translation-contracts": "^1.1|^2|^3", "psr\/cache": "^1.0|^2.0|^3.0", "twig\/twig": "^2.13|^3.0.4" }, "provide": { "psr\/log-implementation": "1.0|2.0" }, "conflict": { "symfony\/browser-kit": "<5.4", "symfony\/cache": "<5.0", "symfony\/config": "<5.0", "symfony\/console": "<4.4", "symfony\/form": "<5.0", "symfony\/dependency-injection": "<5.3", "symfony\/doctrine-bridge": "<5.0", "symfony\/http-client": "<5.0", "symfony\/mailer": "<5.0", "symfony\/messenger": "<5.0", "symfony\/translation": "<5.0", "symfony\/twig-bridge": "<5.0", "symfony\/validator": "<5.0", "twig\/twig": "<2.13" }, "suggest": { "symfony\/browser-kit": "", "symfony\/config": "", "symfony\/console": "", "symfony\/dependency-injection": "" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\HttpKernel\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; /** * Allows the Kernel to be rebooted using a temporary cache directory. * * @author Nicolas Grekas */ interface RebootableInterface { /** * Reboots a kernel. * * The getBuildDir() method of a rebootable kernel should not be called * while building the container. Use the %kernel.build_dir% parameter instead. * * @param string|null $warmupDir pass null to reboot in the regular build directory */ public function reboot(?string $warmupDir); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; use _ContaoManager\Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use _ContaoManager\Symfony\Component\Config\Builder\ConfigBuilderGenerator; use _ContaoManager\Symfony\Component\Config\ConfigCache; use _ContaoManager\Symfony\Component\Config\Loader\DelegatingLoader; use _ContaoManager\Symfony\Component\Config\Loader\LoaderResolver; use _ContaoManager\Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PassConfig; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Dumper\PhpDumper; use _ContaoManager\Symfony\Component\DependencyInjection\Dumper\Preloader; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\ClosureLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\DirectoryLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\GlobFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\IniFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use _ContaoManager\Symfony\Component\ErrorHandler\DebugClassLoader; use _ContaoManager\Symfony\Component\Filesystem\Filesystem; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\BundleInterface; use _ContaoManager\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use _ContaoManager\Symfony\Component\HttpKernel\Config\FileLocator; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; use _ContaoManager\Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; // Help opcache.preload discover always-needed symbols \class_exists(ConfigCache::class); /** * The Kernel is the heart of the Symfony system. * * It manages an environment made of bundles. * * Environment names must always start with a letter and * they must only contain letters and numbers. * * @author Fabien Potencier */ abstract class Kernel implements KernelInterface, RebootableInterface, TerminableInterface { /** * @var array */ protected $bundles = []; protected $container; protected $environment; protected $debug; protected $booted = \false; protected $startTime; private $projectDir; private $warmupDir; private $requestStackSize = 0; private $resetServices = \false; /** * @var array */ private static $freshCache = []; public const VERSION = '5.4.35'; public const VERSION_ID = 50435; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; public const RELEASE_VERSION = 35; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2024'; public const END_OF_LIFE = '11/2025'; public function __construct(string $environment, bool $debug) { if (!($this->environment = $environment)) { throw new \InvalidArgumentException(\sprintf('Invalid environment provided to "%s": the environment cannot be empty.', \get_debug_type($this))); } $this->debug = $debug; } public function __clone() { $this->booted = \false; $this->container = null; $this->requestStackSize = 0; $this->resetServices = \false; } /** * {@inheritdoc} */ public function boot() { if (\true === $this->booted) { if (!$this->requestStackSize && $this->resetServices) { if ($this->container->has('services_resetter')) { $this->container->get('services_resetter')->reset(); } $this->resetServices = \false; if ($this->debug) { $this->startTime = \microtime(\true); } } return; } if (null === $this->container) { $this->preBoot(); } foreach ($this->getBundles() as $bundle) { $bundle->setContainer($this->container); $bundle->boot(); } $this->booted = \true; } /** * {@inheritdoc} */ public function reboot(?string $warmupDir) { $this->shutdown(); $this->warmupDir = $warmupDir; $this->boot(); } /** * {@inheritdoc} */ public function terminate(Request $request, Response $response) { if (\false === $this->booted) { return; } if ($this->getHttpKernel() instanceof TerminableInterface) { $this->getHttpKernel()->terminate($request, $response); } } /** * {@inheritdoc} */ public function shutdown() { if (\false === $this->booted) { return; } $this->booted = \false; foreach ($this->getBundles() as $bundle) { $bundle->shutdown(); $bundle->setContainer(null); } $this->container = null; $this->requestStackSize = 0; $this->resetServices = \false; } /** * {@inheritdoc} */ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = \true) { if (!$this->booted) { $container = $this->container ?? $this->preBoot(); if ($container->has('http_cache')) { return $container->get('http_cache')->handle($request, $type, $catch); } } $this->boot(); ++$this->requestStackSize; $this->resetServices = \true; try { return $this->getHttpKernel()->handle($request, $type, $catch); } finally { --$this->requestStackSize; } } /** * Gets an HTTP kernel from the container. * * @return HttpKernelInterface */ protected function getHttpKernel() { return $this->container->get('http_kernel'); } /** * {@inheritdoc} */ public function getBundles() { return $this->bundles; } /** * {@inheritdoc} */ public function getBundle(string $name) { if (!isset($this->bundles[$name])) { throw new \InvalidArgumentException(\sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, \get_debug_type($this))); } return $this->bundles[$name]; } /** * {@inheritdoc} */ public function locateResource(string $name) { if ('@' !== $name[0]) { throw new \InvalidArgumentException(\sprintf('A resource name must start with @ ("%s" given).', $name)); } if (\str_contains($name, '..')) { throw new \RuntimeException(\sprintf('File name "%s" contains invalid characters (..).', $name)); } $bundleName = \substr($name, 1); $path = ''; if (\str_contains($bundleName, '/')) { [$bundleName, $path] = \explode('/', $bundleName, 2); } $bundle = $this->getBundle($bundleName); if (\file_exists($file = $bundle->getPath() . '/' . $path)) { return $file; } throw new \InvalidArgumentException(\sprintf('Unable to find file "%s".', $name)); } /** * {@inheritdoc} */ public function getEnvironment() { return $this->environment; } /** * {@inheritdoc} */ public function isDebug() { return $this->debug; } /** * Gets the application root dir (path of the project's composer file). * * @return string */ public function getProjectDir() { if (null === $this->projectDir) { $r = new \ReflectionObject($this); if (!\is_file($dir = $r->getFileName())) { throw new \LogicException(\sprintf('Cannot auto-detect project dir for kernel of class "%s".', $r->name)); } $dir = $rootDir = \dirname($dir); while (!\is_file($dir . '/composer.json')) { if ($dir === \dirname($dir)) { return $this->projectDir = $rootDir; } $dir = \dirname($dir); } $this->projectDir = $dir; } return $this->projectDir; } /** * {@inheritdoc} */ public function getContainer() { if (!$this->container) { throw new \LogicException('Cannot retrieve the container from a non-booted kernel.'); } return $this->container; } /** * @internal */ public function setAnnotatedClassCache(array $annotatedClasses) { \file_put_contents(($this->warmupDir ?: $this->getBuildDir()) . '/annotations.map', \sprintf('debug && null !== $this->startTime ? $this->startTime : -\INF; } /** * {@inheritdoc} */ public function getCacheDir() { return $this->getProjectDir() . '/var/cache/' . $this->environment; } /** * {@inheritdoc} */ public function getBuildDir() : string { // Returns $this->getCacheDir() for backward compatibility return $this->getCacheDir(); } /** * {@inheritdoc} */ public function getLogDir() { return $this->getProjectDir() . '/var/log'; } /** * {@inheritdoc} */ public function getCharset() { return 'UTF-8'; } /** * Gets the patterns defining the classes to parse and cache for annotations. */ public function getAnnotatedClassesToCompile() : array { return []; } /** * Initializes bundles. * * @throws \LogicException if two bundles share a common name */ protected function initializeBundles() { // init bundles $this->bundles = []; foreach ($this->registerBundles() as $bundle) { $name = $bundle->getName(); if (isset($this->bundles[$name])) { throw new \LogicException(\sprintf('Trying to register two bundles with the same name "%s".', $name)); } $this->bundles[$name] = $bundle; } } /** * The extension point similar to the Bundle::build() method. * * Use this method to register compiler passes and manipulate the container during the building process. */ protected function build(ContainerBuilder $container) { } /** * Gets the container class. * * @return string * * @throws \InvalidArgumentException If the generated classname is invalid */ protected function getContainerClass() { $class = static::class; $class = \str_contains($class, "@anonymous\x00") ? \get_parent_class($class) . \str_replace('.', '_', ContainerBuilder::hash($class)) : $class; $class = \str_replace('\\', '_', $class) . \ucfirst($this->environment) . ($this->debug ? 'Debug' : '') . 'Container'; if (!\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $class)) { throw new \InvalidArgumentException(\sprintf('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names.', $this->environment)); } return $class; } /** * Gets the container's base class. * * All names except Container must be fully qualified. * * @return string */ protected function getContainerBaseClass() { return 'Container'; } /** * Initializes the service container. * * The built version of the service container is used when fresh, otherwise the * container is built. */ protected function initializeContainer() { $class = $this->getContainerClass(); $buildDir = $this->warmupDir ?: $this->getBuildDir(); $cache = new ConfigCache($buildDir.'/'.str_replace('_ContaoManager_', '', $class).'.php', $this->debug); $cachePath = $cache->getPath(); // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors $errorLevel = \error_reporting(\E_ALL ^ \E_WARNING); try { if (\is_file($cachePath) && \is_object($this->container = (include $cachePath)) && (!$this->debug || (self::$freshCache[$cachePath] ?? $cache->isFresh()))) { self::$freshCache[$cachePath] = \true; $this->container->set('kernel', $this); \error_reporting($errorLevel); return; } } catch (\Throwable $e) { } $oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : ($this->container = null); try { \is_dir($buildDir) ?: \mkdir($buildDir, 0777, \true); if ($lock = \fopen($cachePath . '.lock', 'w+')) { if (!\flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock) && !\flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) { \fclose($lock); $lock = null; } elseif (!\is_file($cachePath) || !\is_object($this->container = (include $cachePath))) { $this->container = null; } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { \flock($lock, \LOCK_UN); \fclose($lock); $this->container->set('kernel', $this); return; } } } catch (\Throwable $e) { } finally { \error_reporting($errorLevel); } if ($collectDeprecations = $this->debug && !\defined('_ContaoManager\\PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; $previousHandler = \set_error_handler(function ($type, $message, $file, $line) use(&$collectedLogs, &$previousHandler) { if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) { return $previousHandler ? $previousHandler($type, $message, $file, $line) : \false; } if (isset($collectedLogs[$message])) { ++$collectedLogs[$message]['count']; return null; } $backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5); // Clean the trace by removing first frames added by the error handler itself. for ($i = 0; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { $backtrace = \array_slice($backtrace, 1 + $i); break; } } for ($i = 0; isset($backtrace[$i]); ++$i) { if (!isset($backtrace[$i]['file'], $backtrace[$i]['line'], $backtrace[$i]['function'])) { continue; } if (!isset($backtrace[$i]['class']) && 'trigger_deprecation' === $backtrace[$i]['function']) { $file = $backtrace[$i]['file']; $line = $backtrace[$i]['line']; $backtrace = \array_slice($backtrace, 1 + $i); break; } } // Remove frames added by DebugClassLoader. for ($i = \count($backtrace) - 2; 0 < $i; --$i) { if (\in_array($backtrace[$i]['class'] ?? null, [DebugClassLoader::class, LegacyDebugClassLoader::class], \true)) { $backtrace = [$backtrace[$i + 1]]; break; } } $collectedLogs[$message] = ['type' => $type, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => [$backtrace[0]], 'count' => 1]; return null; }); } try { $container = null; $container = $this->buildContainer(); $container->compile(); } finally { if ($collectDeprecations) { \restore_error_handler(); @\file_put_contents($buildDir . '/' . $class . 'Deprecations.log', \serialize(\array_values($collectedLogs))); @\file_put_contents($buildDir . '/' . $class . 'Compiler.log', null !== $container ? \implode("\n", $container->getCompiler()->getLog()) : ''); } } $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); if ($lock) { \flock($lock, \LOCK_UN); \fclose($lock); } $this->container = (require $cachePath); $this->container->set('kernel', $this); if ($oldContainer && \get_class($this->container) !== $oldContainer->name) { // Because concurrent requests might still be using them, // old container files are not removed immediately, // but on a next dump of the container. static $legacyContainers = []; $oldContainerDir = \dirname($oldContainer->getFileName()); $legacyContainers[$oldContainerDir . '.legacy'] = \true; foreach (\glob(\dirname($oldContainerDir) . \DIRECTORY_SEPARATOR . '*.legacy', \GLOB_NOSORT) as $legacyContainer) { if (!isset($legacyContainers[$legacyContainer]) && @\unlink($legacyContainer)) { (new Filesystem())->remove(\substr($legacyContainer, 0, -7)); } } \touch($oldContainerDir . '.legacy'); } $preload = $this instanceof WarmableInterface ? (array) $this->warmUp($this->container->getParameter('kernel.cache_dir')) : []; if ($this->container->has('cache_warmer')) { $preload = \array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'))); } if ($preload && \method_exists(Preloader::class, 'append') && \file_exists($preloadFile = $buildDir . '/' . $class . '.preload.php')) { Preloader::append($preloadFile, $preload); } } /** * Returns the kernel parameters. * * @return array */ protected function getKernelParameters() { $bundles = []; $bundlesMetadata = []; foreach ($this->bundles as $name => $bundle) { $bundles[$name] = \get_class($bundle); $bundlesMetadata[$name] = ['path' => $bundle->getPath(), 'namespace' => $bundle->getNamespace()]; } return ['kernel.project_dir' => \realpath($this->getProjectDir()) ?: $this->getProjectDir(), 'kernel.environment' => $this->environment, 'kernel.runtime_environment' => '%env(default:kernel.environment:APP_RUNTIME_ENV)%', 'kernel.debug' => $this->debug, 'kernel.build_dir' => \realpath($buildDir = $this->warmupDir ?: $this->getBuildDir()) ?: $buildDir, 'kernel.cache_dir' => \realpath($cacheDir = $this->getCacheDir() === $this->getBuildDir() ? $this->warmupDir ?: $this->getCacheDir() : $this->getCacheDir()) ?: $cacheDir, 'kernel.logs_dir' => \realpath($this->getLogDir()) ?: $this->getLogDir(), 'kernel.bundles' => $bundles, 'kernel.bundles_metadata' => $bundlesMetadata, 'kernel.charset' => $this->getCharset(), 'kernel.container_class' => $this->getContainerClass()]; } /** * Builds the service container. * * @return ContainerBuilder * * @throws \RuntimeException */ protected function buildContainer() { foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir(), 'logs' => $this->getLogDir()] as $name => $dir) { if (!\is_dir($dir)) { if (\false === @\mkdir($dir, 0777, \true) && !\is_dir($dir)) { throw new \RuntimeException(\sprintf('Unable to create the "%s" directory (%s).', $name, $dir)); } } elseif (!\is_writable($dir)) { throw new \RuntimeException(\sprintf('Unable to write in the "%s" directory (%s).', $name, $dir)); } } $container = $this->getContainerBuilder(); $container->addObjectResource($this); $this->prepareContainer($container); if (null !== ($cont = $this->registerContainerConfiguration($this->getContainerLoader($container)))) { \trigger_deprecation('symfony/http-kernel', '5.3', 'Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.', \get_debug_type($this)); $container->merge($cont); } $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); return $container; } /** * Prepares the ContainerBuilder before it is compiled. */ protected function prepareContainer(ContainerBuilder $container) { $extensions = []; foreach ($this->bundles as $bundle) { if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); } if ($this->debug) { $container->addObjectResource($bundle); } } foreach ($this->bundles as $bundle) { $bundle->build($container); } $this->build($container); foreach ($container->getExtensions() as $extension) { $extensions[] = $extension->getAlias(); } // ensure these extensions are implicitly loaded $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); } /** * Gets a new ContainerBuilder instance used to build the service container. * * @return ContainerBuilder */ protected function getContainerBuilder() { $container = new ContainerBuilder(); $container->getParameterBag()->add($this->getKernelParameters()); if ($this instanceof ExtensionInterface) { $container->registerExtension($this); } if ($this instanceof CompilerPassInterface) { $container->addCompilerPass($this, PassConfig::TYPE_BEFORE_OPTIMIZATION, -10000); } if (\class_exists(\_ContaoManager\ProxyManager\Configuration::class) && \class_exists(RuntimeInstantiator::class)) { $container->setProxyInstantiator(new RuntimeInstantiator()); } return $container; } /** * Dumps the service container to PHP code in the cache. * * @param string $class The name of the class to generate * @param string $baseClass The name of the container's base class */ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, string $baseClass) { // cache the container $dumper = new PhpDumper($container); if (\class_exists(\_ContaoManager\ProxyManager\Configuration::class) && \class_exists(ProxyDumper::class)) { $dumper->setProxyDumper(new ProxyDumper()); } $content = $dumper->dump(['class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'as_files' => \true, 'debug' => $this->debug, 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : \time(), 'preload_classes' => \array_map('get_class', $this->bundles)]); $rootCode = \array_pop($content); $dir = \dirname($cache->getPath()) . '/'; $fs = new Filesystem(); foreach ($content as $file => $code) { $fs->dumpFile($dir . $file, $code); @\chmod($dir . $file, 0666 & ~\umask()); } $legacyFile = \dirname($dir . \key($content)) . '.legacy'; if (\is_file($legacyFile)) { @\unlink($legacyFile); } $cache->write($rootCode, $container->getResources()); } /** * Returns a loader for the container. * * @return DelegatingLoader */ protected function getContainerLoader(ContainerInterface $container) { $env = $this->getEnvironment(); $locator = new FileLocator($this); $resolver = new LoaderResolver([new XmlFileLoader($container, $locator, $env), new YamlFileLoader($container, $locator, $env), new IniFileLoader($container, $locator, $env), new PhpFileLoader($container, $locator, $env, \class_exists(ConfigBuilderGenerator::class) ? new ConfigBuilderGenerator($this->getBuildDir()) : null), new GlobFileLoader($container, $locator, $env), new DirectoryLoader($container, $locator, $env), new ClosureLoader($container, $env)]); return new DelegatingLoader($resolver); } private function preBoot() : ContainerInterface { if ($this->debug) { $this->startTime = \microtime(\true); } if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { if (\function_exists('putenv')) { \putenv('SHELL_VERBOSITY=3'); } $_ENV['SHELL_VERBOSITY'] = 3; $_SERVER['SHELL_VERBOSITY'] = 3; } $this->initializeBundles(); $this->initializeContainer(); $container = $this->container; if ($container->hasParameter('kernel.trusted_hosts') && ($trustedHosts = $container->getParameter('kernel.trusted_hosts'))) { Request::setTrustedHosts($trustedHosts); } if ($container->hasParameter('kernel.trusted_proxies') && $container->hasParameter('kernel.trusted_headers') && ($trustedProxies = $container->getParameter('kernel.trusted_proxies'))) { Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : \array_map('trim', \explode(',', $trustedProxies)), $container->getParameter('kernel.trusted_headers')); } return $container; } /** * Removes comments from a PHP source string. * * We don't use the PHP php_strip_whitespace() function * as we want the content to be readable and well-formatted. * * @return string */ public static function stripComments(string $source) { if (!\function_exists('token_get_all')) { return $source; } $rawChunk = ''; $output = ''; $tokens = \token_get_all($source); $ignoreSpace = \false; for ($i = 0; isset($tokens[$i]); ++$i) { $token = $tokens[$i]; if (!isset($token[1]) || 'b"' === $token) { $rawChunk .= $token; } elseif (\T_START_HEREDOC === $token[0]) { $output .= $rawChunk . $token[1]; do { $token = $tokens[++$i]; $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; } while (\T_END_HEREDOC !== $token[0]); $rawChunk = ''; } elseif (\T_WHITESPACE === $token[0]) { if ($ignoreSpace) { $ignoreSpace = \false; continue; } // replace multiple new lines with a single newline $rawChunk .= \preg_replace(['/\\n{2,}/S'], "\n", $token[1]); } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], \true)) { $rawChunk .= ' '; } $ignoreSpace = \true; } else { $rawChunk .= $token[1]; // The PHP-open tag already has a new-line if (\T_OPEN_TAG === $token[0]) { $ignoreSpace = \true; } else { $ignoreSpace = \false; } } } $output .= $rawChunk; unset($tokens, $rawChunk); \gc_mem_caches(); return $output; } /** * @return array */ public function __sleep() { return ['environment', 'debug']; } public function __wakeup() { if (\is_object($this->environment) || \is_object($this->debug)) { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } $this->__construct($this->environment, $this->debug); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * Signs URIs. * * @author Fabien Potencier */ class UriSigner { private $secret; private $parameter; /** * @param string $secret A secret * @param string $parameter Query string parameter to use */ public function __construct(string $secret, string $parameter = '_hash') { $this->secret = $secret; $this->parameter = $parameter; } /** * Signs a URI. * * The given URI is signed by adding the query string parameter * which value depends on the URI and the secret. * * @return string */ public function sign(string $uri) { $url = \parse_url($uri); if (isset($url['query'])) { \parse_str($url['query'], $params); } else { $params = []; } $uri = $this->buildUrl($url, $params); $params[$this->parameter] = $this->computeHash($uri); return $this->buildUrl($url, $params); } /** * Checks that a URI contains the correct hash. * * @return bool */ public function check(string $uri) { $url = \parse_url($uri); if (isset($url['query'])) { \parse_str($url['query'], $params); } else { $params = []; } if (empty($params[$this->parameter])) { return \false; } $hash = $params[$this->parameter]; unset($params[$this->parameter]); return \hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash); } public function checkRequest(Request $request) : bool { $qs = ($qs = $request->server->get('QUERY_STRING')) ? '?' . $qs : ''; // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) return $this->check($request->getSchemeAndHttpHost() . $request->getBaseUrl() . $request->getPathInfo() . $qs); } private function computeHash(string $uri) : string { return \base64_encode(\hash_hmac('sha256', $uri, $this->secret, \true)); } private function buildUrl(array $url, array $params = []) : string { \ksort($params, \SORT_STRING); $url['query'] = \http_build_query($params, '', '&'); $scheme = isset($url['scheme']) ? $url['scheme'] . '://' : ''; $host = $url['host'] ?? ''; $port = isset($url['port']) ? ':' . $url['port'] : ''; $user = $url['user'] ?? ''; $pass = isset($url['pass']) ? ':' . $url['pass'] : ''; $pass = $user || $pass ? "{$pass}@" : ''; $path = $url['path'] ?? ''; $query = isset($url['query']) && $url['query'] ? '?' . $url['query'] : ''; $fragment = isset($url['fragment']) ? '#' . $url['fragment'] : ''; return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Debug; use _ContaoManager\Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * Collects some data about event listeners. * * This event dispatcher delegates the dispatching to another one. * * @author Fabien Potencier */ class TraceableEventDispatcher extends BaseTraceableEventDispatcher { /** * {@inheritdoc} */ protected function beforeDispatch(string $eventName, object $event) { switch ($eventName) { case KernelEvents::REQUEST: $event->getRequest()->attributes->set('_stopwatch_token', \substr(\hash('sha256', \uniqid(\mt_rand(), \true)), 0, 6)); $this->stopwatch->openSection(); break; case KernelEvents::VIEW: case KernelEvents::RESPONSE: // stop only if a controller has been executed if ($this->stopwatch->isStarted('controller')) { $this->stopwatch->stop('controller'); } break; case KernelEvents::TERMINATE: $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); if (null === $sectionId) { break; } // There is a very special case when using built-in AppCache class as kernel wrapper, in the case // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception // which must be caught. try { $this->stopwatch->openSection($sectionId); } catch (\LogicException $e) { } break; } } /** * {@inheritdoc} */ protected function afterDispatch(string $eventName, object $event) { switch ($eventName) { case KernelEvents::CONTROLLER_ARGUMENTS: $this->stopwatch->start('controller', 'section'); break; case KernelEvents::RESPONSE: $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); if (null === $sectionId) { break; } $this->stopwatch->stopSection($sectionId); break; case KernelEvents::TERMINATE: // In the special case described in the `preDispatch` method above, the `$token` section // does not exist, then closing it throws an exception which must be caught. $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); if (null === $sectionId) { break; } try { $this->stopwatch->stopSection($sectionId); } catch (\LogicException $e) { } break; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\Debug; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** * Formats debug file links. * * @author Jérémy Romey * * @final */ class FileLinkFormatter { private const FORMATS = ['textmate' => 'txmt://open?url=file://%f&line=%l', 'macvim' => 'mvim://open?url=file://%f&line=%l', 'emacs' => 'emacs://open?url=file://%f&line=%l', 'sublime' => 'subl://open?url=file://%f&line=%l', 'phpstorm' => 'phpstorm://open?file=%f&line=%l', 'atom' => 'atom://core/open/file?filename=%f&line=%l', 'vscode' => 'vscode://file/%f:%l']; private $fileLinkFormat; private $requestStack; private $baseDir; private $urlFormat; /** * @param string|array|null $fileLinkFormat * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand */ public function __construct($fileLinkFormat = null, ?RequestStack $requestStack = null, ?string $baseDir = null, $urlFormat = null) { if (!\is_array($fileLinkFormat) && ($fileLinkFormat = (self::FORMATS[$fileLinkFormat] ?? $fileLinkFormat ?: \ini_get('xdebug.file_link_format')) ?: \get_cfg_var('xdebug.file_link_format'))) { $i = \strpos($f = $fileLinkFormat, '&', \max(\strrpos($f, '%f'), \strrpos($f, '%l'))) ?: \strlen($f); $fileLinkFormat = [\substr($f, 0, $i)] + \preg_split('/&([^>]++)>/', \substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); } $this->fileLinkFormat = $fileLinkFormat; $this->requestStack = $requestStack; $this->baseDir = $baseDir; $this->urlFormat = $urlFormat; } public function format(string $file, int $line) { if ($fmt = $this->getFileLinkFormat()) { for ($i = 1; isset($fmt[$i]); ++$i) { if (\str_starts_with($file, $k = $fmt[$i++])) { $file = \substr_replace($file, $fmt[$i], 0, \strlen($k)); break; } } return \strtr($fmt[0], ['%f' => $file, '%l' => $line]); } return \false; } /** * @internal */ public function __sleep() : array { $this->fileLinkFormat = $this->getFileLinkFormat(); return ['fileLinkFormat']; } /** * @internal */ public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString) : ?string { try { return $router->generate($routeName) . $queryString; } catch (\Throwable $e) { return null; } } private function getFileLinkFormat() { if ($this->fileLinkFormat) { return $this->fileLinkFormat; } if ($this->requestStack && $this->baseDir && $this->urlFormat) { $request = $this->requestStack->getMainRequest(); if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || ($this->urlFormat = ($this->urlFormat)()))) { return [$request->getSchemeAndHttpHost() . $this->urlFormat, $this->baseDir . \DIRECTORY_SEPARATOR, '']; } } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel; use _ContaoManager\Symfony\Component\BrowserKit\AbstractBrowser; use _ContaoManager\Symfony\Component\BrowserKit\CookieJar; use _ContaoManager\Symfony\Component\BrowserKit\History; use _ContaoManager\Symfony\Component\BrowserKit\Request as DomRequest; use _ContaoManager\Symfony\Component\BrowserKit\Response as DomResponse; use _ContaoManager\Symfony\Component\HttpFoundation\File\UploadedFile; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Simulates a browser and makes requests to an HttpKernel instance. * * @author Fabien Potencier * * @method Request getRequest() * @method Response getResponse() */ class HttpKernelBrowser extends AbstractBrowser { protected $kernel; private $catchExceptions = \true; /** * @param array $server The server parameters (equivalent of $_SERVER) */ public function __construct(HttpKernelInterface $kernel, array $server = [], ?History $history = null, ?CookieJar $cookieJar = null) { // These class properties must be set before calling the parent constructor, as it may depend on it. $this->kernel = $kernel; $this->followRedirects = \false; parent::__construct($server, $history, $cookieJar); } /** * Sets whether to catch exceptions when the kernel is handling a request. */ public function catchExceptions(bool $catchExceptions) { $this->catchExceptions = $catchExceptions; } /** * {@inheritdoc} * * @param Request $request * * @return Response */ protected function doRequest(object $request) { $response = $this->kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, $this->catchExceptions); if ($this->kernel instanceof TerminableInterface) { $this->kernel->terminate($request, $response); } return $response; } /** * {@inheritdoc} * * @param Request $request * * @return string */ protected function getScript(object $request) { $kernel = \var_export(\serialize($this->kernel), \true); $request = \var_export(\serialize($request), \true); $errorReporting = \error_reporting(); $requires = ''; foreach (\get_declared_classes() as $class) { if (0 === \strpos($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); $file = \dirname($r->getFileName(), 2) . '/autoload.php'; if (\file_exists($file)) { $requires .= 'require_once ' . \var_export($file, \true) . ";\n"; } } } if (!$requires) { throw new \RuntimeException('Composer autoloader not found.'); } $code = <<getHandleScript(); } protected function getHandleScript() { return <<<'EOF' $response = $kernel->handle($request); if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { $kernel->terminate($request, $response); } echo serialize($response); EOF; } /** * {@inheritdoc} * * @return Request */ protected function filterRequest(DomRequest $request) { $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $server = $request->getServer(), $request->getContent()); if (!isset($server['HTTP_ACCEPT'])) { $httpRequest->headers->remove('Accept'); } foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { $httpRequest->files->set($key, $value); } return $httpRequest; } /** * Filters an array of files. * * This method created test instances of UploadedFile so that the move() * method can be called on those instances. * * If the size of a file is greater than the allowed size (from php.ini) then * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. * * @see UploadedFile * * @return array */ protected function filterFiles(array $files) { $filtered = []; foreach ($files as $key => $value) { if (\is_array($value)) { $filtered[$key] = $this->filterFiles($value); } elseif ($value instanceof UploadedFile) { if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { $filtered[$key] = new UploadedFile('', $value->getClientOriginalName(), $value->getClientMimeType(), \UPLOAD_ERR_INI_SIZE, \true); } else { $filtered[$key] = new UploadedFile($value->getPathname(), $value->getClientOriginalName(), $value->getClientMimeType(), $value->getError(), \true); } } } return $filtered; } /** * {@inheritdoc} * * @param Response $response * * @return DomResponse */ protected function filterResponse(object $response) { // this is needed to support StreamedResponse \ob_start(); $response->sendContent(); $content = \ob_get_clean(); return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\StreamedResponse; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * StreamedResponseListener is responsible for sending the Response * to the client. * * @author Fabien Potencier * * @final */ class StreamedResponseListener implements EventSubscriberInterface { /** * Filters the Response. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $response = $event->getResponse(); if ($response instanceof StreamedResponse) { $response->send(); } } public static function getSubscribedEvents() : array { return [KernelEvents::RESPONSE => ['onKernelResponse', -1024]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * ResponseListener fixes the Response headers based on the Request. * * @author Fabien Potencier * * @final */ class ResponseListener implements EventSubscriberInterface { private $charset; private $addContentLanguageHeader; public function __construct(string $charset, bool $addContentLanguageHeader = \false) { $this->charset = $charset; $this->addContentLanguageHeader = $addContentLanguageHeader; } /** * Filters the Response. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $response = $event->getResponse(); if (null === $response->getCharset()) { $response->setCharset($this->charset); } if ($this->addContentLanguageHeader && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) { $response->headers->set('Content-Language', $event->getRequest()->getLocale()); } if ($event->getRequest()->attributes->get('_vary_by_language')) { $response->setVary('Accept-Language', \false); } $response->prepare($event->getRequest()); } public static function getSubscribedEvents() : array { return [KernelEvents::RESPONSE => 'onKernelResponse']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\KernelEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Routing\RequestContextAwareInterface; /** * Initializes the locale based on the current request. * * @author Fabien Potencier * * @final */ class LocaleListener implements EventSubscriberInterface { private $router; private $defaultLocale; private $requestStack; private $useAcceptLanguageHeader; private $enabledLocales; public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', ?RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = \false, array $enabledLocales = []) { $this->defaultLocale = $defaultLocale; $this->requestStack = $requestStack; $this->router = $router; $this->useAcceptLanguageHeader = $useAcceptLanguageHeader; $this->enabledLocales = $enabledLocales; } public function setDefaultLocale(KernelEvent $event) { $event->getRequest()->setDefaultLocale($this->defaultLocale); } public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); $this->setLocale($request); $this->setRouterContext($request); } public function onKernelFinishRequest(FinishRequestEvent $event) { if (null !== ($parentRequest = $this->requestStack->getParentRequest())) { $this->setRouterContext($parentRequest); } } private function setLocale(Request $request) { if ($locale = $request->attributes->get('_locale')) { $request->setLocale($locale); } elseif ($this->useAcceptLanguageHeader && $this->enabledLocales) { if ($request->getLanguages() && ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales))) { $request->setLocale($preferredLanguage); } $request->attributes->set('_vary_by_language', \true); } } private function setRouterContext(Request $request) { if (null !== $this->router) { $this->router->getContext()->setParameter('_locale', $request->getLocale()); } } public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => [ ['setDefaultLocale', 100], // must be registered after the Router to have access to the _locale ['onKernelRequest', 16], ], KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\HttpKernel\UriSigner; /** * Handles content fragments represented by special URIs. * * All URL paths starting with /_fragment are handled as * content fragments by this listener. * * Throws an AccessDeniedHttpException exception if the request * is not signed or if it is not an internal sub-request. * * @author Fabien Potencier * * @final */ class FragmentListener implements EventSubscriberInterface { private $signer; private $fragmentPath; /** * @param string $fragmentPath The path that triggers this listener */ public function __construct(UriSigner $signer, string $fragmentPath = '/_fragment') { $this->signer = $signer; $this->fragmentPath = $fragmentPath; } /** * Fixes request attributes when the path is '/_fragment'. * * @throws AccessDeniedHttpException if the request does not come from a trusted IP */ public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); if ($this->fragmentPath !== \rawurldecode($request->getPathInfo())) { return; } if ($request->attributes->has('_controller')) { // Is a sub-request: no need to parse _path but it should still be removed from query parameters as below. $request->query->remove('_path'); return; } if ($event->isMainRequest()) { $this->validateRequest($request); } \parse_str($request->query->get('_path', ''), $attributes); $request->attributes->add($attributes); $request->attributes->set('_route_params', \array_replace($request->attributes->get('_route_params', []), $attributes)); $request->query->remove('_path'); } protected function validateRequest(Request $request) { // is the Request safe? if (!$request->isMethodSafe()) { throw new AccessDeniedHttpException(); } // is the Request signed? if ($this->signer->checkRequest($request)) { return; } throw new AccessDeniedHttpException(); } public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => [['onKernelRequest', 48]]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionUtils; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * Sets the session onto the request on the "kernel.request" event and saves * it on the "kernel.response" event. * * In addition, if the session has been started it overrides the Cache-Control * header in such a way that all caching is disabled in that case. * If you have a scenario where caching responses with session information in * them makes sense, you can disable this behaviour by setting the header * AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response. * * @author Johannes M. Schmitt * @author Tobias Schultze */ abstract class AbstractSessionListener implements EventSubscriberInterface, ResetInterface { public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl'; /** * @internal */ protected $container; private $sessionUsageStack = []; private $debug; /** * @var array */ private $sessionOptions; /** * @internal */ public function __construct(?ContainerInterface $container = null, bool $debug = \false, array $sessionOptions = []) { $this->container = $container; $this->debug = $debug; $this->sessionOptions = $sessionOptions; } /** * @internal */ public function onKernelRequest(RequestEvent $event) { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); if (!$request->hasSession()) { // This variable prevents calling `$this->getSession()` twice in case the Request (and the below factory) is cloned $sess = null; $request->setSessionFactory(function () use(&$sess, $request) { if (!$sess) { $sess = $this->getSession(); $request->setSession($sess); /* * For supporting sessions in php runtime with runners like roadrunner or swoole, the session * cookie needs to be read from the cookie bag and set on the session storage. * * Do not set it when a native php session is active. */ if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== \session_status()) { $sessionId = $sess->getId() ?: $request->cookies->get($sess->getName(), ''); $sess->setId($sessionId); } } return $sess; }); } $session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null; $this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : 0; } /** * @internal */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest() || !$this->container->has('initialized_session') && !$event->getRequest()->hasSession()) { return; } $response = $event->getResponse(); $autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER); // Always remove the internal header if present $response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER); if (!($session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : ($event->getRequest()->hasSession() ? $event->getRequest()->getSession() : null))) { return; } if ($session->isStarted()) { /* * Saves the session, in case it is still open, before sending the response/headers. * * This ensures several things in case the developer did not save the session explicitly: * * * If a session save handler without locking is used, it ensures the data is available * on the next request, e.g. after a redirect. PHPs auto-save at script end via * session_register_shutdown is executed after fastcgi_finish_request. So in this case * the data could be missing the next request because it might not be saved the moment * the new request is processed. * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like * the one above. But by saving the session before long-running things in the terminate event, * we ensure the session is not blocked longer than needed. * * When regenerating the session ID no locking is involved in PHPs session design. See * https://bugs.php.net/61470 for a discussion. So in this case, the session must * be saved anyway before sending the headers with the new session ID. Otherwise session * data could get lost again for concurrent requests with the new ID. One result could be * that you get logged out after just logging in. * * This listener should be executed as one of the last listeners, so that previous listeners * can still operate on the open session. This prevents the overhead of restarting it. * Listeners after closing the session can still work with the session as usual because * Symfonys session implementation starts the session on demand. So writing to it after * it is saved will just restart it. */ $session->save(); /* * For supporting sessions in php runtime with runners like roadrunner or swoole the session * cookie need to be written on the response object and should not be written by PHP itself. */ $sessionName = $session->getName(); $sessionId = $session->getId(); $sessionOptions = $this->getSessionOptions($this->sessionOptions); $sessionCookiePath = $sessionOptions['cookie_path'] ?? '/'; $sessionCookieDomain = $sessionOptions['cookie_domain'] ?? null; $sessionCookieSecure = $sessionOptions['cookie_secure'] ?? \false; $sessionCookieHttpOnly = $sessionOptions['cookie_httponly'] ?? \true; $sessionCookieSameSite = $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX; $sessionUseCookies = $sessionOptions['use_cookies'] ?? \true; SessionUtils::popSessionCookie($sessionName, $sessionId); if ($sessionUseCookies) { $request = $event->getRequest(); $requestSessionCookieId = $request->cookies->get($sessionName); $isSessionEmpty = $session->isEmpty() && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions if ($requestSessionCookieId && $isSessionEmpty) { // PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument // which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy // when the session gets invalidated (for example on logout) so we must handle this case here too // otherwise we would send two Set-Cookie headers back with the response SessionUtils::popSessionCookie($sessionName, 'deleted'); $response->headers->clearCookie($sessionName, $sessionCookiePath, $sessionCookieDomain, $sessionCookieSecure, $sessionCookieHttpOnly, $sessionCookieSameSite); } elseif ($sessionId !== $requestSessionCookieId && !$isSessionEmpty) { $expire = 0; $lifetime = $sessionOptions['cookie_lifetime'] ?? null; if ($lifetime) { $expire = \time() + $lifetime; } $response->headers->setCookie(Cookie::create($sessionName, $sessionId, $expire, $sessionCookiePath, $sessionCookieDomain, $sessionCookieSecure, $sessionCookieHttpOnly, \false, $sessionCookieSameSite)); } } } if ($session instanceof Session ? $session->getUsageIndex() === \end($this->sessionUsageStack) : !$session->isStarted()) { return; } if ($autoCacheControl) { $maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge(); $response->setExpires(new \DateTimeImmutable('+' . $maxAge . ' seconds'))->setPrivate()->setMaxAge($maxAge)->headers->addCacheControlDirective('must-revalidate'); } if (!$event->getRequest()->attributes->get('_stateless', \false)) { return; } if ($this->debug) { throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.'); } if ($this->container->has('logger')) { $this->container->get('logger')->warning('Session was used while the request was declared stateless.'); } } /** * @internal */ public function onFinishRequest(FinishRequestEvent $event) { if ($event->isMainRequest()) { \array_pop($this->sessionUsageStack); } } /** * @internal */ public function onSessionUsage() : void { if (!$this->debug) { return; } if ($this->container && $this->container->has('session_collector')) { $this->container->get('session_collector')(); } if (!($requestStack = $this->container && $this->container->has('request_stack') ? $this->container->get('request_stack') : null)) { return; } $stateless = \false; $clonedRequestStack = clone $requestStack; while (null !== ($request = $clonedRequestStack->pop()) && !$stateless) { $stateless = $request->attributes->get('_stateless'); } if (!$stateless) { return; } if (!($session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $requestStack->getCurrentRequest()->getSession())) { return; } if ($session->isStarted()) { $session->save(); } throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.'); } /** * @internal */ public static function getSubscribedEvents() : array { return [ KernelEvents::REQUEST => ['onKernelRequest', 128], // low priority to come after regular response listeners, but higher than StreamedResponseListener KernelEvents::RESPONSE => ['onKernelResponse', -1000], KernelEvents::FINISH_REQUEST => ['onFinishRequest'], ]; } /** * @internal */ public function reset() : void { if (\PHP_SESSION_ACTIVE === \session_status()) { \session_abort(); } \session_unset(); $_SESSION = []; if (!\headers_sent()) { // session id can only be reset when no headers were so we check for headers_sent first \session_id(''); } } /** * Gets the session object. * * @internal * * @return SessionInterface|null */ protected abstract function getSession(); private function getSessionOptions(array $sessionOptions) : array { $mergedSessionOptions = []; foreach (\session_get_cookie_params() as $key => $value) { $mergedSessionOptions['cookie_' . $key] = $value; } foreach ($sessionOptions as $key => $value) { // do the same logic as in the NativeSessionStorage if ('cookie_secure' === $key && 'auto' === $value) { continue; } $mergedSessionOptions[$key] = $value; } return $mergedSessionOptions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; \trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated, use "%s" instead.', TestSessionListener::class, SessionListener::class); /** * Sets the session in the request. * * @author Fabien Potencier * * @final * * @deprecated since Symfony 5.4, use SessionListener instead */ class TestSessionListener extends AbstractTestSessionListener { private $container; public function __construct(ContainerInterface $container, array $sessionOptions = []) { $this->container = $container; parent::__construct($sessionOptions); } protected function getSession() : ?SessionInterface { if ($this->container->has('session')) { return $this->container->get('session'); } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * Validates Requests. * * @author Magnus Nordlander * * @final */ class ValidateRequestListener implements EventSubscriberInterface { /** * Performs the validation. */ public function onKernelRequest(RequestEvent $event) { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); if ($request::getTrustedProxies()) { $request->getClientIps(); } $request->getHost(); } /** * {@inheritdoc} */ public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => [['onKernelRequest', 256]]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * Ensures that the application is not indexed by search engines. * * @author Gary PEGEOT */ class DisallowRobotsIndexingListener implements EventSubscriberInterface { private const HEADER_NAME = 'X-Robots-Tag'; public function onResponse(ResponseEvent $event) : void { if (!$event->getResponse()->headers->has(static::HEADER_NAME)) { $event->getResponse()->headers->set(static::HEADER_NAME, 'noindex'); } } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [KernelEvents::RESPONSE => ['onResponse', -255]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * Adds configured formats to each request. * * @author Gildas Quemener * * @final */ class AddRequestFormatsListener implements EventSubscriberInterface { protected $formats; public function __construct(array $formats) { $this->formats = $formats; } /** * Adds request formats. */ public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); foreach ($this->formats as $format => $mimeTypes) { $request->setFormat($format, $mimeTypes); } } /** * {@inheritdoc} */ public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => ['onKernelRequest', 100]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestMatcherInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Session; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\TerminateEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\HttpKernel\Profiler\Profile; use _ContaoManager\Symfony\Component\HttpKernel\Profiler\Profiler; /** * ProfilerListener collects data for the current request by listening to the kernel events. * * @author Fabien Potencier * * @final */ class ProfilerListener implements EventSubscriberInterface { protected $profiler; protected $matcher; protected $onlyException; protected $onlyMainRequests; protected $exception; /** @var \SplObjectStorage */ protected $profiles; protected $requestStack; protected $collectParameter; /** @var \SplObjectStorage */ protected $parents; /** * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise * @param bool $onlyMainRequests True if the profiler only collects data when the request is the main request, false otherwise */ public function __construct(Profiler $profiler, RequestStack $requestStack, ?RequestMatcherInterface $matcher = null, bool $onlyException = \false, bool $onlyMainRequests = \false, ?string $collectParameter = null) { $this->profiler = $profiler; $this->matcher = $matcher; $this->onlyException = $onlyException; $this->onlyMainRequests = $onlyMainRequests; $this->profiles = new \SplObjectStorage(); $this->parents = new \SplObjectStorage(); $this->requestStack = $requestStack; $this->collectParameter = $collectParameter; } /** * Handles the onKernelException event. */ public function onKernelException(ExceptionEvent $event) { if ($this->onlyMainRequests && !$event->isMainRequest()) { return; } $this->exception = $event->getThrowable(); } /** * Handles the onKernelResponse event. */ public function onKernelResponse(ResponseEvent $event) { if ($this->onlyMainRequests && !$event->isMainRequest()) { return; } if ($this->onlyException && null === $this->exception) { return; } $request = $event->getRequest(); if (null !== $this->collectParameter && null !== ($collectParameterValue = $request->get($this->collectParameter))) { \true === $collectParameterValue || \filter_var($collectParameterValue, \FILTER_VALIDATE_BOOLEAN) ? $this->profiler->enable() : $this->profiler->disable(); } $exception = $this->exception; $this->exception = null; if (null !== $this->matcher && !$this->matcher->matches($request)) { return; } $session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null; if ($session instanceof Session) { $usageIndexValue = $usageIndexReference =& $session->getUsageIndex(); $usageIndexReference = \PHP_INT_MIN; } try { if (!($profile = $this->profiler->collect($request, $event->getResponse(), $exception))) { return; } } finally { if ($session instanceof Session) { $usageIndexReference = $usageIndexValue; } } $this->profiles[$request] = $profile; $this->parents[$request] = $this->requestStack->getParentRequest(); } public function onKernelTerminate(TerminateEvent $event) { // attach children to parents foreach ($this->profiles as $request) { if (null !== ($parentRequest = $this->parents[$request])) { if (isset($this->profiles[$parentRequest])) { $this->profiles[$parentRequest]->addChild($this->profiles[$request]); } } } // save profiles foreach ($this->profiles as $request) { $this->profiler->saveProfile($this->profiles[$request]); } $this->profiles = new \SplObjectStorage(); $this->parents = new \SplObjectStorage(); } public static function getSubscribedEvents() : array { return [KernelEvents::RESPONSE => ['onKernelResponse', -100], KernelEvents::EXCEPTION => ['onKernelException', 0], KernelEvents::TERMINATE => ['onKernelTerminate', -1024]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\HttpCache; use _ContaoManager\Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. * * @author Fabien Potencier * * @final */ class SurrogateListener implements EventSubscriberInterface { private $surrogate; public function __construct(?SurrogateInterface $surrogate = null) { $this->surrogate = $surrogate; } /** * Filters the Response. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $kernel = $event->getKernel(); $surrogate = $this->surrogate; if ($kernel instanceof HttpCache) { $surrogate = $kernel->getSurrogate(); if (null !== $this->surrogate && $this->surrogate->getName() !== $surrogate->getName()) { $surrogate = $this->surrogate; } } if (null === $surrogate) { return; } $surrogate->addSurrogateControl($event->getResponse()); } public static function getSubscribedEvents() : array { return [KernelEvents::RESPONSE => 'onKernelResponse']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\Console\Event\ConsoleEvent; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorHandler; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\KernelEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; /** * Configures errors and exceptions handlers. * * @author Nicolas Grekas * * @final * * @internal since Symfony 5.3 */ class DebugHandlersListener implements EventSubscriberInterface { private $earlyHandler; private $exceptionHandler; private $logger; private $deprecationLogger; private $levels; private $throwAt; private $scream; private $scope; private $firstCall = \true; private $hasTerminatedWithException; /** * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged * @param bool $scope Enables/disables scoping mode */ public function __construct(?callable $exceptionHandler = null, ?LoggerInterface $logger = null, $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = \true, $scope = \true, $deprecationLogger = null, $fileLinkFormat = null) { if (!\is_bool($scope)) { \trigger_deprecation('symfony/http-kernel', '5.4', 'Passing a $fileLinkFormat is deprecated.'); $scope = $deprecationLogger; $deprecationLogger = $fileLinkFormat; } $handler = \set_exception_handler('is_int'); $this->earlyHandler = \is_array($handler) ? $handler[0] : null; \restore_exception_handler(); $this->exceptionHandler = $exceptionHandler; $this->logger = $logger; $this->levels = $levels ?? \E_ALL; $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); $this->scream = $scream; $this->scope = $scope; $this->deprecationLogger = $deprecationLogger; } /** * Configures the error handler. */ public function configure(?object $event = null) { if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true)) { return; } if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) { return; } $this->firstCall = $this->hasTerminatedWithException = \false; $handler = \set_exception_handler('is_int'); $handler = \is_array($handler) ? $handler[0] : null; \restore_exception_handler(); if (!$handler instanceof ErrorHandler) { $handler = $this->earlyHandler; } if ($handler instanceof ErrorHandler) { if ($this->logger || $this->deprecationLogger) { $this->setDefaultLoggers($handler); if (\is_array($this->levels)) { $levels = 0; foreach ($this->levels as $type => $log) { $levels |= $type; } } else { $levels = $this->levels; } if ($this->scream) { $handler->screamAt($levels); } if ($this->scope) { $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); } else { $handler->scopeAt(0, \true); } $this->logger = $this->deprecationLogger = $this->levels = null; } if (null !== $this->throwAt) { $handler->throwAt($this->throwAt, \true); } } if (!$this->exceptionHandler) { if ($event instanceof KernelEvent) { if (\method_exists($kernel = $event->getKernel(), 'terminateWithException')) { $request = $event->getRequest(); $hasRun =& $this->hasTerminatedWithException; $this->exceptionHandler = static function (\Throwable $e) use($kernel, $request, &$hasRun) { if ($hasRun) { throw $e; } $hasRun = \true; $kernel->terminateWithException($e, $request); }; } } elseif ($event instanceof ConsoleEvent && ($app = $event->getCommand()->getApplication())) { $output = $event->getOutput(); if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->exceptionHandler = static function (\Throwable $e) use($app, $output) { $app->renderThrowable($e, $output); }; } } if ($this->exceptionHandler) { if ($handler instanceof ErrorHandler) { $handler->setExceptionHandler($this->exceptionHandler); } $this->exceptionHandler = null; } } private function setDefaultLoggers(ErrorHandler $handler) : void { if (\is_array($this->levels)) { $levelsDeprecatedOnly = []; $levelsWithoutDeprecated = []; foreach ($this->levels as $type => $log) { if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { $levelsDeprecatedOnly[$type] = $log; } else { $levelsWithoutDeprecated[$type] = $log; } } } else { $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; } $defaultLoggerLevels = $this->levels; if ($this->deprecationLogger && $levelsDeprecatedOnly) { $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); $defaultLoggerLevels = $levelsWithoutDeprecated; } if ($this->logger && $defaultLoggerLevels) { $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); } } public static function getSubscribedEvents() : array { $events = [KernelEvents::REQUEST => ['configure', 2048]]; if (\defined('Symfony\\Component\\Console\\ConsoleEvents::COMMAND')) { $events[ConsoleEvents::COMMAND] = ['configure', 2048]; } return $events; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; \trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated use "%s" instead.', AbstractTestSessionListener::class, AbstractSessionListener::class); /** * TestSessionListener. * * Saves session in test environment. * * @author Bulat Shakirzyanov * @author Fabien Potencier * * @internal * * @deprecated since Symfony 5.4, use AbstractSessionListener instead */ abstract class AbstractTestSessionListener implements EventSubscriberInterface { private $sessionId; private $sessionOptions; public function __construct(array $sessionOptions = []) { $this->sessionOptions = $sessionOptions; } public function onKernelRequest(RequestEvent $event) { if (!$event->isMainRequest()) { return; } // bootstrap the session if ($event->getRequest()->hasSession()) { $session = $event->getRequest()->getSession(); } elseif (!($session = $this->getSession())) { return; } $cookies = $event->getRequest()->cookies; if ($cookies->has($session->getName())) { $this->sessionId = $cookies->get($session->getName()); $session->setId($this->sessionId); } } /** * Checks if session was initialized and saves if current request is the main request * Runs on 'kernel.response' in test environment. */ public function onKernelResponse(ResponseEvent $event) { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); if (!$request->hasSession()) { return; } $session = $request->getSession(); if ($wasStarted = $session->isStarted()) { $session->save(); } if ($session instanceof Session ? !$session->isEmpty() || null !== $this->sessionId && $session->getId() !== $this->sessionId : $wasStarted) { $params = \session_get_cookie_params() + ['samesite' => null]; foreach ($this->sessionOptions as $k => $v) { if (\str_starts_with($k, 'cookie_')) { $params[\substr($k, 7)] = $v; } } foreach ($event->getResponse()->headers->getCookies() as $cookie) { if ($session->getName() === $cookie->getName() && $params['path'] === $cookie->getPath() && $params['domain'] == $cookie->getDomain()) { return; } } $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : \time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'], \false, $params['samesite'] ?: null)); $this->sessionId = $session->getId(); } } public static function getSubscribedEvents() : array { return [ KernelEvents::REQUEST => ['onKernelRequest', 127], // AFTER SessionListener KernelEvents::RESPONSE => ['onKernelResponse', -128], ]; } /** * Gets the session object. * * @return SessionInterface|null */ protected abstract function getSession(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Contracts\Translation\LocaleAwareInterface; /** * Pass the current locale to the provided services. * * @author Pierre Bobiet */ class LocaleAwareListener implements EventSubscriberInterface { private $localeAwareServices; private $requestStack; /** * @param iterable $localeAwareServices */ public function __construct(iterable $localeAwareServices, RequestStack $requestStack) { $this->localeAwareServices = $localeAwareServices; $this->requestStack = $requestStack; } public function onKernelRequest(RequestEvent $event) : void { $this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale()); } public function onKernelFinishRequest(FinishRequestEvent $event) : void { if (null === ($parentRequest = $this->requestStack->getParentRequest())) { foreach ($this->localeAwareServices as $service) { $service->setLocale($event->getRequest()->getDefaultLocale()); } return; } $this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale()); } public static function getSubscribedEvents() { return [ // must be registered after the Locale listener KernelEvents::REQUEST => [['onKernelRequest', 15]], KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]], ]; } private function setLocale(string $locale, string $defaultLocale) : void { foreach ($this->localeAwareServices as $service) { try { $service->setLocale($locale); } catch (\InvalidArgumentException $e) { $service->setLocale($defaultLocale); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\ClonerInterface; use _ContaoManager\Symfony\Component\VarDumper\Dumper\DataDumperInterface; use _ContaoManager\Symfony\Component\VarDumper\Server\Connection; use _ContaoManager\Symfony\Component\VarDumper\VarDumper; /** * Configures dump() handler. * * @author Nicolas Grekas */ class DumpListener implements EventSubscriberInterface { private $cloner; private $dumper; private $connection; public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, ?Connection $connection = null) { $this->cloner = $cloner; $this->dumper = $dumper; $this->connection = $connection; } public function configure() { $cloner = $this->cloner; $dumper = $this->dumper; $connection = $this->connection; VarDumper::setHandler(static function ($var) use($cloner, $dumper, $connection) { $data = $cloner->cloneVar($var); if (!$connection || !$connection->write($data)) { $dumper->dump($data); } }); } public static function getSubscribedEvents() { if (!\class_exists(ConsoleEvents::class)) { return []; } // Register early to have a working dump() as early as possible return [ConsoleEvents::COMMAND => ['configure', 1024]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\FlattenException; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\ResponseEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpException; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use _ContaoManager\Symfony\Component\HttpKernel\HttpKernelInterface; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\HttpKernel\Log\DebugLoggerInterface; /** * @author Fabien Potencier */ class ErrorListener implements EventSubscriberInterface { protected $controller; protected $logger; protected $debug; /** * @var array|null}> */ protected $exceptionsMapping; /** * @param array|null}> $exceptionsMapping */ public function __construct($controller, ?LoggerInterface $logger = null, bool $debug = \false, array $exceptionsMapping = []) { $this->controller = $controller; $this->logger = $logger; $this->debug = $debug; $this->exceptionsMapping = $exceptionsMapping; } public function logKernelException(ExceptionEvent $event) { $throwable = $event->getThrowable(); $logLevel = null; foreach ($this->exceptionsMapping as $class => $config) { if ($throwable instanceof $class && $config['log_level']) { $logLevel = $config['log_level']; break; } } foreach ($this->exceptionsMapping as $class => $config) { if (!$throwable instanceof $class || !$config['status_code']) { continue; } if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() !== $config['status_code']) { $headers = $throwable instanceof HttpExceptionInterface ? $throwable->getHeaders() : []; $throwable = new HttpException($config['status_code'], $throwable->getMessage(), $throwable, $headers); $event->setThrowable($throwable); } break; } $e = FlattenException::createFromThrowable($throwable); $this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel); } public function onKernelException(ExceptionEvent $event) { if (null === $this->controller) { return; } $throwable = $event->getThrowable(); $request = $this->duplicateRequest($throwable, $event->getRequest()); try { $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, \false); } catch (\Exception $e) { $f = FlattenException::createFromThrowable($e); $this->logException($e, \sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine())); $prev = $e; do { if ($throwable === ($wrapper = $prev)) { throw $e; } } while ($prev = $wrapper->getPrevious()); $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous'); $prev->setAccessible(\true); $prev->setValue($wrapper, $throwable); throw $e; } $event->setResponse($response); if ($this->debug) { $event->getRequest()->attributes->set('_remove_csp_headers', \true); } } public function removeCspHeader(ResponseEvent $event) : void { if ($this->debug && $event->getRequest()->attributes->get('_remove_csp_headers', \false)) { $event->getResponse()->headers->remove('Content-Security-Policy'); } } public function onControllerArguments(ControllerArgumentsEvent $event) { $e = $event->getRequest()->attributes->get('exception'); if (!$e instanceof \Throwable || \false === ($k = \array_search($e, $event->getArguments(), \true))) { return; } $r = new \ReflectionFunction(\Closure::fromCallable($event->getController())); $r = $r->getParameters()[$k] ?? null; if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || \in_array($r->getName(), [FlattenException::class, LegacyFlattenException::class], \true))) { $arguments = $event->getArguments(); $arguments[$k] = FlattenException::createFromThrowable($e); $event->setArguments($arguments); } } public static function getSubscribedEvents() : array { return [KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments', KernelEvents::EXCEPTION => [['logKernelException', 0], ['onKernelException', -128]], KernelEvents::RESPONSE => ['removeCspHeader', -128]]; } /** * Logs an exception. */ protected function logException(\Throwable $exception, string $message, ?string $logLevel = null) : void { if (null !== $this->logger) { if (null !== $logLevel) { $this->logger->log($logLevel, $message, ['exception' => $exception]); } elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { $this->logger->critical($message, ['exception' => $exception]); } else { $this->logger->error($message, ['exception' => $exception]); } } } /** * Clones the request for the exception. */ protected function duplicateRequest(\Throwable $exception, Request $request) : Request { $attributes = ['_controller' => $this->controller, 'exception' => $exception, 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null]; $request = $request->duplicate(null, null, $attributes); $request->setMethod('GET'); return $request; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; /** * Sets the session in the request. * * When the passed container contains a "session_storage" entry which * holds a NativeSessionStorage instance, the "cookie_secure" option * will be set to true whenever the current main request is secure. * * @author Fabien Potencier * * @final */ class SessionListener extends AbstractSessionListener { public function onKernelRequest(RequestEvent $event) { parent::onKernelRequest($event); if (!$event->isMainRequest() || !$this->container->has('session') && !$this->container->has('session_factory')) { return; } if ($this->container->has('session_storage') && ($storage = $this->container->get('session_storage')) instanceof NativeSessionStorage && ($mainRequest = $this->container->get('request_stack')->getMainRequest()) && $mainRequest->isSecure()) { $storage->setOptions(['cookie_secure' => \true]); } } protected function getSession() : ?SessionInterface { if ($this->container->has('session')) { return $this->container->get('session'); } if ($this->container->has('session_factory')) { return $this->container->get('session_factory')->createSession(); } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpKernel\EventListener; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Event\ExceptionEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\FinishRequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Event\RequestEvent; use _ContaoManager\Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use _ContaoManager\Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use _ContaoManager\Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; use _ContaoManager\Symfony\Component\HttpKernel\KernelEvents; use _ContaoManager\Symfony\Component\Routing\Exception\MethodNotAllowedException; use _ContaoManager\Symfony\Component\Routing\Exception\NoConfigurationException; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; use _ContaoManager\Symfony\Component\Routing\Matcher\RequestMatcherInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\UrlMatcherInterface; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Routing\RequestContextAwareInterface; /** * Initializes the context from the request and sets request attributes based on a matching route. * * @author Fabien Potencier * @author Yonel Ceruto * * @final */ class RouterListener implements EventSubscriberInterface { private $matcher; private $context; private $logger; private $requestStack; private $projectDir; private $debug; /** * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) * * @throws \InvalidArgumentException */ public function __construct($matcher, RequestStack $requestStack, ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $projectDir = null, bool $debug = \true) { if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); } if (null === $context && !$matcher instanceof RequestContextAwareInterface) { throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); } $this->matcher = $matcher; $this->context = $context ?? $matcher->getContext(); $this->requestStack = $requestStack; $this->logger = $logger; $this->projectDir = $projectDir; $this->debug = $debug; } private function setCurrentRequest(?Request $request = null) { if (null !== $request) { try { $this->context->fromRequest($request); } catch (\UnexpectedValueException $e) { throw new BadRequestHttpException($e->getMessage(), $e, $e->getCode()); } } } /** * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator * operates on the correct context again. */ public function onKernelFinishRequest(FinishRequestEvent $event) { $this->setCurrentRequest($this->requestStack->getParentRequest()); } public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); $this->setCurrentRequest($request); if ($request->attributes->has('_controller')) { // routing is already done return; } // add attributes based on the request (routing) try { // matching a request is more powerful than matching a URL path + context, so try that first if ($this->matcher instanceof RequestMatcherInterface) { $parameters = $this->matcher->matchRequest($request); } else { $parameters = $this->matcher->match($request->getPathInfo()); } if (null !== $this->logger) { $this->logger->info('Matched route "{route}".', ['route' => $parameters['_route'] ?? 'n/a', 'route_parameters' => $parameters, 'request_uri' => $request->getUri(), 'method' => $request->getMethod()]); } $request->attributes->add($parameters); unset($parameters['_route'], $parameters['_controller']); $request->attributes->set('_route_params', $parameters); } catch (ResourceNotFoundException $e) { $message = \sprintf('No route found for "%s %s"', $request->getMethod(), $request->getUriForPath($request->getPathInfo())); if ($referer = $request->headers->get('referer')) { $message .= \sprintf(' (from "%s")', $referer); } throw new NotFoundHttpException($message, $e); } catch (MethodNotAllowedException $e) { $message = \sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getUriForPath($request->getPathInfo()), \implode(', ', $e->getAllowedMethods())); throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); } } public function onKernelException(ExceptionEvent $event) { if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) { return; } if ($e->getPrevious() instanceof NoConfigurationException) { $event->setResponse($this->createWelcomeResponse()); } } public static function getSubscribedEvents() : array { return [KernelEvents::REQUEST => [['onKernelRequest', 32]], KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]], KernelEvents::EXCEPTION => ['onKernelException', -64]]; } private function createWelcomeResponse() : Response { $version = Kernel::VERSION; $projectDir = \realpath((string) $this->projectDir) . \DIRECTORY_SEPARATOR; $docVersion = \substr(Kernel::VERSION, 0, 3); \ob_start(); include \dirname(__DIR__) . '/Resources/welcome.html.php'; return new Response(\ob_get_clean(), Response::HTTP_NOT_FOUND); } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; use _ContaoManager\Symfony\Component\Yaml\Tag\TaggedValue; /** * Parser parses YAML strings to convert them to PHP arrays. * * @author Fabien Potencier * * @final */ class Parser { public const TAG_PATTERN = '(?P![\\w!.\\/:-]+)'; public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\\||>)(?P\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?P +#.*)?'; public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; private $filename; private $offset = 0; private $numberOfParsedLines = 0; private $totalNumberOfLines; private $lines = []; private $currentLineNb = -1; private $currentLine = ''; private $refs = []; private $skippedLineNumbers = []; private $locallySkippedLineNumbers = []; private $refsBeingParsed = []; /** * Parses a YAML file into a PHP value. * * @param string $filename The path to the YAML file to be parsed * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior * * @return mixed * * @throws ParseException If the file could not be read or the YAML is not valid */ public function parseFile(string $filename, int $flags = 0) { if (!\is_file($filename)) { throw new ParseException(\sprintf('File "%s" does not exist.', $filename)); } if (!\is_readable($filename)) { throw new ParseException(\sprintf('File "%s" cannot be read.', $filename)); } $this->filename = $filename; try { return $this->parse(\file_get_contents($filename), $flags); } finally { $this->filename = null; } } /** * Parses a YAML string to a PHP value. * * @param string $value A YAML string * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior * * @return mixed * * @throws ParseException If the YAML is not valid */ public function parse(string $value, int $flags = 0) { if (\false === \preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); } $this->refs = []; $mbEncoding = null; if (2 & (int) \ini_get('mbstring.func_overload')) { $mbEncoding = \mb_internal_encoding(); \mb_internal_encoding('UTF-8'); } try { $data = $this->doParse($value, $flags); } finally { if (null !== $mbEncoding) { \mb_internal_encoding($mbEncoding); } $this->refsBeingParsed = []; $this->offset = 0; $this->lines = []; $this->currentLine = ''; $this->numberOfParsedLines = 0; $this->refs = []; $this->skippedLineNumbers = []; $this->locallySkippedLineNumbers = []; $this->totalNumberOfLines = null; } return $data; } private function doParse(string $value, int $flags) { $this->currentLineNb = -1; $this->currentLine = ''; $value = $this->cleanup($value); $this->lines = \explode("\n", $value); $this->numberOfParsedLines = \count($this->lines); $this->locallySkippedLineNumbers = []; if (null === $this->totalNumberOfLines) { $this->totalNumberOfLines = $this->numberOfParsedLines; } if (!$this->moveToNextLine()) { return null; } $data = []; $context = null; $allowOverwrite = \false; while ($this->isCurrentLineEmpty()) { if (!$this->moveToNextLine()) { return null; } } // Resolves the tag and returns if end of the document if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, \false)) && !$this->moveToNextLine()) { return new TaggedValue($tag, ''); } do { if ($this->isCurrentLineEmpty()) { continue; } // tab? if ("\t" === $this->currentLine[0]) { throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); $isRef = $mergeNode = \false; if ('-' === $this->currentLine[0] && self::preg_match('#^\\-((?P\\s+)(?P.+))?$#u', \rtrim($this->currentLine), $values)) { if ($context && 'mapping' == $context) { throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $context = 'sequence'; if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; $values['value'] = $matches['value']; } if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } // array if (isset($values['value']) && 0 === \strpos(\ltrim($values['value'], ' '), '-')) { // Inline first child $currentLineNumber = $this->getRealCurrentLineNb(); $sequenceIndentation = \strlen($values['leadspaces']) + 1; $sequenceYaml = \substr($this->currentLine, $sequenceIndentation); $sequenceYaml .= "\n" . $this->getNextEmbedBlock($sequenceIndentation, \true); $data[] = $this->parseBlock($currentLineNumber, \rtrim($sequenceYaml), $flags); } elseif (!isset($values['value']) || '' == \trim($values['value'], ' ') || 0 === \strpos(\ltrim($values['value'], ' '), '#')) { $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, \true) ?? '', $flags); } elseif (null !== ($subTag = $this->getLineTag(\ltrim($values['value'], ' '), $flags))) { $data[] = new TaggedValue($subTag, $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, \true), $flags)); } else { if (isset($values['leadspaces']) && ('!' === $values['value'][0] || self::preg_match('#^(?P' . Inline::REGEX_QUOTED_STRING . '|[^ \'"\\{\\[].*?) *\\:(\\s+(?P.+?))?\\s*$#u', $this->trimTag($values['value']), $matches))) { $block = $values['value']; if ($this->isNextLineIndented() || isset($matches['value']) && '>-' === $matches['value']) { $block .= "\n" . $this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); } $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); } else { $data[] = $this->parseValue($values['value'], $flags, $context); } } if ($isRef) { $this->refs[$isRef] = \end($data); \array_pop($this->refsBeingParsed); } } elseif (self::preg_match('#^(?P(?:![^\\s]++\\s++)?(?:' . Inline::REGEX_QUOTED_STRING . '|(?:!?!php/const:)?[^ \'"\\[\\{!].*?)) *\\:(( |\\t)++(?P.+))?$#u', \rtrim($this->currentLine), $values) && (\false === \strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))) { if ($context && 'sequence' == $context) { throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); } $context = 'mapping'; try { $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if (!\is_string($key) && !\is_int($key)) { throw new ParseException((\is_numeric($key) ? 'Numeric' : 'Non-string') . ' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } // Convert float keys to strings, to avoid being converted to integers by PHP if (\is_float($key)) { $key = (string) $key; } if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { $mergeNode = \true; $allowOverwrite = \true; if (isset($values['value'][0]) && '*' === $values['value'][0]) { $refName = \substr(\rtrim($values['value']), 1); if (!\array_key_exists($refName, $this->refs)) { if (\false !== ($pos = \array_search($refName, $this->refsBeingParsed, \true))) { throw new ParseException(\sprintf('Circular reference [%s] detected for reference "%s".', \implode(', ', \array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); } throw new ParseException(\sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $refValue = $this->refs[$refName]; if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { $refValue = (array) $refValue; } if (!\is_array($refValue)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $data += $refValue; // array union } else { if (isset($values['value']) && '' !== $values['value']) { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { $parsed = (array) $parsed; } if (!\is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } if (isset($parsed[0])) { // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier // in the sequence override keys specified in later mapping nodes. foreach ($parsed as $parsedItem) { if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { $parsedItem = (array) $parsedItem; } if (!\is_array($parsedItem)) { throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); } $data += $parsedItem; // array union } } else { // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the // current mapping, unless the key already exists in it. $data += $parsed; // array union } } } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; $values['value'] = $matches['value']; } $subTag = null; if ($mergeNode) { // Merge keys } elseif (!isset($values['value']) || '' === $values['value'] || 0 === \strpos($values['value'], '#') || null !== ($subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { // hash // if next line is less indented or equal, then it means that the current value is null if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if ($allowOverwrite || !isset($data[$key])) { if (null !== $subTag) { $data[$key] = new TaggedValue($subTag, ''); } else { $data[$key] = null; } } else { throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { // remember the parsed line number here in case we need it to provide some contexts in error messages below $realCurrentLineNbKey = $this->getRealCurrentLineNb(); $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); if ('<<' === $key) { $this->refs[$refMatches['ref']] = $value; if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { $value = (array) $value; } $data += $value; } elseif ($allowOverwrite || !isset($data[$key])) { // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if (null !== $subTag) { $data[$key] = new TaggedValue($subTag, $value); } else { $data[$key] = $value; } } else { throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); } } } else { $value = $this->parseValue(\rtrim($values['value']), $flags, $context); // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } else { throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } if ($isRef) { $this->refs[$isRef] = $data[$key]; \array_pop($this->refsBeingParsed); } } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { if (null !== $context) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } try { return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } elseif ('{' === $this->currentLine[0]) { if (null !== $context) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } try { $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); while ($this->moveToNextLine()) { if (!$this->isCurrentLineEmpty()) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } return $parsedMapping; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } elseif ('[' === $this->currentLine[0]) { if (null !== $context) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } try { $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); while ($this->moveToNextLine()) { if (!$this->isCurrentLineEmpty()) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } return $parsedSequence; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } else { // multiple documents are not supported if ('---' === $this->currentLine) { throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); } if ($deprecatedUsage = isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1]) { throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } // 1-liner optionally followed by newline(s) if (\is_string($value) && $this->lines[0] === \trim($value)) { try { $value = Inline::parse($this->lines[0], $flags, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } return $value; } // try to parse the value as a multi-line string as a last resort if (0 === $this->currentLineNb) { $previousLineWasNewline = \false; $previousLineWasTerminatedWithBackslash = \false; $value = ''; foreach ($this->lines as $line) { $trimmedLine = \trim($line); if ('#' === ($trimmedLine[0] ?? '')) { continue; } // If the indentation is not consistent at offset 0, it is to be considered as a ParseError if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } if (\false !== \strpos($line, ': ')) { throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } if ('' === $trimmedLine) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { $value .= ' '; } if ('' !== $trimmedLine && '\\' === \substr($line, -1)) { $value .= \ltrim(\substr($line, 0, -1)); } elseif ('' !== $trimmedLine) { $value .= $trimmedLine; } if ('' === $trimmedLine) { $previousLineWasNewline = \true; $previousLineWasTerminatedWithBackslash = \false; } elseif ('\\' === \substr($line, -1)) { $previousLineWasNewline = \false; $previousLineWasTerminatedWithBackslash = \true; } else { $previousLineWasNewline = \false; $previousLineWasTerminatedWithBackslash = \false; } } try { return Inline::parse(\trim($value)); } catch (ParseException $e) { // fall-through to the ParseException thrown below } } throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } while ($this->moveToNextLine()); if (null !== $tag) { $data = new TaggedValue($tag, $data); } if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) { $object = new \stdClass(); foreach ($data as $key => $value) { $object->{$key} = $value; } $data = $object; } return empty($data) ? null : $data; } private function parseBlock(int $offset, string $yaml, int $flags) { $skippedLineNumbers = $this->skippedLineNumbers; foreach ($this->locallySkippedLineNumbers as $lineNumber) { if ($lineNumber < $offset) { continue; } $skippedLineNumbers[] = $lineNumber; } $parser = new self(); $parser->offset = $offset; $parser->totalNumberOfLines = $this->totalNumberOfLines; $parser->skippedLineNumbers = $skippedLineNumbers; $parser->refs =& $this->refs; $parser->refsBeingParsed = $this->refsBeingParsed; return $parser->doParse($yaml, $flags); } /** * Returns the current line number (takes the offset into account). * * @internal */ public function getRealCurrentLineNb() : int { $realCurrentLineNumber = $this->currentLineNb + $this->offset; foreach ($this->skippedLineNumbers as $skippedLineNumber) { if ($skippedLineNumber > $realCurrentLineNumber) { break; } ++$realCurrentLineNumber; } return $realCurrentLineNumber; } /** * Returns the current line indentation. */ private function getCurrentLineIndentation() : int { if (' ' !== ($this->currentLine[0] ?? '')) { return 0; } return \strlen($this->currentLine) - \strlen(\ltrim($this->currentLine, ' ')); } /** * Returns the next embed block of YAML. * * @param int|null $indentation The indent level at which the block is to be read, or null for default * @param bool $inSequence True if the enclosing data structure is a sequence * * @throws ParseException When indentation problem are detected */ private function getNextEmbedBlock(?int $indentation = null, bool $inSequence = \false) : string { $oldLineIndentation = $this->getCurrentLineIndentation(); if (!$this->moveToNextLine()) { return ''; } if (null === $indentation) { $newIndent = null; $movements = 0; do { $EOF = \false; // empty and comment-like lines do not influence the indentation depth if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { $EOF = !$this->moveToNextLine(); if (!$EOF) { ++$movements; } } else { $newIndent = $this->getCurrentLineIndentation(); } } while (!$EOF && null === $newIndent); for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } else { $newIndent = $indentation; } $data = []; if ($this->getCurrentLineIndentation() >= $newIndent) { $data[] = \substr($this->currentLine, $newIndent ?? 0); } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { $data[] = $this->currentLine; } else { $this->moveToPreviousLine(); return ''; } if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { // the previous line contained a dash but no item content, this line is a sequence item with the same indentation // and therefore no nested list or mapping $this->moveToPreviousLine(); return ''; } $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); $isItComment = $this->isCurrentLineComment(); while ($this->moveToNextLine()) { if ($isItComment && !$isItUnindentedCollection) { $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); $isItComment = $this->isCurrentLineComment(); } $indent = $this->getCurrentLineIndentation(); if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { $this->moveToPreviousLine(); break; } if ($this->isCurrentLineBlank()) { $data[] = \substr($this->currentLine, $newIndent); continue; } if ($indent >= $newIndent) { $data[] = \substr($this->currentLine, $newIndent); } elseif ($this->isCurrentLineComment()) { $data[] = $this->currentLine; } elseif (0 == $indent) { $this->moveToPreviousLine(); break; } else { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } } return \implode("\n", $data); } private function hasMoreLines() : bool { return \count($this->lines) - 1 > $this->currentLineNb; } /** * Moves the parser to the next line. */ private function moveToNextLine() : bool { if ($this->currentLineNb >= $this->numberOfParsedLines - 1) { return \false; } $this->currentLine = $this->lines[++$this->currentLineNb]; return \true; } /** * Moves the parser to the previous line. */ private function moveToPreviousLine() : bool { if ($this->currentLineNb < 1) { return \false; } $this->currentLine = $this->lines[--$this->currentLineNb]; return \true; } /** * Parses a YAML value. * * @param string $value A YAML value * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior * @param string $context The parser context (either sequence or mapping) * * @return mixed * * @throws ParseException When reference does not exist */ private function parseValue(string $value, int $flags, string $context) { if (0 === \strpos($value, '*')) { if (\false !== ($pos = \strpos($value, '#'))) { $value = \substr($value, 1, $pos - 2); } else { $value = \substr($value, 1); } if (!\array_key_exists($value, $this->refs)) { if (\false !== ($pos = \array_search($value, $this->refsBeingParsed, \true))) { throw new ParseException(\sprintf('Circular reference [%s] detected for reference "%s".', \implode(', ', \array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); } throw new ParseException(\sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); } return $this->refs[$value]; } if (\in_array($value[0], ['!', '|', '>'], \true) && self::preg_match('/^(?:' . self::TAG_PATTERN . ' +)?' . self::BLOCK_SCALAR_HEADER_PATTERN . '$/', $value, $matches)) { $modifiers = $matches['modifiers'] ?? ''; $data = $this->parseBlockScalar($matches['separator'], \preg_replace('#\\d+#', '', $modifiers), \abs((int) $modifiers)); if ('' !== $matches['tag'] && '!' !== $matches['tag']) { if ('!!binary' === $matches['tag']) { return Inline::evaluateBinaryScalar($data); } return new TaggedValue(\substr($matches['tag'], 1), $data); } return $data; } try { if ('' !== $value && '{' === $value[0]) { $cursor = \strlen(\rtrim($this->currentLine)) - \strlen(\rtrim($value)); return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); } elseif ('' !== $value && '[' === $value[0]) { $cursor = \strlen(\rtrim($this->currentLine)) - \strlen(\rtrim($value)); return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); } switch ($value[0] ?? '') { case '"': case "'": $cursor = \strlen(\rtrim($this->currentLine)) - \strlen(\rtrim($value)); $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); if (isset($this->currentLine[$cursor]) && \preg_replace('/\\s*(#.*)?$/A', '', \substr($this->currentLine, $cursor))) { throw new ParseException(\sprintf('Unexpected characters near "%s".', \substr($this->currentLine, $cursor))); } return $parsedValue; default: $lines = []; while ($this->moveToNextLine()) { // unquoted strings end before the first unindented line if (0 === $this->getCurrentLineIndentation()) { $this->moveToPreviousLine(); break; } $lines[] = \trim($this->currentLine); } for ($i = 0, $linesCount = \count($lines), $previousLineBlank = \false; $i < $linesCount; ++$i) { if ('' === $lines[$i]) { $value .= "\n"; $previousLineBlank = \true; } elseif ($previousLineBlank) { $value .= $lines[$i]; $previousLineBlank = \false; } else { $value .= ' ' . $lines[$i]; $previousLineBlank = \false; } } Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); $parsedValue = Inline::parse($value, $flags, $this->refs); if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && \false !== \strpos($parsedValue, ': ')) { throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); } return $parsedValue; } } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } /** * Parses a block scalar. * * @param string $style The style indicator that was used to begin this block scalar (| or >) * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) * @param int $indentation The indentation indicator that was used to begin this block scalar */ private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0) : string { $notEOF = $this->moveToNextLine(); if (!$notEOF) { return ''; } $isCurrentLineBlank = $this->isCurrentLineBlank(); $blockLines = []; // leading blank lines are consumed before determining indentation while ($notEOF && $isCurrentLineBlank) { // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $blockLines[] = ''; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } // determine indentation if not specified if (0 === $indentation) { $currentLineLength = \strlen($this->currentLine); for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { ++$indentation; } } if ($indentation > 0) { $pattern = \sprintf('/^ {%d}(.*)$/', $indentation); while ($notEOF && ($isCurrentLineBlank || self::preg_match($pattern, $this->currentLine, $matches))) { if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { $blockLines[] = \substr($this->currentLine, $indentation); } elseif ($isCurrentLineBlank) { $blockLines[] = ''; } else { $blockLines[] = $matches[1]; } // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $isCurrentLineBlank = $this->isCurrentLineBlank(); } } } elseif ($notEOF) { $blockLines[] = ''; } if ($notEOF) { $blockLines[] = ''; $this->moveToPreviousLine(); } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { $blockLines[] = ''; } // folded style if ('>' === $style) { $text = ''; $previousLineIndented = \false; $previousLineBlank = \false; for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { if ('' === $blockLines[$i]) { $text .= "\n"; $previousLineIndented = \false; $previousLineBlank = \true; } elseif (' ' === $blockLines[$i][0]) { $text .= "\n" . $blockLines[$i]; $previousLineIndented = \true; $previousLineBlank = \false; } elseif ($previousLineIndented) { $text .= "\n" . $blockLines[$i]; $previousLineIndented = \false; $previousLineBlank = \false; } elseif ($previousLineBlank || 0 === $i) { $text .= $blockLines[$i]; $previousLineIndented = \false; $previousLineBlank = \false; } else { $text .= ' ' . $blockLines[$i]; $previousLineIndented = \false; $previousLineBlank = \false; } } } else { $text = \implode("\n", $blockLines); } // deal with trailing newlines if ('' === $chomping) { $text = \preg_replace('/\\n+$/', "\n", $text); } elseif ('-' === $chomping) { $text = \preg_replace('/\\n+$/', '', $text); } return $text; } /** * Returns true if the next line is indented. */ private function isNextLineIndented() : bool { $currentIndentation = $this->getCurrentLineIndentation(); $movements = 0; do { $EOF = !$this->moveToNextLine(); if (!$EOF) { ++$movements; } } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); if ($EOF) { for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } return \false; } $ret = $this->getCurrentLineIndentation() > $currentIndentation; for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } return $ret; } /** * Returns true if the current line is blank or if it is a comment line. */ private function isCurrentLineEmpty() : bool { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } /** * Returns true if the current line is blank. */ private function isCurrentLineBlank() : bool { return '' === $this->currentLine || '' === \trim($this->currentLine, ' '); } /** * Returns true if the current line is a comment line. */ private function isCurrentLineComment() : bool { // checking explicitly the first char of the trim is faster than loops or strpos $ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? \ltrim($this->currentLine, ' ') : $this->currentLine; return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; } private function isCurrentLineLastLineInDocument() : bool { return $this->offset + $this->currentLineNb >= $this->totalNumberOfLines - 1; } /** * Cleanups a YAML string to be parsed. * * @param string $value The input YAML string */ private function cleanup(string $value) : string { $value = \str_replace(["\r\n", "\r"], "\n", $value); // strip YAML header $count = 0; $value = \preg_replace('#^\\%YAML[: ][\\d\\.]+.*\\n#u', '', $value, -1, $count); $this->offset += $count; // remove leading comments $trimmedValue = \preg_replace('#^(\\#.*?\\n)+#s', '', $value, -1, $count); if (1 === $count) { // items have been removed, update the offset $this->offset += \substr_count($value, "\n") - \substr_count($trimmedValue, "\n"); $value = $trimmedValue; } // remove start of the document marker (---) $trimmedValue = \preg_replace('#^\\-\\-\\-.*?\\n#s', '', $value, -1, $count); if (1 === $count) { // items have been removed, update the offset $this->offset += \substr_count($value, "\n") - \substr_count($trimmedValue, "\n"); $value = $trimmedValue; // remove end of the document marker (...) $value = \preg_replace('#\\.\\.\\.\\s*$#', '', $value); } return $value; } /** * Returns true if the next line starts unindented collection. */ private function isNextLineUnIndentedCollection() : bool { $currentIndentation = $this->getCurrentLineIndentation(); $movements = 0; do { $EOF = !$this->moveToNextLine(); if (!$EOF) { ++$movements; } } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); if ($EOF) { return \false; } $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); for ($i = 0; $i < $movements; ++$i) { $this->moveToPreviousLine(); } return $ret; } /** * Returns true if the string is un-indented collection item. */ private function isStringUnIndentedCollectionItem() : bool { return '-' === \rtrim($this->currentLine) || 0 === \strpos($this->currentLine, '- '); } /** * A local wrapper for "preg_match" which will throw a ParseException if there * is an internal error in the PCRE engine. * * This avoids us needing to check for "false" every time PCRE is used * in the YAML engine * * @throws ParseException on a PCRE internal error * * @see preg_last_error() * * @internal */ public static function preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) : int { if (\false === ($ret = \preg_match($pattern, $subject, $matches, $flags, $offset))) { switch (\preg_last_error()) { case \PREG_INTERNAL_ERROR: $error = 'Internal PCRE error.'; break; case \PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached.'; break; case \PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached.'; break; case \PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data.'; break; case \PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; break; default: $error = 'Error.'; } throw new ParseException($error); } return $ret; } /** * Trim the tag on top of the value. * * Prevent values such as "!foo {quz: bar}" to be considered as * a mapping block. */ private function trimTag(string $value) : string { if ('!' === $value[0]) { return \ltrim(\substr($value, 1, \strcspn($value, " \r\n", 1)), ' '); } return $value; } private function getLineTag(string $value, int $flags, bool $nextLineCheck = \true) : ?string { if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^' . self::TAG_PATTERN . ' *( +#.*)?$/', $value, $matches)) { return null; } if ($nextLineCheck && !$this->isNextLineIndented()) { return null; } $tag = \substr($matches['tag'], 1); // Built-in tags if ($tag && '!' === $tag[0]) { throw new ParseException(\sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } if (Yaml::PARSE_CUSTOM_TAGS & $flags) { return $tag; } throw new ParseException(\sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } private function lexInlineQuotedString(int &$cursor = 0) : string { $quotation = $this->currentLine[$cursor]; $value = $quotation; ++$cursor; $previousLineWasNewline = \true; $previousLineWasTerminatedWithBackslash = \false; $lineNumber = 0; do { if (++$lineNumber > 1) { $cursor += \strspn($this->currentLine, ' ', $cursor); } if ($this->isCurrentLineBlank()) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { $value .= ' '; } for (; \strlen($this->currentLine) > $cursor; ++$cursor) { switch ($this->currentLine[$cursor]) { case '\\': if ("'" === $quotation) { $value .= '\\'; } elseif (isset($this->currentLine[++$cursor])) { $value .= '\\' . $this->currentLine[$cursor]; } break; case $quotation: ++$cursor; if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { $value .= "''"; break; } return $value . $quotation; default: $value .= $this->currentLine[$cursor]; } } if ($this->isCurrentLineBlank()) { $previousLineWasNewline = \true; $previousLineWasTerminatedWithBackslash = \false; } elseif ('\\' === $this->currentLine[-1]) { $previousLineWasNewline = \false; $previousLineWasTerminatedWithBackslash = \true; } else { $previousLineWasNewline = \false; $previousLineWasTerminatedWithBackslash = \false; } if ($this->hasMoreLines()) { $cursor = 0; } } while ($this->moveToNextLine()); throw new ParseException('Malformed inline YAML string.'); } private function lexUnquotedString(int &$cursor) : string { $offset = $cursor; $cursor += \strcspn($this->currentLine, '[]{},: ', $cursor); if ($cursor === $offset) { throw new ParseException('Malformed unquoted YAML string.'); } return \substr($this->currentLine, $offset, $cursor - $offset); } private function lexInlineMapping(int &$cursor = 0) : string { return $this->lexInlineStructure($cursor, '}'); } private function lexInlineSequence(int &$cursor = 0) : string { return $this->lexInlineStructure($cursor, ']'); } private function lexInlineStructure(int &$cursor, string $closingTag) : string { $value = $this->currentLine[$cursor]; ++$cursor; do { $this->consumeWhitespaces($cursor); while (isset($this->currentLine[$cursor])) { switch ($this->currentLine[$cursor]) { case '"': case "'": $value .= $this->lexInlineQuotedString($cursor); break; case ':': case ',': $value .= $this->currentLine[$cursor]; ++$cursor; break; case '{': $value .= $this->lexInlineMapping($cursor); break; case '[': $value .= $this->lexInlineSequence($cursor); break; case $closingTag: $value .= $this->currentLine[$cursor]; ++$cursor; return $value; case '#': break 2; default: $value .= $this->lexUnquotedString($cursor); } if ($this->consumeWhitespaces($cursor)) { $value .= ' '; } } if ($this->hasMoreLines()) { $cursor = 0; } } while ($this->moveToNextLine()); throw new ParseException('Malformed inline YAML string.'); } private function consumeWhitespaces(int &$cursor) : bool { $whitespacesConsumed = 0; do { $whitespaceOnlyTokenLength = \strspn($this->currentLine, ' ', $cursor); $whitespacesConsumed += $whitespaceOnlyTokenLength; $cursor += $whitespaceOnlyTokenLength; if (isset($this->currentLine[$cursor])) { return 0 < $whitespacesConsumed; } if ($this->hasMoreLines()) { $cursor = 0; } } while ($this->moveToNextLine()); return 0 < $whitespacesConsumed; } } CHANGELOG ========= 5.4 --- * Add new `lint:yaml dirname --exclude=/dirname/foo.yaml --exclude=/dirname/bar.yaml` option to exclude one or more specific files from multiple file list * Allow negatable for the parse tags option with `--no-parse-tags` 5.3 --- * Added `github` format support & autodetection to render errors as annotations when running the YAML linter command in a Github Action environment. 5.1.0 ----- * Added support for parsing numbers prefixed with `0o` as octal numbers. * Deprecated support for parsing numbers starting with `0` as octal numbers. They will be parsed as strings as of Symfony 6.0. Prefix numbers with `0o` so that they are parsed as octal numbers. Before: ```yaml Yaml::parse('072'); ``` After: ```yaml Yaml::parse('0o72'); ``` * Added `yaml-lint` binary. * Deprecated using the `!php/object` and `!php/const` tags without a value. 5.0.0 ----- * Removed support for mappings inside multi-line strings. * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. 4.4.0 ----- * Added support for parsing the inline notation spanning multiple lines. * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. 4.3.0 ----- * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. 4.2.0 ----- * added support for multiple files or directories in `LintCommand` 4.0.0 ----- * The behavior of the non-specific tag `!` is changed and now forces non-evaluating your values. * complex mappings will throw a `ParseException` * support for the comma as a group separator for floats has been dropped, use the underscore instead * support for the `!!php/object` tag has been dropped, use the `!php/object` tag instead * duplicate mapping keys throw a `ParseException` * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` flag to cast them to strings * `%` at the beginning of an unquoted string throw a `ParseException` * mappings with a colon (`:`) that is not followed by a whitespace throw a `ParseException` * the `Dumper::setIndentation()` method has been removed * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of the parser and dumper is no longer supported, pass bitmask flags instead * the constructor arguments of the `Parser` class have been removed * the `Inline` class is internal and no longer part of the BC promise * removed support for the `!str` tag, use the `!!str` tag instead * added support for tagged scalars. ```yml Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); // returns TaggedValue('foo', 'bar'); ``` 3.4.0 ----- * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method * the `Dumper`, `Parser`, and `Yaml` classes are marked as final * Deprecated the `!php/object:` tag which will be replaced by the `!php/object` tag (without the colon) in 4.0. * Deprecated the `!php/const:` tag which will be replaced by the `!php/const` tag (without the colon) in 4.0. * Support for the `!str` tag is deprecated, use the `!!str` tag instead. * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. 3.3.0 ----- * Starting an unquoted string with a question mark followed by a space is deprecated and will throw a `ParseException` in Symfony 4.0. * Deprecated support for implicitly parsing non-string mapping keys as strings. Mapping keys that are no strings will lead to a `ParseException` in Symfony 4.0. Use quotes to opt-in for keys to be parsed as strings. Before: ```php $yaml = << new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); ``` 3.0.0 ----- * Yaml::parse() now throws an exception when a blackslash is not escaped in double-quoted strings 2.8.0 ----- * Deprecated usage of a colon in an unquoted mapping value * Deprecated usage of @, \`, | and > at the beginning of an unquoted string * When surrounding strings with double-quotes, you must now escape `\` characters. Not escaping those characters (when surrounded by double-quotes) is deprecated. Before: ```yml class: "Foo\Var" ``` After: ```yml class: "Foo\\Var" ``` 2.1.0 ----- * Yaml::parse() does not evaluate loaded files as PHP files by default anymore (call Yaml::enablePhpParsing() to get back the old behavior) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml; /** * Escaper encapsulates escaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski * * @internal */ class Escaper { // Characters that would cause a dumped string to require double quoting. public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]||…| |
|
"; // Mapping arrays for escaping a double quoted string. The backslash is // first to ensure proper escaping because str_replace operates iteratively // on the input arrays. This ordering of the characters avoids the use of strtr, // which performs more slowly. private const ESCAPEES = ['\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\t", "\n", "\v", "\f", "\r", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "", "…", " ", "
", "
"]; private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', '\\x7f', '\\N', '\\_', '\\L', '\\P']; /** * Determines if a PHP value would require double quoting in YAML. * * @param string $value A PHP value */ public static function requiresDoubleQuoting(string $value) : bool { return 0 < \preg_match('/' . self::REGEX_CHARACTER_TO_ESCAPE . '/u', $value); } /** * Escapes and surrounds a PHP value with double quotes. * * @param string $value A PHP value */ public static function escapeWithDoubleQuotes(string $value) : string { return \sprintf('"%s"', \str_replace(self::ESCAPEES, self::ESCAPED, $value)); } /** * Determines if a PHP value would require single quoting in YAML. * * @param string $value A PHP value */ public static function requiresSingleQuoting(string $value) : bool { // Determines if a PHP value is entirely composed of a value that would // require single quoting in YAML. if (\in_array(\strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { return \true; } // Determines if the PHP value contains any single characters that would // cause it to require single quoting in YAML. return 0 < \preg_match('/[ \\s \' " \\: \\{ \\} \\[ \\] , & \\* \\# \\?] | \\A[ \\- ? | < > = ! % @ ` \\p{Zs}]/xu', $value); } /** * Escapes and surrounds a PHP value with single quotes. * * @param string $value A PHP value */ public static function escapeWithSingleQuotes(string $value) : string { return \sprintf("'%s'", \str_replace('\'', '\'\'', $value)); } } #!/usr/bin/env php * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ('cli' !== \PHP_SAPI) { throw new \Exception('This script must be run from the command line.'); } /** * Runs the Yaml lint command. * * @author Jan Schädlich */ use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Yaml\Command\LintCommand; function includeIfExists(string $file) : bool { return \file_exists($file) && (include $file); } if (!includeIfExists(__DIR__ . '/../../../../autoload.php') && !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')) { \fwrite(\STDERR, 'Install dependencies using Composer.' . \PHP_EOL); exit(1); } if (!\class_exists(Application::class)) { \fwrite(\STDERR, 'You need the "symfony/console" component in order to run the Yaml linter.' . \PHP_EOL); exit(1); } (new Application())->add($command = new LintCommand())->getApplication()->setDefaultCommand($command->getName(), \true)->run(); Yaml Component ============== The Yaml component loads and dumps YAML files. Resources --------- * [Documentation](https://symfony.com/doc/current/components/yaml.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; /** * Unescaper encapsulates unescaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski * * @internal */ class Unescaper { /** * Regex fragment that matches an escaped character in a double quoted string. */ public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; /** * Unescapes a single quoted string. * * @param string $value A single quoted string */ public function unescapeSingleQuotedString(string $value) : string { return \str_replace('\'\'', '\'', $value); } /** * Unescapes a double quoted string. * * @param string $value A double quoted string */ public function unescapeDoubleQuotedString(string $value) : string { $callback = function ($match) { return $this->unescapeCharacter($match[0]); }; // evaluate the string return \preg_replace_callback('/' . self::REGEX_ESCAPED_CHARACTER . '/u', $callback, $value); } /** * Unescapes a character that was found in a double-quoted string. * * @param string $value An escaped character */ private function unescapeCharacter(string $value) : string { switch ($value[1]) { case '0': return "\x00"; case 'a': return "\x07"; case 'b': return "\x08"; case 't': return "\t"; case "\t": return "\t"; case 'n': return "\n"; case 'v': return "\v"; case 'f': return "\f"; case 'r': return "\r"; case 'e': return "\x1b"; case ' ': return ' '; case '"': return '"'; case '/': return '/'; case '\\': return '\\'; case 'N': // U+0085 NEXT LINE return "…"; case '_': // U+00A0 NO-BREAK SPACE return " "; case 'L': // U+2028 LINE SEPARATOR return "
"; case 'P': // U+2029 PARAGRAPH SEPARATOR return "
"; case 'x': return self::utf8chr(\hexdec(\substr($value, 2, 2))); case 'u': return self::utf8chr(\hexdec(\substr($value, 2, 4))); case 'U': return self::utf8chr(\hexdec(\substr($value, 2, 8))); default: throw new ParseException(\sprintf('Found unknown escape character "%s".', $value)); } } /** * Get the UTF-8 character for the given code point. */ private static function utf8chr(int $c) : string { if (0x80 > ($c %= 0x200000)) { return \chr($c); } if (0x800 > $c) { return \chr(0xc0 | $c >> 6) . \chr(0x80 | $c & 0x3f); } if (0x10000 > $c) { return \chr(0xe0 | $c >> 12) . \chr(0x80 | $c >> 6 & 0x3f) . \chr(0x80 | $c & 0x3f); } return \chr(0xf0 | $c >> 18) . \chr(0x80 | $c >> 12 & 0x3f) . \chr(0x80 | $c >> 6 & 0x3f) . \chr(0x80 | $c & 0x3f); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml; use _ContaoManager\Symfony\Component\Yaml\Tag\TaggedValue; /** * Dumper dumps PHP variables to YAML strings. * * @author Fabien Potencier * * @final */ class Dumper { /** * The amount of spaces to use for indentation of nested nodes. * * @var int */ protected $indentation; public function __construct(int $indentation = 4) { if ($indentation < 1) { throw new \InvalidArgumentException('The indentation must be greater than zero.'); } $this->indentation = $indentation; } /** * Dumps a PHP value to YAML. * * @param mixed $input The PHP value * @param int $inline The level where you switch to inline YAML * @param int $indent The level of indentation (used internally) * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string */ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0) : string { $output = ''; $prefix = $indent ? \str_repeat(' ', $indent) : ''; $dumpObjectAsInlineMap = \true; if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { $dumpObjectAsInlineMap = empty((array) $input); } if ($inline <= 0 || !\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap || empty($input)) { $output .= $prefix . Inline::dump($input, $flags); } elseif ($input instanceof TaggedValue) { $output .= $this->dumpTaggedValue($input, $inline, $indent, $flags, $prefix); } else { $dumpAsMap = Inline::isHash($input); foreach ($input as $key => $value) { if ('' !== $output && "\n" !== $output[-1]) { $output .= "\n"; } if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && \false !== \strpos($value, "\n") && \false === \strpos($value, "\r")) { $blockIndentationIndicator = $this->getBlockIndentationIndicator($value); if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { $blockChompingIndicator = '+'; } elseif ("\n" === $value[-1]) { $blockChompingIndicator = ''; } else { $blockChompingIndicator = '-'; } $output .= \sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags) . ':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); foreach (\explode("\n", $value) as $row) { if ('' === $row) { $output .= "\n"; } else { $output .= \sprintf("\n%s%s%s", $prefix, \str_repeat(' ', $this->indentation), $row); } } continue; } if ($value instanceof TaggedValue) { $output .= \sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags) . ':' : '-', $value->getTag()); if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && \false !== \strpos($value->getValue(), "\n") && \false === \strpos($value->getValue(), "\r\n")) { $blockIndentationIndicator = $this->getBlockIndentationIndicator($value->getValue()); $output .= \sprintf(' |%s', $blockIndentationIndicator); foreach (\explode("\n", $value->getValue()) as $row) { $output .= \sprintf("\n%s%s%s", $prefix, \str_repeat(' ', $this->indentation), $row); } continue; } if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { $output .= ' ' . $this->dump($value->getValue(), $inline - 1, 0, $flags) . "\n"; } else { $output .= "\n"; $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); } continue; } $dumpObjectAsInlineMap = \true; if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { $dumpObjectAsInlineMap = empty((array) $value); } $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); $output .= \sprintf('%s%s%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags) . ':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags)) . ($willBeInlined ? "\n" : ''); } } return $output; } private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, int $flags, string $prefix) : string { $output = \sprintf('%s!%s', $prefix ? $prefix . ' ' : '', $value->getTag()); if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && \false !== \strpos($value->getValue(), "\n") && \false === \strpos($value->getValue(), "\r\n")) { $blockIndentationIndicator = $this->getBlockIndentationIndicator($value->getValue()); $output .= \sprintf(' |%s', $blockIndentationIndicator); foreach (\explode("\n", $value->getValue()) as $row) { $output .= \sprintf("\n%s%s%s", $prefix, \str_repeat(' ', $this->indentation), $row); } return $output; } if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { return $output . ' ' . $this->dump($value->getValue(), $inline - 1, 0, $flags) . "\n"; } return $output . "\n" . $this->dump($value->getValue(), $inline - 1, $indent, $flags); } private function getBlockIndentationIndicator(string $value) : string { $lines = \explode("\n", $value); // If the first line (that is neither empty nor contains only spaces) // starts with a space character, the spec requires a block indentation indicator // http://www.yaml.org/spec/1.2/spec.html#id2793979 foreach ($lines as $line) { if ('' !== \trim($line, ' ')) { return ' ' === \substr($line, 0, 1) ? (string) $this->indentation : ''; } } return ''; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml; use _ContaoManager\Symfony\Component\Yaml\Exception\DumpException; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; use _ContaoManager\Symfony\Component\Yaml\Tag\TaggedValue; /** * Inline implements a YAML parser/dumper for the YAML inline syntax. * * @author Fabien Potencier * * @internal */ class Inline { public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; public static $parsedLineNumber = -1; public static $parsedFilename; private static $exceptionOnInvalidType = \false; private static $objectSupport = \false; private static $objectForMap = \false; private static $constantSupport = \false; public static function initialize(int $flags, ?int $parsedLineNumber = null, ?string $parsedFilename = null) { self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); self::$parsedFilename = $parsedFilename; if (null !== $parsedLineNumber) { self::$parsedLineNumber = $parsedLineNumber; } } /** * Converts a YAML string to a PHP value. * * @param string|null $value A YAML string * @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior * @param array $references Mapping of variable names to values * * @return mixed * * @throws ParseException */ public static function parse(?string $value = null, int $flags = 0, array &$references = []) { if (null === $value) { return ''; } self::initialize($flags); $value = \trim($value); if ('' === $value) { return ''; } if (2 & (int) \ini_get('mbstring.func_overload')) { $mbEncoding = \mb_internal_encoding(); \mb_internal_encoding('ASCII'); } try { $i = 0; $tag = self::parseTag($value, $i, $flags); switch ($value[$i]) { case '[': $result = self::parseSequence($value, $flags, $i, $references); ++$i; break; case '{': $result = self::parseMapping($value, $flags, $i, $references); ++$i; break; default: $result = self::parseScalar($value, $flags, null, $i, \true, $references); } // some comments are allowed at the end if (\preg_replace('/\\s*#.*$/A', '', \substr($value, $i))) { throw new ParseException(\sprintf('Unexpected characters near "%s".', \substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if (null !== $tag && '' !== $tag) { return new TaggedValue($tag, $result); } return $result; } finally { if (isset($mbEncoding)) { \mb_internal_encoding($mbEncoding); } } } /** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string * * @throws DumpException When trying to dump PHP resource */ public static function dump($value, int $flags = 0) : string { switch (\true) { case \is_resource($value): if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException(\sprintf('Unable to dump PHP resources in a YAML file ("%s").', \get_resource_type($value))); } return self::dumpNull($flags); case $value instanceof \DateTimeInterface: return $value->format('c'); case $value instanceof \UnitEnum: return \sprintf('!php/const %s::%s', \get_class($value), $value->name); case \is_object($value): if ($value instanceof TaggedValue) { return '!' . $value->getTag() . ' ' . self::dump($value->getValue(), $flags); } if (Yaml::DUMP_OBJECT & $flags) { return '!php/object ' . self::dump(\serialize($value)); } if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { $output = []; foreach ($value as $key => $val) { $output[] = \sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } return \sprintf('{ %s }', \implode(', ', $output)); } if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return self::dumpNull($flags); case \is_array($value): return self::dumpArray($value, $flags); case null === $value: return self::dumpNull($flags); case \true === $value: return 'true'; case \false === $value: return 'false'; case \is_int($value): return $value; case \is_numeric($value) && \false === \strpbrk($value, "\f\n\r\t\v"): $locale = \setlocale(\LC_NUMERIC, 0); if (\false !== $locale) { \setlocale(\LC_NUMERIC, 'C'); } if (\is_float($value)) { $repr = (string) $value; if (\is_infinite($value)) { $repr = \str_ireplace('INF', '.Inf', $repr); } elseif (\floor($value) == $value && $repr == $value) { // Preserve float data type since storing a whole number will result in integer value. if (\false === \strpos($repr, 'E')) { $repr = $repr . '.0'; } } } else { $repr = \is_string($value) ? "'{$value}'" : (string) $value; } if (\false !== $locale) { \setlocale(\LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case self::isBinaryString($value): return '!!binary ' . \base64_encode($value); case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): case Parser::preg_match(self::getHexRegex(), $value): case Parser::preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } } /** * Check if given array is hash or just normal indexed array. * * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check */ public static function isHash($value) : bool { if ($value instanceof \stdClass || $value instanceof \ArrayObject) { return \true; } $expectedKey = 0; foreach ($value as $key => $val) { if ($key !== $expectedKey++) { return \true; } } return \false; } /** * Dumps a PHP array to a YAML string. * * @param array $value The PHP array to dump * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string */ private static function dumpArray(array $value, int $flags) : string { // array if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { $output = []; foreach ($value as $val) { $output[] = self::dump($val, $flags); } return \sprintf('[%s]', \implode(', ', $output)); } // hash $output = []; foreach ($value as $key => $val) { $output[] = \sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } return \sprintf('{ %s }', \implode(', ', $output)); } private static function dumpNull(int $flags) : string { if (Yaml::DUMP_NULL_AS_TILDE & $flags) { return '~'; } return 'null'; } /** * Parses a YAML scalar. * * @return mixed * * @throws ParseException When malformed inline YAML string is parsed */ public static function parseScalar(string $scalar, int $flags = 0, ?array $delimiters = null, int &$i = 0, bool $evaluate = \true, array &$references = [], ?bool &$isQuoted = null) { if (\in_array($scalar[$i], ['"', "'"], \true)) { // quoted scalar $isQuoted = \true; $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { $tmp = \ltrim(\substr($scalar, $i), " \n"); if ('' === $tmp) { throw new ParseException(\sprintf('Unexpected end of line, expected one of "%s".', \implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (!\in_array($tmp[0], $delimiters)) { throw new ParseException(\sprintf('Unexpected characters (%s).', \substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } } } else { // "normal" string $isQuoted = \false; if (!$delimiters) { $output = \substr($scalar, $i); $i += \strlen($output); // remove comments if (Parser::preg_match('/[ \\t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { $output = \substr($output, 0, $match[0][1]); } } elseif (Parser::preg_match('/^(.*?)(' . \implode('|', $delimiters) . ')/', \substr($scalar, $i), $match)) { $output = $match[1]; $i += \strlen($output); $output = \trim($output); } else { throw new ParseException(\sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); } // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { throw new ParseException(\sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); } if ($evaluate) { $output = self::evaluateScalar($output, $flags, $references, $isQuoted); } } return $output; } /** * Parses a YAML quoted scalar. * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseQuotedScalar(string $scalar, int &$i = 0) : string { if (!Parser::preg_match('/' . self::REGEX_QUOTED_STRING . '/Au', \substr($scalar, $i), $match)) { throw new ParseException(\sprintf('Malformed inline YAML string: "%s".', \substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } $output = \substr($match[0], 1, -1); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { $output = $unescaper->unescapeDoubleQuotedString($output); } else { $output = $unescaper->unescapeSingleQuotedString($output); } $i += \strlen($match[0]); return $output; } /** * Parses a YAML sequence. * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []) : array { $output = []; $len = \strlen($sequence); ++$i; // [foo, bar, ...] while ($i < $len) { if (']' === $sequence[$i]) { return $output; } if (',' === $sequence[$i] || ' ' === $sequence[$i]) { ++$i; continue; } $tag = self::parseTag($sequence, $i, $flags); switch ($sequence[$i]) { case '[': // nested sequence $value = self::parseSequence($sequence, $flags, $i, $references); break; case '{': // nested mapping $value = self::parseMapping($sequence, $flags, $i, $references); break; default: $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); // the value can be an array if a reference has been resolved to an array var if (\is_string($value) && !$isQuoted && \false !== \strpos($value, ': ')) { // embedded mapping? try { $pos = 0; $value = self::parseMapping('{' . $value . '}', $flags, $pos, $references); } catch (\InvalidArgumentException $e) { // no, it's not } } if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { $references[$matches['ref']] = $matches['value']; $value = $matches['value']; } --$i; } if (null !== $tag && '' !== $tag) { $value = new TaggedValue($tag, $value); } $output[] = $value; ++$i; } throw new ParseException(\sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); } /** * Parses a YAML mapping. * * @return array|\stdClass * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []) { $output = []; $len = \strlen($mapping); ++$i; $allowOverwrite = \false; // {foo: bar, bar:foo, ...} while ($i < $len) { switch ($mapping[$i]) { case ' ': case ',': case "\n": ++$i; continue 2; case '}': if (self::$objectForMap) { return (object) $output; } return $output; } // key $offsetBeforeKeyParsing = $i; $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], \true); $key = self::parseScalar($mapping, $flags, [':', ' '], $i, \false); if ($offsetBeforeKeyParsing === $i) { throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); } if ('!php/const' === $key) { $key .= ' ' . self::parseScalar($mapping, $flags, [':'], $i, \false); $key = self::evaluateScalar($key, $flags); } if (\false === ($i = \strpos($mapping, ':', $i))) { break; } if (!$isKeyQuoted) { $evaluatedKey = self::evaluateScalar($key, $flags, $references); if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); } } if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], \true))) { throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); } if ('<<' === $key) { $allowOverwrite = \true; } while ($i < $len) { if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { ++$i; continue; } $tag = self::parseTag($mapping, $i, $flags); switch ($mapping[$i]) { case '[': // nested sequence $value = self::parseSequence($mapping, $flags, $i, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. // But overwriting is allowed when a merge node is used in current block. if ('<<' === $key) { foreach ($value as $parsedValue) { $output += $parsedValue; } } elseif ($allowOverwrite || !isset($output[$key])) { if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } break; case '{': // nested mapping $value = self::parseMapping($mapping, $flags, $i, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. // But overwriting is allowed when a merge node is used in current block. if ('<<' === $key) { $output += $value; } elseif ($allowOverwrite || !isset($output[$key])) { if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } break; default: $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. // But overwriting is allowed when a merge node is used in current block. if ('<<' === $key) { $output += $value; } elseif ($allowOverwrite || !isset($output[$key])) { if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && !self::isBinaryString($value) && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { $references[$matches['ref']] = $matches['value']; $value = $matches['value']; } if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } --$i; } ++$i; continue 2; } } throw new ParseException(\sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); } /** * Evaluates scalars and replaces magic values. * * @return mixed * * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved */ private static function evaluateScalar(string $scalar, int $flags, array &$references = [], ?bool &$isQuotedString = null) { $isQuotedString = \false; $scalar = \trim($scalar); if (0 === \strpos($scalar, '*')) { if (\false !== ($pos = \strpos($scalar, '#'))) { $value = \substr($scalar, 1, $pos - 2); } else { $value = \substr($scalar, 1); } // an unquoted * if (\false === $value || '' === $value) { throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if (!\array_key_exists($value, $references)) { throw new ParseException(\sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } return $references[$value]; } $scalarLower = \strtolower($scalar); switch (\true) { case 'null' === $scalarLower: case '' === $scalar: case '~' === $scalar: return null; case 'true' === $scalarLower: return \true; case 'false' === $scalarLower: return \false; case '!' === $scalar[0]: switch (\true) { case 0 === \strpos($scalar, '!!str '): $s = (string) \substr($scalar, 6); if (\in_array($s[0] ?? '', ['"', "'"], \true)) { $isQuotedString = \true; $s = self::parseQuotedScalar($s); } return $s; case 0 === \strpos($scalar, '! '): return \substr($scalar, 2); case 0 === \strpos($scalar, '!php/object'): if (self::$objectSupport) { if (!isset($scalar[12])) { \trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/object tag without a value is deprecated.'); return \false; } return \unserialize(self::parseScalar(\substr($scalar, 12))); } if (self::$exceptionOnInvalidType) { throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return null; case 0 === \strpos($scalar, '!php/const'): if (self::$constantSupport) { if (!isset($scalar[11])) { \trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/const tag without a value is deprecated.'); return ''; } $i = 0; if (\defined($const = self::parseScalar(\substr($scalar, 11), 0, null, $i, \false))) { return \constant($const); } throw new ParseException(\sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (self::$exceptionOnInvalidType) { throw new ParseException(\sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return null; case 0 === \strpos($scalar, '!!float '): return (float) \substr($scalar, 8); case 0 === \strpos($scalar, '!!binary '): return self::evaluateBinaryScalar(\substr($scalar, 9)); } throw new ParseException(\sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); case \preg_match('/^(?:\\+|-)?0o(?P[0-7_]++)$/', $scalar, $matches): $value = \str_replace('_', '', $matches['value']); if ('-' === $scalar[0]) { return -\octdec($value); } return \octdec($value); case \in_array($scalar[0], ['+', '-', '.'], \true) || \is_numeric($scalar[0]): if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { $scalar = \str_replace('_', '', $scalar); } switch (\true) { case \ctype_digit($scalar): if (\preg_match('/^0[0-7]+$/', $scalar)) { \trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '0o' . \substr($scalar, 1)); return \octdec($scalar); } $cast = (int) $scalar; return $scalar === (string) $cast ? $cast : $scalar; case '-' === $scalar[0] && \ctype_digit(\substr($scalar, 1)): if (\preg_match('/^-0[0-7]+$/', $scalar)) { \trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '-0o' . \substr($scalar, 2)); return -\octdec(\substr($scalar, 1)); } $cast = (int) $scalar; return $scalar === (string) $cast ? $cast : $scalar; case \is_numeric($scalar): case Parser::preg_match(self::getHexRegex(), $scalar): $scalar = \str_replace('_', '', $scalar); return '0x' === $scalar[0] . $scalar[1] ? \hexdec($scalar) : (float) $scalar; case '.inf' === $scalarLower: case '.nan' === $scalarLower: return -\log(0); case '-.inf' === $scalarLower: return \log(0); case Parser::preg_match('/^(-|\\+)?[0-9][0-9_]*(\\.[0-9_]+)?$/', $scalar): return (float) \str_replace('_', '', $scalar); case Parser::preg_match(self::getTimestampRegex(), $scalar): // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. $time = new \DateTime($scalar, new \DateTimeZone('UTC')); if (Yaml::PARSE_DATETIME & $flags) { return $time; } try { if (\false !== ($scalar = $time->getTimestamp())) { return $scalar; } } catch (\ValueError $e) { // no-op } return $time->format('U'); } } return (string) $scalar; } private static function parseTag(string $value, int &$i, int $flags) : ?string { if ('!' !== $value[$i]) { return null; } $tagLength = \strcspn($value, " \t\n[]{},", $i + 1); $tag = \substr($value, $i + 1, $tagLength); $nextOffset = $i + $tagLength + 1; $nextOffset += \strspn($value, ' ', $nextOffset); if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], \true))) { throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); } // Is followed by a scalar and is a built-in tag if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], \true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { // Manage in {@link self::evaluateScalar()} return null; } $i = $nextOffset; // Built-in tags if ('' !== $tag && '!' === $tag[0]) { throw new ParseException(\sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if ('' !== $tag && !isset($value[$i])) { throw new ParseException(\sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { return $tag; } throw new ParseException(\sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } public static function evaluateBinaryScalar(string $scalar) : string { $parsedBinaryData = self::parseScalar(\preg_replace('/\\s/', '', $scalar)); if (0 !== \strlen($parsedBinaryData) % 4) { throw new ParseException(\sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { throw new ParseException(\sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return \base64_decode($parsedBinaryData, \true); } private static function isBinaryString(string $value) : bool { return !\preg_match('//u', $value) || \preg_match('/[^\\x00\\x07-\\x0d\\x1B\\x20-\\xff]/', $value); } /** * Gets a regex that matches a YAML date. * * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 */ private static function getTimestampRegex() : string { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? \$~x EOF; } /** * Gets a regex that matches a YAML number in hexadecimal notation. */ private static function getHexRegex() : string { return '~^0x[0-9a-f_]++$~i'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; /** * Yaml offers convenience methods to load and dump YAML. * * @author Fabien Potencier * * @final */ class Yaml { public const DUMP_OBJECT = 1; public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; public const PARSE_OBJECT = 4; public const PARSE_OBJECT_FOR_MAP = 8; public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; public const PARSE_DATETIME = 32; public const DUMP_OBJECT_AS_MAP = 64; public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; public const PARSE_CONSTANT = 256; public const PARSE_CUSTOM_TAGS = 512; public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; public const DUMP_NULL_AS_TILDE = 2048; /** * Parses a YAML file into a PHP value. * * Usage: * * $array = Yaml::parseFile('config.yml'); * print_r($array); * * @param string $filename The path to the YAML file to be parsed * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * * @return mixed * * @throws ParseException If the file could not be read or the YAML is not valid */ public static function parseFile(string $filename, int $flags = 0) { $yaml = new Parser(); return $yaml->parseFile($filename, $flags); } /** * Parses YAML into a PHP value. * * Usage: * * $array = Yaml::parse(file_get_contents('config.yml')); * print_r($array); * * * @param string $input A string containing YAML * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * * @return mixed * * @throws ParseException If the YAML is not valid */ public static function parse(string $input, int $flags = 0) { $yaml = new Parser(); return $yaml->parse($input, $flags); } /** * Dumps a PHP value to a YAML string. * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. * * @param mixed $input The PHP value * @param int $inline The level where you switch to inline YAML * @param int $indent The amount of spaces to use for indentation of nested nodes * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string */ public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0) : string { $yaml = new Dumper($indent); return $yaml->dump($input, $inline, 0, $flags); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml\Command; use _ContaoManager\Symfony\Component\Console\CI\GithubActionReporter; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; use _ContaoManager\Symfony\Component\Yaml\Parser; use _ContaoManager\Symfony\Component\Yaml\Yaml; /** * Validates YAML files syntax and outputs encountered errors. * * @author Grégoire Pineau * @author Robin Chalas */ class LintCommand extends Command { protected static $defaultName = 'lint:yaml'; protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors'; private $parser; private $format; private $displayCorrectFiles; private $directoryIteratorProvider; private $isReadableProvider; public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null) { parent::__construct($name); $this->directoryIteratorProvider = $directoryIteratorProvider; $this->isReadableProvider = $isReadableProvider; } /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude')->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null)->setHelp(<<%command.name%
    command lints a YAML file and outputs to STDOUT the first encountered syntax error. You can validates YAML contents passed from STDIN: cat filename | php %command.full_name% - You can also validate the syntax of a file: php %command.full_name% filename Or of a whole directory: php %command.full_name% dirname php %command.full_name% dirname --format=json You can also exclude one or more specific files: php %command.full_name% dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml" EOF ); } protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $filenames = (array) $input->getArgument('filename'); $excludes = $input->getOption('exclude'); $this->format = $input->getOption('format'); $flags = $input->getOption('parse-tags'); if ('github' === $this->format && !\class_exists(GithubActionReporter::class)) { throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); } if (null === $this->format) { // Autodetect format according to CI environment $this->format = \class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; } $flags = $flags ? Yaml::PARSE_CUSTOM_TAGS : 0; $this->displayCorrectFiles = $output->isVerbose(); if (['-'] === $filenames) { return $this->display($io, [$this->validate(\file_get_contents('php://stdin'), $flags)]); } if (!$filenames) { throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); } $filesInfo = []; foreach ($filenames as $filename) { if (!$this->isReadable($filename)) { throw new RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename)); } foreach ($this->getFiles($filename) as $file) { if (!\in_array($file->getPathname(), $excludes, \true)) { $filesInfo[] = $this->validate(\file_get_contents($file), $flags, $file); } } } return $this->display($io, $filesInfo); } private function validate(string $content, int $flags, ?string $file = null) { $prevErrorHandler = \set_error_handler(function ($level, $message, $file, $line) use(&$prevErrorHandler) { if (\E_USER_DEPRECATED === $level) { throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); } return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : \false; }); try { $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); } catch (ParseException $e) { return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => \false, 'message' => $e->getMessage()]; } finally { \restore_error_handler(); } return ['file' => $file, 'valid' => \true]; } private function display(SymfonyStyle $io, array $files) : int { switch ($this->format) { case 'txt': return $this->displayTxt($io, $files); case 'json': return $this->displayJson($io, $files); case 'github': return $this->displayTxt($io, $files, \true); default: throw new InvalidArgumentException(\sprintf('The format "%s" is not supported.', $this->format)); } } private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = \false) : int { $countFiles = \count($filesInfo); $erroredFiles = 0; $suggestTagOption = \false; if ($errorAsGithubAnnotations) { $githubReporter = new GithubActionReporter($io); } foreach ($filesInfo as $info) { if ($info['valid'] && $this->displayCorrectFiles) { $io->comment('OK' . ($info['file'] ? \sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$erroredFiles; $io->text(' ERROR ' . ($info['file'] ? \sprintf(' in %s', $info['file']) : '')); $io->text(\sprintf(' >> %s', $info['message'])); if (\false !== \strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { $suggestTagOption = \true; } if ($errorAsGithubAnnotations) { $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); } } } if (0 === $erroredFiles) { $io->success(\sprintf('All %d YAML files contain valid syntax.', $countFiles)); } else { $io->warning(\sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); } return \min($erroredFiles, 1); } private function displayJson(SymfonyStyle $io, array $filesInfo) : int { $errors = 0; \array_walk($filesInfo, function (&$v) use(&$errors) { $v['file'] = (string) $v['file']; if (!$v['valid']) { ++$errors; } if (isset($v['message']) && \false !== \strpos($v['message'], 'PARSE_CUSTOM_TAGS')) { $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; } }); $io->writeln(\json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); return \min($errors, 1); } private function getFiles(string $fileOrDirectory) : iterable { if (\is_file($fileOrDirectory)) { (yield new \SplFileInfo($fileOrDirectory)); return; } foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { continue; } (yield $file); } } private function getParser() : Parser { if (!$this->parser) { $this->parser = new Parser(); } return $this->parser; } private function getDirectoryIterator(string $directory) : iterable { $default = function ($directory) { return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY); }; if (null !== $this->directoryIteratorProvider) { return ($this->directoryIteratorProvider)($directory, $default); } return $default($directory); } private function isReadable(string $fileOrDirectory) : bool { $default = function ($fileOrDirectory) { return \is_readable($fileOrDirectory); }; if (null !== $this->isReadableProvider) { return ($this->isReadableProvider)($fileOrDirectory, $default); } return $default($fileOrDirectory); } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestOptionValuesFor('format')) { $suggestions->suggestValues(['txt', 'json', 'github']); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during dumping. * * @author Fabien Potencier */ class DumpException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml\Exception; /** * Exception interface for all exceptions thrown by the component. * * @author Fabien Potencier */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during parsing. * * @author Romain Neutron */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during parsing. * * @author Fabien Potencier */ class ParseException extends RuntimeException { private $parsedFile; private $parsedLine; private $snippet; private $rawMessage; /** * @param string $message The error message * @param int $parsedLine The line where the error occurred * @param string|null $snippet The snippet of code near the problem * @param string|null $parsedFile The file name where the error occurred */ public function __construct(string $message, int $parsedLine = -1, ?string $snippet = null, ?string $parsedFile = null, ?\Throwable $previous = null) { $this->parsedFile = $parsedFile; $this->parsedLine = $parsedLine; $this->snippet = $snippet; $this->rawMessage = $message; $this->updateRepr(); parent::__construct($this->message, 0, $previous); } /** * Gets the snippet of code near the error. * * @return string */ public function getSnippet() { return $this->snippet; } /** * Sets the snippet of code near the error. */ public function setSnippet(string $snippet) { $this->snippet = $snippet; $this->updateRepr(); } /** * Gets the filename where the error occurred. * * This method returns null if a string is parsed. * * @return string */ public function getParsedFile() { return $this->parsedFile; } /** * Sets the filename where the error occurred. */ public function setParsedFile(string $parsedFile) { $this->parsedFile = $parsedFile; $this->updateRepr(); } /** * Gets the line where the error occurred. * * @return int */ public function getParsedLine() { return $this->parsedLine; } /** * Sets the line where the error occurred. */ public function setParsedLine(int $parsedLine) { $this->parsedLine = $parsedLine; $this->updateRepr(); } private function updateRepr() { $this->message = $this->rawMessage; $dot = \false; if ('.' === \substr($this->message, -1)) { $this->message = \substr($this->message, 0, -1); $dot = \true; } if (null !== $this->parsedFile) { $this->message .= \sprintf(' in %s', \json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); } if ($this->parsedLine >= 0) { $this->message .= \sprintf(' at line %d', $this->parsedLine); } if ($this->snippet) { $this->message .= \sprintf(' (near "%s")', $this->snippet); } if ($dot) { $this->message .= '.'; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Yaml\Tag; /** * @author Nicolas Grekas * @author Guilhem N. */ final class TaggedValue { private $tag; private $value; public function __construct(string $tag, $value) { $this->tag = $tag; $this->value = $value; } public function getTag() : string { return $this->tag; } public function getValue() { return $this->value; } } { "name": "symfony\/yaml", "type": "library", "description": "Loads and dumps YAML files", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-ctype": "^1.8" }, "require-dev": { "symfony\/console": "^5.3|^6.0" }, "conflict": { "symfony\/console": "<5.3" }, "suggest": { "symfony\/console": "For validating YAML files using the lint command" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Yaml\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "bin": [ "Resources\/bin\/yaml-lint" ], "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; use _ContaoManager\Symfony\Component\String\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\String\Exception\InvalidArgumentException; /** * Represents a string of Unicode code points encoded as UTF-8. * * @author Nicolas Grekas * @author Hugo Hamon * * @throws ExceptionInterface */ class CodePointString extends AbstractUnicodeString { public function __construct(string $string = '') { if ('' !== $string && !\preg_match('//u', $string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $this->string = $string; } public function append(string ...$suffix) : AbstractString { $str = clone $this; $str->string .= 1 >= \count($suffix) ? $suffix[0] ?? '' : \implode('', $suffix); if (!\preg_match('//u', $str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function chunk(int $length = 1) : array { if (1 > $length) { throw new InvalidArgumentException('The chunk length must be greater than zero.'); } if ('' === $this->string) { return []; } $rx = '/('; while (65535 < $length) { $rx .= '.{65535}'; $length -= 65535; } $rx .= '.{' . $length . '})/us'; $str = clone $this; $chunks = []; foreach (\preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { $str->string = $chunk; $chunks[] = clone $str; } return $chunks; } public function codePointsAt(int $offset) : array { $str = $offset ? $this->slice($offset, 1) : $this; return '' === $str->string ? [] : [\mb_ord($str->string, 'UTF-8')]; } public function endsWith($suffix) : bool { if ($suffix instanceof AbstractString) { $suffix = $suffix->string; } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { return parent::endsWith($suffix); } else { $suffix = (string) $suffix; } if ('' === $suffix || !\preg_match('//u', $suffix)) { return \false; } if ($this->ignoreCase) { return \preg_match('{' . \preg_quote($suffix) . '$}iuD', $this->string); } return \strlen($this->string) >= \strlen($suffix) && 0 === \substr_compare($this->string, $suffix, -\strlen($suffix)); } public function equalsTo($string) : bool { if ($string instanceof AbstractString) { $string = $string->string; } elseif (\is_array($string) || $string instanceof \Traversable) { return parent::equalsTo($string); } else { $string = (string) $string; } if ('' !== $string && $this->ignoreCase) { return \strlen($string) === \strlen($this->string) && 0 === \mb_stripos($this->string, $string, 0, 'UTF-8'); } return $string === $this->string; } public function indexOf($needle, int $offset = 0) : ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOf($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? \mb_stripos($this->string, $needle, $offset, 'UTF-8') : \mb_strpos($this->string, $needle, $offset, 'UTF-8'); return \false === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0) : ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOfLast($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? \mb_strripos($this->string, $needle, $offset, 'UTF-8') : \mb_strrpos($this->string, $needle, $offset, 'UTF-8'); return \false === $i ? null : $i; } public function length() : int { return \mb_strlen($this->string, 'UTF-8'); } public function prepend(string ...$prefix) : AbstractString { $str = clone $this; $str->string = (1 >= \count($prefix) ? $prefix[0] ?? '' : \implode('', $prefix)) . $this->string; if (!\preg_match('//u', $str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function replace(string $from, string $to) : AbstractString { $str = clone $this; if ('' === $from || !\preg_match('//u', $from)) { return $str; } if ('' !== $to && !\preg_match('//u', $to)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } if ($this->ignoreCase) { $str->string = \implode($to, \preg_split('{' . \preg_quote($from) . '}iuD', $this->string)); } else { $str->string = \str_replace($from, $to, $this->string); } return $str; } public function slice(int $start = 0, ?int $length = null) : AbstractString { $str = clone $this; $str->string = \mb_substr($this->string, $start, $length, 'UTF-8'); return $str; } public function splice(string $replacement, int $start = 0, ?int $length = null) : AbstractString { if (!\preg_match('//u', $replacement)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $str = clone $this; $start = $start ? \strlen(\mb_substr($this->string, 0, $start, 'UTF-8')) : 0; $length = $length ? \strlen(\mb_substr($this->string, $start, $length, 'UTF-8')) : $length; $str->string = \substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); return $str; } public function split(string $delimiter, ?int $limit = null, ?int $flags = null) : array { if (1 > ($limit = $limit ?? \PHP_INT_MAX)) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } if ('' === $delimiter) { throw new InvalidArgumentException('Split delimiter is empty.'); } if (null !== $flags) { return parent::split($delimiter . 'u', $limit, $flags); } if (!\preg_match('//u', $delimiter)) { throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); } $str = clone $this; $chunks = $this->ignoreCase ? \preg_split('{' . \preg_quote($delimiter) . '}iuD', $this->string, $limit) : \explode($delimiter, $this->string, $limit); foreach ($chunks as &$chunk) { $str->string = $chunk; $chunk = clone $str; } return $chunks; } public function startsWith($prefix) : bool { if ($prefix instanceof AbstractString) { $prefix = $prefix->string; } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { return parent::startsWith($prefix); } else { $prefix = (string) $prefix; } if ('' === $prefix || !\preg_match('//u', $prefix)) { return \false; } if ($this->ignoreCase) { return 0 === \mb_stripos($this->string, $prefix, 0, 'UTF-8'); } return 0 === \strncmp($this->string, $prefix, \strlen($prefix)); } } Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Add `trimSuffix()` and `trimPrefix()` methods 5.3 --- * Made `AsciiSlugger` fallback to parent locale's symbolsMap 5.2.0 ----- * added a `FrenchInflector` class 5.1.0 ----- * added the `AbstractString::reverse()` method * made `AbstractString::width()` follow POSIX.1-2001 * added `LazyString` which provides memoizing stringable objects * The component is not marked as `@experimental` anymore * added the `s()` helper method to get either an `UnicodeString` or `ByteString` instance, depending of the input string UTF-8 compliancy * added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()` * added `AbstractString::containsAny()` * allow passing a string of custom characters to `ByteString::fromRandom()` 5.0.0 ----- * added the component as experimental * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; use _ContaoManager\Symfony\Component\String\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\String\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\String\Exception\RuntimeException; /** * Represents a string of abstract characters. * * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). * This class is the abstract type to use as a type-hint when the logic you want to * implement doesn't care about the exact variant it deals with. * * @author Nicolas Grekas * @author Hugo Hamon * * @throws ExceptionInterface */ abstract class AbstractString implements \Stringable, \JsonSerializable { public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER; public const PREG_SET_ORDER = \PREG_SET_ORDER; public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE; public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL; public const PREG_SPLIT = 0; public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY; public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE; public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE; protected $string = ''; protected $ignoreCase = \false; public abstract function __construct(string $string = ''); /** * Unwraps instances of AbstractString back to strings. * * @return string[]|array */ public static function unwrap(array $values) : array { foreach ($values as $k => $v) { if ($v instanceof self) { $values[$k] = $v->__toString(); } elseif (\is_array($v) && $values[$k] !== ($v = static::unwrap($v))) { $values[$k] = $v; } } return $values; } /** * Wraps (and normalizes) strings in instances of AbstractString. * * @return static[]|array */ public static function wrap(array $values) : array { $i = 0; $keys = null; foreach ($values as $k => $v) { if (\is_string($k) && '' !== $k && $k !== ($j = (string) new static($k))) { $keys = $keys ?? \array_keys($values); $keys[$i] = $j; } if (\is_string($v)) { $values[$k] = new static($v); } elseif (\is_array($v) && $values[$k] !== ($v = static::wrap($v))) { $values[$k] = $v; } ++$i; } return null !== $keys ? \array_combine($keys, $values) : $values; } /** * @param string|string[] $needle * * @return static */ public function after($needle, bool $includeNeedle = \false, int $offset = 0) : self { $str = clone $this; $i = \PHP_INT_MAX; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOf($n, $offset); if (null !== $j && $j < $i) { $i = $j; $str->string = $n; } } if (\PHP_INT_MAX === $i) { return $str; } if (!$includeNeedle) { $i += $str->length(); } return $this->slice($i); } /** * @param string|string[] $needle * * @return static */ public function afterLast($needle, bool $includeNeedle = \false, int $offset = 0) : self { $str = clone $this; $i = null; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOfLast($n, $offset); if (null !== $j && $j >= $i) { $i = $offset = $j; $str->string = $n; } } if (null === $i) { return $str; } if (!$includeNeedle) { $i += $str->length(); } return $this->slice($i); } /** * @return static */ public abstract function append(string ...$suffix) : self; /** * @param string|string[] $needle * * @return static */ public function before($needle, bool $includeNeedle = \false, int $offset = 0) : self { $str = clone $this; $i = \PHP_INT_MAX; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOf($n, $offset); if (null !== $j && $j < $i) { $i = $j; $str->string = $n; } } if (\PHP_INT_MAX === $i) { return $str; } if ($includeNeedle) { $i += $str->length(); } return $this->slice(0, $i); } /** * @param string|string[] $needle * * @return static */ public function beforeLast($needle, bool $includeNeedle = \false, int $offset = 0) : self { $str = clone $this; $i = null; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOfLast($n, $offset); if (null !== $j && $j >= $i) { $i = $offset = $j; $str->string = $n; } } if (null === $i) { return $str; } if ($includeNeedle) { $i += $str->length(); } return $this->slice(0, $i); } /** * @return int[] */ public function bytesAt(int $offset) : array { $str = $this->slice($offset, 1); return '' === $str->string ? [] : \array_values(\unpack('C*', $str->string)); } /** * @return static */ public abstract function camel() : self; /** * @return static[] */ public abstract function chunk(int $length = 1) : array; /** * @return static */ public function collapseWhitespace() : self { $str = clone $this; $str->string = \trim(\preg_replace("/(?:[ \n\r\t\f]{2,}+|[\n\r\t\f])/", ' ', $str->string), " \n\r\t\f"); return $str; } /** * @param string|string[] $needle */ public function containsAny($needle) : bool { return null !== $this->indexOf($needle); } /** * @param string|string[] $suffix */ public function endsWith($suffix) : bool { if (!\is_array($suffix) && !$suffix instanceof \Traversable) { throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($suffix as $s) { if ($this->endsWith((string) $s)) { return \true; } } return \false; } /** * @return static */ public function ensureEnd(string $suffix) : self { if (!$this->endsWith($suffix)) { return $this->append($suffix); } $suffix = \preg_quote($suffix); $regex = '{(' . $suffix . ')(?:' . $suffix . ')++$}D'; return $this->replaceMatches($regex . ($this->ignoreCase ? 'i' : ''), '$1'); } /** * @return static */ public function ensureStart(string $prefix) : self { $prefix = new static($prefix); if (!$this->startsWith($prefix)) { return $this->prepend($prefix); } $str = clone $this; $i = $prefixLen = $prefix->length(); while ($this->indexOf($prefix, $i) === $i) { $str = $str->slice($prefixLen); $i += $prefixLen; } return $str; } /** * @param string|string[] $string */ public function equalsTo($string) : bool { if (!\is_array($string) && !$string instanceof \Traversable) { throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($string as $s) { if ($this->equalsTo((string) $s)) { return \true; } } return \false; } /** * @return static */ public abstract function folded() : self; /** * @return static */ public function ignoreCase() : self { $str = clone $this; $str->ignoreCase = \true; return $str; } /** * @param string|string[] $needle */ public function indexOf($needle, int $offset = 0) : ?int { if (!\is_array($needle) && !$needle instanceof \Traversable) { throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } $i = \PHP_INT_MAX; foreach ($needle as $n) { $j = $this->indexOf((string) $n, $offset); if (null !== $j && $j < $i) { $i = $j; } } return \PHP_INT_MAX === $i ? null : $i; } /** * @param string|string[] $needle */ public function indexOfLast($needle, int $offset = 0) : ?int { if (!\is_array($needle) && !$needle instanceof \Traversable) { throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } $i = null; foreach ($needle as $n) { $j = $this->indexOfLast((string) $n, $offset); if (null !== $j && $j >= $i) { $i = $offset = $j; } } return $i; } public function isEmpty() : bool { return '' === $this->string; } /** * @return static */ public abstract function join(array $strings, ?string $lastGlue = null) : self; public function jsonSerialize() : string { return $this->string; } public abstract function length() : int; /** * @return static */ public abstract function lower() : self; /** * Matches the string using a regular expression. * * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression. * * @return array All matches in a multi-dimensional array ordered according to flags */ public abstract function match(string $regexp, int $flags = 0, int $offset = 0) : array; /** * @return static */ public abstract function padBoth(int $length, string $padStr = ' ') : self; /** * @return static */ public abstract function padEnd(int $length, string $padStr = ' ') : self; /** * @return static */ public abstract function padStart(int $length, string $padStr = ' ') : self; /** * @return static */ public abstract function prepend(string ...$prefix) : self; /** * @return static */ public function repeat(int $multiplier) : self { if (0 > $multiplier) { throw new InvalidArgumentException(\sprintf('Multiplier must be positive, %d given.', $multiplier)); } $str = clone $this; $str->string = \str_repeat($str->string, $multiplier); return $str; } /** * @return static */ public abstract function replace(string $from, string $to) : self; /** * @param string|callable $to * * @return static */ public abstract function replaceMatches(string $fromRegexp, $to) : self; /** * @return static */ public abstract function reverse() : self; /** * @return static */ public abstract function slice(int $start = 0, ?int $length = null) : self; /** * @return static */ public abstract function snake() : self; /** * @return static */ public abstract function splice(string $replacement, int $start = 0, ?int $length = null) : self; /** * @return static[] */ public function split(string $delimiter, ?int $limit = null, ?int $flags = null) : array { if (null === $flags) { throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); } if ($this->ignoreCase) { $delimiter .= 'i'; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (\false === ($chunks = \preg_split($delimiter, $this->string, $limit, $flags))) { $lastError = \preg_last_error(); foreach (\get_defined_constants(\true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === \substr($k, -6)) { throw new RuntimeException('Splitting failed with ' . $k . '.'); } } throw new RuntimeException('Splitting failed with unknown error code.'); } } finally { \restore_error_handler(); } $str = clone $this; if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) { foreach ($chunks as &$chunk) { $str->string = $chunk[0]; $chunk[0] = clone $str; } } else { foreach ($chunks as &$chunk) { $str->string = $chunk; $chunk = clone $str; } } return $chunks; } /** * @param string|string[] $prefix */ public function startsWith($prefix) : bool { if (!\is_array($prefix) && !$prefix instanceof \Traversable) { throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($prefix as $prefix) { if ($this->startsWith((string) $prefix)) { return \true; } } return \false; } /** * @return static */ public abstract function title(bool $allWords = \false) : self; public function toByteString(?string $toEncoding = null) : ByteString { $b = new ByteString(); $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], \true) ? 'UTF-8' : $toEncoding; if (null === $toEncoding || $toEncoding === ($fromEncoding = $this instanceof AbstractUnicodeString || \preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252')) { $b->string = $this->string; return $b; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { try { $b->string = \mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); } catch (InvalidArgumentException|\ValueError $e) { if (!\function_exists('iconv')) { if ($e instanceof \ValueError) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } throw $e; } $b->string = \iconv('UTF-8', $toEncoding, $this->string); } } finally { \restore_error_handler(); } return $b; } public function toCodePointString() : CodePointString { return new CodePointString($this->string); } public function toString() : string { return $this->string; } public function toUnicodeString() : UnicodeString { return new UnicodeString($this->string); } /** * @return static */ public abstract function trim(string $chars = " \t\n\r\x00\v\f ") : self; /** * @return static */ public abstract function trimEnd(string $chars = " \t\n\r\x00\v\f ") : self; /** * @param string|string[] $prefix * * @return static */ public function trimPrefix($prefix) : self { if (\is_array($prefix) || $prefix instanceof \Traversable) { foreach ($prefix as $s) { $t = $this->trimPrefix($s); if ($t->string !== $this->string) { return $t; } } return clone $this; } $str = clone $this; if ($prefix instanceof self) { $prefix = $prefix->string; } else { $prefix = (string) $prefix; } if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === \substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) { $str->string = \substr($this->string, \strlen($prefix)); } return $str; } /** * @return static */ public abstract function trimStart(string $chars = " \t\n\r\x00\v\f ") : self; /** * @param string|string[] $suffix * * @return static */ public function trimSuffix($suffix) : self { if (\is_array($suffix) || $suffix instanceof \Traversable) { foreach ($suffix as $s) { $t = $this->trimSuffix($s); if ($t->string !== $this->string) { return $t; } } return clone $this; } $str = clone $this; if ($suffix instanceof self) { $suffix = $suffix->string; } else { $suffix = (string) $suffix; } if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === \substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) { $str->string = \substr($this->string, 0, -\strlen($suffix)); } return $str; } /** * @return static */ public function truncate(int $length, string $ellipsis = '', bool $cut = \true) : self { $stringLength = $this->length(); if ($stringLength <= $length) { return clone $this; } $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0; if ($length < $ellipsisLength) { $ellipsisLength = 0; } if (!$cut) { if (null === ($length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1))) { return clone $this; } $length += $ellipsisLength; } $str = $this->slice(0, $length - $ellipsisLength); return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; } /** * @return static */ public abstract function upper() : self; /** * Returns the printable length on a terminal. */ public abstract function width(bool $ignoreAnsiDecoration = \true) : int; /** * @return static */ public function wordwrap(int $width = 75, string $break = "\n", bool $cut = \false) : self { $lines = '' !== $break ? $this->split($break) : [clone $this]; $chars = []; $mask = ''; if (1 === \count($lines) && '' === $lines[0]->string) { return $lines[0]; } foreach ($lines as $i => $line) { if ($i) { $chars[] = $break; $mask .= '#'; } foreach ($line->chunk() as $char) { $chars[] = $char->string; $mask .= ' ' === $char->string ? ' ' : '?'; } } $string = ''; $j = 0; $b = $i = -1; $mask = \wordwrap($mask, $width, '#', $cut); while (\false !== ($b = \strpos($mask, '#', $b + 1))) { for (++$i; $i < $b; ++$i) { $string .= $chars[$j]; unset($chars[$j++]); } if ($break === $chars[$j] || ' ' === $chars[$j]) { unset($chars[$j++]); } $string .= $break; } $str = clone $this; $str->string = $string . \implode('', $chars); return $str; } public function __sleep() : array { return ['string']; } public function __clone() { $this->ignoreCase = \false; } public function __toString() : string { return $this->string; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; if (!\function_exists(u::class)) { function u(?string $string = '') : UnicodeString { return new UnicodeString($string ?? ''); } } if (!\function_exists(b::class)) { function b(?string $string = '') : ByteString { return new ByteString($string ?? ''); } } if (!\function_exists(s::class)) { /** * @return UnicodeString|ByteString */ function s(?string $string = '') : AbstractString { $string = $string ?? ''; return \preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Inflector; /** * French inflector. * * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix". */ final class FrenchInflector implements InflectorInterface { /** * A list of all rules for pluralise. * * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php */ private const PLURALIZE_REGEXP = [ // First entry: regexp // Second entry: replacement // Words finishing with "s", "x" or "z" are invariables // Les mots finissant par "s", "x" ou "z" sont invariables ['/(s|x|z)$/i', '\\1'], // Words finishing with "eau" are pluralized with a "x" // Les mots finissant par "eau" prennent tous un "x" au pluriel ['/(eau)$/i', '\\1x'], // Words finishing with "au" are pluralized with a "x" excepted "landau" // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" ['/^(landau)$/i', '\\1s'], ['/(au)$/i', '\\1x'], // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" ['/^(pneu|bleu|émeu)$/i', '\\1s'], ['/(eu)$/i', '\\1x'], // Words finishing with "al" are pluralized with a "aux" excepted // Les mots finissant en "al" se terminent en "aux" sauf ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\\1s'], ['/al$/i', '\\1aux'], // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\\1aux'], // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\\1oux'], // Invariable words ['/^(cinquante|soixante|mille)$/i', '\\1'], // French titles ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', '_ContaoManager\\mes\\2s'], ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', '_ContaoManager\\Mes\\2s'], ]; /** * A list of all rules for singularize. */ private const SINGULARIZE_REGEXP = [ // First entry: regexp // Second entry: replacement // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\\1ail'], // Words finishing with "eau" are pluralized with a "x" // Les mots finissant par "eau" prennent tous un "x" au pluriel ['/(eau)x$/i', '\\1'], // Words finishing with "al" are pluralized with a "aux" expected // Les mots finissant en "al" se terminent en "aux" sauf ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\\1al'], // Words finishing with "au" are pluralized with a "x" excepted "landau" // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" ['/(au)x$/i', '\\1'], // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" ['/(eu)x$/i', '\\1'], // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\\1ou'], // French titles ['/^mes(dame|demoiselle)s$/', '_ContaoManager\\ma\\1'], ['/^Mes(dame|demoiselle)s$/', '_ContaoManager\\Ma\\1'], ['/^mes(sieur|seigneur)s$/', '_ContaoManager\\mon\\1'], ['/^Mes(sieur|seigneur)s$/', '_ContaoManager\\Mon\\1'], // Default rule ['/s$/i', ''], ]; /** * A list of words which should not be inflected. * This list is only used by singularize. */ private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i'; /** * {@inheritdoc} */ public function singularize(string $plural) : array { if ($this->isInflectedWord($plural)) { return [$plural]; } foreach (self::SINGULARIZE_REGEXP as $rule) { [$regexp, $replace] = $rule; if (1 === \preg_match($regexp, $plural)) { return [\preg_replace($regexp, $replace, $plural)]; } } return [$plural]; } /** * {@inheritdoc} */ public function pluralize(string $singular) : array { if ($this->isInflectedWord($singular)) { return [$singular]; } foreach (self::PLURALIZE_REGEXP as $rule) { [$regexp, $replace] = $rule; if (1 === \preg_match($regexp, $singular)) { return [\preg_replace($regexp, $replace, $singular)]; } } return [$singular . 's']; } private function isInflectedWord(string $word) : bool { return 1 === \preg_match(self::UNINFLECTED, $word); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Inflector; interface InflectorInterface { /** * Returns the singular forms of a string. * * If the method can't determine the form with certainty, several possible singulars are returned. * * @return string[] */ public function singularize(string $plural) : array; /** * Returns the plural forms of a string. * * If the method can't determine the form with certainty, several possible plurals are returned. * * @return string[] */ public function pluralize(string $singular) : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Inflector; final class EnglishInflector implements InflectorInterface { /** * Map English plural to singular suffixes. * * @see http://english-zone.com/spelling/plurals.html */ private const PLURAL_MAP = [ // First entry: plural suffix, reversed // Second entry: length of plural suffix // Third entry: Whether the suffix may succeed a vowel // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: singular suffix, normal // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) ['a', 1, \true, \true, ['on', 'um']], // nebulae (nebula) ['ea', 2, \true, \true, 'a'], // services (service) ['secivres', 8, \true, \true, 'service'], // mice (mouse), lice (louse) ['eci', 3, \false, \true, 'ouse'], // geese (goose) ['esee', 4, \false, \true, 'oose'], // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) ['i', 1, \true, \true, 'us'], // men (man), women (woman) ['nem', 3, \true, \true, 'man'], // children (child) ['nerdlihc', 8, \true, \true, 'child'], // oxen (ox) ['nexo', 4, \false, \false, 'ox'], // indices (index), appendices (appendix), prices (price) ['seci', 4, \false, \true, ['ex', 'ix', 'ice']], // codes (code) ['sedoc', 5, \false, \true, 'code'], // selfies (selfie) ['seifles', 7, \true, \true, 'selfie'], // zombies (zombie) ['seibmoz', 7, \true, \true, 'zombie'], // movies (movie) ['seivom', 6, \true, \true, 'movie'], // names (name) ['seman', 5, \true, \false, 'name'], // conspectuses (conspectus), prospectuses (prospectus) ['sesutcep', 8, \true, \true, 'pectus'], // feet (foot) ['teef', 4, \true, \true, 'foot'], // geese (goose) ['eseeg', 5, \true, \true, 'goose'], // teeth (tooth) ['hteet', 5, \true, \true, 'tooth'], // news (news) ['swen', 4, \true, \true, 'news'], // series (series) ['seires', 6, \true, \true, 'series'], // babies (baby) ['sei', 3, \false, \true, 'y'], // accesses (access), addresses (address), kisses (kiss) ['sess', 4, \true, \false, 'ss'], // statuses (status) ['sesutats', 8, \true, \true, 'status'], // analyses (analysis), ellipses (ellipsis), fungi (fungus), // neuroses (neurosis), theses (thesis), emphases (emphasis), // oases (oasis), crises (crisis), houses (house), bases (base), // atlases (atlas) ['ses', 3, \true, \true, ['s', 'se', 'sis']], // objectives (objective), alternative (alternatives) ['sevit', 5, \true, \true, 'tive'], // drives (drive) ['sevird', 6, \false, \true, 'drive'], // lives (life), wives (wife) ['sevi', 4, \false, \true, 'ife'], // moves (move) ['sevom', 5, \true, \true, 'move'], // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff) ['sev', 3, \true, \true, ['f', 've', 'ff']], // axes (axis), axes (ax), axes (axe) ['sexa', 4, \false, \false, ['ax', 'axe', 'axis']], // indexes (index), matrixes (matrix) ['sex', 3, \true, \false, 'x'], // quizzes (quiz) ['sezz', 4, \true, \false, 'z'], // bureaus (bureau) ['suae', 4, \false, \true, 'eau'], // fees (fee), trees (tree), employees (employee) ['see', 3, \true, \true, 'ee'], // edges (edge) ['segd', 4, \true, \true, 'dge'], // roses (rose), garages (garage), cassettes (cassette), // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), // shoes (shoe) ['se', 2, \true, \true, ['', 'e']], // status (status) ['sutats', 6, \true, \true, 'status'], // tags (tag) ['s', 1, \true, \true, ''], // chateaux (chateau) ['xuae', 4, \false, \true, 'eau'], // people (person) ['elpoep', 6, \true, \true, 'person'], ]; /** * Map English singular to plural suffixes. * * @see http://english-zone.com/spelling/plurals.html */ private const SINGULAR_MAP = [ // First entry: singular suffix, reversed // Second entry: length of singular suffix // Third entry: Whether the suffix may succeed a vowel // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: plural suffix, normal // axes (axis) ['sixa', 4, \false, \false, 'axes'], // criterion (criteria) ['airetirc', 8, \false, \false, 'criterion'], // nebulae (nebula) ['aluben', 6, \false, \false, 'nebulae'], // children (child) ['dlihc', 5, \true, \true, 'children'], // prices (price) ['eci', 3, \false, \true, 'ices'], // services (service) ['ecivres', 7, \true, \true, 'services'], // lives (life), wives (wife) ['efi', 3, \false, \true, 'ives'], // selfies (selfie) ['eifles', 6, \true, \true, 'selfies'], // movies (movie) ['eivom', 5, \true, \true, 'movies'], // lice (louse) ['esuol', 5, \false, \true, 'lice'], // mice (mouse) ['esuom', 5, \false, \true, 'mice'], // geese (goose) ['esoo', 4, \false, \true, 'eese'], // houses (house), bases (base) ['es', 2, \true, \true, 'ses'], // geese (goose) ['esoog', 5, \true, \true, 'geese'], // caves (cave) ['ev', 2, \true, \true, 'ves'], // drives (drive) ['evird', 5, \false, \true, 'drives'], // objectives (objective), alternative (alternatives) ['evit', 4, \true, \true, 'tives'], // moves (move) ['evom', 4, \true, \true, 'moves'], // staves (staff) ['ffats', 5, \true, \true, 'staves'], // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) ['ff', 2, \true, \true, 'ffs'], // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) ['f', 1, \true, \true, ['fs', 'ves']], // arches (arch) ['hc', 2, \true, \true, 'ches'], // bushes (bush) ['hs', 2, \true, \true, 'shes'], // teeth (tooth) ['htoot', 5, \true, \true, 'teeth'], // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) ['mu', 2, \true, \true, 'a'], // men (man), women (woman) ['nam', 3, \true, \true, 'men'], // people (person) ['nosrep', 6, \true, \true, ['persons', 'people']], // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) ['noi', 3, \true, \true, 'ions'], // coupon (coupons) ['nop', 3, \true, \true, 'pons'], // seasons (season), treasons (treason), poisons (poison), lessons (lesson) ['nos', 3, \true, \true, 'sons'], // icons (icon) ['noc', 3, \true, \true, 'cons'], // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) ['no', 2, \true, \true, 'a'], // echoes (echo) ['ohce', 4, \true, \true, 'echoes'], // heroes (hero) ['oreh', 4, \true, \true, 'heroes'], // atlases (atlas) ['salta', 5, \true, \true, 'atlases'], // irises (iris) ['siri', 4, \true, \true, 'irises'], // analyses (analysis), ellipses (ellipsis), neuroses (neurosis) // theses (thesis), emphases (emphasis), oases (oasis), // crises (crisis) ['sis', 3, \true, \true, 'ses'], // accesses (access), addresses (address), kisses (kiss) ['ss', 2, \true, \false, 'sses'], // syllabi (syllabus) ['suballys', 8, \true, \true, 'syllabi'], // buses (bus) ['sub', 3, \true, \true, 'buses'], // circuses (circus) ['suc', 3, \true, \true, 'cuses'], // status (status) ['sutats', 6, \true, \true, ['status', 'statuses']], // conspectuses (conspectus), prospectuses (prospectus) ['sutcep', 6, \true, \true, 'pectuses'], // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) ['su', 2, \true, \true, 'i'], // news (news) ['swen', 4, \true, \true, 'news'], // feet (foot) ['toof', 4, \true, \true, 'feet'], // chateaux (chateau), bureaus (bureau) ['uae', 3, \false, \true, ['eaus', 'eaux']], // oxen (ox) ['xo', 2, \false, \false, 'oxen'], // hoaxes (hoax) ['xaoh', 4, \true, \false, 'hoaxes'], // indices (index) ['xedni', 5, \false, \true, ['indicies', 'indexes']], // boxes (box) ['xo', 2, \false, \true, 'oxes'], // indexes (index), matrixes (matrix) ['x', 1, \true, \false, ['cies', 'xes']], // appendices (appendix) ['xi', 2, \false, \true, 'ices'], // babies (baby) ['y', 1, \false, \true, 'ies'], // quizzes (quiz) ['ziuq', 4, \true, \false, 'quizzes'], // waltzes (waltz) ['z', 1, \true, \true, 'zes'], ]; /** * A list of words which should not be inflected, reversed. */ private const UNINFLECTED = [ '', // data 'atad', // deer 'reed', // equipment 'tnempiuqe', // feedback 'kcabdeef', // fish 'hsif', // health 'htlaeh', // history 'yrotsih', // info 'ofni', // information 'noitamrofni', // money 'yenom', // moose 'esoom', // series 'seires', // sheep 'peehs', // species 'seiceps', // traffic 'ciffart', // aircraft 'tfarcria', ]; /** * {@inheritdoc} */ public function singularize(string $plural) : array { $pluralRev = \strrev($plural); $lowerPluralRev = \strtolower($pluralRev); $pluralLength = \strlen($lowerPluralRev); // Check if the word is one which is not inflected, return early if so if (\in_array($lowerPluralRev, self::UNINFLECTED, \true)) { return [$plural]; } // The outer loop iterates over the entries of the plural table // The inner loop $j iterates over the characters of the plural suffix // in the plural table to compare them with the characters of the actual // given plural suffix foreach (self::PLURAL_MAP as $map) { $suffix = $map[0]; $suffixLength = $map[1]; $j = 0; // Compare characters in the plural table and of the suffix of the // given plural one by one while ($suffix[$j] === $lowerPluralRev[$j]) { // Let $j point to the next character ++$j; // Successfully compared the last character // Add an entry with the singular suffix to the singular array if ($j === $suffixLength) { // Is there any character preceding the suffix in the plural string? if ($j < $pluralLength) { $nextIsVowel = \false !== \strpos('aeiou', $lowerPluralRev[$j]); if (!$map[2] && $nextIsVowel) { // suffix may not succeed a vowel but next char is one break; } if (!$map[3] && !$nextIsVowel) { // suffix may not succeed a consonant but next char is one break; } } $newBase = \substr($plural, 0, $pluralLength - $suffixLength); $newSuffix = $map[4]; // Check whether the first character in the plural suffix // is uppercased. If yes, uppercase the first character in // the singular suffix too $firstUpper = \ctype_upper($pluralRev[$j - 1]); if (\is_array($newSuffix)) { $singulars = []; foreach ($newSuffix as $newSuffixEntry) { $singulars[] = $newBase . ($firstUpper ? \ucfirst($newSuffixEntry) : $newSuffixEntry); } return $singulars; } return [$newBase . ($firstUpper ? \ucfirst($newSuffix) : $newSuffix)]; } // Suffix is longer than word if ($j === $pluralLength) { break; } } } // Assume that plural and singular is identical return [$plural]; } /** * {@inheritdoc} */ public function pluralize(string $singular) : array { $singularRev = \strrev($singular); $lowerSingularRev = \strtolower($singularRev); $singularLength = \strlen($lowerSingularRev); // Check if the word is one which is not inflected, return early if so if (\in_array($lowerSingularRev, self::UNINFLECTED, \true)) { return [$singular]; } // The outer loop iterates over the entries of the singular table // The inner loop $j iterates over the characters of the singular suffix // in the singular table to compare them with the characters of the actual // given singular suffix foreach (self::SINGULAR_MAP as $map) { $suffix = $map[0]; $suffixLength = $map[1]; $j = 0; // Compare characters in the singular table and of the suffix of the // given plural one by one while ($suffix[$j] === $lowerSingularRev[$j]) { // Let $j point to the next character ++$j; // Successfully compared the last character // Add an entry with the plural suffix to the plural array if ($j === $suffixLength) { // Is there any character preceding the suffix in the plural string? if ($j < $singularLength) { $nextIsVowel = \false !== \strpos('aeiou', $lowerSingularRev[$j]); if (!$map[2] && $nextIsVowel) { // suffix may not succeed a vowel but next char is one break; } if (!$map[3] && !$nextIsVowel) { // suffix may not succeed a consonant but next char is one break; } } $newBase = \substr($singular, 0, $singularLength - $suffixLength); $newSuffix = $map[4]; // Check whether the first character in the singular suffix // is uppercased. If yes, uppercase the first character in // the singular suffix too $firstUpper = \ctype_upper($singularRev[$j - 1]); if (\is_array($newSuffix)) { $plurals = []; foreach ($newSuffix as $newSuffixEntry) { $plurals[] = $newBase . ($firstUpper ? \ucfirst($newSuffixEntry) : $newSuffixEntry); } return $plurals; } return [$newBase . ($firstUpper ? \ucfirst($newSuffix) : $newSuffix)]; } // Suffix is longer than word if ($j === $singularLength) { break; } } } // Assume that plural is singular with a trailing `s` return [$singular . 's']; } } String Component ================ The String component provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way. Resources --------- * [Documentation](https://symfony.com/doc/current/components/string.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; use _ContaoManager\Symfony\Component\String\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\String\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\String\Exception\RuntimeException; /** * Represents a string of abstract Unicode characters. * * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). * This class is the abstract type to use as a type-hint when the logic you want to * implement is Unicode-aware but doesn't care about code points vs grapheme clusters. * * @author Nicolas Grekas * * @throws ExceptionInterface */ abstract class AbstractUnicodeString extends AbstractString { public const NFC = \Normalizer::NFC; public const NFD = \Normalizer::NFD; public const NFKC = \Normalizer::NFKC; public const NFKD = \Normalizer::NFKD; // all ASCII letters sorted by typical frequency of occurrence private const ASCII = " eiasntrolud][cmp'\ng|hv.fb,:=-q10C2*yx)(L9AS/P\"EjMIk3>5T>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; private static $transliterators = []; private static $tableZero; private static $tableWide; /** * @return static */ public static function fromCodePoints(int ...$codes) : self { $string = ''; foreach ($codes as $code) { if (0x80 > ($code %= 0x200000)) { $string .= \chr($code); } elseif (0x800 > $code) { $string .= \chr(0xc0 | $code >> 6) . \chr(0x80 | $code & 0x3f); } elseif (0x10000 > $code) { $string .= \chr(0xe0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f); } else { $string .= \chr(0xf0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3f) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f); } } return new static($string); } /** * Generic UTF-8 to ASCII transliteration. * * Install the intl extension for best results. * * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs() */ public function ascii(array $rules = []) : self { $str = clone $this; $s = $str->string; $str->string = ''; \array_unshift($rules, 'nfd'); $rules[] = 'latin-ascii'; if (\function_exists('transliterator_transliterate')) { $rules[] = 'any-latin/bgn'; } $rules[] = 'nfkd'; $rules[] = '[:nonspacing mark:] remove'; while (\strlen($s) - 1 > ($i = \strspn($s, self::ASCII))) { if (0 < --$i) { $str->string .= \substr($s, 0, $i); $s = \substr($s, $i); } if (!($rule = \array_shift($rules))) { $rules = []; // An empty rule interrupts the next ones } if ($rule instanceof \Transliterator) { $s = $rule->transliterate($s); } elseif ($rule instanceof \Closure) { $s = $rule($s); } elseif ($rule) { if ('nfd' === ($rule = \strtolower($rule))) { \normalizer_is_normalized($s, self::NFD) ?: ($s = \normalizer_normalize($s, self::NFD)); } elseif ('nfkd' === $rule) { \normalizer_is_normalized($s, self::NFKD) ?: ($s = \normalizer_normalize($s, self::NFKD)); } elseif ('[:nonspacing mark:] remove' === $rule) { $s = \preg_replace('/\\p{Mn}++/u', '', $s); } elseif ('latin-ascii' === $rule) { $s = \str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s); } elseif ('de-ascii' === $rule) { $s = \preg_replace("/([AUO])̈(?=\\p{Ll})/u", '$1e', $s); $s = \str_replace(["ä", "ö", "ü", "Ä", "Ö", "Ü"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s); } elseif (\function_exists('transliterator_transliterate')) { if (null === ($transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule)))) { if ('any-latin/bgn' === $rule) { $rule = 'any-latin'; $transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule)); } if (null === $transliterator) { throw new InvalidArgumentException(\sprintf('Unknown transliteration rule "%s".', $rule)); } self::$transliterators['any-latin/bgn'] = $transliterator; } $s = $transliterator->transliterate($s); } } elseif (!\function_exists('iconv')) { $s = \preg_replace('/[^\\x00-\\x7F]/u', '?', $s); } else { $s = @\preg_replace_callback('/[^\\x00-\\x7F]/u', static function ($c) { $c = (string) \iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); if ('' === $c && '' === \iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { throw new \LogicException(\sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); } return 1 < \strlen($c) ? \ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); }, $s); } } $str->string .= $s; return $str; } public function camel() : parent { $str = clone $this; $str->string = \str_replace(' ', '', \preg_replace_callback('/\\b.(?![A-Z]{2,})/u', static function ($m) use(&$i) { return 1 === ++$i ? 'İ' === $m[0] ? 'i̇' : \mb_strtolower($m[0], 'UTF-8') : \mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); }, \preg_replace('/[^\\pL0-9]++/u', ' ', $this->string))); return $str; } /** * @return int[] */ public function codePointsAt(int $offset) : array { $str = $this->slice($offset, 1); if ('' === $str->string) { return []; } $codePoints = []; foreach (\preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { $codePoints[] = \mb_ord($c, 'UTF-8'); } return $codePoints; } public function folded(bool $compat = \true) : parent { $str = clone $this; if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) { $str->string = \normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); $str->string = \mb_strtolower(\str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8'); } else { $str->string = \normalizer_normalize($str->string, \Normalizer::NFKC_CF); } return $str; } public function join(array $strings, ?string $lastGlue = null) : parent { $str = clone $this; $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue . \array_pop($strings) : ''; $str->string = \implode($this->string, $strings) . $tail; if (!\preg_match('//u', $str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function lower() : parent { $str = clone $this; $str->string = \mb_strtolower(\str_replace('İ', 'i̇', $str->string), 'UTF-8'); return $str; } public function match(string $regexp, int $flags = 0, int $offset = 0) : array { $match = (\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags ? 'preg_match_all' : 'preg_match'; if ($this->ignoreCase) { $regexp .= 'i'; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (\false === $match($regexp . 'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { $lastError = \preg_last_error(); foreach (\get_defined_constants(\true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === \substr($k, -6)) { throw new RuntimeException('Matching failed with ' . $k . '.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { \restore_error_handler(); } return $matches; } /** * @return static */ public function normalize(int $form = self::NFC) : self { if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { throw new InvalidArgumentException('Unsupported normalization form.'); } $str = clone $this; \normalizer_is_normalized($str->string, $form) ?: ($str->string = \normalizer_normalize($str->string, $form)); return $str; } public function padBoth(int $length, string $padStr = ' ') : parent { if ('' === $padStr || !\preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $pad = clone $this; $pad->string = $padStr; return $this->pad($length, $pad, \STR_PAD_BOTH); } public function padEnd(int $length, string $padStr = ' ') : parent { if ('' === $padStr || !\preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $pad = clone $this; $pad->string = $padStr; return $this->pad($length, $pad, \STR_PAD_RIGHT); } public function padStart(int $length, string $padStr = ' ') : parent { if ('' === $padStr || !\preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $pad = clone $this; $pad->string = $padStr; return $this->pad($length, $pad, \STR_PAD_LEFT); } public function replaceMatches(string $fromRegexp, $to) : parent { if ($this->ignoreCase) { $fromRegexp .= 'i'; } if (\is_array($to) || $to instanceof \Closure) { if (!\is_callable($to)) { throw new \TypeError(\sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); } $replace = 'preg_replace_callback'; $to = static function (array $m) use($to) : string { $to = $to($m); if ('' !== $to && (!\is_string($to) || !\preg_match('//u', $to))) { throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.'); } return $to; }; } elseif ('' !== $to && !\preg_match('//u', $to)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } else { $replace = 'preg_replace'; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (null === ($string = $replace($fromRegexp . 'u', $to, $this->string))) { $lastError = \preg_last_error(); foreach (\get_defined_constants(\true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === \substr($k, -6)) { throw new RuntimeException('Matching failed with ' . $k . '.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { \restore_error_handler(); } $str = clone $this; $str->string = $string; return $str; } public function reverse() : parent { $str = clone $this; $str->string = \implode('', \array_reverse(\preg_split('/(\\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); return $str; } public function snake() : parent { $str = $this->camel(); $str->string = \mb_strtolower(\preg_replace(['/(\\p{Lu}+)(\\p{Lu}\\p{Ll})/u', '/([\\p{Ll}0-9])(\\p{Lu})/u'], '_ContaoManager\\1_\\2', $str->string), 'UTF-8'); return $str; } public function title(bool $allWords = \false) : parent { $str = clone $this; $limit = $allWords ? -1 : 1; $str->string = \preg_replace_callback('/\\b./u', static function (array $m) : string { return \mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); }, $str->string, $limit); return $str; } public function trim(string $chars = " \t\n\r\x00\v\f ") : parent { if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); } $chars = \preg_quote($chars); $str = clone $this; $str->string = \preg_replace("{^[{$chars}]++|[{$chars}]++\$}uD", '', $str->string); return $str; } public function trimEnd(string $chars = " \t\n\r\x00\v\f ") : parent { if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); } $chars = \preg_quote($chars); $str = clone $this; $str->string = \preg_replace("{[{$chars}]++\$}uD", '', $str->string); return $str; } public function trimPrefix($prefix) : parent { if (!$this->ignoreCase) { return parent::trimPrefix($prefix); } $str = clone $this; if ($prefix instanceof \Traversable) { $prefix = \iterator_to_array($prefix, \false); } elseif ($prefix instanceof parent) { $prefix = $prefix->string; } $prefix = \implode('|', \array_map('preg_quote', (array) $prefix)); $str->string = \preg_replace("{^(?:{$prefix})}iuD", '', $this->string); return $str; } public function trimStart(string $chars = " \t\n\r\x00\v\f ") : parent { if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); } $chars = \preg_quote($chars); $str = clone $this; $str->string = \preg_replace("{^[{$chars}]++}uD", '', $str->string); return $str; } public function trimSuffix($suffix) : parent { if (!$this->ignoreCase) { return parent::trimSuffix($suffix); } $str = clone $this; if ($suffix instanceof \Traversable) { $suffix = \iterator_to_array($suffix, \false); } elseif ($suffix instanceof parent) { $suffix = $suffix->string; } $suffix = \implode('|', \array_map('preg_quote', (array) $suffix)); $str->string = \preg_replace("{(?:{$suffix})\$}iuD", '', $this->string); return $str; } public function upper() : parent { $str = clone $this; $str->string = \mb_strtoupper($str->string, 'UTF-8'); if (\PHP_VERSION_ID < 70300) { $str->string = \str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string); } return $str; } public function width(bool $ignoreAnsiDecoration = \true) : int { $width = 0; $s = \str_replace(["\x00", "\x05", "\x07"], '', $this->string); if (\false !== \strpos($s, "\r")) { $s = \str_replace(["\r\n", "\r"], "\n", $s); } if (!$ignoreAnsiDecoration) { $s = \preg_replace('/[\\p{Cc}\\x7F]++/u', '', $s); } foreach (\explode("\n", $s) as $s) { if ($ignoreAnsiDecoration) { $s = \preg_replace('/(?:\\x1B(?: \\[ [\\x30-\\x3F]*+ [\\x20-\\x2F]*+ [\\x40-\\x7E] | [P\\]X^_] .*? \\x1B\\\\ | [\\x41-\\x7E] )|[\\p{Cc}\\x7F]++)/xu', '', $s); } $lineWidth = $this->wcswidth($s); if ($lineWidth > $width) { $width = $lineWidth; } } return $width; } /** * @return static */ private function pad(int $len, self $pad, int $type) : parent { $sLen = $this->length(); if ($len <= $sLen) { return clone $this; } $padLen = $pad->length(); $freeLen = $len - $sLen; $len = $freeLen % $padLen; switch ($type) { case \STR_PAD_RIGHT: return $this->append(\str_repeat($pad->string, \intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : '')); case \STR_PAD_LEFT: return $this->prepend(\str_repeat($pad->string, \intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : '')); case \STR_PAD_BOTH: $freeLen /= 2; $rightLen = \ceil($freeLen); $len = $rightLen % $padLen; $str = $this->append(\str_repeat($pad->string, \intdiv($rightLen, $padLen)) . ($len ? $pad->slice(0, $len) : '')); $leftLen = \floor($freeLen); $len = $leftLen % $padLen; return $str->prepend(\str_repeat($pad->string, \intdiv($leftLen, $padLen)) . ($len ? $pad->slice(0, $len) : '')); default: throw new InvalidArgumentException('Invalid padding type.'); } } /** * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c. */ private function wcswidth(string $string) : int { $width = 0; foreach (\preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { $codePoint = \mb_ord($c, 'UTF-8'); if (0 === $codePoint || 0x34f === $codePoint || 0x200b <= $codePoint && 0x200f >= $codePoint || 0x2028 === $codePoint || 0x2029 === $codePoint || 0x202a <= $codePoint && 0x202e >= $codePoint || 0x2060 <= $codePoint && 0x2063 >= $codePoint) { continue; } // Non printable characters if (32 > $codePoint || 0x7f <= $codePoint && 0xa0 > $codePoint) { return -1; } if (null === self::$tableZero) { self::$tableZero = (require __DIR__ . '/Resources/data/wcswidth_table_zero.php'); } if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { $lbound = 0; while ($ubound >= $lbound) { $mid = \floor(($lbound + $ubound) / 2); if ($codePoint > self::$tableZero[$mid][1]) { $lbound = $mid + 1; } elseif ($codePoint < self::$tableZero[$mid][0]) { $ubound = $mid - 1; } else { continue 2; } } } if (null === self::$tableWide) { self::$tableWide = (require __DIR__ . '/Resources/data/wcswidth_table_wide.php'); } if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { $lbound = 0; while ($ubound >= $lbound) { $mid = \floor(($lbound + $ubound) / 2); if ($codePoint > self::$tableWide[$mid][1]) { $lbound = $mid + 1; } elseif ($codePoint < self::$tableWide[$mid][0]) { $ubound = $mid - 1; } else { $width += 2; continue 2; } } } ++$width; } return $width; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; /** * A string whose value is computed lazily by a callback. * * @author Nicolas Grekas */ class LazyString implements \Stringable, \JsonSerializable { private $value; /** * @param callable|array $callback A callable or a [Closure, method] lazy-callable * * @return static */ public static function fromCallable($callback, ...$arguments) : self { if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, \get_debug_type($callback))); } $lazyString = new static(); $lazyString->value = static function () use(&$callback, &$arguments, &$value) : string { if (null !== $arguments) { if (!\is_callable($callback)) { $callback[0] = $callback[0](); $callback[1] = $callback[1] ?? '__invoke'; } $value = $callback(...$arguments); $callback = self::getPrettyName($callback); $arguments = null; } return $value ?? ''; }; return $lazyString; } /** * @param string|int|float|bool|\Stringable $value * * @return static */ public static function fromStringable($value) : self { if (!self::isStringable($value)) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, \get_debug_type($value))); } if (\is_object($value)) { return static::fromCallable([$value, '__toString']); } $lazyString = new static(); $lazyString->value = (string) $value; return $lazyString; } /** * Tells whether the provided value can be cast to string. */ public static final function isStringable($value) : bool { return \is_string($value) || $value instanceof self || (\is_object($value) ? \method_exists($value, '__toString') : \is_scalar($value)); } /** * Casts scalars and stringable objects to strings. * * @param object|string|int|float|bool $value * * @throws \TypeError When the provided value is not stringable */ public static final function resolve($value) : string { return $value; } /** * @return string */ public function __toString() { if (\is_string($this->value)) { return $this->value; } try { return $this->value = ($this->value)(); } catch (\Throwable $e) { if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) { $type = \explode(', ', $e->getMessage()); $type = \substr(\array_pop($type), 0, -\strlen(' returned')); $r = new \ReflectionFunction($this->value); $callback = $r->getStaticVariables()['callback']; $e = new \TypeError(\sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); } if (\PHP_VERSION_ID < 70400) { // leverage the ErrorHandler component with graceful fallback when it's not available return \trigger_error($e, \E_USER_ERROR); } throw $e; } } public function __sleep() : array { $this->__toString(); return ['value']; } public function jsonSerialize() : string { return $this->__toString(); } private function __construct() { } private static function getPrettyName(callable $callback) : string { if (\is_string($callback)) { return $callback; } if (\is_array($callback)) { $class = \is_object($callback[0]) ? \get_debug_type($callback[0]) : $callback[0]; $method = $callback[1]; } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); if (\false !== \strpos($r->name, '{closure}') || !($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass())) { return $r->name; } $class = $class->name; $method = $r->name; } else { $class = \get_debug_type($callback); $method = '__invoke'; } return $class . '::' . $method; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; use _ContaoManager\Symfony\Component\String\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\String\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\String\Exception\RuntimeException; /** * Represents a binary-safe string of bytes. * * @author Nicolas Grekas * @author Hugo Hamon * * @throws ExceptionInterface */ class ByteString extends AbstractString { private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; public function __construct(string $string = '') { $this->string = $string; } /* * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03) * * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16 * * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE). * * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) */ public static function fromRandom(int $length = 16, ?string $alphabet = null) : self { if ($length <= 0) { throw new InvalidArgumentException(\sprintf('A strictly positive length is expected, "%d" given.', $length)); } $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; $alphabetSize = \strlen($alphabet); $bits = (int) \ceil(\log($alphabetSize, 2.0)); if ($bits <= 0 || $bits > 56) { throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); } $ret = ''; while ($length > 0) { $urandomLength = (int) \ceil(2 * $length * $bits / 8.0); $data = \random_bytes($urandomLength); $unpackedData = 0; $unpackedBits = 0; for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { // Unpack 8 bits $unpackedData = $unpackedData << 8 | \ord($data[$i]); $unpackedBits += 8; // While we have enough bits to select a character from the alphabet, keep // consuming the random data for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { $index = $unpackedData & (1 << $bits) - 1; $unpackedData >>= $bits; // Unfortunately, the alphabet size is not necessarily a power of two. // Worst case, it is 2^k + 1, which means we need (k+1) bits and we // have around a 50% chance of missing as k gets larger if ($index < $alphabetSize) { $ret .= $alphabet[$index]; --$length; } } } } return new static($ret); } public function bytesAt(int $offset) : array { $str = $this->string[$offset] ?? ''; return '' === $str ? [] : [\ord($str)]; } public function append(string ...$suffix) : parent { $str = clone $this; $str->string .= 1 >= \count($suffix) ? $suffix[0] ?? '' : \implode('', $suffix); return $str; } public function camel() : parent { $str = clone $this; $parts = \explode(' ', \trim(\ucwords(\preg_replace('/[^a-zA-Z0-9\\x7f-\\xff]++/', ' ', $this->string)))); $parts[0] = 1 !== \strlen($parts[0]) && \ctype_upper($parts[0]) ? $parts[0] : \lcfirst($parts[0]); $str->string = \implode('', $parts); return $str; } public function chunk(int $length = 1) : array { if (1 > $length) { throw new InvalidArgumentException('The chunk length must be greater than zero.'); } if ('' === $this->string) { return []; } $str = clone $this; $chunks = []; foreach (\str_split($this->string, $length) as $chunk) { $str->string = $chunk; $chunks[] = clone $str; } return $chunks; } public function endsWith($suffix) : bool { if ($suffix instanceof parent) { $suffix = $suffix->string; } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { return parent::endsWith($suffix); } else { $suffix = (string) $suffix; } return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === \substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); } public function equalsTo($string) : bool { if ($string instanceof parent) { $string = $string->string; } elseif (\is_array($string) || $string instanceof \Traversable) { return parent::equalsTo($string); } else { $string = (string) $string; } if ('' !== $string && $this->ignoreCase) { return 0 === \strcasecmp($string, $this->string); } return $string === $this->string; } public function folded() : parent { $str = clone $this; $str->string = \strtolower($str->string); return $str; } public function indexOf($needle, int $offset = 0) : ?int { if ($needle instanceof parent) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOf($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? \stripos($this->string, $needle, $offset) : \strpos($this->string, $needle, $offset); return \false === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0) : ?int { if ($needle instanceof parent) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOfLast($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? \strripos($this->string, $needle, $offset) : \strrpos($this->string, $needle, $offset); return \false === $i ? null : $i; } public function isUtf8() : bool { return '' === $this->string || \preg_match('//u', $this->string); } public function join(array $strings, ?string $lastGlue = null) : parent { $str = clone $this; $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue . \array_pop($strings) : ''; $str->string = \implode($this->string, $strings) . $tail; return $str; } public function length() : int { return \strlen($this->string); } public function lower() : parent { $str = clone $this; $str->string = \strtolower($str->string); return $str; } public function match(string $regexp, int $flags = 0, int $offset = 0) : array { $match = (\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags ? 'preg_match_all' : 'preg_match'; if ($this->ignoreCase) { $regexp .= 'i'; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (\false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { $lastError = \preg_last_error(); foreach (\get_defined_constants(\true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === \substr($k, -6)) { throw new RuntimeException('Matching failed with ' . $k . '.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { \restore_error_handler(); } return $matches; } public function padBoth(int $length, string $padStr = ' ') : parent { $str = clone $this; $str->string = \str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); return $str; } public function padEnd(int $length, string $padStr = ' ') : parent { $str = clone $this; $str->string = \str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); return $str; } public function padStart(int $length, string $padStr = ' ') : parent { $str = clone $this; $str->string = \str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); return $str; } public function prepend(string ...$prefix) : parent { $str = clone $this; $str->string = (1 >= \count($prefix) ? $prefix[0] ?? '' : \implode('', $prefix)) . $str->string; return $str; } public function replace(string $from, string $to) : parent { $str = clone $this; if ('' !== $from) { $str->string = $this->ignoreCase ? \str_ireplace($from, $to, $this->string) : \str_replace($from, $to, $this->string); } return $str; } public function replaceMatches(string $fromRegexp, $to) : parent { if ($this->ignoreCase) { $fromRegexp .= 'i'; } if (\is_array($to)) { if (!\is_callable($to)) { throw new \TypeError(\sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); } $replace = 'preg_replace_callback'; } else { $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (null === ($string = $replace($fromRegexp, $to, $this->string))) { $lastError = \preg_last_error(); foreach (\get_defined_constants(\true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === \substr($k, -6)) { throw new RuntimeException('Matching failed with ' . $k . '.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { \restore_error_handler(); } $str = clone $this; $str->string = $string; return $str; } public function reverse() : parent { $str = clone $this; $str->string = \strrev($str->string); return $str; } public function slice(int $start = 0, ?int $length = null) : parent { $str = clone $this; $str->string = (string) \substr($this->string, $start, $length ?? \PHP_INT_MAX); return $str; } public function snake() : parent { $str = $this->camel(); $str->string = \strtolower(\preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\\d])([A-Z])/'], '_ContaoManager\\1_\\2', $str->string)); return $str; } public function splice(string $replacement, int $start = 0, ?int $length = null) : parent { $str = clone $this; $str->string = \substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); return $str; } public function split(string $delimiter, ?int $limit = null, ?int $flags = null) : array { if (1 > ($limit = $limit ?? \PHP_INT_MAX)) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } if ('' === $delimiter) { throw new InvalidArgumentException('Split delimiter is empty.'); } if (null !== $flags) { return parent::split($delimiter, $limit, $flags); } $str = clone $this; $chunks = $this->ignoreCase ? \preg_split('{' . \preg_quote($delimiter) . '}iD', $this->string, $limit) : \explode($delimiter, $this->string, $limit); foreach ($chunks as &$chunk) { $str->string = $chunk; $chunk = clone $str; } return $chunks; } public function startsWith($prefix) : bool { if ($prefix instanceof parent) { $prefix = $prefix->string; } elseif (!\is_string($prefix)) { return parent::startsWith($prefix); } return '' !== $prefix && 0 === ($this->ignoreCase ? \strncasecmp($this->string, $prefix, \strlen($prefix)) : \strncmp($this->string, $prefix, \strlen($prefix))); } public function title(bool $allWords = \false) : parent { $str = clone $this; $str->string = $allWords ? \ucwords($str->string) : \ucfirst($str->string); return $str; } public function toUnicodeString(?string $fromEncoding = null) : UnicodeString { return new UnicodeString($this->toCodePointString($fromEncoding)->string); } public function toCodePointString(?string $fromEncoding = null) : CodePointString { $u = new CodePointString(); if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], \true) && \preg_match('//u', $this->string)) { $u->string = $this->string; return $u; } \set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { try { $validEncoding = \false !== \mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', \true); } catch (InvalidArgumentException $e) { if (!\function_exists('iconv')) { throw $e; } $u->string = \iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string); return $u; } } finally { \restore_error_handler(); } if (!$validEncoding) { throw new InvalidArgumentException(\sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); } $u->string = \mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); return $u; } public function trim(string $chars = " \t\n\r\x00\v\f") : parent { $str = clone $this; $str->string = \trim($str->string, $chars); return $str; } public function trimEnd(string $chars = " \t\n\r\x00\v\f") : parent { $str = clone $this; $str->string = \rtrim($str->string, $chars); return $str; } public function trimStart(string $chars = " \t\n\r\x00\v\f") : parent { $str = clone $this; $str->string = \ltrim($str->string, $chars); return $str; } public function upper() : parent { $str = clone $this; $str->string = \strtoupper($str->string); return $str; } public function width(bool $ignoreAnsiDecoration = \true) : int { $string = \preg_match('//u', $this->string) ? $this->string : \preg_replace('/[\\x80-\\xFF]/', '?', $this->string); return (new CodePointString($string))->width($ignoreAnsiDecoration); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String; use _ContaoManager\Symfony\Component\String\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\String\Exception\InvalidArgumentException; /** * Represents a string of Unicode grapheme clusters encoded as UTF-8. * * A letter followed by combining characters (accents typically) form what Unicode defines * as a grapheme cluster: a character as humans mean it in written texts. This class knows * about the concept and won't split a letter apart from its combining accents. It also * ensures all string comparisons happen on their canonically-composed representation, * ignoring e.g. the order in which accents are listed when a letter has many of them. * * @see https://unicode.org/reports/tr15/ * * @author Nicolas Grekas * @author Hugo Hamon * * @throws ExceptionInterface */ class UnicodeString extends AbstractUnicodeString { public function __construct(string $string = '') { $this->string = \normalizer_is_normalized($string) ? $string : \normalizer_normalize($string); if (\false === $this->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } } public function append(string ...$suffix) : AbstractString { $str = clone $this; $str->string = $this->string . (1 >= \count($suffix) ? $suffix[0] ?? '' : \implode('', $suffix)); \normalizer_is_normalized($str->string) ?: ($str->string = \normalizer_normalize($str->string)); if (\false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function chunk(int $length = 1) : array { if (1 > $length) { throw new InvalidArgumentException('The chunk length must be greater than zero.'); } if ('' === $this->string) { return []; } $rx = '/('; while (65535 < $length) { $rx .= '\\X{65535}'; $length -= 65535; } $rx .= '\\X{' . $length . '})/u'; $str = clone $this; $chunks = []; foreach (\preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { $str->string = $chunk; $chunks[] = clone $str; } return $chunks; } public function endsWith($suffix) : bool { if ($suffix instanceof AbstractString) { $suffix = $suffix->string; } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { return parent::endsWith($suffix); } else { $suffix = (string) $suffix; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; \normalizer_is_normalized($suffix, $form) ?: ($suffix = \normalizer_normalize($suffix, $form)); if ('' === $suffix || \false === $suffix) { return \false; } if ($this->ignoreCase) { return 0 === \mb_stripos(\grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8'); } return $suffix === \grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); } public function equalsTo($string) : bool { if ($string instanceof AbstractString) { $string = $string->string; } elseif (\is_array($string) || $string instanceof \Traversable) { return parent::equalsTo($string); } else { $string = (string) $string; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; \normalizer_is_normalized($string, $form) ?: ($string = \normalizer_normalize($string, $form)); if ('' !== $string && \false !== $string && $this->ignoreCase) { return \strlen($string) === \strlen($this->string) && 0 === \mb_stripos($this->string, $string, 0, 'UTF-8'); } return $string === $this->string; } public function indexOf($needle, int $offset = 0) : ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOf($needle, $offset); } else { $needle = (string) $needle; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; \normalizer_is_normalized($needle, $form) ?: ($needle = \normalizer_normalize($needle, $form)); if ('' === $needle || \false === $needle) { return null; } try { $i = $this->ignoreCase ? \grapheme_stripos($this->string, $needle, $offset) : \grapheme_strpos($this->string, $needle, $offset); } catch (\ValueError $e) { return null; } return \false === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0) : ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOfLast($needle, $offset); } else { $needle = (string) $needle; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; \normalizer_is_normalized($needle, $form) ?: ($needle = \normalizer_normalize($needle, $form)); if ('' === $needle || \false === $needle) { return null; } $string = $this->string; if (0 > $offset) { // workaround https://bugs.php.net/74264 if (0 > ($offset += \grapheme_strlen($needle))) { $string = \grapheme_substr($string, 0, $offset); } $offset = 0; } $i = $this->ignoreCase ? \grapheme_strripos($string, $needle, $offset) : \grapheme_strrpos($string, $needle, $offset); return \false === $i ? null : $i; } public function join(array $strings, ?string $lastGlue = null) : AbstractString { $str = parent::join($strings, $lastGlue); \normalizer_is_normalized($str->string) ?: ($str->string = \normalizer_normalize($str->string)); return $str; } public function length() : int { return \grapheme_strlen($this->string); } /** * @return static */ public function normalize(int $form = self::NFC) : parent { $str = clone $this; if (\in_array($form, [self::NFC, self::NFKC], \true)) { \normalizer_is_normalized($str->string, $form) ?: ($str->string = \normalizer_normalize($str->string, $form)); } elseif (!\in_array($form, [self::NFD, self::NFKD], \true)) { throw new InvalidArgumentException('Unsupported normalization form.'); } elseif (!\normalizer_is_normalized($str->string, $form)) { $str->string = \normalizer_normalize($str->string, $form); $str->ignoreCase = null; } return $str; } public function prepend(string ...$prefix) : AbstractString { $str = clone $this; $str->string = (1 >= \count($prefix) ? $prefix[0] ?? '' : \implode('', $prefix)) . $this->string; \normalizer_is_normalized($str->string) ?: ($str->string = \normalizer_normalize($str->string)); if (\false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function replace(string $from, string $to) : AbstractString { $str = clone $this; \normalizer_is_normalized($from) ?: ($from = \normalizer_normalize($from)); if ('' !== $from && \false !== $from) { $tail = $str->string; $result = ''; $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; while ('' !== $tail && \false !== ($i = $indexOf($tail, $from))) { $slice = \grapheme_substr($tail, 0, $i); $result .= $slice . $to; $tail = \substr($tail, \strlen($slice) + \strlen($from)); } $str->string = $result . $tail; \normalizer_is_normalized($str->string) ?: ($str->string = \normalizer_normalize($str->string)); if (\false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } } return $str; } public function replaceMatches(string $fromRegexp, $to) : AbstractString { $str = parent::replaceMatches($fromRegexp, $to); \normalizer_is_normalized($str->string) ?: ($str->string = \normalizer_normalize($str->string)); return $str; } public function slice(int $start = 0, ?int $length = null) : AbstractString { $str = clone $this; if (\PHP_VERSION_ID < 80000 && 0 > $start && \grapheme_strlen($this->string) < -$start) { $start = 0; } $str->string = (string) \grapheme_substr($this->string, $start, $length ?? 2147483647); return $str; } public function splice(string $replacement, int $start = 0, ?int $length = null) : AbstractString { $str = clone $this; if (\PHP_VERSION_ID < 80000 && 0 > $start && \grapheme_strlen($this->string) < -$start) { $start = 0; } $start = $start ? \strlen(\grapheme_substr($this->string, 0, $start)) : 0; $length = $length ? \strlen(\grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; $str->string = \substr_replace($this->string, $replacement, $start, $length ?? 2147483647); \normalizer_is_normalized($str->string) ?: ($str->string = \normalizer_normalize($str->string)); if (\false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function split(string $delimiter, ?int $limit = null, ?int $flags = null) : array { if (1 > ($limit = $limit ?? 2147483647)) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } if ('' === $delimiter) { throw new InvalidArgumentException('Split delimiter is empty.'); } if (null !== $flags) { return parent::split($delimiter . 'u', $limit, $flags); } \normalizer_is_normalized($delimiter) ?: ($delimiter = \normalizer_normalize($delimiter)); if (\false === $delimiter) { throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); } $str = clone $this; $tail = $this->string; $chunks = []; $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; while (1 < $limit && \false !== ($i = $indexOf($tail, $delimiter))) { $str->string = \grapheme_substr($tail, 0, $i); $chunks[] = clone $str; $tail = \substr($tail, \strlen($str->string) + \strlen($delimiter)); --$limit; } $str->string = $tail; $chunks[] = clone $str; return $chunks; } public function startsWith($prefix) : bool { if ($prefix instanceof AbstractString) { $prefix = $prefix->string; } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { return parent::startsWith($prefix); } else { $prefix = (string) $prefix; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; \normalizer_is_normalized($prefix, $form) ?: ($prefix = \normalizer_normalize($prefix, $form)); if ('' === $prefix || \false === $prefix) { return \false; } if ($this->ignoreCase) { return 0 === \mb_stripos(\grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8'); } return $prefix === \grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); } public function __wakeup() { if (!\is_string($this->string)) { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } \normalizer_is_normalized($this->string) ?: ($this->string = \normalizer_normalize($this->string)); } public function __clone() { if (null === $this->ignoreCase) { \normalizer_is_normalized($this->string) ?: ($this->string = \normalizer_normalize($this->string)); } $this->ignoreCase = \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Exception; interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Exception; class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Exception; class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } { "name": "symfony\/string", "type": "library", "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "keywords": [ "string", "utf8", "utf-8", "grapheme", "i18n", "unicode" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/polyfill-ctype": "~1.8", "symfony\/polyfill-intl-grapheme": "~1.0", "symfony\/polyfill-intl-normalizer": "~1.0", "symfony\/polyfill-mbstring": "~1.0", "symfony\/polyfill-php80": "~1.15" }, "require-dev": { "symfony\/error-handler": "^4.4|^5.0|^6.0", "symfony\/http-client": "^4.4|^5.0|^6.0", "symfony\/translation-contracts": "^1.1|^2", "symfony\/var-exporter": "^4.4|^5.0|^6.0" }, "conflict": { "symfony\/translation-contracts": ">=3.0" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\String\\": "" }, "files": [ "Resources\/functions.php" ], "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Slugger; use _ContaoManager\Symfony\Component\String\AbstractUnicodeString; /** * Creates a URL-friendly slug from a given string. * * @author Titouan Galopin */ interface SluggerInterface { /** * Creates a slug for the given string and locale, using appropriate transliteration when needed. */ public function slug(string $string, string $separator = '-', ?string $locale = null) : AbstractUnicodeString; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\String\Slugger; use _ContaoManager\Symfony\Component\String\AbstractUnicodeString; use _ContaoManager\Symfony\Component\String\UnicodeString; use _ContaoManager\Symfony\Contracts\Translation\LocaleAwareInterface; if (!\interface_exists(LocaleAwareInterface::class)) { throw new \LogicException('You cannot use the "Symfony\\Component\\String\\Slugger\\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".'); } /** * @author Titouan Galopin */ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface { private const LOCALE_TO_TRANSLITERATOR_ID = ['am' => 'Amharic-Latin', 'ar' => 'Arabic-Latin', 'az' => 'Azerbaijani-Latin', 'be' => 'Belarusian-Latin', 'bg' => 'Bulgarian-Latin', 'bn' => 'Bengali-Latin', 'de' => 'de-ASCII', 'el' => 'Greek-Latin', 'fa' => 'Persian-Latin', 'he' => 'Hebrew-Latin', 'hy' => 'Armenian-Latin', 'ka' => 'Georgian-Latin', 'kk' => 'Kazakh-Latin', 'ky' => 'Kirghiz-Latin', 'ko' => 'Korean-Latin', 'mk' => 'Macedonian-Latin', 'mn' => 'Mongolian-Latin', 'or' => 'Oriya-Latin', 'ps' => 'Pashto-Latin', 'ru' => 'Russian-Latin', 'sr' => 'Serbian-Latin', 'sr_Cyrl' => 'Serbian-Latin', 'th' => 'Thai-Latin', 'tk' => 'Turkmen-Latin', 'uk' => 'Ukrainian-Latin', 'uz' => 'Uzbek-Latin', 'zh' => 'Han-Latin']; private $defaultLocale; private $symbolsMap = ['en' => ['@' => 'at', '&' => 'and']]; /** * Cache of transliterators per locale. * * @var \Transliterator[] */ private $transliterators = []; /** * @param array|\Closure|null $symbolsMap */ public function __construct(?string $defaultLocale = null, $symbolsMap = null) { if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) { throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap))); } $this->defaultLocale = $defaultLocale; $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } /** * {@inheritdoc} */ public function setLocale($locale) { $this->defaultLocale = $locale; } /** * {@inheritdoc} */ public function getLocale() { return $this->defaultLocale; } /** * {@inheritdoc} */ public function slug(string $string, string $separator = '-', ?string $locale = null) : AbstractUnicodeString { $locale = $locale ?? $this->defaultLocale; $transliterator = []; if ($locale && ('de' === $locale || 0 === \strpos($locale, 'de_'))) { // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl) $transliterator = ['de-ASCII']; } elseif (\function_exists('transliterator_transliterate') && $locale) { $transliterator = (array) $this->createTransliterator($locale); } if ($this->symbolsMap instanceof \Closure) { // If the symbols map is passed as a closure, there is no need to fallback to the parent locale // as the closure can just provide substitutions for all locales of interest. $symbolsMap = $this->symbolsMap; \array_unshift($transliterator, static function ($s) use($symbolsMap, $locale) { return $symbolsMap($s, $locale); }); } $unicodeString = (new UnicodeString($string))->ascii($transliterator); if (\is_array($this->symbolsMap)) { $map = null; if (isset($this->symbolsMap[$locale])) { $map = $this->symbolsMap[$locale]; } else { $parent = self::getParentLocale($locale); if ($parent && isset($this->symbolsMap[$parent])) { $map = $this->symbolsMap[$parent]; } } if ($map) { foreach ($map as $char => $replace) { $unicodeString = $unicodeString->replace($char, ' ' . $replace . ' '); } } } return $unicodeString->replaceMatches('/[^A-Za-z0-9]++/', $separator)->trim($separator); } private function createTransliterator(string $locale) : ?\Transliterator { if (\array_key_exists($locale, $this->transliterators)) { return $this->transliterators[$locale]; } // Exact locale supported, cache and return if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) { return $this->transliterators[$locale] = \Transliterator::create($id . '/BGN') ?? \Transliterator::create($id); } // Locale not supported and no parent, fallback to any-latin if (!($parent = self::getParentLocale($locale))) { return $this->transliterators[$locale] = null; } // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { $transliterator = \Transliterator::create($id . '/BGN') ?? \Transliterator::create($id); } return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; } private static function getParentLocale(?string $locale) : ?string { if (!$locale) { return null; } if (\false === ($str = \strrchr($locale, '_'))) { // no parent locale return null; } return \substr($locale, 0, -\strlen($str)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Role; /** * Allows migrating session payloads from v4. * * @internal */ class SwitchUserRole extends Role { private $deprecationTriggered; private $source; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Role; /** * RoleHierarchyInterface is the interface for a role hierarchy. * * @author Fabien Potencier */ interface RoleHierarchyInterface { /** * @param string[] $roles * * @return string[] */ public function getReachableRoleNames(array $roles) : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Role; /** * Allows migrating session payloads from v4. * * @internal */ class Role { private $role; private function __construct() { } public function __toString() : string { return $this->role; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Role; /** * RoleHierarchy defines a role hierarchy. * * @author Fabien Potencier */ class RoleHierarchy implements RoleHierarchyInterface { private $hierarchy; /** @var array> */ protected $map; /** * @param array> $hierarchy */ public function __construct(array $hierarchy) { $this->hierarchy = $hierarchy; $this->buildRoleMap(); } /** * {@inheritdoc} */ public function getReachableRoleNames(array $roles) : array { $reachableRoles = $roles; foreach ($roles as $role) { if (!isset($this->map[$role])) { continue; } foreach ($this->map[$role] as $r) { $reachableRoles[] = $r; } } return \array_values(\array_unique($reachableRoles)); } protected function buildRoleMap() { $this->map = []; foreach ($this->hierarchy as $main => $roles) { $this->map[$main] = $roles; $visited = []; $additionalRoles = $roles; while ($role = \array_shift($additionalRoles)) { if (!isset($this->hierarchy[$role])) { continue; } $visited[] = $role; foreach ($this->hierarchy[$role] as $roleToAdd) { $this->map[$main][] = $roleToAdd; } foreach (\array_diff($this->hierarchy[$role], $visited) as $additionalRole) { $additionalRoles[] = $additionalRole; } } $this->map[$main] = \array_unique($this->map[$main]); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Validator\Constraints; use _ContaoManager\Symfony\Component\Validator\Constraint; /** * @Annotation * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class UserPassword extends Constraint { public $message = 'This value should be the user\'s current password.'; public $service = 'security.validator.user_password'; public function __construct(?array $options = null, ?string $message = null, ?string $service = null, ?array $groups = null, $payload = null) { parent::__construct($options, $groups, $payload); $this->message = $message ?? $this->message; $this->service = $service ?? $this->service; } /** * {@inheritdoc} */ public function validatedBy() { return $this->service; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Validator\Constraints; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Validator\Constraint; use _ContaoManager\Symfony\Component\Validator\ConstraintValidator; use _ContaoManager\Symfony\Component\Validator\Exception\ConstraintDefinitionException; use _ContaoManager\Symfony\Component\Validator\Exception\UnexpectedTypeException; class UserPasswordValidator extends ConstraintValidator { private $tokenStorage; private $hasherFactory; /** * @param PasswordHasherFactoryInterface $hasherFactory */ public function __construct(TokenStorageInterface $tokenStorage, $hasherFactory) { if ($hasherFactory instanceof EncoderFactoryInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); } $this->tokenStorage = $tokenStorage; $this->hasherFactory = $hasherFactory; } /** * {@inheritdoc} */ public function validate($password, Constraint $constraint) { if (!$constraint instanceof UserPassword) { throw new UnexpectedTypeException($constraint, UserPassword::class); } if (null === $password || '' === $password) { $this->context->addViolation($constraint->message); return; } if (!\is_string($password)) { throw new UnexpectedTypeException($password, 'string'); } $user = $this->tokenStorage->getToken()->getUser(); if (!$user instanceof UserInterface) { throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.'); } if (!$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Using the "%s" validation constraint without implementing the "%s" interface is deprecated, the "%s" class should implement it.', UserPassword::class, PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $salt = $user->getSalt(); if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $hasher = $this->hasherFactory instanceof EncoderFactoryInterface ? $this->hasherFactory->getEncoder($user) : $this->hasherFactory->getPasswordHasher($user); if (null === $user->getPassword() || !($hasher instanceof PasswordEncoderInterface ? $hasher->isPasswordValid($user->getPassword(), $password, $user->getSalt()) : $hasher->verify($user->getPassword(), $password, $user->getSalt()))) { $this->context->addViolation($constraint->message); } } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Test; use _ContaoManager\PHPUnit\Framework\TestCase; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Abstract test case for access decision strategies. * * @author Alexander M. Turek */ abstract class AccessDecisionStrategyTestCase extends TestCase { /** * @dataProvider provideStrategyTests * * @param VoterInterface[] $voters */ public final function testDecide(AccessDecisionStrategyInterface $strategy, array $voters, bool $expected) { $token = $this->createMock(TokenInterface::class); $manager = new AccessDecisionManager($voters, $strategy); $this->assertSame($expected, $manager->decide($token, ['ROLE_FOO'])); } /** * @return iterable */ public static abstract function provideStrategyTests() : iterable; /** * @return VoterInterface[] */ protected static final function getVoters(int $grants, int $denies, int $abstains) : array { $voters = []; for ($i = 0; $i < $grants; ++$i) { $voters[] = static::getVoter(VoterInterface::ACCESS_GRANTED); } for ($i = 0; $i < $denies; ++$i) { $voters[] = static::getVoter(VoterInterface::ACCESS_DENIED); } for ($i = 0; $i < $abstains; ++$i) { $voters[] = static::getVoter(VoterInterface::ACCESS_ABSTAIN); } return $voters; } protected static final function getVoter(int $vote) : VoterInterface { return new class($vote) implements VoterInterface { private $vote; public function __construct(int $vote) { $this->vote = $vote; } public function vote(TokenInterface $token, $subject, array $attributes) : int { return $this->vote; } }; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Signature; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; /** * @author Ryan Weaver */ final class ExpiredSignatureStorage { private $cache; private $lifetime; public function __construct(CacheItemPoolInterface $cache, int $lifetime) { $this->cache = $cache; $this->lifetime = $lifetime; } public function countUsages(string $hash) : int { $key = \rawurlencode($hash); if (!$this->cache->hasItem($key)) { return 0; } return $this->cache->getItem($key)->get(); } public function incrementUsages(string $hash) : void { $item = $this->cache->getItem(\rawurlencode($hash)); if (!$item->isHit()) { $item->expiresAfter($this->lifetime); } $item->set($this->countUsages($hash) + 1); $this->cache->save($item); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Signature\Exception; use _ContaoManager\Symfony\Component\Security\Core\Exception\RuntimeException; /** * @author Wouter de Jong */ class InvalidSignatureException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Signature\Exception; use _ContaoManager\Symfony\Component\Security\Core\Exception\RuntimeException; /** * @author Wouter de Jong */ class ExpiredSignatureException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Signature; use _ContaoManager\Symfony\Component\PropertyAccess\PropertyAccessorInterface; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException; use _ContaoManager\Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Creates and validates secure hashes used in login links and remember-me cookies. * * @author Wouter de Jong * @author Ryan Weaver */ class SignatureHasher { private $propertyAccessor; private $signatureProperties; private $secret; private $expiredSignaturesStorage; private $maxUses; /** * @param array $signatureProperties Properties of the User; the hash is invalidated if these properties change * @param ExpiredSignatureStorage|null $expiredSignaturesStorage If provided, secures a sequence of hashes that are expired * @param int|null $maxUses Used together with $expiredSignatureStorage to allow a maximum usage of a hash */ public function __construct(PropertyAccessorInterface $propertyAccessor, array $signatureProperties, string $secret, ?ExpiredSignatureStorage $expiredSignaturesStorage = null, ?int $maxUses = null) { $this->propertyAccessor = $propertyAccessor; $this->signatureProperties = $signatureProperties; $this->secret = $secret; $this->expiredSignaturesStorage = $expiredSignaturesStorage; $this->maxUses = $maxUses; } /** * Verifies the hash using the provided user identifier and expire time. * * This method must be called before the user object is loaded from a provider. * * @param int $expires The expiry time as a unix timestamp * @param string $hash The plaintext hash provided by the request * * @throws InvalidSignatureException If the signature does not match the provided parameters * @throws ExpiredSignatureException If the signature is no longer valid */ public function acceptSignatureHash(string $userIdentifier, int $expires, string $hash) : void { if ($expires < \time()) { throw new ExpiredSignatureException('Signature has expired.'); } $hmac = \substr($hash, 0, 44); $payload = \substr($hash, 44) . ':' . $expires . ':' . $userIdentifier; if (!\hash_equals($hmac, $this->generateHash($payload))) { throw new InvalidSignatureException('Invalid or expired signature.'); } } /** * Verifies the hash using the provided user and expire time. * * @param int $expires The expiry time as a unix timestamp * @param string $hash The plaintext hash provided by the request * * @throws InvalidSignatureException If the signature does not match the provided parameters * @throws ExpiredSignatureException If the signature is no longer valid */ public function verifySignatureHash(UserInterface $user, int $expires, string $hash) : void { if ($expires < \time()) { throw new ExpiredSignatureException('Signature has expired.'); } if (!\hash_equals($hash, $this->computeSignatureHash($user, $expires))) { throw new InvalidSignatureException('Invalid or expired signature.'); } if ($this->expiredSignaturesStorage && $this->maxUses) { if ($this->expiredSignaturesStorage->countUsages($hash) >= $this->maxUses) { throw new ExpiredSignatureException(\sprintf('Signature can only be used "%d" times.', $this->maxUses)); } $this->expiredSignaturesStorage->incrementUsages($hash); } } /** * Computes the secure hash for the provided user and expire time. * * @param int $expires The expiry time as a unix timestamp */ public function computeSignatureHash(UserInterface $user, int $expires) : string { $userIdentifier = \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); $fieldsHash = \hash_init('sha256'); foreach ($this->signatureProperties as $property) { $value = $this->propertyAccessor->getValue($user, $property) ?? ''; if ($value instanceof \DateTimeInterface) { $value = $value->format('c'); } if (!\is_scalar($value) && !(\is_object($value) && \method_exists($value, '__toString'))) { throw new \InvalidArgumentException(\sprintf('The property path "%s" on the user object "%s" must return a value that can be cast to a string, but "%s" was returned.', $property, \get_class($user), \get_debug_type($value))); } \hash_update($fieldsHash, ':' . \base64_encode($value)); } $fieldsHash = \strtr(\base64_encode(\hash_final($fieldsHash, \true)), '+/=', '-_~'); return $this->generateHash($fieldsHash . ':' . $expires . ':' . $userIdentifier) . $fieldsHash; } private function generateHash(string $tokenValue) : string { return \strtr(\base64_encode(\hash_hmac('sha256', $tokenValue, $this->secret, \true)), '+/=', '-_~'); } } CHANGELOG ========= 5.4.21 ------ * [BC BREAK] `AccessDecisionStrategyTestCase::provideStrategyTests()` is now static 5.4 --- * Add a `CacheableVoterInterface` for voters that vote only on identified attributes and subjects * Deprecate `AuthenticationEvents::AUTHENTICATION_FAILURE`, use the `LoginFailureEvent` instead * Deprecate `AnonymousToken`, as the related authenticator was deprecated in 5.3 * Deprecate `Token::getCredentials()`, tokens should no longer contain credentials (as they represent authenticated sessions) * Deprecate returning `string|\Stringable` from `Token::getUser()` (it must return a `UserInterface`) * Deprecate `AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY` and `AuthenticatedVoter::IS_ANONYMOUS`, use `AuthenticatedVoter::IS_AUTHENTICATED_FULLY` or `AuthenticatedVoter::IS_AUTHENTICATED` instead. * Deprecate `AuthenticationTrustResolverInterface::isAnonymous()` and the `is_anonymous()` expression function as anonymous no longer exists in version 6, use the `isFullFledged()` or the new `isAuthenticated()` instead if you want to check if the request is (fully) authenticated. * Deprecate the `$authenticationManager` argument of the `AuthorizationChecker` constructor * Deprecate setting the `$alwaysAuthenticate` argument to `true` and not setting the `$exceptionOnNoToken` argument to `false` of `AuthorizationChecker` * Deprecate methods `TokenInterface::isAuthenticated()` and `setAuthenticated`, return null from "getUser()" instead when a token is not authenticated * Add `AccessDecisionStrategyInterface` to allow custom access decision strategies * Add access decision strategies `AffirmativeStrategy`, `ConsensusStrategy`, `PriorityStrategy`, `UnanimousStrategy` * Deprecate passing the strategy as string to `AccessDecisionManager`, pass an instance of `AccessDecisionStrategyInterface` instead * Flag `AccessDecisionManager` as `@final` 5.3 --- The CHANGELOG for version 5.3 and earlier can be found at https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/Security/CHANGELOG.md * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; /** * @internal */ trait LegacyEncoderTrait { /** * @var PasswordHasherInterface|LegacyPasswordHasherInterface */ private $hasher; /** * {@inheritdoc} */ public function encodePassword(string $raw, ?string $salt) : string { try { return $this->hasher->hash($raw, $salt); } catch (InvalidPasswordException $e) { throw new BadCredentialsException('Bad credentials.'); } } /** * {@inheritdoc} */ public function isPasswordValid(string $encoded, string $raw, ?string $salt) : bool { return $this->hasher->verify($encoded, $raw, $salt); } /** * {@inheritdoc} */ public function needsRehash(string $encoded) : bool { return $this->hasher->needsRehash($encoded); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactoryInterface::class, PasswordHasherFactoryInterface::class); /** * EncoderFactoryInterface to support different encoders for different accounts. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.3, use {@link PasswordHasherFactoryInterface} instead */ interface EncoderFactoryInterface { /** * Returns the password encoder to use for the given account. * * @param UserInterface|string $user A UserInterface instance or a class name * * @return PasswordEncoderInterface * * @throws \RuntimeException when no password encoder could be found for the user */ public function getEncoder($user); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', BasePasswordEncoder::class, CheckPasswordLengthTrait::class); /** * BasePasswordEncoder is the base class for all password encoders. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use CheckPasswordLengthTrait instead */ abstract class BasePasswordEncoder implements PasswordEncoderInterface { public const MAX_PASSWORD_LENGTH = 4096; /** * {@inheritdoc} */ public function needsRehash(string $encoded) : bool { return \false; } /** * Demerges a merge password and salt string. * * @return array An array where the first element is the password and the second the salt */ protected function demergePasswordAndSalt(string $mergedPasswordSalt) { if (empty($mergedPasswordSalt)) { return ['', '']; } $password = $mergedPasswordSalt; $salt = ''; $saltBegins = \strrpos($mergedPasswordSalt, '{'); if (\false !== $saltBegins && $saltBegins + 1 < \strlen($mergedPasswordSalt)) { $salt = \substr($mergedPasswordSalt, $saltBegins + 1, -1); $password = \substr($mergedPasswordSalt, 0, $saltBegins); } return [$password, $salt]; } /** * Merges a password and a salt. * * @return string * * @throws \InvalidArgumentException */ protected function mergePasswordAndSalt(string $password, ?string $salt) { if (empty($salt)) { return $password; } if (\false !== \strrpos($salt, '{') || \false !== \strrpos($salt, '}')) { throw new \InvalidArgumentException('Cannot use { or } in salt.'); } return $password . '{' . $salt . '}'; } /** * Compares two passwords. * * This method implements a constant-time algorithm to compare passwords to * avoid (remote) timing attacks. * * @return bool */ protected function comparePasswords(string $password1, string $password2) { return \hash_equals($password1, $password2); } /** * Checks if the password is too long. * * @return bool */ protected function isPasswordTooLong(string $password) { return \strlen($password) > static::MAX_PASSWORD_LENGTH; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', Pbkdf2PasswordEncoder::class, Pbkdf2PasswordHasher::class); /** * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2). * * Providing a high level of Cryptographic security, * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). * * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. * PBKDF2 should be used with caution and care. * * @author Sebastiaan Stok * @author Andrew Johnson * @author Fabien Potencier * * @deprecated since Symfony 5.3, use {@link Pbkdf2PasswordHasher} instead */ class Pbkdf2PasswordEncoder extends BasePasswordEncoder { use LegacyEncoderTrait; /** * @param string $algorithm The digest algorithm to use * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash * @param int $iterations The number of iterations to use to stretch the password hash * @param int $length Length of derived key to create */ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = \true, int $iterations = 1000, int $length = 40) { $this->hasher = new Pbkdf2PasswordHasher($algorithm, $encodeHashAsBase64, $iterations, $length); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', EncoderFactory::class, PasswordHasherFactory::class); /** * A generic encoder factory implementation. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.3, use {@link PasswordHasherFactory} instead */ class EncoderFactory implements EncoderFactoryInterface { private $encoders; public function __construct(array $encoders) { $this->encoders = $encoders; } /** * {@inheritdoc} */ public function getEncoder($user) { $encoderKey = null; if ($user instanceof PasswordHasherAwareInterface && null !== ($encoderName = $user->getPasswordHasherName()) || $user instanceof EncoderAwareInterface && null !== ($encoderName = $user->getEncoderName())) { if (!\array_key_exists($encoderName, $this->encoders)) { throw new \RuntimeException(\sprintf('The encoder "%s" was not configured.', $encoderName)); } $encoderKey = $encoderName; } else { foreach ($this->encoders as $class => $encoder) { if (\is_object($user) && $user instanceof $class || !\is_object($user) && (\is_subclass_of($user, $class) || $user == $class)) { $encoderKey = $class; break; } } } if (null === $encoderKey) { throw new \RuntimeException(\sprintf('No encoder has been configured for account "%s".', \is_object($user) ? \get_debug_type($user) : $user)); } if (!$this->encoders[$encoderKey] instanceof PasswordEncoderInterface) { if ($this->encoders[$encoderKey] instanceof LegacyPasswordHasherInterface) { $this->encoders[$encoderKey] = new LegacyPasswordHasherEncoder($this->encoders[$encoderKey]); } elseif ($this->encoders[$encoderKey] instanceof PasswordHasherInterface) { $this->encoders[$encoderKey] = new PasswordHasherEncoder($this->encoders[$encoderKey]); } else { $this->encoders[$encoderKey] = $this->createEncoder($this->encoders[$encoderKey]); } } return $this->encoders[$encoderKey]; } /** * Creates the actual encoder instance. * * @throws \InvalidArgumentException */ private function createEncoder(array $config, bool $isExtra = \false) : PasswordEncoderInterface { if (isset($config['algorithm'])) { $rawConfig = $config; $config = $this->getEncoderConfigFromAlgorithm($config); } if (!isset($config['class'])) { throw new \InvalidArgumentException('"class" must be set in ' . \json_encode($config)); } if (!isset($config['arguments'])) { throw new \InvalidArgumentException('"arguments" must be set in ' . \json_encode($config)); } $encoder = new $config['class'](...$config['arguments']); if ($isExtra || !\in_array($config['class'], [NativePasswordEncoder::class, SodiumPasswordEncoder::class], \true)) { return $encoder; } if ($rawConfig ?? null) { $extraEncoders = \array_map(function (string $algo) use($rawConfig) : PasswordEncoderInterface { $rawConfig['algorithm'] = $algo; return $this->createEncoder($rawConfig); }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']); } else { $extraEncoders = [new Pbkdf2PasswordEncoder(), new MessageDigestPasswordEncoder()]; } return new MigratingPasswordEncoder($encoder, ...$extraEncoders); } private function getEncoderConfigFromAlgorithm(array $config) : array { if ('auto' === $config['algorithm']) { $encoderChain = []; // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly foreach ([SodiumPasswordEncoder::isSupported() ? 'sodium' : 'native', 'pbkdf2', $config['hash_algorithm']] as $algo) { $config['algorithm'] = $algo; $encoderChain[] = $this->createEncoder($config, \true); } return ['class' => MigratingPasswordEncoder::class, 'arguments' => $encoderChain]; } if ($fromEncoders = $config['migrate_from'] ?? \false) { unset($config['migrate_from']); $encoderChain = [$this->createEncoder($config, \true)]; foreach ($fromEncoders as $name) { if ($encoder = $this->encoders[$name] ?? \false) { $encoder = $encoder instanceof PasswordEncoderInterface ? $encoder : $this->createEncoder($encoder, \true); } else { $encoder = $this->createEncoder(['algorithm' => $name], \true); } $encoderChain[] = $encoder; } return ['class' => MigratingPasswordEncoder::class, 'arguments' => $encoderChain]; } switch ($config['algorithm']) { case 'plaintext': return ['class' => PlaintextPasswordEncoder::class, 'arguments' => [$config['ignore_case']]]; case 'pbkdf2': return ['class' => Pbkdf2PasswordEncoder::class, 'arguments' => [$config['hash_algorithm'] ?? 'sha512', $config['encode_as_base64'] ?? \true, $config['iterations'] ?? 1000, $config['key_length'] ?? 40]]; case 'bcrypt': $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_BCRYPT; return $this->getEncoderConfigFromAlgorithm($config); case 'native': return ['class' => NativePasswordEncoder::class, 'arguments' => [$config['time_cost'] ?? null, ($config['memory_cost'] ?? 0) << 10 ?: null, $config['cost'] ?? null] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : [])]; case 'sodium': return ['class' => SodiumPasswordEncoder::class, 'arguments' => [$config['time_cost'] ?? null, ($config['memory_cost'] ?? 0) << 10 ?: null]]; case 'argon2i': if (SodiumPasswordEncoder::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2I')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { throw new LogicException(\sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : '')); } return $this->getEncoderConfigFromAlgorithm($config); case 'argon2id': if (($hasSodium = SodiumPasswordEncoder::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2ID')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { throw new LogicException(\sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : '')); } return $this->getEncoderConfigFromAlgorithm($config); } return ['class' => MessageDigestPasswordEncoder::class, 'arguments' => [$config['algorithm'], $config['encode_as_base64'] ?? \true, $config['iterations'] ?? 5000]]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', PlaintextPasswordEncoder::class, PlaintextPasswordHasher::class); /** * PlaintextPasswordEncoder does not do any encoding but is useful in testing environments. * * As this encoder is not cryptographically secure, usage of it in production environments is discouraged. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use {@link PlaintextPasswordHasher} instead */ class PlaintextPasswordEncoder extends BasePasswordEncoder { use LegacyEncoderTrait; /** * @param bool $ignorePasswordCase Compare password case-insensitive */ public function __construct(bool $ignorePasswordCase = \false) { $this->hasher = new PlaintextPasswordHasher($ignorePasswordCase); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', PasswordEncoderInterface::class, PasswordHasherInterface::class); /** * PasswordEncoderInterface is the interface for all encoders. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use {@link PasswordHasherInterface} instead */ interface PasswordEncoderInterface { /** * Encodes the raw password. * * @return string * * @throws BadCredentialsException If the raw password is invalid, e.g. excessively long * @throws \InvalidArgumentException If the salt is invalid */ public function encodePassword(string $raw, ?string $salt); /** * Checks a raw password against an encoded password. * * @param string $encoded An encoded password * @param string $raw A raw password * @param string|null $salt The salt * * @return bool * * @throws \InvalidArgumentException If the salt is invalid */ public function isPasswordValid(string $encoded, string $raw, ?string $salt); /** * Checks if an encoded password would benefit from rehashing. */ public function needsRehash(string $encoded) : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; /** * Forward compatibility for new new PasswordHasher component. * * @author Alexander M. Turek * * @internal To be removed in Symfony 6 */ final class PasswordHasherAdapter implements LegacyPasswordHasherInterface { private $passwordEncoder; public function __construct(PasswordEncoderInterface $passwordEncoder) { $this->passwordEncoder = $passwordEncoder; } public function hash(string $plainPassword, ?string $salt = null) : string { return $this->passwordEncoder->encodePassword($plainPassword, $salt); } public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null) : bool { return $this->passwordEncoder->isPasswordValid($hashedPassword, $plainPassword, $salt); } public function needsRehash(string $hashedPassword) : bool { return $this->passwordEncoder->needsRehash($hashedPassword); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', SodiumPasswordEncoder::class, SodiumPasswordHasher::class); /** * Hashes passwords using libsodium. * * @author Robin Chalas * @author Zan Baldwin * @author Dominik Müller * * @deprecated since Symfony 5.3, use {@link SodiumPasswordHasher} instead */ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface { use LegacyEncoderTrait; public function __construct(?int $opsLimit = null, ?int $memLimit = null) { $this->hasher = new SodiumPasswordHasher($opsLimit, $memLimit); } public static function isSupported() : bool { return SodiumPasswordHasher::isSupported(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\MigratingPasswordHasher; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', MigratingPasswordEncoder::class, MigratingPasswordHasher::class); /** * Hashes passwords using the best available encoder. * Validates them using a chain of encoders. * * /!\ Don't put a PlaintextPasswordEncoder in the list as that'd mean a leaked hash * could be used to authenticate successfully without knowing the cleartext password. * * @author Nicolas Grekas * * @deprecated since Symfony 5.3, use {@link MigratingPasswordHasher} instead */ final class MigratingPasswordEncoder extends BasePasswordEncoder implements SelfSaltingEncoderInterface { private $bestEncoder; private $extraEncoders; public function __construct(PasswordEncoderInterface $bestEncoder, PasswordEncoderInterface ...$extraEncoders) { $this->bestEncoder = $bestEncoder; $this->extraEncoders = $extraEncoders; } /** * {@inheritdoc} */ public function encodePassword(string $raw, ?string $salt) : string { return $this->bestEncoder->encodePassword($raw, $salt); } /** * {@inheritdoc} */ public function isPasswordValid(string $encoded, string $raw, ?string $salt) : bool { if ($this->bestEncoder->isPasswordValid($encoded, $raw, $salt)) { return \true; } if (!$this->bestEncoder->needsRehash($encoded)) { return \false; } foreach ($this->extraEncoders as $encoder) { if ($encoder->isPasswordValid($encoded, $raw, $salt)) { return \true; } } return \false; } /** * {@inheritdoc} */ public function needsRehash(string $encoded) : bool { return $this->bestEncoder->needsRehash($encoded); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', MessageDigestPasswordEncoder::class, MessageDigestPasswordHasher::class); /** * MessageDigestPasswordEncoder uses a message digest algorithm. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use {@link MessageDigestPasswordHasher} instead */ class MessageDigestPasswordEncoder extends BasePasswordEncoder { private $algorithm; private $encodeHashAsBase64; private $iterations = 1; private $encodedLength = -1; /** * @param string $algorithm The digest algorithm to use * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash * @param int $iterations The number of iterations to use to stretch the password hash */ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = \true, int $iterations = 5000) { $this->algorithm = $algorithm; $this->encodeHashAsBase64 = $encodeHashAsBase64; try { $this->encodedLength = \strlen($this->encodePassword('', 'salt')); } catch (\LogicException $e) { // ignore algorithm not supported } $this->iterations = $iterations; } /** * {@inheritdoc} */ public function encodePassword(string $raw, ?string $salt) { if ($this->isPasswordTooLong($raw)) { throw new BadCredentialsException('Invalid password.'); } if (!\in_array($this->algorithm, \hash_algos(), \true)) { throw new \LogicException(\sprintf('The algorithm "%s" is not supported.', $this->algorithm)); } $salted = $this->mergePasswordAndSalt($raw, $salt); $digest = \hash($this->algorithm, $salted, \true); // "stretch" hash for ($i = 1; $i < $this->iterations; ++$i) { $digest = \hash($this->algorithm, $digest . $salted, \true); } return $this->encodeHashAsBase64 ? \base64_encode($digest) : \bin2hex($digest); } /** * {@inheritdoc} */ public function isPasswordValid(string $encoded, string $raw, ?string $salt) { if (\strlen($encoded) !== $this->encodedLength || \str_contains($encoded, '$')) { return \false; } return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" interface is deprecated, use "%s" on hasher implementations that deal with salts instead.', SelfSaltingEncoderInterface::class, LegacyPasswordHasherInterface::class); /** * SelfSaltingEncoderInterface is a marker interface for encoders that do not * require a user-generated salt. * * @author Zan Baldwin * * @deprecated since Symfony 5.3, use {@link LegacyPasswordHasherInterface} instead */ interface SelfSaltingEncoderInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; /** * Forward compatibility for new new PasswordHasher component. * * @author Alexander M. Turek * * @internal To be removed in Symfony 6 */ final class LegacyPasswordHasherEncoder implements PasswordEncoderInterface { private $passwordHasher; public function __construct(LegacyPasswordHasherInterface $passwordHasher) { $this->passwordHasher = $passwordHasher; } public function encodePassword(string $raw, ?string $salt) : string { try { return $this->passwordHasher->hash($raw, $salt); } catch (InvalidPasswordException $e) { throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e); } } public function isPasswordValid(string $encoded, string $raw, ?string $salt) : bool { return $this->passwordHasher->verify($encoded, $raw, $salt); } public function needsRehash(string $encoded) : bool { return $this->passwordHasher->needsRehash($encoded); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; use _ContaoManager\Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UserPasswordEncoder::class, UserPasswordHasher::class); /** * A generic password encoder. * * @author Ariel Ferrandini * * @deprecated since Symfony 5.3, use {@link UserPasswordHasher} instead */ class UserPasswordEncoder implements UserPasswordEncoderInterface { private $encoderFactory; public function __construct(EncoderFactoryInterface $encoderFactory) { $this->encoderFactory = $encoderFactory; } /** * {@inheritdoc} */ public function encodePassword(UserInterface $user, string $plainPassword) { $encoder = $this->encoderFactory->getEncoder($user); if (!$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/password-hasher', '5.3', 'Not implementing the "%s" interface while using "%s" is deprecated, the "%s" class should implement it.', PasswordAuthenticatedUserInterface::class, __CLASS__, \get_debug_type($user)); } $salt = $user->getSalt(); if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/password-hasher', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } return $encoder->encodePassword($plainPassword, $user->getSalt()); } /** * {@inheritdoc} */ public function isPasswordValid(UserInterface $user, string $raw) { if (null === $user->getPassword()) { return \false; } $encoder = $this->encoderFactory->getEncoder($user); return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt()); } /** * {@inheritdoc} */ public function needsRehash(UserInterface $user) : bool { if (null === $user->getPassword()) { return \false; } $encoder = $this->encoderFactory->getEncoder($user); return $encoder->needsRehash($user->getPassword()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; /** * Forward compatibility for new new PasswordHasher component. * * @author Alexander M. Turek * * @internal To be removed in Symfony 6 */ final class PasswordHasherEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface { private $passwordHasher; public function __construct(PasswordHasherInterface $passwordHasher) { $this->passwordHasher = $passwordHasher; } public function encodePassword(string $raw, ?string $salt) : string { if (null !== $salt) { throw new \InvalidArgumentException('This password hasher does not support passing a salt.'); } try { return $this->passwordHasher->hash($raw); } catch (InvalidPasswordException $e) { throw new BadCredentialsException($e->getMessage(), $e->getCode(), $e); } } public function isPasswordValid(string $encoded, string $raw, ?string $salt) : bool { if (null !== $salt) { throw new \InvalidArgumentException('This password hasher does not support passing a salt.'); } return $this->passwordHasher->verify($encoded, $raw); } public function needsRehash(string $encoded) : bool { return $this->passwordHasher->needsRehash($encoded); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', NativePasswordEncoder::class, NativePasswordHasher::class); /** * Hashes passwords using password_hash(). * * @author Elnur Abdurrakhimov * @author Terje Bråten * @author Nicolas Grekas * * @deprecated since Symfony 5.3, use {@link NativePasswordHasher} instead */ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface { use LegacyEncoderTrait; /** * @param string|null $algo An algorithm supported by password_hash() or null to use the stronger available algorithm */ public function __construct(?int $opsLimit = null, ?int $memLimit = null, ?int $cost = null, ?string $algo = null) { $this->hasher = new NativePasswordHasher($opsLimit, $memLimit, $cost, $algo); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface; /** * @author Christophe Coevoet * * @deprecated since Symfony 5.3, use {@link PasswordHasherAwareInterface} instead. */ interface EncoderAwareInterface { /** * Gets the name of the encoder used to encode the password. * * If the method returns null, the standard way to retrieve the encoder * will be used instead. * * @return string|null */ public function getEncoderName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Encoder; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" interface is deprecated, use "%s" instead.', UserPasswordEncoderInterface::class, UserPasswordHasherInterface::class); /** * UserPasswordEncoderInterface is the interface for the password encoder service. * * @author Ariel Ferrandini * * @deprecated since Symfony 5.3, use {@link UserPasswordHasherInterface} instead */ interface UserPasswordEncoderInterface { /** * Encodes the plain password. * * @return string */ public function encodePassword(UserInterface $user, string $plainPassword); /** * @return bool */ public function isPasswordValid(UserInterface $user, string $raw); /** * Checks if an encoded password would benefit from rehashing. */ public function needsRehash(UserInterface $user) : bool; } An authentication exception occurred. Ndodhi një problem në autentikim. Authentication credentials could not be found. Kredencialet e autentikimit nuk mund të gjendeshin. Authentication request could not be processed due to a system problem. Kërkesa për autentikim nuk mund të përpunohej për shkak të një problemi në sistem. Invalid credentials. Kredenciale të pavlefshme. Cookie has already been used by someone else. Cookie është përdorur tashmë nga dikush tjetër. Not privileged to request the resource. Nuk është i privilegjuar të kërkojë burimin. Invalid CSRF token. Identifikues i pavlefshëm CSRF. No authentication provider found to support the authentication token. Asnjë ofrues i vërtetimit nuk u gjet që të mbështesë simbolin e vërtetimit. No session available, it either timed out or cookies are not enabled. Nuk ka asnjë sesion të vlefshëm, i ka skaduar koha ose cookies nuk janë aktivizuar. No token could be found. Asnjë simbol identifikimi nuk mund të gjendej. Username could not be found. Emri i përdoruesit nuk mund të gjendej. Account has expired. Llogaria ka skaduar. Credentials have expired. Kredencialet kanë skaduar. Account is disabled. Llogaria është çaktivizuar. Account is locked. Llogaria është e kyçur. Too many failed login attempts, please try again later. Shumë përpjekje të dështuara autentikimi, provo përsëri më vonë. Invalid or expired login link. Link hyrje i pavlefshëm ose i skaduar. Too many failed login attempts, please try again in %minutes% minute. Shumë përpjekje të dështuara për identifikim; provo sërish pas %minutes% minutë. An authentication exception occurred. Ошибка аутентификации. Authentication credentials could not be found. Аутентификационные данные не найдены. Authentication request could not be processed due to a system problem. Запрос аутентификации не может быть обработан в связи с проблемой в системе. Invalid credentials. Недействительные аутентификационные данные. Cookie has already been used by someone else. Cookie уже был использован кем-то другим. Not privileged to request the resource. Отсутствуют права на запрос этого ресурса. Invalid CSRF token. Недействительный токен CSRF. No authentication provider found to support the authentication token. Не найден провайдер аутентификации, поддерживающий токен аутентификации. No session available, it either timed out or cookies are not enabled. Сессия не найдена, ее время истекло, либо cookies не включены. No token could be found. Токен не найден. Username could not be found. Имя пользователя не найдено. Account has expired. Время действия учетной записи истекло. Credentials have expired. Время действия аутентификационных данных истекло. Account is disabled. Учетная запись отключена. Account is locked. Учетная запись заблокирована. Too many failed login attempts, please try again later. Слишком много неудачных попыток входа, пожалуйста, попробуйте позже. Invalid or expired login link. Ссылка для входа недействительна или просрочена. Too many failed login attempts, please try again in %minutes% minute. Слишком много неудачных попыток входа в систему, повторите попытку через %minutes% минуту. An authentication exception occurred. Նույնականացման սխալ։ Authentication credentials could not be found. Նույնականացման տվյալները չեն գտնվել։ Authentication request could not be processed due to a system problem. Համակարգային սխալ՝ նույնականացման հացրման պրոցեսինգի ժամանակ։ Invalid credentials. Սխալ մուտքային տվյալներ Cookie has already been used by someone else. Cookie-ն արդեն օգտագործվում է ուրիշի կողմից։ Not privileged to request the resource. Ռեսուրսի հարցման համար չկա թույլատվություն։ Invalid CSRF token. Անվավեր CSRF թոքեն։ No authentication provider found to support the authentication token. Նույնականացման ոչ մի մատակարար չի գտնվել, որ աջակցի նույնականացման թոքենը։ No session available, it either timed out or cookies are not enabled. Հասանելի սեսիա չկա, կամ այն սպառվել է կամ cookie-ները անջատված են: No token could be found. Թոքենը չի գտնվել։ Username could not be found. Օգտանունը չի գտնվել։ Account has expired. Հաշիվը ժամկետանց է։ Credentials have expired. Մուտքային տվյալները ժամկետանց են։ Account is disabled. Հաշիվը դեկատիվացված է։ Account is locked. Հաշիվն արգելափակված է։ Too many failed login attempts, please try again later. Չափից շատ մուտքի փորձեր, խնդրում ենք փորձել մի փոքր ուշ Invalid or expired login link. Անվավեր կամ ժամկետանց մուտքի հղում։ Too many failed login attempts, please try again in %minutes% minute. Մուտքի չափազանց շատ անհաջող փորձեր: Խնդրում ենք կրկին փորձել %minutes րոպե: An authentication exception occurred. Une exception d'authentification s'est produite. Authentication credentials could not be found. Les identifiants d'authentification n'ont pas pu être trouvés. Authentication request could not be processed due to a system problem. La requête d'authentification n'a pas pu être executée à cause d'un problème système. Invalid credentials. Identifiants invalides. Cookie has already been used by someone else. Le cookie a déjà été utilisé par quelqu'un d'autre. Not privileged to request the resource. Privilèges insuffisants pour accéder à la ressource. Invalid CSRF token. Jeton CSRF invalide. No authentication provider found to support the authentication token. Aucun fournisseur d'authentification n'a été trouvé pour supporter le jeton d'authentification. No session available, it either timed out or cookies are not enabled. Aucune session disponible, celle-ci a expiré ou les cookies ne sont pas activés. No token could be found. Aucun jeton n'a pu être trouvé. Username could not be found. Le nom d'utilisateur n'a pas pu être trouvé. Account has expired. Le compte a expiré. Credentials have expired. Les identifiants ont expiré. Account is disabled. Le compte est désactivé. Account is locked. Le compte est bloqué. Too many failed login attempts, please try again later. Plusieurs tentatives de connexion ont échoué, veuillez réessayer plus tard. Invalid or expired login link. Lien de connexion invalide ou expiré. Too many failed login attempts, please try again in %minutes% minute. Plusieurs tentatives de connexion ont échoué, veuillez réessayer dans %minutes% minute. An authentication exception occurred. خطایی هنگام احراز هویت رخ داده است. Authentication credentials could not be found. شرایط احراز هویت یافت نشد. Authentication request could not be processed due to a system problem. درخواست احراز هویت به دلیل وجود مشکل در سیستم قابل پردازش نمی باشد. Invalid credentials. احراز هویت نامعتبر می باشد. Cookie has already been used by someone else. Cookie قبلا توسط شخص دیگری استفاده گردیده است. Not privileged to request the resource. دسترسی لازم برای درخواست از این منبع را دارا نمی باشید. Invalid CSRF token. توکن CSRF معتبر نمی باشد. No authentication provider found to support the authentication token. هیچ ارائه دهنده احراز هویتی برای پشتیبانی از توکن احراز هویت پیدا نشد. No session available, it either timed out or cookies are not enabled. هیچ جلسه‌ای در دسترس نمی باشد. این میتواند به دلیل پایان یافتن زمان و یا فعال نبودن کوکی ها باشد. No token could be found. هیچ توکنی پیدا نشد. Username could not be found. نام ‌کاربری پیدا نشد. Account has expired. حساب کاربری منقضی گردیده است. Credentials have expired. مجوزهای احراز هویت منقضی گردیده‌اند. Account is disabled. حساب کاربری غیرفعال می باشد. Account is locked. حساب کاربری قفل گردیده است. Too many failed login attempts, please try again later. تلاش‌های ناموفق زیادی برای ورود صورت گرفته است، لطفاً بعداً دوباره امتحان کنید. Invalid or expired login link. لینک ورود نامعتبر یا تاریخ‌گذشته است. Too many failed login attempts, please try again in %minutes% minute. تلاش‌های ناموفق زیادی برای ورود صورت گرفته است، لطفاً %minutes% دقیقه دیگر دوباره امتحان کنید. An authentication exception occurred. Wystąpił błąd uwierzytelniania. Authentication credentials could not be found. Dane uwierzytelniania nie zostały znalezione. Authentication request could not be processed due to a system problem. Żądanie uwierzytelniania nie mogło zostać pomyślnie zakończone z powodu problemu z systemem. Invalid credentials. Nieprawidłowe dane. Cookie has already been used by someone else. To ciasteczko jest używane przez kogoś innego. Not privileged to request the resource. Brak uprawnień dla żądania wskazanego zasobu. Invalid CSRF token. Nieprawidłowy token CSRF. No authentication provider found to support the authentication token. Nie znaleziono mechanizmu uwierzytelniania zdolnego do obsługi przesłanego tokenu. No session available, it either timed out or cookies are not enabled. Brak danych sesji, sesja wygasła lub ciasteczka nie są włączone. No token could be found. Nie znaleziono tokenu. Username could not be found. Użytkownik o podanej nazwie nie istnieje. Account has expired. Konto wygasło. Credentials have expired. Dane uwierzytelniania wygasły. Account is disabled. Konto jest wyłączone. Account is locked. Konto jest zablokowane. Too many failed login attempts, please try again later. Zbyt dużo nieudanych prób logowania, proszę spróbować ponownie później. Invalid or expired login link. Nieprawidłowy lub wygasły link logowania. Too many failed login attempts, please try again in %minutes% minute. Zbyt wiele nieudanych prób logowania, spróbuj ponownie po upływie %minutes% minut. An authentication exception occurred. Ett autentiseringsfel har inträffat. Authentication credentials could not be found. Uppgifterna för autentisering kunde inte hittas. Authentication request could not be processed due to a system problem. Autentiseringen kunde inte genomföras på grund av systemfel. Invalid credentials. Felaktiga uppgifter. Cookie has already been used by someone else. Cookien har redan använts av någon annan. Not privileged to request the resource. Saknar rättigheter för resursen. Invalid CSRF token. Ogiltig CSRF-token. No authentication provider found to support the authentication token. Ingen leverantör för autentisering hittades för angiven autentiseringstoken. No session available, it either timed out or cookies are not enabled. Ingen session finns tillgänglig, antingen har den förfallit eller är cookies inte aktiverat. No token could be found. Ingen token kunde hittas. Username could not be found. Användarnamnet kunde inte hittas. Account has expired. Kontot har förfallit. Credentials have expired. Uppgifterna har förfallit. Account is disabled. Kontot är inaktiverat. Account is locked. Kontot är låst. Too many failed login attempts, please try again later. För många misslyckade inloggningsförsök, försök igen senare. Invalid or expired login link. Ogiltig eller utgången inloggningslänk. Too many failed login attempts, please try again in %minutes% minute. För många misslyckade inloggningsförsök, försök igen om %minutes% minut. An authentication exception occurred. Συνέβη ένα σφάλμα πιστοποίησης. Authentication credentials could not be found. Τα στοιχεία πιστοποίησης δε βρέθηκαν. Authentication request could not be processed due to a system problem. Το αίτημα πιστοποίησης δε μπορεί να επεξεργαστεί λόγω σφάλματος του συστήματος. Invalid credentials. Λανθασμένα στοιχεία σύνδεσης. Cookie has already been used by someone else. Το Cookie έχει ήδη χρησιμοποιηθεί από κάποιον άλλο. Not privileged to request the resource. Δεν είστε εξουσιοδοτημένος για πρόσβαση στο συγκεκριμένο περιεχόμενο. Invalid CSRF token. Μη έγκυρο CSRF token. No authentication provider found to support the authentication token. Δε βρέθηκε κάποιος πάροχος πιστοποίησης που να υποστηρίζει το token πιστοποίησης. No session available, it either timed out or cookies are not enabled. Δεν υπάρχει ενεργή σύνοδος (session), είτε έχει λήξει ή τα cookies δεν είναι ενεργοποιημένα. No token could be found. Δεν ήταν δυνατόν να βρεθεί κάποιο token. Username could not be found. Το όνομα χρήστη δε βρέθηκε. Account has expired. Ο λογαριασμός έχει λήξει. Credentials have expired. Τα στοιχεία σύνδεσης έχουν λήξει. Account is disabled. Ο λογαριασμός είναι απενεργοποιημένος. Account is locked. Ο λογαριασμός είναι κλειδωμένος. Too many failed login attempts, please try again later. Πολλαπλές αποτυχημένες απόπειρες σύνδεσης, παρακαλούμε ξαναδοκιμάστε αργότερα. Invalid or expired login link. Μη έγκυρος ή ληγμένος σύνδεσμος σύνδεσης. Too many failed login attempts, please try again in %minutes% minute. Πολλαπλές αποτυχημένες απόπειρες σύνδεσης, παρακαλούμε ξαναδοκιμάστε σε %minutes% λεπτό. An authentication exception occurred. Изузетак при аутентификацији. Authentication credentials could not be found. Аутентификациони подаци нису пронађени. Authentication request could not be processed due to a system problem. Захтев за аутентификацију не може бити обрађен због системских проблема. Invalid credentials. Невалидни подаци за аутентификацију. Cookie has already been used by someone else. Колачић је већ искоришћен од стране неког другог. Not privileged to request the resource. Немате права приступа овом ресурсу. Invalid CSRF token. Невалидан CSRF токен. No authentication provider found to support the authentication token. Аутентификациони провајдер за подршку токена није пронађен. No session available, it either timed out or cookies are not enabled. Сесија није доступна, истекла је или су колачићи искључени. No token could be found. Токен не може бити пронађен. Username could not be found. Корисничко име не може бити пронађено. Account has expired. Налог је истекао. Credentials have expired. Подаци за аутентификацију су истекли. Account is disabled. Налог је онемогућен. Account is locked. Налог је закључан. Too many failed login attempts, please try again later. Превише неуспешних покушаја пријављивања, молим покушајте поново касније. Invalid or expired login link. Линк за пријављивање је истекао или је неисправан. Too many failed login attempts, please try again in %minutes% minute. Превише неуспешних покушаја пријављивања, молим покушајте поново за %minutes% минут. An authentication exception occurred. An authentication exception occurred. Authentication credentials could not be found. Authentication credentials could not be found. Authentication request could not be processed due to a system problem. Authentication request could not be processed due to a system problem. Invalid credentials. Invalid credentials. Cookie has already been used by someone else. Cookie has already been used by someone else. Not privileged to request the resource. Not privileged to request the resource. Invalid CSRF token. Invalid CSRF token. No authentication provider found to support the authentication token. No authentication provider found to support the authentication token. No session available, it either timed out or cookies are not enabled. No session available, it either timed out or cookies are not enabled. No token could be found. No token could be found. Username could not be found. Username could not be found. Account has expired. Account has expired. Credentials have expired. Credentials have expired. Account is disabled. Account is disabled. Account is locked. Account is locked. Too many failed login attempts, please try again later. Too many failed login attempts, please try again later. Invalid or expired login link. Invalid or expired login link. Too many failed login attempts, please try again in %minutes% minute. Too many failed login attempts, please try again in %minutes% minute. An authentication exception occurred. 認証エラーが発生しました。 Authentication credentials could not be found. 認証資格がありません。 Authentication request could not be processed due to a system problem. システムの問題により認証要求を処理できませんでした。 Invalid credentials. 資格が無効です。 Cookie has already been used by someone else. Cookie が別のユーザーで使用されています。 Not privileged to request the resource. リソースをリクエストする権限がありません。 Invalid CSRF token. CSRF トークンが無効です。 No authentication provider found to support the authentication token. 認証トークンをサポートする認証プロバイダーが見つかりません。 No session available, it either timed out or cookies are not enabled. 利用可能なセッションがありません。タイムアウトしたか、Cookie が無効になっています。 No token could be found. トークンが見つかりません。 Username could not be found. ユーザー名が見つかりません。 Account has expired. アカウントが有効期限切れです。 Credentials have expired. 資格が有効期限切れです。 Account is disabled. アカウントが無効です。 Account is locked. アカウントはロックされています。 Too many failed login attempts, please try again later. ログイン試行回数を超えました。しばらくして再度お試しください。 Invalid or expired login link. ログインリンクが有効期限切れ、もしくは無効です。 Too many failed login attempts, please try again in %minutes% minute. ログイン試行回数が多すぎます。%minutes%分後に再度お試しください。 An authentication exception occurred. Uma exceção ocorreu durante a autenticação. Authentication credentials could not be found. As credenciais de autenticação não foram encontradas. Authentication request could not be processed due to a system problem. A solicitação de autenticação não pôde ser processada devido a um problema no sistema. Invalid credentials. Credenciais inválidas. Cookie has already been used by someone else. Este cookie já foi usado por outra pessoa. Not privileged to request the resource. Sem privilégio para solicitar o recurso. Invalid CSRF token. Token CSRF inválido. No authentication provider found to support the authentication token. Nenhum provedor de autenticação encontrado para suportar o token de autenticação. No session available, it either timed out or cookies are not enabled. Nenhuma sessão disponível, ela expirou ou os cookies não estão habilitados. No token could be found. Nenhum token foi encontrado. Username could not be found. Nome de usuário não encontrado. Account has expired. A conta está expirada. Credentials have expired. As credenciais estão expiradas. Account is disabled. Conta desativada. Account is locked. A conta está travada. Too many failed login attempts, please try again later. Muitas tentativas de login malsucedidas, tente novamente mais tarde. Invalid or expired login link. Link de login inválido ou expirado. Too many failed login attempts, please try again in %minutes% minute. Muitas tentativas de login inválidas, por favor, tente novamente em um minuto. An authentication exception occurred. En autentiseringsfeil har skjedd. Authentication credentials could not be found. Påloggingsinformasjonen kunne ikke bli funnet. Authentication request could not be processed due to a system problem. Autentiserings forespørselen kunne ikke bli prosessert grunnet en system feil. Invalid credentials. Ugyldig påloggingsinformasjon. Cookie has already been used by someone else. Cookie har allerede blitt brukt av noen andre. Not privileged to request the resource. Ingen tilgang til å be om gitt ressurs. Invalid CSRF token. Ugyldig CSRF token. No authentication provider found to support the authentication token. Ingen autentiserings tilbyder funnet som støtter gitt autentiserings token. No session available, it either timed out or cookies are not enabled. Ingen sesjon tilgjengelig, sesjonen er enten utløpt eller cookies ikke skrudd på. No token could be found. Ingen token kunne bli funnet. Username could not be found. Brukernavn kunne ikke bli funnet. Account has expired. Brukerkonto har utgått. Credentials have expired. Påloggingsinformasjon har utløpt. Account is disabled. Brukerkonto er deaktivert. Account is locked. Brukerkonto er sperret. Too many failed login attempts, please try again later. For mange mislykkede påloggingsforsøk. Prøv igjen senere. Invalid or expired login link. Ugyldig eller utløpt påloggingskobling. Too many failed login attempts, please try again in %minutes% minute. For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutt. An authentication exception occurred. Có lỗi trong quá trình xác thực. Authentication credentials could not be found. Thông tin dùng để xác thực không tìm thấy. Authentication request could not be processed due to a system problem. Yêu cầu xác thực không thể thực hiện do lỗi của hệ thống. Invalid credentials. Thông tin dùng để xác thực không hợp lệ. Cookie has already been used by someone else. Cookie đã được dùng bởi người dùng khác. Not privileged to request the resource. Không được phép yêu cầu tài nguyên. Invalid CSRF token. Mã CSRF không hợp lệ. No authentication provider found to support the authentication token. Không tìm thấy nhà cung cấp dịch vụ xác thực nào cho mã xác thực mà bạn sử dụng. No session available, it either timed out or cookies are not enabled. Không tìm thấy phiên làm việc. Phiên làm việc hoặc cookie có thể bị tắt. No token could be found. Không tìm thấy mã token. Username could not be found. Không tìm thấy tên người dùng. Account has expired. Tài khoản đã hết hạn. Credentials have expired. Thông tin xác thực đã hết hạn. Account is disabled. Tài khoản bị tạm ngừng. Account is locked. Tài khoản bị khóa. Too many failed login attempts, please try again later. Đăng nhập sai quá nhiều lần, vui lòng thử lại lần nữa. Invalid or expired login link. Liên kết đăng nhập không hợp lệ hoặc quá hạn. Too many failed login attempts, please try again in %minutes% minute. Quá nhiều lần thử đăng nhập không thành công, vui lòng thử lại sau %minutes% phút. An authentication exception occurred. ایک تصدیقي خرابی پیش آگئی ۓ Authentication credentials could not be found. درج کردھ ریکارڈ نہیں مل سکا Authentication request could not be processed due to a system problem. سسٹم کی خرابی کی وجہ سے تصدیق کی درخواست پر کارروائی نہیں ہو سکی Invalid credentials. غلط ڈیٹا Cookie has already been used by someone else. کوکی پہلے ہی کسی اور کے ذریعہ استعمال ہو چکی ہے Not privileged to request the resource. وسائل کی درخواست کرنے کا اختیار نہیں ہے Invalid CSRF token. ٹوکن غلط ہے CSRF No authentication provider found to support the authentication token. تصدیقی ٹوکن کو سپورٹ کرنے کے لیے کوئی تصدیقی کنندہ نہیں ملا No session available, it either timed out or cookies are not enabled. کوئی سیشن دستیاب نہیں ہے، یا تو اس کا وقت ختم ہو گیا ہے یا کوکیز فعال نہیں ہیں No token could be found. کوئی ٹوکن نہیں مل سکا Username could not be found. يوذر نہیں مل سکا Account has expired. اکاؤنٹ کی میعاد ختم ہو گئی ہے Credentials have expired. اسناد کی میعاد ختم ہو چکی ہے Account is disabled. اکاؤنٹ بند کر دیا گیا ہے Account is locked. اکاؤنٹ لاک ہے Too many failed login attempts, please try again later. لاگ ان کی بہت زیادہ ناکام کوششیں ہو چکی ہیں، براۓ کرم بعد میں دوبارہ کوشش کریں Invalid or expired login link. غلط یا ختم شدھ لاگ ان لنک Too many failed login attempts, please try again in %minutes% minute. منٹ باد %minutes% لاگ ان کی بہت زیادہ ناکام کوششیں ہو چکی ہیں، براۓ کرم دوبارھ کوشيش کريں An authentication exception occurred. Innlogginga har feila. Authentication credentials could not be found. Innloggingsinformasjonen vart ikkje funnen. Authentication request could not be processed due to a system problem. Innlogginga vart ikkje fullført på grunn av ein systemfeil. Invalid credentials. Ugyldig innloggingsinformasjon. Cookie has already been used by someone else. Informasjonskapselen er allereie brukt av ein annan brukar. Not privileged to request the resource. Du har ikkje åtgang til å be om denne ressursen. Invalid CSRF token. Ugyldig CSRF-teikn. No authentication provider found to support the authentication token. Fann ingen innloggingstilbydar som støttar dette innloggingsteiknet. No session available, it either timed out or cookies are not enabled. Ingen sesjon tilgjengeleg. Sesjonen er anten ikkje lenger gyldig, eller informasjonskapslar er ikkje skrudd på i nettlesaren. No token could be found. Fann ingen innloggingsteikn. Username could not be found. Fann ikkje brukarnamnet. Account has expired. Brukarkontoen er utgjengen. Credentials have expired. Innloggingsinformasjonen er utgjengen. Account is disabled. Brukarkontoen er sperra. Account is locked. Brukarkontoen er sperra. Too many failed login attempts, please try again later. For mange innloggingsforsøk har feila, prøv igjen seinare. Invalid or expired login link. Innloggingslenka er ugyldig eller utgjengen. Too many failed login attempts, please try again in %minutes% minute. For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutt. An authentication exception occurred. Er heeft zich een authenticatieprobleem voorgedaan. Authentication credentials could not be found. Authenticatiegegevens konden niet worden gevonden. Authentication request could not be processed due to a system problem. Authenticatieaanvraag kon niet worden verwerkt door een technisch probleem. Invalid credentials. Ongeldige inloggegevens. Cookie has already been used by someone else. Cookie is al door een ander persoon gebruikt. Not privileged to request the resource. Onvoldoende rechten om de aanvraag te verwerken. Invalid CSRF token. CSRF-code is ongeldig. No authentication provider found to support the authentication token. Geen authenticatieprovider gevonden die de authenticatietoken ondersteunt. No session available, it either timed out or cookies are not enabled. Geen sessie beschikbaar, mogelijk is deze verlopen of cookies zijn uitgeschakeld. No token could be found. Er kon geen authenticatietoken worden gevonden. Username could not be found. Gebruikersnaam kon niet worden gevonden. Account has expired. Account is verlopen. Credentials have expired. Authenticatiegegevens zijn verlopen. Account is disabled. Account is gedeactiveerd. Account is locked. Account is geblokkeerd. Too many failed login attempts, please try again later. Te veel onjuiste inlogpogingen, probeer het later nogmaals. Invalid or expired login link. Ongeldige of verlopen inloglink. Too many failed login attempts, please try again in %minutes% minute. Te veel onjuiste inlogpogingen, probeer het opnieuw over %minutes% minuut. An authentication exception occurred. Digwyddodd eithriad dilysu. Authentication credentials could not be found. Ni ellid dod o hyd i ddogfennau dilysu. Authentication request could not be processed due to a system problem. Ni ellid prosesu cais dilysu oherwydd problem gyda'r system. Invalid credentials. Dogfennau annilys. Cookie has already been used by someone else. Mae rhywun arall eisoes wedi defnyddio'r cwcis. Not privileged to request the resource. Heb y fraint i ofyn am yr adnodd. Invalid CSRF token. Tocyn CSRF annilys. No authentication provider found to support the authentication token. Heb ddod o hyd i ddarparwr dilysu i gefnogi'r tocyn dilysu. No session available, it either timed out or cookies are not enabled. Dim sesiwn ar gael, naill ai mae wedi dod i ben neu nid yw cwcis wedi'u galluogi. No token could be found. Heb ddod o hyd i docyn. Username could not be found. Heb ddod o hyd i enw defnyddiwr. Account has expired. Mae'r cyfrif wedi dod i ben. Credentials have expired. Mae'r dogfennau wedi dod i ben. Account is disabled. Mae'r cyfrif wedi'i analluogi. Account is locked. Mae'r cyfrif wedi'i gloi. Too many failed login attempts, please try again later. Gormod o ymdrechion mewngofnodi wedi methu, ceisiwch eto'n hwyrach. Invalid or expired login link. Dolen mewngofnodi annilys neu wedi dod i ben. Too many failed login attempts, please try again in %minutes% minute. Gormod o ymdrechion mewngofnodi wedi methu, ceisiwch eto ymhen %minutes% munud. An authentication exception occurred. 'n Verifikasie probleem het voorgekom. Authentication credentials could not be found. Verifikasiebewyse kon nie gevind word nie. Authentication request could not be processed due to a system problem. Verifikasieversoek kon weens 'n stelselprobleem nie verwerk word nie. Invalid credentials. Ongedige verifikasiebewyse. Cookie has already been used by someone else. Die koekie is alreeds deur iemand anders gebruik. Not privileged to request the resource. Nie bevoorreg om die hulpbron aan te vra nie. Invalid CSRF token. Ongeldige CSRF-teken. No authentication provider found to support the authentication token. Geen verifikasieverskaffer is gevind wat die verifikasietoken kan ondersteun nie. No session available, it either timed out or cookies are not enabled. Geen sessie is beskikbaar, die het verval of koekies is nie geaktiveer nie. No token could be found. Geen teken kon gevind word nie. Username could not be found. Gebruikersnaam kon nie gevind word nie. Account has expired. Rekening het verval. Credentials have expired. Verifikasiebewyse het verval. Account is disabled. Rekening is deaktiveer. Account is locked. Rekening is gesluit. Too many failed login attempts, please try again later. Te veel mislukte aanmeldpogings, probeer asseblief later weer. Invalid or expired login link. Ongeldige of vervalde aanmeldskakel. Too many failed login attempts, please try again in %minutes% minute. Te veel mislukte aanmeldpogings, probeer asseblief weer oor %minutes% minuut. An authentication exception occurred. Radās autentifikācijas kļūda. Authentication credentials could not be found. Autentifikācijas dati nav atrasti. Authentication request could not be processed due to a system problem. Autentifikācijas pieprasījums nevar tikt apstrādāts sistēmas problēmas dēļ. Invalid credentials. Nederīgi autentifikācijas dati. Cookie has already been used by someone else. Kāds cits jau izmantoja sīkdatni. Not privileged to request the resource. Nav tiesību šī resursa izsaukšanai. Invalid CSRF token. Nederīgs CSRF talons. No authentication provider found to support the authentication token. Nav atrasts, autentifikācijas talonu atbalstošs, autentifikācijas sniedzējs. No session available, it either timed out or cookies are not enabled. Sesija nav pieejama - vai nu tā beidzās, vai nu sīkdatnes nav iespējotas. No token could be found. Nevar atrast nevienu talonu. Username could not be found. Nevar atrast lietotājvārdu. Account has expired. Konta derīguma termiņš ir beidzies. Credentials have expired. Autentifikācijas datu derīguma termiņš ir beidzies. Account is disabled. Konts ir atspējots. Account is locked. Konts ir slēgts. Too many failed login attempts, please try again later. Pārāk daudz atteiktu autentifikācijas mēģinājumu, lūdzu, mēģiniet vēlreiz vēlāk. Invalid or expired login link. Autentifikācijas saite ir nederīga vai arī tai ir beidzies derīguma termiņš. Too many failed login attempts, please try again in %minutes% minute. Pārāk daudz nesekmīgu autentifikācijas mēģinājumu, lūdzu mēģiniet vēlreiz pēc %minutes% minūtes. An authentication exception occurred. حدث خطأ اثناء الدخول. Authentication credentials could not be found. لم استطع العثور على معلومات الدخول. Authentication request could not be processed due to a system problem. لم يكتمل طلب الدخول نتيجه عطل فى النظام. Invalid credentials. معلومات الدخول خاطئة. Cookie has already been used by someone else. ملفات تعريف الارتباط(cookies) تم استخدامها من قبل شخص اخر. Not privileged to request the resource. ليست لديك الصلاحيات الكافية لهذا الطلب. Invalid CSRF token. رمز الموقع غير صحيح. No authentication provider found to support the authentication token. لا يوجد معرف للدخول يدعم الرمز المستخدم للدخول. No session available, it either timed out or cookies are not enabled. لا يوجد صلة بينك و بين الموقع اما انها انتهت او ان متصفحك لا يدعم خاصية ملفات تعريف الارتباط (cookies). No token could be found. لم استطع العثور على الرمز. Username could not be found. لم استطع العثور على اسم الدخول. Account has expired. انتهت صلاحية الحساب. Credentials have expired. انتهت صلاحية معلومات الدخول. Account is disabled. الحساب موقوف. Account is locked. الحساب مغلق. Too many failed login attempts, please try again later. عدد كبير جدا من محاولات الدخول الفاشلة، يرجى المحاولة مرة أخرى في وقت لاحق. Invalid or expired login link. رابط تسجيل الدخول غير صالح أو منتهي الصلاحية. Too many failed login attempts, please try again in %minutes% minute. عدد كبير جدا من محاولات الدخول الفاشلة، يرجى اعادة المحاولة بعد %minutes% دقيقة. An authentication exception occurred. Bei der Authentifikatioun ass e Feeler opgetrueden. Authentication credentials could not be found. Et konnte keng Zouganksdate fonnt ginn. Authentication request could not be processed due to a system problem. D'Ufro fir eng Authentifikatioun konnt wéinst engem Problem vum System net beaarbecht ginn. Invalid credentials. Ongëlteg Zouganksdaten. Cookie has already been used by someone else. De Cookie gouf scho vun engem anere benotzt. Not privileged to request the resource. Keng Rechter fir d'Ressource unzefroen. Invalid CSRF token. Ongëltegen CSRF-Token. No authentication provider found to support the authentication token. Et gouf keen Authentifizéierungs-Provider fonnt deen den Authentifizéierungs-Token ënnerstëtzt. No session available, it either timed out or cookies are not enabled. Keng Sëtzung disponibel. Entweder ass se ofgelaf oder Cookies sinn net aktivéiert. No token could be found. Et konnt keen Token fonnt ginn. Username could not be found. De Benotzernumm konnt net fonnt ginn. Account has expired. Den Account ass ofgelaf. Credentials have expired. D'Zouganksdate sinn ofgelaf. Account is disabled. De Konto ass deaktivéiert. Account is locked. De Konto ass gespaart. Too many failed login attempts, please try again later. Ze vill mësslonge Login-Versich, w.e.g. méi spéit nach emol probéieren. Invalid or expired login link. Ongëltegen oder ofgelafene Login-Link. Too many failed login attempts, please try again in %minutes% minute. Zu vill fehlgeschloen Loginversich, w. e. g. probéiert nach am %minutes% Minutt. An authentication exception occurred. Bir yetkilendirme istisnası oluştu. Authentication credentials could not be found. Kimlik bilgileri bulunamadı. Authentication request could not be processed due to a system problem. Bir sistem hatası nedeniyle yetkilendirme isteği işleme alınamıyor. Invalid credentials. Geçersiz kimlik bilgileri. Cookie has already been used by someone else. Çerez bir başkası tarafından zaten kullanılmıştı. Not privileged to request the resource. Kaynak talebi için imtiyaz bulunamadı. Invalid CSRF token. Geçersiz CSRF fişi. No authentication provider found to support the authentication token. Yetkilendirme fişini destekleyecek yetkilendirme sağlayıcısı bulunamadı. No session available, it either timed out or cookies are not enabled. Oturum bulunamadı, zaman aşımına uğradı veya çerezler etkin değil. No token could be found. Fiş bulunamadı. Username could not be found. Kullanıcı adı bulunamadı. Account has expired. Hesap zaman aşımına uğradı. Credentials have expired. Kimlik bilgileri zaman aşımına uğradı. Account is disabled. Hesap engellenmiş. Account is locked. Hesap kilitlenmiş. Too many failed login attempts, please try again later. Çok fazla başarısız giriş denemesi, lütfen daha sonra tekrar deneyin. Invalid or expired login link. Geçersiz veya süresi dolmuş oturum açma bağlantısı. Too many failed login attempts, please try again in %minutes% minute. Çok fazla başarısız giriş denemesi, lütfen %minutes% dakika sonra tekrar deneyin. An authentication exception occurred. Įvyko autentifikacijos klaida. Authentication credentials could not be found. Nepavyko rasti autentifikacijos duomenų. Authentication request could not be processed due to a system problem. Autentifikacijos užklausos nepavyko įvykdyti dėl sistemos klaidų. Invalid credentials. Klaidingi duomenys. Cookie has already been used by someone else. Slapukas buvo panaudotas kažkam kitam. Not privileged to request the resource. Neturite teisių pasiektį resursą. Invalid CSRF token. Neteisingas CSRF raktas. No authentication provider found to support the authentication token. Nerastas autentifikacijos tiekėjas, kuris palaikytų autentifikacijos raktą. No session available, it either timed out or cookies are not enabled. Sesija yra nepasiekiama, pasibaigė galiojimo laikas arba slapukai yra išjungti. No token could be found. Nepavyko rasti rakto. Username could not be found. Tokio naudotojo vardo nepavyko rasti. Account has expired. Paskyros galiojimo laikas baigėsi. Credentials have expired. Autentifikacijos duomenų galiojimo laikas baigėsi. Account is disabled. Paskyra yra išjungta. Account is locked. Paskyra yra užblokuota. Too many failed login attempts, please try again later. Per daug nepavykusių prisijungimo bandymų, pabandykite dar kartą vėliau. Invalid or expired login link. Netinkama arba pasibaigusio galiojimo laiko prisijungimo nuoroda. Too many failed login attempts, please try again in %minutes% minute. Per daug nepavykusių prisijungimo bandymų, pabandykite dar kartą po %minutes% minutės. An authentication exception occurred. 身份验证发生异常。 Authentication credentials could not be found. 没有找到身份验证的凭证。 Authentication request could not be processed due to a system problem. 由于系统故障,身份验证的请求无法被处理。 Invalid credentials. 无效的凭证。 Cookie has already been used by someone else. Cookie 已经被其他人使用。 Not privileged to request the resource. 没有权限请求此资源。 Invalid CSRF token. 无效的 CSRF token 。 No authentication provider found to support the authentication token. 没有找到支持此 token 的身份验证服务提供方。 No session available, it either timed out or cookies are not enabled. Session 不可用。会话超时或没有启用 cookies 。 No token could be found. 找不到 token 。 Username could not be found. 找不到用户名。 Account has expired. 帐号已过期。 Credentials have expired. 凭证已过期。 Account is disabled. 帐号已被禁用。 Account is locked. 帐号已被锁定。 Too many failed login attempts, please try again later. 登入失败的次数过多,请稍后再试。 Invalid or expired login link. 失效或过期的登入链接。 Too many failed login attempts, please try again in %minutes% minute. 登入失败的次数过多,请在%minutes%分钟后再试。 An authentication exception occurred. Ha succeït un error d'autenticació. Authentication credentials could not be found. No s'han trobat les credencials d'autenticació. Authentication request could not be processed due to a system problem. La solicitud d'autenticació no s'ha pogut processar per un problema del sistema. Invalid credentials. Credencials no vàlides. Cookie has already been used by someone else. La cookie ja ha estat utilitzada per una altra persona. Not privileged to request the resource. No té privilegis per solicitar el recurs. Invalid CSRF token. Token CSRF no vàlid. No authentication provider found to support the authentication token. No s'ha trobat un proveïdor d'autenticació que suporti el token d'autenticació. No session available, it either timed out or cookies are not enabled. No hi ha sessió disponible, ha expirat o les cookies no estan habilitades. No token could be found. No s'ha trobat cap token. Username could not be found. No s'ha trobat el nom d'usuari. Account has expired. El compte ha expirat. Credentials have expired. Les credencials han expirat. Account is disabled. El compte està deshabilitat. Account is locked. El compte està bloquejat. Too many failed login attempts, please try again later. Massa intents d'inici de sessió fallits, torneu-ho a provar més tard. Invalid or expired login link. Enllaç d'inici de sessió no vàlid o caducat. Too many failed login attempts, please try again in %minutes% minute. Massa intents d'inici de sessió fallits, torneu-ho a provar en %minutes% minut. An authentication exception occurred. Памылка аўтэнтыфікацыі. Authentication credentials could not be found. Дадзеныя аўтэнтыфікацыі не знойдзены. Authentication request could not be processed due to a system problem. Запыт аўтэнтыфікацыі не можа быць апрацаваны ў сувязі з праблемай у сістэме. Invalid credentials. Несапраўдныя дадзеныя аўтэнтыфікацыі. Cookie has already been used by someone else. Нехта іншы ўжо выкарыстаў гэтыя кукі (cookie). Not privileged to request the resource. Адсутнічаюць правы на запыт гэтага рэсурсу. Invalid CSRF token. Несапраўдны CSRF-токен. No authentication provider found to support the authentication token. Не знойдзен правайдар аўтэнтыфікацыі, які можа падтрымліваць гэты токен аўтэнтыфікацыі. No session available, it either timed out or cookies are not enabled. Сесія не даступна, яе час скончыўся, або кукі (cookies) выключаны. No token could be found. Токен не знойдзен. Username could not be found. Імя карыстальніка не знойдзена. Account has expired. Скончыўся тэрмін дзеяння акаўнта. Credentials have expired. Скончыўся тэрмін дзеяння дадзеных аўтэнтыфікацыі. Account is disabled. Акаўнт адключан. Account is locked. Акаўнт заблакіраван. Too many failed login attempts, please try again later. Зашмат няўдалых спроб уваходу, калі ласка, паспрабуйце пазней. Invalid or expired login link. Спасылка для ўваходу несапраўдная або пратэрмінаваная. Too many failed login attempts, please try again in %minutes% minute. Занадта шмат няўдалых спроб уваходу ў сістэму, паспрабуйце спробу праз %minutes% хвіліну. An authentication exception occurred. พบความผิดพลาดในการรับรองตัวตน Authentication credentials could not be found. ไม่พบข้อมูลในการรับรองตัวตน (credentials) Authentication request could not be processed due to a system problem. คำร้องในการรับรองตัวตนไม่สามารถดำเนินการได้ เนื่องมาจากปัญหาของระบบ Invalid credentials. ข้อมูลการรับรองตัวตนไม่ถูกต้อง Cookie has already been used by someone else. Cookie ถูกใช้งานไปแล้วด้วยผู้อื่น Not privileged to request the resource. ไม่ได้รับสิทธิ์ให้ใช้งานส่วนนี้ได้ Invalid CSRF token. CSRF token ไม่ถูกต้อง No authentication provider found to support the authentication token. ไม่พบ authentication provider ที่รองรับสำหรับ authentication token No session available, it either timed out or cookies are not enabled. ไม่มี session ที่พร้อมใช้งาน, Session หมดอายุไปแล้วหรือ cookies ไม่ถูกเปิดใช้งาน No token could be found. ไม่พบ token Username could not be found. ไม่พบ Username Account has expired. บัญชีหมดอายุไปแล้ว Credentials have expired. ข้อมูลการระบุตัวตนหมดอายุแล้ว Account is disabled. บัญชีถูกระงับแล้ว Account is locked. บัญชีถูกล็อกแล้ว Too many failed login attempts, please try again later. มีความพยายามเข้าสู่ระบบล้มเหลวมากเกินไป กรุณาลองใหม่ภายหลัง Invalid or expired login link. ลิงค์เข้าสู่ระบบไม่ถูกต้องหรือหมดอายุไปแล้ว Too many failed login attempts, please try again in %minutes% minute. มีความพยายามเข้าสู่ระบบล้มเหลวมากเกินไป โปรดลองอีกครั้งใน %minutes% นาที An authentication exception occurred. Autentifikatsiyada xatolik. Authentication credentials could not be found. Autentifikatsiya ma'lumotlari topilmadi. Authentication request could not be processed due to a system problem. Tizimdagi muammo tufayli autentifikatsiya so'rovi bajarilmadi. Invalid credentials. Noto'g'ri ma'lumot. Cookie has already been used by someone else. Cookie faylini allaqachon kimdir ishlatgan. Not privileged to request the resource. Sizda ushbu manbani talab qilishga ruxsat yo'q.. Invalid CSRF token. Noto'g'ri CSRF belgisi. No authentication provider found to support the authentication token. Haqiqiylikni tasdiqlovchi belgini qo'llab-quvvatlovchi biron bir autentifikatsiya provayderi topilmadi. No session available, it either timed out or cookies are not enabled. Sessiya topilmadi, muddati tugamadi yoki cookie-fayllar yoqilmagan. No token could be found. To'ken topilmadi. Username could not be found. Foydalanuvchi nomi topilmadi. Account has expired. Akkunt muddati tugagan. Credentials have expired. Autentifikatsiya ma'lumotlari muddati tugagan. Account is disabled. Akkunt o'chirilgan. Account is locked. Akkunt bloklangan. Too many failed login attempts, please try again later. Kirish urinishlari muvaffaqiyatsiz tugadi, keyinroq qayta urinib ko'ring. Invalid or expired login link. Kirish havolasi yaroqsiz yoki muddati tugagan. Too many failed login attempts, please try again in %minutes% minute. Kirish uchun muvaffaqiyatsiz urinishlar, %minutes% daqiqadan so'ng qayta urinib ko'ring. An authentication exception occurred. Настана грешка во автентикацијата. Authentication credentials could not be found. Акредитивите за автентикација не се пронајдени. Authentication request could not be processed due to a system problem. Барањето за автентикација не можеше да биде процесуирано заради системски проблем. Invalid credentials. Невалидни акредитиви. Cookie has already been used by someone else. Колачето е веќе користено од некој друг. Not privileged to request the resource. Немате привилегии за да го побарате ресурсот. Invalid CSRF token. Невалиден CSRF токен. No authentication provider found to support the authentication token. Не е пронајден провајдер за автентикација кој го поддржува токенот за автентикација. No session available, it either timed out or cookies are not enabled. Сесијата е недостапна, или е истечена, или колачињата не се овозможени. No token could be found. Токенот не е најден. Username could not be found. Корисничкото име не е најдено. Account has expired. Корисничката сметка е истечена. Credentials have expired. Акредитивите се истечени. Account is disabled. Корисничката сметка е деактивирана. Account is locked. Корисничката сметка е заклучена. Too many failed login attempts, please try again later. Премногу неуспешни обиди за најавување, ве молиме обидете се повторно подоцна. Invalid or expired login link. Неважечка или истечена врска за најавување. Too many failed login attempts, please try again in %minutes% minute. Премногу неуспешни обиди за најавување, обидете се повторно за %minutes% минута. An authentication exception occurred. Došlo je do autentifikacijskog izuzetka (exception). Authentication credentials could not be found. Autentifikacijski podaci nisu pronađeni. Authentication request could not be processed due to a system problem. Autentifikacijski zahtjev ne može biti obrađen zbog sistemskog problema. Invalid credentials. Autentifikacijski podaci su neispravni. Cookie has already been used by someone else. Neko drugi je već iskoristio ovaj kolačić (cookie). Not privileged to request the resource. Nemate privilegije potrebne za pristup ovom resursu. Invalid CSRF token. CSRF žeton (token) je neispravan. No authentication provider found to support the authentication token. Nije pronađen autentifikacijski provajder koji bi podržao dati autentifikacijski žeton (token). No session available, it either timed out or cookies are not enabled. Nema dostupnih sesija; ili je istekla ili su kolačići (cookies) iksljučeni. No token could be found. Nije pronađen nijedan žeton (token). Username could not be found. Korisničko ime nije pronađeno. Account has expired. Nalog je istekao. Credentials have expired. Autentifikacijski podaci su istekli. Account is disabled. Nalog je onemogućen. Account is locked. Nalog je zaključan. Too many failed login attempts, please try again later. Previše neuspješnih pokušaja prijavljivanja, molim pokušajte ponovo kasnije. Invalid or expired login link. Link za prijavljivanje je istekao ili je neispravan. Too many failed login attempts, please try again in %minutes% minute. Previše neuspjelih pokušaja prijave, pokušajte ponovo za %minutes% minuta. An authentication exception occurred. Грешка при автентикация. Authentication credentials could not be found. Удостоверението за автентикация не е открито. Authentication request could not be processed due to a system problem. Заявката за автентикация не може да бъде обработената поради системна грешка. Invalid credentials. Невалидно удостоверение за автентикация. Cookie has already been used by someone else. Тази бисквитка вече се ползва от някой друг. Not privileged to request the resource. Нямате права за достъп до този ресурс. Invalid CSRF token. Невалиден CSRF токен. No authentication provider found to support the authentication token. Не е открит провайдър, който да поддържа този токен за автентикация. No session available, it either timed out or cookies are not enabled. Сесията не е достъпна, или времето за достъп е изтекло, или бисквитките не са разрешени. No token could be found. Токенът не е открит. Username could not be found. Потребителското име не е открито. Account has expired. Акаунтът е изтекъл. Credentials have expired. Удостоверението за автентикация е изтекло. Account is disabled. Акаунтът е деактивиран. Account is locked. Акаунтът е заключен. Too many failed login attempts, please try again later. Твърде много неуспешни опити за вход, моля опитайте по-късно. Invalid or expired login link. Невалиден или изтекъл линк за вход. Too many failed login attempts, please try again in %minutes% minute. Твърде много неуспешни опити за вход, моля опитайте отново след %minutes% минута. An authentication exception occurred. Izuzetak pri autentifikaciji. Authentication credentials could not be found. Autentifikacioni podaci nisu pronađeni. Authentication request could not be processed due to a system problem. Zahtev za autentifikaciju ne može biti obrađen zbog sistemskih problema. Invalid credentials. Nevalidni podaci za autentifikaciju. Cookie has already been used by someone else. Kolačić je već iskorišćen od strane nekog drugog. Not privileged to request the resource. Nemate prava pristupa ovom resursu. Invalid CSRF token. Nevalidan CSRF token. No authentication provider found to support the authentication token. Autentifikacioni provajder za podršku tokena nije pronađen. No session available, it either timed out or cookies are not enabled. Sesija nije dostupna, istekla je ili su kolačići isključeni. No token could be found. Token ne može biti pronađen. Username could not be found. Korisničko ime ne može biti pronađeno. Account has expired. Nalog je istekao. Credentials have expired. Podaci za autentifikaciju su istekli. Account is disabled. Nalog je onemogućen. Account is locked. Nalog je zaključan. Too many failed login attempts, please try again later. Previše neuspešnih pokušaja prijavljivanja, molim pokušajte ponovo kasnije. Invalid or expired login link. Link za prijavljivanje je istekao ili je neispravan. Too many failed login attempts, please try again in %minutes% minute. Previše neuspešnih pokušaja prijavljivanja, molim pokušajte ponovo za %minutes% minut. An authentication exception occurred. Помилка автентифікації. Authentication credentials could not be found. Автентифікаційні дані не знайдено. Authentication request could not be processed due to a system problem. Запит на автентифікацію не може бути опрацьовано у зв’язку з проблемою в системі. Invalid credentials. Невірні автентифікаційні дані. Cookie has already been used by someone else. Хтось інший вже використав цей сookie. Not privileged to request the resource. Відсутні права на запит цього ресурсу. Invalid CSRF token. Невірний токен CSRF. No authentication provider found to support the authentication token. Не знайдено провайдера автентифікації, що підтримує токен автентифікаціії. No session available, it either timed out or cookies are not enabled. Сесія недоступна, її час вийшов, або cookies вимкнено. No token could be found. Токен не знайдено. Username could not be found. Ім’я користувача не знайдено. Account has expired. Термін дії облікового запису вичерпано. Credentials have expired. Термін дії автентифікаційних даних вичерпано. Account is disabled. Обліковий запис відключено. Account is locked. Обліковий запис заблоковано. Too many failed login attempts, please try again later. Забагато невдалих спроб входу. Будь ласка, спробуйте пізніше. Invalid or expired login link. Посилання для входу недійсне, або термін його дії закінчився. Too many failed login attempts, please try again in %minutes% minute. Забагато невдалих спроб входу. Будь ласка, спробуйте знову через %minutes% хвилину. An authentication exception occurred. 身份驗證發生異常。 Authentication credentials could not be found. 沒有找到身份驗證的憑證。 Authentication request could not be processed due to a system problem. 由於系統故障,身份驗證的請求無法被處理。 Invalid credentials. 無效的憑證。 Cookie has already been used by someone else. Cookie 已經被其他人使用。 Not privileged to request the resource. 沒有權限請求此資源。 Invalid CSRF token. 無效的 CSRF token 。 No authentication provider found to support the authentication token. 沒有找到支持此 token 的身份驗證服務提供方。 No session available, it either timed out or cookies are not enabled. Session 不可用。回話超時或沒有啓用 cookies 。 No token could be found. 找不到 token 。 Username could not be found. 找不到用戶名。 Account has expired. 賬號已逾期。 Credentials have expired. 憑證已逾期。 Account is disabled. 賬號已被禁用。 Account is locked. 賬號已被鎖定。 Too many failed login attempts, please try again later. 登入失敗的次數過多,請稍後再試。 Invalid or expired login link. 失效或過期的登入鏈接。 Too many failed login attempts, please try again in %minutes% minute. 登錄失敗的次數過多,請在%minutes%分鐘後再試。 An authentication exception occurred. Při ověřování došlo k chybě. Authentication credentials could not be found. Ověřovací údaje nebyly nalezeny. Authentication request could not be processed due to a system problem. Požadavek na ověření nemohl být zpracován kvůli systémové chybě. Invalid credentials. Neplatné přihlašovací údaje. Cookie has already been used by someone else. Cookie již bylo použité někým jiným. Not privileged to request the resource. Nemáte oprávnění přistupovat k prostředku. Invalid CSRF token. Neplatný CSRF token. No authentication provider found to support the authentication token. Poskytovatel pro ověřovací token nebyl nalezen. No session available, it either timed out or cookies are not enabled. Session není k dispozici, vypršela její platnost, nebo jsou zakázané cookies. No token could be found. Token nebyl nalezen. Username could not be found. Přihlašovací jméno nebylo nalezeno. Account has expired. Platnost účtu vypršela. Credentials have expired. Platnost přihlašovacích údajů vypršela. Account is disabled. Účet je zakázaný. Account is locked. Účet je zablokovaný. Too many failed login attempts, please try again later. Příliš mnoho nepovedených pokusů přihlášení. Zkuste to prosím později. Invalid or expired login link. Neplatný nebo expirovaný odkaz na přihlášení. Too many failed login attempts, please try again in %minutes% minute. Příliš mnoho neúspěšných pokusů o přihlášení, zkuste to prosím znovu za %minutes% minutu. An authentication exception occurred. Nagkaroon ng isang pagbubukod sa pagpapatotoo. Authentication credentials could not be found. Hindi matagpuan ang mga kredensyal ng pagpapatotoo. Authentication request could not be processed due to a system problem. Ang kahilingan sa pagpapatotoo ay hindi naproseso dahil sa isang problema sa system. Invalid credentials. Di-wastong mga kredensyal. Cookie has already been used by someone else. Ang Cookie ay ginamit na ng ibang tao. Not privileged to request the resource. Walang pribilehiyo upang humingi ng mga bagong mapagkukunan. Invalid CSRF token. Di-wastong token ng CSRF. No authentication provider found to support the authentication token. Walang nahanap na provider ng pagpapatotoo upang suportahan ang token ng pagpapatotoo. No session available, it either timed out or cookies are not enabled. Walang magagamit na session, alinman sa nag-time out o ang cookies ay hindi pinagana. No token could be found. Walang makitang token. Username could not be found. Hindi makita ang username. Account has expired. Nag-expire na ang account. Credentials have expired. Nag-expire na ang mga kredensyal. Account is disabled. Ang account ay hindi pinagana. Account is locked. Ang account ay naka-lock. Too many failed login attempts, please try again later. Napakaraming nabigong mga pagtatangka sa pag-login, mangyaring subukang muli sa ibang pagkakataon. Invalid or expired login link. Inbalido o nagexpire na ang link para makapaglogin. Too many failed login attempts, please try again in %minutes% minute. Napakaraming nabigong mga pagtatangka sa pag-login, pakisubukan ulit sa% minuto% minuto. An authentication exception occurred. အသုံးပြုခွင့် ခြွင်းချက်တစ်ခုဖြစ်သွားသည်။ Authentication credentials could not be found. အသုံးပြုခွင့် အထောက်အထားများ ရှာမတွေ့ပါ။ Authentication request could not be processed due to a system problem. System ပြဿနာအခက်အခဲရှိ နေပါသဖြင့် အသုံးပြုခွင့်တောင်းဆိုချက်ကို ဆောင်ရွက်၍မရ နိုင်ပါ။ Invalid credentials. သင့်လျှော်သော် အထောက်အထားမဟုတ်ပါ။ Cookie has already been used by someone else. Cookie ကို တစ်စုံတစ်ယောက်မှ အသုံးပြုပြီးဖြစ်သည်။ Not privileged to request the resource. အရင်းအမြစ်ကိုတောင်းဆိုရန်အခွင့်ထူးမရပါ။ Invalid CSRF token. သင့်လျှော်သော် CSRF token မဟုတ်ပါ။ No authentication provider found to support the authentication token. အထောက်အထားစိစစ်ခြင်းသင်္ကေတကိုပံ့ပိုးရန် မည်သည့်အထောက်အထားစိစစ်ရေး ၀န်ဆောင်မှုမှမတွေ့ပါ။ No session available, it either timed out or cookies are not enabled. Session မအားလပ်ပါ။ Session အချိန်ကုန်သွားခြင်း (သို့မဟုတ်) cookies များကိုဖွင့်ထားခြင်းမရှိပါ။ No token could be found. Toke ရှာမတွေ့ပါ။ Username could not be found. အသုံးပြုသူအမည် ရှာဖွေတွေ့ရှိချင်းမရှိပါ။ Account has expired. အကောင့် သက်တမ်းကုန်လွန်သွားပါပြီ။ Credentials have expired. အထောက်အထားသက်တန်း ကုန်လွန်သွားပါပြီ။ Account is disabled. အကောင့်ပိတ်ထားပါသည်။ Account is locked. အကောင့် လောခ်ကျသွားပါပြီ။ Too many failed login attempts, please try again later. Login ၀င်ရန်ကြိုးစားမှုများလွန်းပါသည်၊ ကျေးဇူးပြု၍ နောက်မှထပ်ကြိုးစားပါ။ Invalid or expired login link. မသင့်လျှော်သော် (သို့မဟုတ်) သက်တန်းကုန်သော login link ဖြစ်ပါသည်။ Too many failed login attempts, please try again in %minutes% minute. Login ၀င်ရန်ကြိုးစားမှုများလွန်းပါသည်၊ ကျေးဇူးပြု၍ နောက် %minutes% မှထပ်မံကြိုးစားပါ။ An authentication exception occurred. En autentiseringsfeil har skjedd. Authentication credentials could not be found. Påloggingsinformasjonen kunne ikke bli funnet. Authentication request could not be processed due to a system problem. Autentiserings forespørselen kunne ikke bli prosessert grunnet en system feil. Invalid credentials. Ugyldig påloggingsinformasjon. Cookie has already been used by someone else. Cookie har allerede blitt brukt av noen andre. Not privileged to request the resource. Ingen tilgang til å be om gitt ressurs. Invalid CSRF token. Ugyldig CSRF token. No authentication provider found to support the authentication token. Ingen autentiserings tilbyder funnet som støtter gitt autentiserings token. No session available, it either timed out or cookies are not enabled. Ingen sesjon tilgjengelig, sesjonen er enten utløpt eller cookies ikke skrudd på. No token could be found. Ingen token kunne bli funnet. Username could not be found. Brukernavn kunne ikke bli funnet. Account has expired. Brukerkonto har utgått. Credentials have expired. Påloggingsinformasjon har utløpt. Account is disabled. Brukerkonto er deaktivert. Account is locked. Brukerkonto er sperret. Too many failed login attempts, please try again later. For mange mislykkede påloggingsforsøk. Prøv igjen senere. Invalid or expired login link. Ugyldig eller utløpt påloggingskobling. Too many failed login attempts, please try again in %minutes% minute. For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutt. An authentication exception occurred. Нэвтрэх хүсэлтийн алдаа гарав. Authentication credentials could not be found. Нэвтрэх эрхийн мэдээлэл олдсонгүй. Authentication request could not be processed due to a system problem. Системийн алдаанаас болон нэвтрэх хүсэлтийг гүйцэтгэх боломжгүй байна. Invalid credentials. Буруу нэвтрэх эрхийн мэдээлэл. Cookie has already been used by someone else. Күүки файлыг аль хэдийн өөр хүн хэрэглэж байна. Not privileged to request the resource. Энэхүү мэдээллийг авах эрх хүрэхгүй байна. Invalid CSRF token. Тохиромжгүй CSRF токен. No authentication provider found to support the authentication token. Нэвтрэх токенг дэмжих нэвтрэх эрхийн хангагч олдсонгүй. No session available, it either timed out or cookies are not enabled. Хэрэглэгчийн session олдсонгүй, хугацаа нь дууссан эсвэл күүки идэвхижүүлээгүй байна. No token could be found. Токен олдсонгүй. Username could not be found. Нэвтрэх нэр олсонгүй. Account has expired. Бүртгэлийн хугацаа дууссан байна. Credentials have expired. Нэвтрэх эрхийн хугацаа дууссан байна. Account is disabled. Бүртгэлийг хаасан байна. Account is locked. Бүртгэлийг цоожилсон байна. Too many failed login attempts, please try again later. Хэтэрхий олон амжилтгүй оролдлого, түр хүлээгээд дахин оролдоно уу. Invalid or expired login link. Буруу эсвэл хугацаа нь дууссан нэвтрэх зам. Too many failed login attempts, please try again in %minutes% minute. Нэвтрэх оролдлого ихээр амжилтгүй болсон, %minutes% минутын дараа дахин оролдоно уу. An authentication exception occurred. Doğrulama istisnası baş verdi. Authentication credentials could not be found. Doğrulama məlumatları tapılmadı. Authentication request could not be processed due to a system problem. Sistem xətası səbəbilə doğrulama istəyi emal edilə bilmədi. Invalid credentials. Yanlış məlumat. Cookie has already been used by someone else. Kuki başqası tərəfindən istifadə edilib. Not privileged to request the resource. Resurs istəyi üçün imtiyaz yoxdur. Invalid CSRF token. Yanlış CSRF nişanı. No authentication provider found to support the authentication token. Doğrulama nişanını dəstəkləyəcək provayder tapılmadı. No session available, it either timed out or cookies are not enabled. Uyğun seans yoxdur, vaxtı keçib və ya kuki aktiv deyil. No token could be found. Nişan tapılmadı. Username could not be found. İstifadəçi adı tapılmadı. Account has expired. Hesabın istifadə müddəti bitib. Credentials have expired. Məlumatların istifadə müddəti bitib. Account is disabled. Hesab qeyri-aktiv edilib. Account is locked. Hesab kilitlənib. Too many failed login attempts, please try again later. Çoxlu uğursuz giriş təşəbbüsü, zəhmət olmasa daha sonra yeniden yoxlayın. Invalid or expired login link. Yanlış və ya müddəti keçmiş giriş keçidi. Too many failed login attempts, please try again in %minutes% minute. Həddindən artıq uğursuz giriş cəhdi, lütfən %minutes% dəqiqə ərzində yenidən yoxlayın. An authentication exception occurred. Autentifikazio-errorea gertatu da. Authentication credentials could not be found. Ez dira aurkitu autentifikazio-kredentzialak. Authentication request could not be processed due to a system problem. Ezin izan da autentifikazio-eskaera prozesatu, sistema-arazo bat gertatu da eta. Invalid credentials. Kredentzialak okerrak dira. Cookie has already been used by someone else. Dagoeneko beste pertsona batek erabili du cookiea. Not privileged to request the resource. Ez duzu baliabidea eskatzeko aukerarik. Invalid CSRF token. CSRF tokena okerra da. No authentication provider found to support the authentication token. Ez da aurkitu autentifikazio-tokena eutsi dezakeen autentifikazio-hornitzailerik. No session available, it either timed out or cookies are not enabled. Ez dago saiorik erabilgarri, iraungi egin da edo cookieak ez daude gaituta. No token could be found. Ez da tokenik aurkitu. Username could not be found. Ez da erabiltzaile-izena aurkitu. Account has expired. Kontua iraungi da. Credentials have expired. Kredentzialak iraungi dira. Account is disabled. Kontua desgaituta dago. Account is locked. Kontua blokeatuta dago. Too many failed login attempts, please try again later. Saioa hasteko saio huts gehiegi, saiatu berriro geroago. Invalid or expired login link. Sartzeko esteka baliogabea edo iraungia. Too many failed login attempts, please try again in %minutes% minute. Saioa hasteko huts gehiegi egin dira, saiatu berriro minutu %minutes% geroago. An authentication exception occurred. שגיאה באימות Authentication credentials could not be found. פרטי זיהוי לא נמצאו. Authentication request could not be processed due to a system problem. לא ניתן היה לעבד את בקשת אימות בגלל בעיית מערכת. Invalid credentials. שם משתמש או סיסמא שגויים. Cookie has already been used by someone else. עוגיה כבר שומשה. Not privileged to request the resource. אין הרשאה מתאימה. Invalid CSRF token. אסימון CSRF לא חוקי. No authentication provider found to support the authentication token. לא נמצא ספק אימות המתאימה לבקשה. No session available, it either timed out or cookies are not enabled. אין סיישן זמין, או שתם הזמן הקצוב או העוגיות אינן מופעלות. No token could be found. הטוקן לא נמצא. Username could not be found. שם משתמש לא נמצא. Account has expired. החשבון פג תוקף. Credentials have expired. פרטי התחברות פקעו תוקף. Account is disabled. החשבון מבוטל. Account is locked. החשבון נעול. Too many failed login attempts, please try again later. יותר מדי ניסיונות כניסה כושלים, אנא נסה שוב מאוחר יותר. Invalid or expired login link. קישור כניסה לא חוקי או שפג תוקפו. Too many failed login attempts, please try again in %minutes% minute. יותר מדי ניסיונות כניסה כושלים, אנא נסה שוב בוד %minutes% דקה. An authentication exception occurred. Dogodila se autentifikacijske iznimka. Authentication credentials could not be found. Autentifikacijski podaci nisu pronađeni. Authentication request could not be processed due to a system problem. Autentifikacijski zahtjev nije moguće provesti uslijed sistemskog problema. Invalid credentials. Neispravni akreditacijski podaci. Cookie has already been used by someone else. Cookie je već netko drugi iskoristio. Not privileged to request the resource. Nemate privilegije zahtijevati resurs. Invalid CSRF token. Neispravan CSRF token. No authentication provider found to support the authentication token. Nije pronađen autentifikacijski provider koji bi podržao autentifikacijski token. No session available, it either timed out or cookies are not enabled. Sesija nije dostupna, ili je istekla ili cookies nisu omogućeni. No token could be found. Token nije pronađen. Username could not be found. Korisničko ime nije pronađeno. Account has expired. Račun je isteko. Credentials have expired. Akreditacijski podaci su istekli. Account is disabled. Račun je onemogućen. Account is locked. Račun je zaključan. Too many failed login attempts, please try again later. Previše neuspjelih pokušaja prijave, molim pokušajte ponovo kasnije. Invalid or expired login link. Link za prijavu je isteako ili je neispravan. Too many failed login attempts, please try again in %minutes% minute. Previše neuspjelih pokušaja prijave, molim pokušajte ponovo za %minutes% minutu. An authentication exception occurred. Ocorreu uma excepção durante a autenticação. Authentication credentials could not be found. As credenciais de autenticação não foram encontradas. Authentication request could not be processed due to a system problem. O pedido de autenticação não foi concluído devido a um problema no sistema. Invalid credentials. Credenciais inválidas. Cookie has already been used by someone else. Este cookie já está em uso. Not privileged to request the resource. Não possui privilégios para aceder a este recurso. Invalid CSRF token. Token CSRF inválido. No authentication provider found to support the authentication token. Nenhum fornecedor de autenticação encontrado para suportar o token de autenticação. No session available, it either timed out or cookies are not enabled. Não existe sessão disponível, esta expirou ou os cookies estão desativados. No token could be found. O token não foi encontrado. Username could not be found. Nome de utilizador não encontrado. Account has expired. A conta expirou. Credentials have expired. As credenciais expiraram. Account is disabled. Conta desativada. Account is locked. A conta está trancada. Too many failed login attempts, please try again later. Várias tentativas de login falhadas, por favor tente mais tarde. Invalid or expired login link. Ligação de login inválida ou expirada. Too many failed login attempts, please try again in %minutes% minute. Demasiadas tentativas de login, tente novamente num minuto. An authentication exception occurred. Autentimisel juhtus ootamatu viga. Authentication credentials could not be found. Autentimisandmeid ei leitud. Authentication request could not be processed due to a system problem. Autentimispäring ei õnnestunud süsteemi probleemi tõttu. Invalid credentials. Vigased autentimisandmed. Cookie has already been used by someone else. Küpsis on juba kellegi teise poolt kasutuses. Not privileged to request the resource. Ressursi pärimiseks pole piisavalt õiguseid. Invalid CSRF token. Vigane CSRF märgis. No authentication provider found to support the authentication token. Ei leitud sobivat autentimismeetodit, mis toetaks autentimismärgist. No session available, it either timed out or cookies are not enabled. Seanss puudub, see on kas aegunud või pole küpsised lubatud. No token could be found. Identsustõendit ei leitud. Username could not be found. Kasutajanime ei leitud. Account has expired. Kasutajakonto on aegunud. Credentials have expired. Autentimistunnused on aegunud. Account is disabled. Kasutajakonto on keelatud. Account is locked. Kasutajakonto on lukustatud. Too many failed login attempts, please try again later. Liiga palju ebaõnnestunud autentimise katseid, palun proovi hiljem uuesti. Invalid or expired login link. Vigane või aegunud sisselogimise link. Too many failed login attempts, please try again in %minutes% minute. Liiga palju ebaõnnestunud autentimise katseid, palun proovi uuesti %minutes% minuti pärast. An authentication exception occurred. Es ist ein Fehler bei der Authentifikation aufgetreten. Authentication credentials could not be found. Es konnten keine Zugangsdaten gefunden werden. Authentication request could not be processed due to a system problem. Die Authentifikation konnte wegen eines Systemproblems nicht bearbeitet werden. Invalid credentials. Fehlerhafte Zugangsdaten. Cookie has already been used by someone else. Cookie wurde bereits von jemand anderem verwendet. Not privileged to request the resource. Keine Rechte, um die Ressource anzufragen. Invalid CSRF token. Ungültiges CSRF-Token. No authentication provider found to support the authentication token. Es wurde kein Authentifizierungs-Provider gefunden, der das Authentifizierungs-Token unterstützt. No session available, it either timed out or cookies are not enabled. Keine Session verfügbar, entweder ist diese abgelaufen oder Cookies sind nicht aktiviert. No token could be found. Es wurde kein Token gefunden. Username could not be found. Der Benutzername wurde nicht gefunden. Account has expired. Der Account ist abgelaufen. Credentials have expired. Die Zugangsdaten sind abgelaufen. Account is disabled. Der Account ist deaktiviert. Account is locked. Der Account ist gesperrt. Too many failed login attempts, please try again later. Zu viele fehlgeschlagene Anmeldeversuche, bitte versuchen Sie es später noch einmal. Invalid or expired login link. Ungültiger oder abgelaufener Anmelde-Link. Too many failed login attempts, please try again in %minutes% minute. Zu viele fehlgeschlagene Anmeldeversuche, bitte versuchen Sie es in einer Minute noch einmal. An authentication exception occurred. Prišlo je do izjeme pri preverjanju avtentikacije. Authentication credentials could not be found. Poverilnic za avtentikacijo ni bilo mogoče najti. Authentication request could not be processed due to a system problem. Zahteve za avtentikacijo ni bilo mogoče izvesti zaradi sistemske težave. Invalid credentials. Neveljavne pravice. Cookie has already been used by someone else. Piškotek je uporabil že nekdo drug. Not privileged to request the resource. Nimate privilegijev za zahtevani vir. Invalid CSRF token. Neveljaven CSRF žeton. No authentication provider found to support the authentication token. Ponudnika avtentikacije za podporo prijavnega žetona ni bilo mogoče najti. No session available, it either timed out or cookies are not enabled. Seja ni na voljo, ali je potekla ali pa piškotki niso omogočeni. No token could be found. Žetona ni bilo mogoče najti. Username could not be found. Uporabniškega imena ni bilo mogoče najti. Account has expired. Račun je potekel. Credentials have expired. Poverilnice so potekle. Account is disabled. Račun je onemogočen. Account is locked. Račun je zaklenjen. Too many failed login attempts, please try again later. Preveč neuspelih poskusov prijave, poskusite znova pozneje. Invalid or expired login link. Neveljavna ali potekla povezava prijave. Too many failed login attempts, please try again in %minutes% minute. Preveč neuspelih poskusov prijave, poskusite znova čez %minutes% minuto. An authentication exception occurred. Si è verificato un errore di autenticazione. Authentication credentials could not be found. Impossibile trovare le credenziali di autenticazione. Authentication request could not be processed due to a system problem. La richiesta di autenticazione non può essere processata a causa di un errore di sistema. Invalid credentials. Credenziali non valide. Cookie has already been used by someone else. Il cookie è già stato usato da qualcun altro. Not privileged to request the resource. Non hai i privilegi per richiedere questa risorsa. Invalid CSRF token. CSRF token non valido. No authentication provider found to support the authentication token. Non è stato trovato un valido fornitore di autenticazione per supportare il token. No session available, it either timed out or cookies are not enabled. Nessuna sessione disponibile, può essere scaduta o i cookie non sono abilitati. No token could be found. Nessun token trovato. Username could not be found. Username non trovato. Account has expired. Account scaduto. Credentials have expired. Credenziali scadute. Account is disabled. L'account è disabilitato. Account is locked. L'account è bloccato. Too many failed login attempts, please try again later. Troppi tentativi di login falliti, riprova tra un po'. Invalid or expired login link. Link di login scaduto o non valido. Too many failed login attempts, please try again in %minutes% minute. Troppi tentativi di login falliti, riprova tra %minutes% minuto. An authentication exception occurred. Ocorreu un erro de autenticación. Authentication credentials could not be found. Non se atoparon as credenciais de autenticación. Authentication request could not be processed due to a system problem. A solicitude de autenticación no puido ser procesada debido a un problema do sistema. Invalid credentials. Credenciais non válidas. Cookie has already been used by someone else. A cookie xa foi empregado por outro usuario. Not privileged to request the resource. Non ten privilexios para solicitar o recurso. Invalid CSRF token. Token CSRF non válido. No authentication provider found to support the authentication token. Non se atopou un provedor de autenticación que soporte o token de autenticación. No session available, it either timed out or cookies are not enabled. Non hai ningunha sesión dispoñible, expirou ou as cookies non están habilitadas. No token could be found. Non se atopou ningún token. Username could not be found. Non se atopou o nome de usuario. Account has expired. A conta expirou. Credentials have expired. As credenciais expiraron. Account is disabled. A conta está deshabilitada. Account is locked. A conta está bloqueada. Too many failed login attempts, please try again later. Demasiados intentos de inicio de sesión fallados. Téntao de novo máis tarde. Invalid or expired login link. Ligazón de inicio de sesión non válida ou caducada. Too many failed login attempts, please try again in %minutes% minute. Demasiados intentos de inicio de sesión errados, por favor, ténteo de novo en %minutes% minuto. An authentication exception occurred. Ocurrió un error de autenticación. Authentication credentials could not be found. No se encontraron las credenciales de autenticación. Authentication request could not be processed due to a system problem. La solicitud de autenticación no se pudo procesar debido a un problema del sistema. Invalid credentials. Credenciales no válidas. Cookie has already been used by someone else. La cookie ya ha sido usada por otra persona. Not privileged to request the resource. No tiene privilegios para solicitar el recurso. Invalid CSRF token. Token CSRF no válido. No authentication provider found to support the authentication token. No se encontró un proveedor de autenticación que soporte el token de autenticación. No session available, it either timed out or cookies are not enabled. No hay ninguna sesión disponible, ha expirado o las cookies no están habilitados. No token could be found. No se encontró ningún token. Username could not be found. No se encontró el nombre de usuario. Account has expired. La cuenta ha expirado. Credentials have expired. Las credenciales han expirado. Account is disabled. La cuenta está deshabilitada. Account is locked. La cuenta está bloqueada. Too many failed login attempts, please try again later. Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo más tarde. Invalid or expired login link. Enlace de inicio de sesión inválido o expirado. Too many failed login attempts, please try again in %minutes% minute. Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minuto. An authentication exception occurred. Hitelesítési hiba lépett fel. Authentication credentials could not be found. Nem találhatók hitelesítési információk. Authentication request could not be processed due to a system problem. A hitelesítési kérést rendszerhiba miatt nem lehet feldolgozni. Invalid credentials. Érvénytelen hitelesítési információk. Cookie has already been used by someone else. Ezt a sütit valaki más már felhasználta. Not privileged to request the resource. Nem rendelkezik az erőforrás eléréséhez szükséges jogosultsággal. Invalid CSRF token. Érvénytelen CSRF token. No authentication provider found to support the authentication token. Nem található a hitelesítési tokent támogató hitelesítési szolgáltatás. No session available, it either timed out or cookies are not enabled. Munkamenet nem áll rendelkezésre, túllépte az időkeretet vagy a sütik le vannak tiltva. No token could be found. Nem található token. Username could not be found. A felhasználónév nem található. Account has expired. A fiók lejárt. Credentials have expired. A hitelesítési információk lejártak. Account is disabled. Felfüggesztett fiók. Account is locked. Zárolt fiók. Too many failed login attempts, please try again later. Túl sok sikertelen bejelentkezési kísérlet, kérjük próbálja újra később. Invalid or expired login link. Érvénytelen vagy lejárt bejelentkezési link. Too many failed login attempts, please try again in %minutes% minute. Túl sok sikertelen bejelentkezési kísérlet, kérjük próbálja újra %minutes% perc múlva. An authentication exception occurred. En fejl indtraf ved godkendelse. Authentication credentials could not be found. Loginoplysninger kan ikke findes. Authentication request could not be processed due to a system problem. Godkendelsesanmodning kan ikke behandles på grund af et systemfejl. Invalid credentials. Ugyldige loginoplysninger. Cookie has already been used by someone else. Cookie er allerede brugt af en anden. Not privileged to request the resource. Ingen adgang til at forespørge ressourcen. Invalid CSRF token. Ugyldig CSRF-token. No authentication provider found to support the authentication token. Ingen godkendelsesudbyder er fundet til understøttelsen af godkendelsestoken. No session available, it either timed out or cookies are not enabled. Ingen session tilgængelig, sessionen er enten udløbet eller cookies er ikke aktiveret. No token could be found. Ingen token kan findes. Username could not be found. Brugernavn kan ikke findes. Account has expired. Brugerkonto er udløbet. Credentials have expired. Loginoplysninger er udløbet. Account is disabled. Brugerkonto er deaktiveret. Account is locked. Brugerkonto er låst. Too many failed login attempts, please try again later. For mange fejlede login forsøg, prøv venligst senere. Invalid or expired login link. Ugyldigt eller udløbet login link. Too many failed login attempts, please try again in %minutes% minute. For mange fejlede login forsøg, prøv igen om %minutes% minut. An authentication exception occurred. Autentikointi poikkeus tapahtui. Authentication credentials could not be found. Autentikoinnin tunnistetietoja ei löydetty. Authentication request could not be processed due to a system problem. Autentikointipyyntöä ei voitu käsitellä järjestelmäongelman vuoksi. Invalid credentials. Virheelliset tunnistetiedot. Cookie has already been used by someone else. Eväste on jo jonkin muun käytössä. Not privileged to request the resource. Ei oikeutta resurssiin. Invalid CSRF token. Virheellinen CSRF tunnus. No authentication provider found to support the authentication token. Autentikointi tunnukselle ei löydetty tuettua autentikointi tarjoajaa. No session available, it either timed out or cookies are not enabled. Sessio ei ole saatavilla, se on joko vanhentunut tai evästeet eivät ole käytössä. No token could be found. Tunnusta ei löytynyt. Username could not be found. Käyttäjätunnusta ei löydetty. Account has expired. Tili on vanhentunut. Credentials have expired. Tunnistetiedot ovat vanhentuneet. Account is disabled. Tili on poistettu käytöstä. Account is locked. Tili on lukittu. Too many failed login attempts, please try again later. Liian monta epäonnistunutta kirjautumisyritystä, yritä myöhemmin uudelleen. Invalid or expired login link. Virheellinen tai vanhentunut kirjautumislinkki. Too many failed login attempts, please try again in %minutes% minute. Liian monta epäonnistunutta kirjautumisyritystä, yritä uudelleen %minutes% minuutin kuluttua. An authentication exception occurred. Terjadi sebuah pengecualian otentikasi. Authentication credentials could not be found. Kredensial otentikasi tidak bisa ditemukan. Authentication request could not be processed due to a system problem. Permintaan otentikasi tidak bisa diproses karena masalah sistem. Invalid credentials. Kredensial salah. Cookie has already been used by someone else. Cookie sudah digunakan oleh orang lain. Not privileged to request the resource. Tidak berhak untuk meminta sumber daya. Invalid CSRF token. Token CSRF salah. No authentication provider found to support the authentication token. Tidak ditemukan penyedia otentikasi untuk mendukung token otentikasi. No session available, it either timed out or cookies are not enabled. Tidak ada sesi yang tersedia, mungkin waktu sudah habis atau cookie tidak diaktifkan No token could be found. Tidak ada token yang bisa ditemukan. Username could not be found. Username tidak bisa ditemukan. Account has expired. Akun telah berakhir. Credentials have expired. Kredensial telah berakhir. Account is disabled. Akun dinonaktifkan. Account is locked. Akun terkunci. Too many failed login attempts, please try again later. Terlalu banyak percobaan login yang salah, silahkan coba lagi nanti. Invalid or expired login link. Link login salah atau sudah kedaluwarsa. Too many failed login attempts, please try again in %minutes% minute. Terlalu banyak percobaan login yang salah, silahkan coba lagi dalam %minutes% menit. An authentication exception occurred. Pri overovaní došlo k chybe. Authentication credentials could not be found. Overovacie údaje neboli nájdené. Authentication request could not be processed due to a system problem. Požiadavok na overenie nemohol byť spracovaný kvôli systémovej chybe. Invalid credentials. Neplatné prihlasovacie údaje. Cookie has already been used by someone else. Cookie už bolo použité niekým iným. Not privileged to request the resource. Nemáte oprávnenie pristupovať k prostriedku. Invalid CSRF token. Neplatný CSRF token. No authentication provider found to support the authentication token. Poskytovateľ pre overovací token nebol nájdený. No session available, it either timed out or cookies are not enabled. Session nie je k dispozíci, vypršala jej platnosť, alebo sú zakázané cookies. No token could be found. Token nebol nájdený. Username could not be found. Prihlasovacie meno nebolo nájdené. Account has expired. Platnosť účtu skončila. Credentials have expired. Platnosť prihlasovacích údajov skončila. Account is disabled. Účet je zakázaný. Account is locked. Účet je zablokovaný. Too many failed login attempts, please try again later. Príliš mnoho neúspešných pokusov o prihlásenie. Skúste to prosím znovu neskôr. Invalid or expired login link. Neplatný alebo expirovaný odkaz na prihlásenie. Too many failed login attempts, please try again in %minutes% minute. Príliš veľa neúspešných pokusov o prihlásenie. Skúste to znova o %minutes% minútu. An authentication exception occurred. A apărut o eroare de autentificare. Authentication credentials could not be found. Informațiile de autentificare nu au fost găsite. Authentication request could not be processed due to a system problem. Sistemul nu a putut procesa cererea de autentificare din cauza unei erori. Invalid credentials. Date de autentificare invalide. Cookie has already been used by someone else. Cookie este folosit deja de altcineva. Not privileged to request the resource. Permisiuni insuficiente pentru resursa cerută. Invalid CSRF token. Token CSRF este invalid. No authentication provider found to support the authentication token. Nu a fost găsit nici un agent de autentificare pentru tokenul specificat. No session available, it either timed out or cookies are not enabled. Sesiunea nu mai este disponibilă, a expirat sau suportul pentru cookies nu este activat. No token could be found. Tokenul nu a putut fi găsit. Username could not be found. Numele de utilizator nu a fost găsit. Account has expired. Contul a expirat. Credentials have expired. Datele de autentificare au expirat. Account is disabled. Contul este dezactivat. Account is locked. Contul este blocat. Too many failed login attempts, please try again later. Prea multe încercări de autentificare eșuate, vă rugăm să încercați mai târziu. Invalid or expired login link. Link de autentificare invalid sau expirat. Too many failed login attempts, please try again in %minutes% minute. Prea multe încercări nereușite, încearcă din nou în %minutes% minut. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountExpiredException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CredentialsExpiredException; use _ContaoManager\Symfony\Component\Security\Core\Exception\DisabledException; use _ContaoManager\Symfony\Component\Security\Core\Exception\LockedException; /** * Checks the state of the in-memory user account. * * @author Fabien Potencier */ class InMemoryUserChecker implements UserCheckerInterface { public function checkPreAuth(UserInterface $user) { // @deprecated since Symfony 5.3, in 6.0 change to: // if (!$user instanceof InMemoryUser) { if (!$user instanceof InMemoryUser && !$user instanceof User) { return; } if (!$user->isEnabled()) { $ex = new DisabledException('User account is disabled.'); $ex->setUser($user); throw $ex; } // @deprecated since Symfony 5.3 if (User::class === \get_class($user)) { if (!$user->isAccountNonLocked()) { $ex = new LockedException('User account is locked.'); $ex->setUser($user); throw $ex; } if (!$user->isAccountNonExpired()) { $ex = new AccountExpiredException('User account has expired.'); $ex->setUser($user); throw $ex; } } } public function checkPostAuth(UserInterface $user) { // @deprecated since Symfony 5.3, noop in 6.0 if (User::class !== \get_class($user)) { return; } if (!$user->isCredentialsNonExpired()) { $ex = new CredentialsExpiredException('User credentials have expired.'); $ex->setUser($user); throw $ex; } } } if (!\class_exists(UserChecker::class, \false)) { \class_alias(InMemoryUserChecker::class, UserChecker::class); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * Represents the interface that all user classes must implement. * * This interface is useful because the authentication layer can deal with * the object through its lifecycle, using the object to get the hashed * password (for checking against a submitted password), assigning roles * and so on. * * Regardless of how your users are loaded or where they come from (a database, * configuration, web service, etc.), you will have a class that implements * this interface. Objects that implement this interface are created and * loaded by different objects that implement UserProviderInterface. * * @see UserProviderInterface * * @method string getUserIdentifier() returns the identifier for this user (e.g. its username or email address) * * @author Fabien Potencier */ interface UserInterface { /** * Returns the roles granted to the user. * * public function getRoles() * { * return ['ROLE_USER']; * } * * Alternatively, the roles might be stored in a ``roles`` property, * and populated in any number of different ways when the user object * is created. * * @return string[] */ public function getRoles(); /** * Returns the password used to authenticate the user. * * This should be the hashed password. On authentication, a plain-text * password will be hashed, and then compared to this value. * * This method is deprecated since Symfony 5.3, implement it from {@link PasswordAuthenticatedUserInterface} instead. * * @return string|null */ public function getPassword(); /** * Returns the salt that was originally used to hash the password. * * This can return null if the password was not hashed using a salt. * * This method is deprecated since Symfony 5.3, implement it from {@link LegacyPasswordAuthenticatedUserInterface} instead. * * @return string|null */ public function getSalt(); /** * Removes sensitive data from the user. * * This is important if, at any given point, sensitive information like * the plain-text password is stored on this object. */ public function eraseCredentials(); /** * @return string * * @deprecated since Symfony 5.3, use getUserIdentifier() instead */ public function getUsername(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; use _ContaoManager\Symfony\Component\Security\Core\Exception\UnsupportedUserException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * Represents a class that loads UserInterface objects from some source for the authentication system. * * In a typical authentication configuration, a user identifier (e.g. a * username or email address) credential enters the system (via form login, or * any method). The user provider that is configured with that authentication * method is asked to load the UserInterface object for the given identifier (via * loadUserByIdentifier) so that the rest of the process can continue. * * Internally, a user provider can load users from any source (databases, * configuration, web service). This is totally independent of how the authentication * information is submitted or what the UserInterface object looks like. * * @see UserInterface * * @method UserInterface loadUserByIdentifier(string $identifier) loads the user for the given user identifier (e.g. username or email). * This method must throw UserNotFoundException if the user is not found. * * @author Fabien Potencier */ interface UserProviderInterface { /** * Refreshes the user. * * It is up to the implementation to decide if the user data should be * totally reloaded (e.g. from the database), or if the UserInterface * object can just be merged into some internal array of users / identity * map. * * @return UserInterface * * @throws UnsupportedUserException if the user is not supported * @throws UserNotFoundException if the user is not found */ public function refreshUser(UserInterface $user); /** * Whether this provider supports the given user class. * * @return bool */ public function supportsClass(string $class); /** * @return UserInterface * * @throws UserNotFoundException * * @deprecated since Symfony 5.3, use loadUserByIdentifier() instead */ public function loadUserByUsername(string $username); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * User is the user implementation used by the in-memory user provider. * * This should not be used for anything else. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use {@link InMemoryUser} instead */ class User implements UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface { private $username; private $password; private $enabled; private $accountNonExpired; private $credentialsNonExpired; private $accountNonLocked; private $roles; private $extraFields; public function __construct(?string $username, ?string $password, array $roles = [], bool $enabled = \true, bool $userNonExpired = \true, bool $credentialsNonExpired = \true, bool $userNonLocked = \true, array $extraFields = []) { if (InMemoryUser::class !== static::class) { \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', self::class, InMemoryUser::class); } if ('' === $username || null === $username) { throw new \InvalidArgumentException('The username cannot be empty.'); } $this->username = $username; $this->password = $password; $this->enabled = $enabled; $this->accountNonExpired = $userNonExpired; $this->credentialsNonExpired = $credentialsNonExpired; $this->accountNonLocked = $userNonLocked; $this->roles = $roles; $this->extraFields = $extraFields; } public function __toString() : string { return $this->getUserIdentifier(); } /** * {@inheritdoc} */ public function getRoles() : array { return $this->roles; } /** * {@inheritdoc} */ public function getPassword() : ?string { return $this->password; } /** * {@inheritdoc} */ public function getSalt() : ?string { return null; } /** * {@inheritdoc} */ public function getUsername() : string { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); return $this->username; } /** * Returns the identifier for this user (e.g. its username or email address). */ public function getUserIdentifier() : string { return $this->username; } /** * Checks whether the user's account has expired. * * Internally, if this method returns false, the authentication system * will throw an AccountExpiredException and prevent login. * * @see AccountExpiredException */ public function isAccountNonExpired() : bool { return $this->accountNonExpired; } /** * Checks whether the user is locked. * * Internally, if this method returns false, the authentication system * will throw a LockedException and prevent login. * * @see LockedException */ public function isAccountNonLocked() : bool { return $this->accountNonLocked; } /** * Checks whether the user's credentials (password) has expired. * * Internally, if this method returns false, the authentication system * will throw a CredentialsExpiredException and prevent login. * * @see CredentialsExpiredException */ public function isCredentialsNonExpired() : bool { return $this->credentialsNonExpired; } /** * Checks whether the user is enabled. * * Internally, if this method returns false, the authentication system * will throw a DisabledException and prevent login. * * @see DisabledException */ public function isEnabled() : bool { return $this->enabled; } /** * {@inheritdoc} */ public function eraseCredentials() { } public function getExtraFields() : array { return $this->extraFields; } /** * {@inheritdoc} */ public function isEqualTo(UserInterface $user) : bool { if (!$user instanceof self) { return \false; } if ($this->getPassword() !== $user->getPassword()) { return \false; } if ($this->getSalt() !== $user->getSalt()) { return \false; } $currentRoles = \array_map('strval', (array) $this->getRoles()); $newRoles = \array_map('strval', (array) $user->getRoles()); $rolesChanged = \count($currentRoles) !== \count($newRoles) || \count($currentRoles) !== \count(\array_intersect($currentRoles, $newRoles)); if ($rolesChanged) { return \false; } if ($this->getUserIdentifier() !== $user->getUserIdentifier()) { return \false; } if (self::class === static::class) { if ($this->isAccountNonExpired() !== $user->isAccountNonExpired()) { return \false; } if ($this->isAccountNonLocked() !== $user->isAccountNonLocked()) { return \false; } if ($this->isCredentialsNonExpired() !== $user->isCredentialsNonExpired()) { return \false; } } if ($this->isEnabled() !== $user->isEnabled()) { return \false; } return \true; } public function setPassword(string $password) { $this->password = $password; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; use _ContaoManager\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; /** * MissingUserProvider is a dummy user provider used to throw proper exception * when a firewall requires a user provider but none was defined. * * @internal */ class MissingUserProvider implements UserProviderInterface { /** * @param string $firewall the firewall missing a provider */ public function __construct(string $firewall) { throw new InvalidConfigurationException(\sprintf('"%s" firewall requires a user provider but none was defined.', $firewall)); } /** * {@inheritdoc} */ public function loadUserByUsername(string $username) : UserInterface { throw new \BadMethodCallException(); } public function loadUserByIdentifier(string $identifier) : UserInterface { throw new \BadMethodCallException(); } /** * {@inheritdoc} */ public function refreshUser(UserInterface $user) : UserInterface { throw new \BadMethodCallException(); } /** * {@inheritdoc} */ public function supportsClass(string $class) : bool { throw new \BadMethodCallException(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * For users that can be authenticated using a password/salt couple. * * Once all password hashes have been upgraded to a modern algorithm via password migrations, * implement {@see PasswordAuthenticatedUserInterface} instead. * * @author Robin Chalas */ interface LegacyPasswordAuthenticatedUserInterface extends PasswordAuthenticatedUserInterface { /** * Returns the salt that was originally used to hash the password. */ public function getSalt() : ?string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * @author Nicolas Grekas * * @method void upgradePassword(PasswordAuthenticatedUserInterface|UserInterface $user, string $newHashedPassword) Upgrades the hashed password of a user, typically for using a better hash algorithm. * This method should persist the new password in the user storage and update the $user object accordingly. * Because you don't want your users not being able to log in, this method should be opportunistic: * it's fine if it does nothing or if it fails without throwing any exception. */ interface PasswordUpgraderInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * UserInterface implementation used by the in-memory user provider. * * This should not be used for anything else. * * @author Robin Chalas * @author Fabien Potencier */ final class InMemoryUser extends User { /** * {@inheritdoc} * * @deprecated since Symfony 5.3 */ public function isAccountNonExpired() : bool { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); return parent::isAccountNonExpired(); } /** * {@inheritdoc} * * @deprecated since Symfony 5.3 */ public function isAccountNonLocked() : bool { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); return parent::isAccountNonLocked(); } /** * {@inheritdoc} * * @deprecated since Symfony 5.3 */ public function isCredentialsNonExpired() : bool { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); return parent::isCredentialsNonExpired(); } /** * @deprecated since Symfony 5.3 */ public function getExtraFields() : array { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); return parent::getExtraFields(); } public function setPassword(string $password) { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, you should stop using it.', __METHOD__); parent::setPassword($password); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; use _ContaoManager\Symfony\Component\Security\Core\Exception\UnsupportedUserException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * InMemoryUserProvider is a simple non persistent user provider. * * Useful for testing, demonstration, prototyping, and for simple needs * (a backend with a unique admin for instance) * * @author Fabien Potencier */ class InMemoryUserProvider implements UserProviderInterface { /** * @var array */ private $users; /** * The user array is a hash where the keys are usernames and the values are * an array of attributes: 'password', 'enabled', and 'roles'. * * @param array}> $users An array of users */ public function __construct(array $users = []) { foreach ($users as $username => $attributes) { $password = $attributes['password'] ?? null; $enabled = $attributes['enabled'] ?? \true; $roles = $attributes['roles'] ?? []; $user = new InMemoryUser($username, $password, $roles, $enabled); $this->createUser($user); } } /** * Adds a new User to the provider. * * @throws \LogicException */ public function createUser(UserInterface $user) { // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 $userIdentifier = \strtolower(\method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()); if (isset($this->users[$userIdentifier])) { throw new \LogicException('Another user with the same username already exists.'); } $this->users[$userIdentifier] = $user; } /** * {@inheritdoc} */ public function loadUserByUsername(string $username) { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); return $this->loadUserByIdentifier($username); } public function loadUserByIdentifier(string $identifier) : UserInterface { $user = $this->getUser($identifier); // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 return new InMemoryUser(\method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled()); } /** * {@inheritdoc} */ public function refreshUser(UserInterface $user) { if (!$user instanceof InMemoryUser && !$user instanceof User) { throw new UnsupportedUserException(\sprintf('Instances of "%s" are not supported.', \get_debug_type($user))); } // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 $storedUser = $this->getUser(\method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()); $userIdentifier = \method_exists($storedUser, 'getUserIdentifier') ? $storedUser->getUserIdentifier() : $storedUser->getUsername(); // @deprecated since Symfony 5.3 if (User::class === \get_class($user)) { if (User::class !== \get_class($storedUser)) { $accountNonExpired = \true; $credentialsNonExpired = $storedUser->getPassword() === $user->getPassword(); $accountNonLocked = \true; } else { $accountNonExpired = $storedUser->isAccountNonExpired(); $credentialsNonExpired = $storedUser->isCredentialsNonExpired() && $storedUser->getPassword() === $user->getPassword(); $accountNonLocked = $storedUser->isAccountNonLocked(); } return new User($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled(), $accountNonExpired, $credentialsNonExpired, $accountNonLocked); } return new InMemoryUser($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); } /** * {@inheritdoc} */ public function supportsClass(string $class) { // @deprecated since Symfony 5.3 if (User::class === $class) { return \true; } return InMemoryUser::class == $class; } /** * Returns the user by given username. * * @throws UserNotFoundException if user whose given username does not exist */ private function getUser(string $username) { if (!isset($this->users[\strtolower($username)])) { $ex = new UserNotFoundException(\sprintf('Username "%s" does not exist.', $username)); $ex->setUserIdentifier($username); throw $ex; } return $this->users[\strtolower($username)]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * EquatableInterface used to test if two objects are equal in security * and re-authentication context. * * @author Dariusz Górecki */ interface EquatableInterface { /** * The equality comparison should neither be done by referential equality * nor by comparing identities (i.e. getId() === getId()). * * However, you do not need to compare every attribute, but only those that * are relevant for assessing whether re-authentication is required. * * @return bool */ public function isEqualTo(UserInterface $user); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; /** * For users that can be authenticated using a password. * * @author Robin Chalas * @author Wouter de Jong */ interface PasswordAuthenticatedUserInterface { /** * Returns the hashed password used to authenticate the user. * * Usually on authentication, a plain-text password will be compared to this value. */ public function getPassword() : ?string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountStatusException; /** * Implement to throw AccountStatusException during the authentication process. * * Can be used when you want to check the account status, e.g when the account is * disabled or blocked. This should not be used to make authentication decisions. * * @author Fabien Potencier */ interface UserCheckerInterface { /** * Checks the user account before authentication. * * @throws AccountStatusException */ public function checkPreAuth(UserInterface $user); /** * Checks the user account after authentication. * * @throws AccountStatusException */ public function checkPostAuth(UserInterface $user); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UserChecker::class, InMemoryUserChecker::class); \class_exists(InMemoryUserChecker::class); if (\false) { /** * UserChecker checks the user account flags. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use {@link InMemoryUserChecker} instead */ class UserChecker { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\User; use _ContaoManager\Symfony\Component\Security\Core\Exception\UnsupportedUserException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * Chain User Provider. * * This provider calls several leaf providers in a chain until one is able to * handle the request. * * @author Johannes M. Schmitt */ class ChainUserProvider implements UserProviderInterface, PasswordUpgraderInterface { private $providers; /** * @param iterable $providers */ public function __construct(iterable $providers) { $this->providers = $providers; } /** * @return UserProviderInterface[] */ public function getProviders() { if ($this->providers instanceof \Traversable) { return \iterator_to_array($this->providers); } return $this->providers; } /** * {@inheritdoc} */ public function loadUserByUsername(string $username) { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); return $this->loadUserByIdentifier($username); } public function loadUserByIdentifier(string $identifier) : UserInterface { foreach ($this->providers as $provider) { try { // @deprecated since Symfony 5.3, change to $provider->loadUserByIdentifier() in 6.0 if (!\method_exists($provider, 'loadUserByIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($provider)); return $provider->loadUserByUsername($identifier); } return $provider->loadUserByIdentifier($identifier); } catch (UserNotFoundException $e) { // try next one } } $ex = new UserNotFoundException(\sprintf('There is no user with identifier "%s".', $identifier)); $ex->setUserIdentifier($identifier); throw $ex; } /** * {@inheritdoc} */ public function refreshUser(UserInterface $user) { $supportedUserFound = \false; foreach ($this->providers as $provider) { try { if (!$provider->supportsClass(\get_debug_type($user))) { continue; } return $provider->refreshUser($user); } catch (UnsupportedUserException $e) { // try next one } catch (UserNotFoundException $e) { $supportedUserFound = \true; // try next one } } if ($supportedUserFound) { // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 $username = \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); $e = new UserNotFoundException(\sprintf('There is no user with name "%s".', $username)); $e->setUserIdentifier($username); throw $e; } else { throw new UnsupportedUserException(\sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', \get_debug_type($user))); } } /** * {@inheritdoc} */ public function supportsClass(string $class) { foreach ($this->providers as $provider) { if ($provider->supportsClass($class)) { return \true; } } return \false; } /** * @param PasswordAuthenticatedUserInterface $user * * {@inheritdoc} */ public function upgradePassword($user, string $newHashedPassword) : void { if (!$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'The "%s::upgradePassword()" method expects an instance of "%s" as first argument, the "%s" class should implement it.', PasswordUpgraderInterface::class, PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); if (!$user instanceof UserInterface) { throw new \TypeError(\sprintf('The "%s::upgradePassword()" method expects an instance of "%s" as first argument, "%s" given.', static::class, PasswordAuthenticatedUserInterface::class, \get_debug_type($user))); } } foreach ($this->providers as $provider) { if ($provider instanceof PasswordUpgraderInterface) { try { $provider->upgradePassword($user, $newHashedPassword); } catch (UnsupportedUserException $e) { // ignore: password upgrades are opportunistic } } } } } Security Component - Core ========================= Security provides an infrastructure for sophisticated authorization systems, which makes it possible to easily separate the actual authorization logic from so called user providers that hold the users credentials. Getting Started --------------- ``` $ composer require symfony/security-core ``` ```php use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Role\RoleHierarchy; $accessDecisionManager = new AccessDecisionManager([ new AuthenticatedVoter(new AuthenticationTrustResolver()), new RoleVoter(), new RoleHierarchyVoter(new RoleHierarchy([ 'ROLE_ADMIN' => ['ROLE_USER'], ])) ]); $user = new \App\Entity\User(...); $token = new UsernamePasswordToken($user, 'main', $user->getRoles()); if (!$accessDecisionManager->decide($token, ['ROLE_ADMIN'])) { throw new AccessDeniedException(); } ``` Sponsor ------- The Security component for Symfony 5.4/6.0 is [backed][1] by [SymfonyCasts][2]. Learn Symfony faster by watching real projects being built and actively coding along with them. SymfonyCasts bridges that learning gap, bringing you video tutorials and coding challenges. Code on! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/security.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://symfonycasts.com [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; /** * The AuthorizationCheckerInterface. * * @author Johannes M. Schmitt */ interface AuthorizationCheckerInterface { /** * Checks if the attribute is granted against the current authentication token and optionally supplied subject. * * @param mixed $attribute A single attribute to vote on (can be of any type, string and instance of Expression are supported by the core) * @param mixed $subject * * @return bool */ public function isGranted($attribute, $subject = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunction; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; /** * Define some ExpressionLanguage functions. * * @author Fabien Potencier */ class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface { public function getFunctions() { return [ new ExpressionFunction('is_anonymous', function () { return 'trigger_deprecation("symfony/security-core", "5.4", "The \\"is_anonymous()\\" expression function is deprecated.") || ($token && $auth_checker->isGranted("IS_ANONYMOUS"))'; }, function (array $variables) { \trigger_deprecation('symfony/security-core', '5.4', 'The "is_anonymous()" expression function is deprecated.'); return $variables['token'] && $variables['auth_checker']->isGranted('IS_ANONYMOUS'); }), // @deprecated remove the ternary and always use IS_AUTHENTICATED in 6.0 new ExpressionFunction('is_authenticated', function () { return 'defined("' . AuthenticatedVoter::class . '::IS_AUTHENTICATED") ? $auth_checker->isGranted("IS_AUTHENTICATED") : ($token && !$auth_checker->isGranted("IS_ANONYMOUS"))'; }, function (array $variables) { return \defined(AuthenticatedVoter::class . '::IS_AUTHENTICATED') ? $variables['auth_checker']->isGranted('IS_AUTHENTICATED') : $variables['token'] && !$variables['auth_checker']->isGranted('IS_ANONYMOUS'); }), new ExpressionFunction('is_fully_authenticated', function () { return '$token && $auth_checker->isGranted("IS_AUTHENTICATED_FULLY")'; }, function (array $variables) { return $variables['token'] && $variables['auth_checker']->isGranted('IS_AUTHENTICATED_FULLY'); }), new ExpressionFunction('is_granted', function ($attributes, $object = 'null') { return \sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object); }, function (array $variables, $attributes, $object = null) { return $variables['auth_checker']->isGranted($attributes, $object); }), new ExpressionFunction('is_remember_me', function () { return '$token && $auth_checker->isGranted("IS_REMEMBERED")'; }, function (array $variables) { return $variables['token'] && $variables['auth_checker']->isGranted('IS_REMEMBERED'); }), ]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Decorates the original AccessDecisionManager class to log information * about the security voters and the decisions made by them. * * @author Javier Eguiluz * * @internal */ class TraceableAccessDecisionManager implements AccessDecisionManagerInterface { private $manager; private $strategy; /** @var iterable */ private $voters = []; private $decisionLog = []; // All decision logs private $currentLog = []; // Logs being filled in public function __construct(AccessDecisionManagerInterface $manager) { $this->manager = $manager; if ($this->manager instanceof AccessDecisionManager) { // The strategy and voters are stored in a private properties of the decorated service $reflection = new \ReflectionProperty(AccessDecisionManager::class, 'strategy'); $reflection->setAccessible(\true); $this->strategy = $reflection->getValue($manager); $reflection = new \ReflectionProperty(AccessDecisionManager::class, 'voters'); $reflection->setAccessible(\true); $this->voters = $reflection->getValue($manager); } } /** * {@inheritdoc} * * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array */ public function decide(TokenInterface $token, array $attributes, $object = null) : bool { $currentDecisionLog = ['attributes' => $attributes, 'object' => $object, 'voterDetails' => []]; $this->currentLog[] =& $currentDecisionLog; $result = $this->manager->decide($token, $attributes, $object, 3 < \func_num_args() && \func_get_arg(3)); $currentDecisionLog['result'] = $result; $this->decisionLog[] = \array_pop($this->currentLog); // Using a stack since decide can be called by voters return $result; } /** * Adds voter vote and class to the voter details. * * @param array $attributes attributes used for the vote * @param int $vote vote of the voter */ public function addVoterVote(VoterInterface $voter, array $attributes, int $vote) { $currentLogIndex = \count($this->currentLog) - 1; $this->currentLog[$currentLogIndex]['voterDetails'][] = ['voter' => $voter, 'attributes' => $attributes, 'vote' => $vote]; } public function getStrategy() : string { if (null === $this->strategy) { return '-'; } if (\method_exists($this->strategy, '__toString')) { return (string) $this->strategy; } return \get_debug_type($this->strategy); } /** * @return iterable */ public function getVoters() : iterable { return $this->voters; } public function getDecisionLog() : array { return $this->decisionLog; } } if (!\class_exists(DebugAccessDecisionManager::class, \false)) { \class_alias(TraceableAccessDecisionManager::class, DebugAccessDecisionManager::class); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\CacheableVoterInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\InvalidArgumentException; /** * AccessDecisionManager is the base class for all access decision managers * that use decision voters. * * @author Fabien Potencier * * @final since Symfony 5.4 */ class AccessDecisionManager implements AccessDecisionManagerInterface { /** * @deprecated use {@see AffirmativeStrategy} instead */ public const STRATEGY_AFFIRMATIVE = 'affirmative'; /** * @deprecated use {@see ConsensusStrategy} instead */ public const STRATEGY_CONSENSUS = 'consensus'; /** * @deprecated use {@see UnanimousStrategy} instead */ public const STRATEGY_UNANIMOUS = 'unanimous'; /** * @deprecated use {@see PriorityStrategy} instead */ public const STRATEGY_PRIORITY = 'priority'; private const VALID_VOTES = [VoterInterface::ACCESS_GRANTED => \true, VoterInterface::ACCESS_DENIED => \true, VoterInterface::ACCESS_ABSTAIN => \true]; private $voters; private $votersCacheAttributes; private $votersCacheObject; private $strategy; /** * @param iterable $voters An array or an iterator of VoterInterface instances * @param AccessDecisionStrategyInterface|null $strategy The vote strategy * * @throws \InvalidArgumentException */ public function __construct( iterable $voters = [], /* AccessDecisionStrategyInterface */ $strategy = null ) { $this->voters = $voters; if (\is_string($strategy)) { \trigger_deprecation('symfony/security-core', '5.4', 'Passing the access decision strategy as a string is deprecated, pass an instance of "%s" instead.', AccessDecisionStrategyInterface::class); $allowIfAllAbstainDecisions = 3 <= \func_num_args() && \func_get_arg(2); $allowIfEqualGrantedDeniedDecisions = 4 > \func_num_args() || \func_get_arg(3); $strategy = $this->createStrategy($strategy, $allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions); } elseif (null !== $strategy && !$strategy instanceof AccessDecisionStrategyInterface) { throw new \TypeError(\sprintf('"%s": Parameter #2 ($strategy) is expected to be an instance of "%s" or null, "%s" given.', __METHOD__, AccessDecisionStrategyInterface::class, \get_debug_type($strategy))); } $this->strategy = $strategy ?? new AffirmativeStrategy(); } /** * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array * * {@inheritdoc} */ public function decide(TokenInterface $token, array $attributes, $object = null) { $allowMultipleAttributes = 3 < \func_num_args() && \func_get_arg(3); // Special case for AccessListener, do not remove the right side of the condition before 6.0 if (\count($attributes) > 1 && !$allowMultipleAttributes) { throw new InvalidArgumentException(\sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__)); } return $this->strategy->decide($this->collectResults($token, $attributes, $object)); } /** * @param mixed $object * * @return \Traversable */ private function collectResults(TokenInterface $token, array $attributes, $object) : \Traversable { foreach ($this->getVoters($attributes, $object) as $voter) { $result = $voter->vote($token, $object, $attributes); if (!\is_int($result) || !(self::VALID_VOTES[$result] ?? \false)) { \trigger_deprecation('symfony/security-core', '5.3', 'Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".', \var_export($result, \true), \get_debug_type($voter), VoterInterface::class); } (yield $result); } } /** * @throws \InvalidArgumentException if the $strategy is invalid */ private function createStrategy(string $strategy, bool $allowIfAllAbstainDecisions, bool $allowIfEqualGrantedDeniedDecisions) : AccessDecisionStrategyInterface { switch ($strategy) { case self::STRATEGY_AFFIRMATIVE: return new AffirmativeStrategy($allowIfAllAbstainDecisions); case self::STRATEGY_CONSENSUS: return new ConsensusStrategy($allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions); case self::STRATEGY_UNANIMOUS: return new UnanimousStrategy($allowIfAllAbstainDecisions); case self::STRATEGY_PRIORITY: return new PriorityStrategy($allowIfAllAbstainDecisions); } throw new \InvalidArgumentException(\sprintf('The strategy "%s" is not supported.', $strategy)); } /** * @return iterable */ private function getVoters(array $attributes, $object = null) : iterable { $keyAttributes = []; foreach ($attributes as $attribute) { $keyAttributes[] = \is_string($attribute) ? $attribute : null; } // use `get_class` to handle anonymous classes $keyObject = \is_object($object) ? \get_class($object) : \get_debug_type($object); foreach ($this->voters as $key => $voter) { if (!$voter instanceof CacheableVoterInterface) { (yield $voter); continue; } $supports = \true; // The voter supports the attributes if it supports at least one attribute of the list foreach ($keyAttributes as $keyAttribute) { if (null === $keyAttribute) { $supports = \true; } elseif (!isset($this->votersCacheAttributes[$keyAttribute][$key])) { $this->votersCacheAttributes[$keyAttribute][$key] = $supports = $voter->supportsAttribute($keyAttribute); } else { $supports = $this->votersCacheAttributes[$keyAttribute][$key]; } if ($supports) { break; } } if (!$supports) { continue; } if (!isset($this->votersCacheObject[$keyObject][$key])) { $this->votersCacheObject[$keyObject][$key] = $supports = $voter->supportsType($keyObject); } else { $supports = $this->votersCacheObject[$keyObject][$key]; } if (!$supports) { continue; } (yield $voter); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * AccessDecisionManagerInterface makes authorization decisions. * * @author Fabien Potencier */ interface AccessDecisionManagerInterface { /** * Decides whether the access is possible or not. * * @param array $attributes An array of attributes associated with the method being invoked * @param mixed $object The object to secure * * @return bool */ public function decide(TokenInterface $token, array $attributes, $object = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * VoterInterface is the interface implemented by all voters. * * @author Fabien Potencier */ interface VoterInterface { public const ACCESS_GRANTED = 1; public const ACCESS_ABSTAIN = 0; public const ACCESS_DENIED = -1; /** * Returns the vote for the given parameters. * * This method must return one of the following constants: * ACCESS_GRANTED, ACCESS_DENIED, or ACCESS_ABSTAIN. * * @param mixed $subject The subject to secure * @param array $attributes An array of attributes associated with the method being invoked * * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED */ public function vote(TokenInterface $token, $subject, array $attributes); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; /** * Let voters expose the attributes and types they care about. * * By returning false to either `supportsAttribute` or `supportsType`, the * voter will never be called for the specified attribute or subject. * * @author Jérémy Derussé */ interface CacheableVoterInterface extends VoterInterface { public function supportsAttribute(string $attribute) : bool; /** * @param string $subjectType The type of the subject inferred by `get_class` or `get_debug_type` */ public function supportsType(string $subjectType) : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\ExpressionLanguage; use _ContaoManager\Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** * ExpressionVoter votes based on the evaluation of an expression. * * @author Fabien Potencier */ class ExpressionVoter implements CacheableVoterInterface { private $expressionLanguage; private $trustResolver; private $authChecker; private $roleHierarchy; public function __construct(ExpressionLanguage $expressionLanguage, AuthenticationTrustResolverInterface $trustResolver, AuthorizationCheckerInterface $authChecker, ?RoleHierarchyInterface $roleHierarchy = null) { $this->expressionLanguage = $expressionLanguage; $this->trustResolver = $trustResolver; $this->authChecker = $authChecker; $this->roleHierarchy = $roleHierarchy; } public function supportsAttribute(string $attribute) : bool { return \false; } public function supportsType(string $subjectType) : bool { return \true; } /** * {@inheritdoc} */ public function vote(TokenInterface $token, $subject, array $attributes) { $result = VoterInterface::ACCESS_ABSTAIN; $variables = null; foreach ($attributes as $attribute) { if (!$attribute instanceof Expression) { continue; } if (null === $variables) { $variables = $this->getVariables($token, $subject); } $result = VoterInterface::ACCESS_DENIED; if ($this->expressionLanguage->evaluate($attribute, $variables)) { return VoterInterface::ACCESS_GRANTED; } } return $result; } private function getVariables(TokenInterface $token, $subject) : array { $roleNames = $token->getRoleNames(); if (null !== $this->roleHierarchy) { $roleNames = $this->roleHierarchy->getReachableRoleNames($roleNames); } $variables = ['token' => $token, 'user' => $token->getUser(), 'object' => $subject, 'subject' => $subject, 'role_names' => $roleNames, 'trust_resolver' => $this->trustResolver, 'auth_checker' => $this->authChecker]; // this is mainly to propose a better experience when the expression is used // in an access control rule, as the developer does not know that it's going // to be handled by this voter if ($subject instanceof Request) { $variables['request'] = $subject; } return $variables; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** * RoleHierarchyVoter uses a RoleHierarchy to determine the roles granted to * the user before voting. * * @author Fabien Potencier */ class RoleHierarchyVoter extends RoleVoter { private $roleHierarchy; public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') { $this->roleHierarchy = $roleHierarchy; parent::__construct($prefix); } /** * {@inheritdoc} */ protected function extractRoles(TokenInterface $token) { return $this->roleHierarchy->getReachableRoleNames($token->getRoleNames()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Event\VoteEvent; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Decorates voter classes to send result events. * * @author Laurent VOULLEMIER * * @internal */ class TraceableVoter implements CacheableVoterInterface { private $voter; private $eventDispatcher; public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher) { $this->voter = $voter; $this->eventDispatcher = $eventDispatcher; } public function vote(TokenInterface $token, $subject, array $attributes) : int { $result = $this->voter->vote($token, $subject, $attributes); $this->eventDispatcher->dispatch(new VoteEvent($this->voter, $subject, $attributes, $result), 'debug.security.authorization.vote'); return $result; } public function getDecoratedVoter() : VoterInterface { return $this->voter; } public function supportsAttribute(string $attribute) : bool { return !$this->voter instanceof CacheableVoterInterface || $this->voter->supportsAttribute($attribute); } public function supportsType(string $subjectType) : bool { return !$this->voter instanceof CacheableVoterInterface || $this->voter->supportsType($subjectType); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * Voter is an abstract default implementation of a voter. * * @author Roman Marintšenko * @author Grégoire Pineau */ abstract class Voter implements VoterInterface, CacheableVoterInterface { /** * {@inheritdoc} */ public function vote(TokenInterface $token, $subject, array $attributes) { // abstain vote by default in case none of the attributes are supported $vote = self::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { try { if (!$this->supports($attribute, $subject)) { continue; } } catch (\TypeError $e) { if (\PHP_VERSION_ID < 80000) { if (0 === \strpos($e->getMessage(), 'Argument 1 passed to') && \false !== \strpos($e->getMessage(), '::supports() must be of the type string')) { continue; } } elseif (\false !== \strpos($e->getMessage(), 'supports(): Argument #1')) { continue; } throw $e; } // as soon as at least one attribute is supported, default is to deny access $vote = self::ACCESS_DENIED; if ($this->voteOnAttribute($attribute, $subject, $token)) { // grant access as soon as at least one attribute returns a positive response return self::ACCESS_GRANTED; } } return $vote; } /** * Return false if your voter doesn't support the given attribute. Symfony will cache * that decision and won't call your voter again for that attribute. */ public function supportsAttribute(string $attribute) : bool { return \true; } /** * Return false if your voter doesn't support the given subject type. Symfony will cache * that decision and won't call your voter again for that subject type. * * @param string $subjectType The type of the subject inferred by `get_class()` or `get_debug_type()` */ public function supportsType(string $subjectType) : bool { return \true; } /** * Determines if the attribute and subject are supported by this voter. * * @param string $attribute An attribute * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type * * @return bool */ protected abstract function supports(string $attribute, $subject); /** * Perform a single access check operation on a given attribute, subject and token. * It is safe to assume that $attribute and $subject already passed the "supports()" method check. * * @param mixed $subject * * @return bool */ protected abstract function voteOnAttribute(string $attribute, $subject, TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * RoleVoter votes if any attribute starts with a given prefix. * * @author Fabien Potencier */ class RoleVoter implements CacheableVoterInterface { private $prefix; public function __construct(string $prefix = 'ROLE_') { $this->prefix = $prefix; } /** * {@inheritdoc} */ public function vote(TokenInterface $token, $subject, array $attributes) { $result = VoterInterface::ACCESS_ABSTAIN; $roles = $this->extractRoles($token); foreach ($attributes as $attribute) { if (!\is_string($attribute) || !\str_starts_with($attribute, $this->prefix)) { continue; } if ('ROLE_PREVIOUS_ADMIN' === $attribute) { \trigger_deprecation('symfony/security-core', '5.1', 'The ROLE_PREVIOUS_ADMIN role is deprecated and will be removed in version 6.0, use the IS_IMPERSONATOR attribute instead.'); } $result = VoterInterface::ACCESS_DENIED; foreach ($roles as $role) { if ($attribute === $role) { return VoterInterface::ACCESS_GRANTED; } } } return $result; } public function supportsAttribute(string $attribute) : bool { return \str_starts_with($attribute, $this->prefix); } public function supportsType(string $subjectType) : bool { return \true; } protected function extractRoles(TokenInterface $token) { return $token->getRoleNames(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * AuthenticatedVoter votes if an attribute like IS_AUTHENTICATED_FULLY, * IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED is present. * * This list is most restrictive to least restrictive checking. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class AuthenticatedVoter implements CacheableVoterInterface { public const IS_AUTHENTICATED_FULLY = 'IS_AUTHENTICATED_FULLY'; public const IS_AUTHENTICATED_REMEMBERED = 'IS_AUTHENTICATED_REMEMBERED'; /** * @deprecated since Symfony 5.4 */ public const IS_AUTHENTICATED_ANONYMOUSLY = 'IS_AUTHENTICATED_ANONYMOUSLY'; /** * @deprecated since Symfony 5.4 */ public const IS_ANONYMOUS = 'IS_ANONYMOUS'; public const IS_AUTHENTICATED = 'IS_AUTHENTICATED'; public const IS_IMPERSONATOR = 'IS_IMPERSONATOR'; public const IS_REMEMBERED = 'IS_REMEMBERED'; public const PUBLIC_ACCESS = 'PUBLIC_ACCESS'; private $authenticationTrustResolver; public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver) { $this->authenticationTrustResolver = $authenticationTrustResolver; } /** * {@inheritdoc} */ public function vote(TokenInterface $token, $subject, array $attributes) { if ($attributes === [self::PUBLIC_ACCESS]) { return VoterInterface::ACCESS_GRANTED; } $result = VoterInterface::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { if (null === $attribute || self::IS_AUTHENTICATED_FULLY !== $attribute && self::IS_AUTHENTICATED_REMEMBERED !== $attribute && self::IS_AUTHENTICATED_ANONYMOUSLY !== $attribute && self::IS_AUTHENTICATED !== $attribute && self::IS_ANONYMOUS !== $attribute && self::IS_IMPERSONATOR !== $attribute && self::IS_REMEMBERED !== $attribute) { continue; } $result = VoterInterface::ACCESS_DENIED; if (self::IS_AUTHENTICATED_FULLY === $attribute && $this->authenticationTrustResolver->isFullFledged($token)) { return VoterInterface::ACCESS_GRANTED; } if (self::IS_AUTHENTICATED_REMEMBERED === $attribute && ($this->authenticationTrustResolver->isRememberMe($token) || $this->authenticationTrustResolver->isFullFledged($token))) { return VoterInterface::ACCESS_GRANTED; } if (self::IS_AUTHENTICATED_ANONYMOUSLY === $attribute && ($this->authenticationTrustResolver->isAnonymous($token) || $this->authenticationTrustResolver->isRememberMe($token) || $this->authenticationTrustResolver->isFullFledged($token))) { \trigger_deprecation('symfony/security-core', '5.4', 'The "IS_AUTHENTICATED_ANONYMOUSLY" security attribute is deprecated, use "PUBLIC_ACCESS" for public resources, otherwise use "IS_AUTHENTICATED" or "IS_AUTHENTICATED_FULLY" instead if you want to check if the request is (fully) authenticated.'); return VoterInterface::ACCESS_GRANTED; } // @deprecated $this->authenticationTrustResolver must implement isAuthenticated() in 6.0 if (self::IS_AUTHENTICATED === $attribute && (\method_exists($this->authenticationTrustResolver, 'isAuthenticated') ? $this->authenticationTrustResolver->isAuthenticated($token) : $token && $token->getUser())) { return VoterInterface::ACCESS_GRANTED; } if (self::IS_REMEMBERED === $attribute && $this->authenticationTrustResolver->isRememberMe($token)) { return VoterInterface::ACCESS_GRANTED; } if (self::IS_ANONYMOUS === $attribute && $this->authenticationTrustResolver->isAnonymous($token)) { \trigger_deprecation('symfony/security-core', '5.4', 'The "IS_ANONYMOUSLY" security attribute is deprecated, anonymous no longer exists in version 6.'); return VoterInterface::ACCESS_GRANTED; } if (self::IS_IMPERSONATOR === $attribute && $token instanceof SwitchUserToken) { return VoterInterface::ACCESS_GRANTED; } } return $result; } public function supportsAttribute(string $attribute) : bool { return \in_array($attribute, [self::IS_AUTHENTICATED_FULLY, self::IS_AUTHENTICATED_REMEMBERED, self::IS_AUTHENTICATED_ANONYMOUSLY, self::IS_AUTHENTICATED, self::IS_ANONYMOUS, self::IS_IMPERSONATOR, self::IS_REMEMBERED, self::PUBLIC_ACCESS], \true); } public function supportsType(string $subjectType) : bool { return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; if (!\class_exists(BaseExpressionLanguage::class)) { throw new \LogicException(\sprintf('The "%s" class requires the "ExpressionLanguage" component. Try running "composer require symfony/expression-language".', ExpressionLanguage::class)); } else { // Help opcache.preload discover always-needed symbols \class_exists(ExpressionLanguageProvider::class); /** * Adds some function to the default ExpressionLanguage. * * @author Fabien Potencier * * @see ExpressionLanguageProvider */ class ExpressionLanguage extends BaseExpressionLanguage { /** * {@inheritdoc} */ public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) { // prepend the default provider to let users override it easily \array_unshift($providers, new ExpressionLanguageProvider()); parent::__construct($cache, $providers); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\NullToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; /** * AuthorizationChecker is the main authorization point of the Security component. * * It gives access to the token representing the current user authentication. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class AuthorizationChecker implements AuthorizationCheckerInterface { private $tokenStorage; private $accessDecisionManager; private $authenticationManager; private $alwaysAuthenticate; private $exceptionOnNoToken; public function __construct( TokenStorageInterface $tokenStorage, /* AccessDecisionManagerInterface */ $accessDecisionManager, /* bool */ $alwaysAuthenticate = \false, /* bool */ $exceptionOnNoToken = \true ) { if ($accessDecisionManager instanceof AuthenticationManagerInterface) { \trigger_deprecation('symfony/security-core', '5.4', 'The $autenticationManager argument of "%s" is deprecated.', __METHOD__); $this->authenticationManager = $accessDecisionManager; $accessDecisionManager = $alwaysAuthenticate; $alwaysAuthenticate = $exceptionOnNoToken; $exceptionOnNoToken = \func_num_args() > 4 ? \func_get_arg(4) : \true; } if (\false !== $alwaysAuthenticate) { \trigger_deprecation('symfony/security-core', '5.4', 'Not setting the 4th argument of "%s" to "false" is deprecated.', __METHOD__); } if (\false !== $exceptionOnNoToken) { \trigger_deprecation('symfony/security-core', '5.4', 'Not setting the 5th argument of "%s" to "false" is deprecated.', __METHOD__); } if (!$accessDecisionManager instanceof AccessDecisionManagerInterface) { throw new \TypeError(\sprintf('Argument 2 of "%s" must be instance of "%s", "%s" given.', __METHOD__, AccessDecisionManagerInterface::class, \get_debug_type($accessDecisionManager))); } $this->tokenStorage = $tokenStorage; $this->accessDecisionManager = $accessDecisionManager; $this->alwaysAuthenticate = $alwaysAuthenticate; $this->exceptionOnNoToken = $exceptionOnNoToken; } /** * {@inheritdoc} * * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token and $exceptionOnNoToken is set to true */ public final function isGranted($attribute, $subject = null) : bool { $token = $this->tokenStorage->getToken(); if (!$token || !$token->getUser()) { if ($this->exceptionOnNoToken) { throw new AuthenticationCredentialsNotFoundException('The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.'); } $token = new NullToken(); } else { $authenticated = \true; // @deprecated since Symfony 5.4 if ($this->alwaysAuthenticate || !($authenticated = $token->isAuthenticated(\false))) { if (!($authenticated ?? \true)) { \trigger_deprecation('symfony/core', '5.4', 'Returning false from "%s::isAuthenticated()" is deprecated, return null from "getUser()" instead.', \get_debug_type($token)); } $this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token)); } } return $this->accessDecisionManager->decide($token, [$attribute], $subject); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Grant or deny access depending on the first voter that does not abstain. * The priority of voters can be used to overrule a decision. * * If all voters abstained from voting, the decision will be based on the * allowIfAllAbstainDecisions property value (defaults to false). * * @author Fabien Potencier * @author Alexander M. Turek */ final class PriorityStrategy implements AccessDecisionStrategyInterface, \Stringable { private $allowIfAllAbstainDecisions; public function __construct(bool $allowIfAllAbstainDecisions = \false) { $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; } /** * {@inheritdoc} */ public function decide(\Traversable $results) : bool { foreach ($results as $result) { if (VoterInterface::ACCESS_GRANTED === $result) { return \true; } if (VoterInterface::ACCESS_DENIED === $result) { return \false; } } return $this->allowIfAllAbstainDecisions; } public function __toString() : string { return 'priority'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Grants access if only grant (or abstain) votes were received. * * If all voters abstained from voting, the decision will be based on the * allowIfAllAbstainDecisions property value (defaults to false). * * @author Fabien Potencier * @author Alexander M. Turek */ final class UnanimousStrategy implements AccessDecisionStrategyInterface, \Stringable { private $allowIfAllAbstainDecisions; public function __construct(bool $allowIfAllAbstainDecisions = \false) { $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; } /** * {@inheritdoc} */ public function decide(\Traversable $results) : bool { $grant = 0; foreach ($results as $result) { if (VoterInterface::ACCESS_DENIED === $result) { return \false; } if (VoterInterface::ACCESS_GRANTED === $result) { ++$grant; } } // no deny votes if ($grant > 0) { return \true; } return $this->allowIfAllAbstainDecisions; } public function __toString() : string { return 'unanimous'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Grants access if any voter returns an affirmative response. * * If all voters abstained from voting, the decision will be based on the * allowIfAllAbstainDecisions property value (defaults to false). * * @author Fabien Potencier * @author Alexander M. Turek */ final class AffirmativeStrategy implements AccessDecisionStrategyInterface, \Stringable { /** * @var bool */ private $allowIfAllAbstainDecisions; public function __construct(bool $allowIfAllAbstainDecisions = \false) { $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; } /** * {@inheritdoc} */ public function decide(\Traversable $results) : bool { $deny = 0; foreach ($results as $result) { if (VoterInterface::ACCESS_GRANTED === $result) { return \true; } if (VoterInterface::ACCESS_DENIED === $result) { ++$deny; } } if ($deny > 0) { return \false; } return $this->allowIfAllAbstainDecisions; } public function __toString() : string { return 'affirmative'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; /** * Grants access if there is consensus of granted against denied responses. * * Consensus means majority-rule (ignoring abstains) rather than unanimous * agreement (ignoring abstains). If you require unanimity, see * UnanimousBased. * * If there were an equal number of grant and deny votes, the decision will * be based on the allowIfEqualGrantedDeniedDecisions property value * (defaults to true). * * If all voters abstained from voting, the decision will be based on the * allowIfAllAbstainDecisions property value (defaults to false). * * @author Fabien Potencier * @author Alexander M. Turek */ final class ConsensusStrategy implements AccessDecisionStrategyInterface, \Stringable { private $allowIfAllAbstainDecisions; private $allowIfEqualGrantedDeniedDecisions; public function __construct(bool $allowIfAllAbstainDecisions = \false, bool $allowIfEqualGrantedDeniedDecisions = \true) { $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions; $this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions; } public function decide(\Traversable $results) : bool { $grant = 0; $deny = 0; foreach ($results as $result) { if (VoterInterface::ACCESS_GRANTED === $result) { ++$grant; } elseif (VoterInterface::ACCESS_DENIED === $result) { ++$deny; } } if ($grant > $deny) { return \true; } if ($deny > $grant) { return \false; } if ($grant > 0) { return $this->allowIfEqualGrantedDeniedDecisions; } return $this->allowIfAllAbstainDecisions; } public function __toString() : string { return 'consensus'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authorization\Strategy; /** * A strategy for turning a stream of votes into a final decision. * * @author Alexander M. Turek */ interface AccessDecisionStrategyInterface { /** * @param \Traversable $results */ public function decide(\Traversable $results) : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Helper class for commonly-needed security tasks. * * @final */ class Security implements AuthorizationCheckerInterface { public const ACCESS_DENIED_ERROR = '_security.403_error'; public const AUTHENTICATION_ERROR = '_security.last_error'; public const LAST_USERNAME = '_security.last_username'; public const MAX_USERNAME_LENGTH = 4096; private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function getUser() : ?UserInterface { if (!($token = $this->getToken())) { return null; } $user = $token->getUser(); // @deprecated since Symfony 5.4, $user will always be a UserInterface instance if (!$user instanceof UserInterface) { return null; } return $user; } /** * Checks if the attributes are granted against the current authentication token and optionally supplied subject. * * @param mixed $attributes * @param mixed $subject */ public function isGranted($attributes, $subject = null) : bool { return $this->container->get('security.authorization_checker')->isGranted($attributes, $subject); } public function getToken() : ?TokenInterface { return $this->container->get('security.token_storage')->getToken(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * AccessDeniedException is thrown when the account has not the required role. * * @author Fabien Potencier */ class AccessDeniedException extends RuntimeException { private $attributes = []; private $subject; public function __construct(string $message = 'Access Denied.', ?\Throwable $previous = null) { parent::__construct($message, 403, $previous); } /** * @return array */ public function getAttributes() { return $this->attributes; } /** * @param array|string $attributes */ public function setAttributes($attributes) { $this->attributes = (array) $attributes; } /** * @return mixed */ public function getSubject() { return $this->subject; } /** * @param mixed $subject */ public function setSubject($subject) { $this->subject = $subject; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * This exception is thrown when the csrf token is invalid. * * @author Johannes M. Schmitt * @author Alexander */ class InvalidCsrfTokenException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Invalid CSRF token.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * LogoutException is thrown when the account cannot be logged out. * * @author Jeremy Mikola */ class LogoutException extends RuntimeException { public function __construct(string $message = 'Logout Exception', ?\Throwable $previous = null) { parent::__construct($message, 403, $previous); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * Base LogicException for the Security component. * * @author Iltar van der Berg */ class LogicException extends \LogicException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * A signaling exception that wraps a lazily computed response. * * @author Nicolas Grekas */ class LazyResponseException extends \Exception implements ExceptionInterface { private $response; public function __construct(Response $response) { $this->response = $response; } public function getResponse() : Response { return $this->response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * An authentication exception where you can control the message shown to the user. * * Be sure that the message passed to this exception is something that * can be shown safely to your user. In other words, avoid catching * other exceptions and passing their message directly to this class. * * @author Ryan Weaver */ class CustomUserMessageAuthenticationException extends AuthenticationException { private $messageKey; private $messageData = []; public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->setSafeMessage($message, $messageData); } /** * Set a message that will be shown to the user. * * @param string $messageKey The message or message key * @param array $messageData Data to be passed into the translator */ public function setSafeMessage(string $messageKey, array $messageData = []) { $this->messageKey = $messageKey; $this->messageData = $messageData; } public function getMessageKey() { return $this->messageKey; } public function getMessageData() { return $this->messageData; } /** * {@inheritdoc} */ public function __serialize() : array { return [parent::__serialize(), $this->messageKey, $this->messageData]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$parentData, $this->messageKey, $this->messageData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * Base ExceptionInterface for the Security component. * * @author Bernhard Schussek */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * BadCredentialsException is thrown when the user credentials are invalid. * * @author Fabien Potencier * @author Alexander */ class BadCredentialsException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Invalid credentials.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * Base RuntimeException for the Security component. * * @author Bernhard Schussek */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * DisabledException is thrown when the user account is disabled. * * @author Fabien Potencier * @author Alexander */ class DisabledException extends AccountStatusException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Account is disabled.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * TokenNotFoundException is thrown if a Token cannot be found. * * @author Johannes M. Schmitt * @author Alexander */ class TokenNotFoundException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'No token could be found.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * ProviderNotFoundException is thrown when no AuthenticationProviderInterface instance * supports an authentication Token. * * @author Fabien Potencier * @author Alexander */ class ProviderNotFoundException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'No authentication provider found to support the authentication token.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * This exception is thrown when no session is available. * * Possible reasons for this are: * * a) The session timed out because the user waited too long. * b) The user has disabled cookies, and a new session is started on each * request. * * @author Johannes M. Schmitt * @author Alexander */ class SessionUnavailableException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'No session available, it either timed out or cookies are not enabled.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * Base InvalidArgumentException for the Security component. * * @author Bernhard Schussek */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * AccountExpiredException is thrown when the user account has expired. * * @author Fabien Potencier * @author Alexander */ class AccountExpiredException extends AccountStatusException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Account has expired.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * AuthenticationExpiredException is thrown when an authentication token becomes un-authenticated between requests. * * In practice, this is due to the User changing between requests (e.g. password changes), * causes the token to become un-authenticated. * * @author Ryan Weaver */ class AuthenticationExpiredException extends AccountStatusException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Authentication expired because your account information has changed.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UsernameNotFoundException::class, UserNotFoundException::class); \class_exists(UserNotFoundException::class); if (\false) { /** * @deprecated since Symfony 5.3 to be removed in 6.0, use UserNotFoundException instead. */ class UsernameNotFoundException extends AuthenticationException { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * This exception is thrown if there where too many failed login attempts in * this session. * * @author Wouter de Jong */ class TooManyLoginAttemptsAuthenticationException extends AuthenticationException { private $threshold; public function __construct(?int $threshold = null) { $this->threshold = $threshold; } /** * {@inheritdoc} */ public function getMessageData() : array { return ['%minutes%' => $this->threshold, '%count%' => (int) $this->threshold]; } /** * {@inheritdoc} */ public function getMessageKey() : string { return 'Too many failed login attempts, please try again ' . ($this->threshold ? 'in %minutes% minute' . ($this->threshold > 1 ? 's' : '') . '.' : 'later.'); } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->threshold, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->threshold, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * This exception is thrown when the RememberMeServices implementation * detects that a presented cookie has already been used by someone else. * * @author Johannes M. Schmitt * @author Alexander */ class CookieTheftException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Cookie has already been used by someone else.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * AuthenticationServiceException is thrown when an authentication request could not be processed due to a system problem. * * @author Fabien Potencier * @author Alexander */ class AuthenticationServiceException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Authentication request could not be processed due to a system problem.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * This exception is thrown when an account is reloaded from a provider which * doesn't support the passed implementation of UserInterface. * * @author Johannes M. Schmitt */ class UnsupportedUserException extends AuthenticationServiceException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * AccountStatusException is the base class for authentication exceptions * caused by the user account status. * * @author Fabien Potencier * @author Alexander */ abstract class AccountStatusException extends AuthenticationException { private $user; /** * Get the user. * * @return UserInterface|null */ public function getUser() { return $this->user; } public function setUser(UserInterface $user) { $this->user = $user; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->user, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->user, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * An authentication exception caused by the user account status * where you can control the message shown to the user. * * Be sure that the message passed to this exception is something that * can be shown safely to your user. In other words, avoid catching * other exceptions and passing their message directly to this class. * * @author Vincent Langlet */ class CustomUserMessageAccountStatusException extends AccountStatusException { private $messageKey; private $messageData = []; public function __construct(string $message = '', array $messageData = [], int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->setSafeMessage($message, $messageData); } /** * Sets a message that will be shown to the user. * * @param string $messageKey The message or message key * @param array $messageData Data to be passed into the translator */ public function setSafeMessage(string $messageKey, array $messageData = []) { $this->messageKey = $messageKey; $this->messageData = $messageData; } public function getMessageKey() { return $this->messageKey; } public function getMessageData() { return $this->messageData; } /** * {@inheritdoc} */ public function __serialize() : array { return [parent::__serialize(), $this->messageKey, $this->messageData]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$parentData, $this->messageKey, $this->messageData] = $data; parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * InsufficientAuthenticationException is thrown if the user credentials are not sufficiently trusted. * * This is the case when a user is anonymous and the resource to be displayed has an access role. * * @author Fabien Potencier * @author Alexander */ class InsufficientAuthenticationException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Not privileged to request the resource.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * LockedException is thrown if the user account is locked. * * @author Fabien Potencier * @author Alexander */ class LockedException extends AccountStatusException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Account is locked.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * AuthenticationException is the base class for all authentication exceptions. * * @author Fabien Potencier * @author Alexander */ class AuthenticationException extends RuntimeException { /** @internal */ protected $serialized; private $token; public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null) { unset($this->serialized); parent::__construct($message, $code, $previous); } /** * @return TokenInterface|null */ public function getToken() { return $this->token; } public function setToken(TokenInterface $token) { $this->token = $token; } /** * Returns all the necessary state of the object for serialization purposes. * * There is no need to serialize any entry, they should be returned as-is. * If you extend this method, keep in mind you MUST guarantee parent data is present in the state. * Here is an example of how to extend this method: * * public function __serialize(): array * { * return [$this->childAttribute, parent::__serialize()]; * } * * * @see __unserialize() */ public function __serialize() : array { return [$this->token, $this->code, $this->message, $this->file, $this->line]; } /** * Restores the object state from an array given by __serialize(). * * There is no need to unserialize any entry in $data, they are already ready-to-use. * If you extend this method, keep in mind you MUST pass the parent data to its respective class. * Here is an example of how to extend this method: * * public function __unserialize(array $data): void * { * [$this->childAttribute, $parentData] = $data; * parent::__unserialize($parentData); * } * * * @see __serialize() */ public function __unserialize(array $data) : void { [$this->token, $this->code, $this->message, $this->file, $this->line] = $data; } /** * Message key to be used by the translation component. * * @return string */ public function getMessageKey() { return 'An authentication exception occurred.'; } /** * Message data to be used by the translation component. * * @return array */ public function getMessageData() { return []; } /** * @internal */ public function __sleep() : array { $this->serialized = $this->__serialize(); return ['serialized']; } /** * @internal */ public function __wakeup() : void { $this->__unserialize($this->serialized); unset($this->serialized); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * UserNotFoundException is thrown if a User cannot be found for the given identifier. * * @author Fabien Potencier * @author Alexander */ class UserNotFoundException extends AuthenticationException { private $identifier; /** * {@inheritdoc} */ public function getMessageKey() { return 'Username could not be found.'; } /** * Get the user identifier (e.g. username or email address). */ public function getUserIdentifier() : ?string { return $this->identifier; } /** * @return string * * @deprecated */ public function getUsername() { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); return $this->identifier; } /** * Set the user identifier (e.g. username or email address). */ public function setUserIdentifier(string $identifier) : void { $this->identifier = $identifier; } /** * @deprecated */ public function setUsername(string $username) { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use setUserIdentifier() instead.', __METHOD__); $this->identifier = $username; } /** * {@inheritdoc} */ public function getMessageData() { return ['{{ username }}' => $this->identifier, '{{ user_identifier }}' => $this->identifier]; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->identifier, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->identifier, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } if (!\class_exists(UsernameNotFoundException::class, \false)) { \class_alias(UserNotFoundException::class, UsernameNotFoundException::class); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * AuthenticationCredentialsNotFoundException is thrown when an authentication is rejected * because no Token is available. * * @author Fabien Potencier * @author Alexander */ class AuthenticationCredentialsNotFoundException extends AuthenticationException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Authentication credentials could not be found.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Exception; /** * CredentialsExpiredException is thrown when the user account credentials have expired. * * @author Fabien Potencier * @author Alexander */ class CredentialsExpiredException extends AccountStatusException { /** * {@inheritdoc} */ public function getMessageKey() { return 'Credentials have expired.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\EquatableInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Base class for Token instances. * * @author Fabien Potencier * @author Johannes M. Schmitt */ abstract class AbstractToken implements TokenInterface { private $user; private $roleNames = []; private $authenticated = \false; private $attributes = []; /** * @param string[] $roles An array of roles * * @throws \InvalidArgumentException */ public function __construct(array $roles = []) { foreach ($roles as $role) { $this->roleNames[] = $role; } } /** * {@inheritdoc} */ public function getRoleNames() : array { return $this->roleNames; } /** * {@inheritdoc} */ public function getUsername() { if (1 === \func_num_args() && \false === \func_get_arg(0)) { return null; } \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); if ($this->user instanceof UserInterface) { return \method_exists($this->user, 'getUserIdentifier') ? $this->user->getUserIdentifier() : $this->user->getUsername(); } return (string) $this->user; } /** * {@inheritdoc} */ public function getUserIdentifier() : string { // method returns "null" in non-legacy mode if not overridden $username = $this->getUsername(\false); if (null !== $username) { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s::getUsername()" is deprecated, override "getUserIdentifier()" instead.', \get_debug_type($this)); } if ($this->user instanceof UserInterface) { // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 return \method_exists($this->user, 'getUserIdentifier') ? $this->user->getUserIdentifier() : $this->user->getUsername(); } return (string) $this->user; } /** * {@inheritdoc} */ public function getUser() { return $this->user; } /** * {@inheritdoc} */ public function setUser($user) { if (!($user instanceof UserInterface || \is_object($user) && \method_exists($user, '__toString') || \is_string($user))) { throw new \InvalidArgumentException('$user must be an instanceof UserInterface, an object implementing a __toString method, or a primitive string.'); } if (!$user instanceof UserInterface) { \trigger_deprecation('symfony/security-core', '5.4', 'Using an object that is not an instance of "%s" as $user in "%s" is deprecated.', UserInterface::class, static::class); } // @deprecated since Symfony 5.4, remove the whole block if/elseif/else block in 6.0 if (1 < \func_num_args() && !\func_get_arg(1)) { // ContextListener checks if the user has changed on its own and calls `setAuthenticated()` subsequently, // avoid doing the same checks twice $changed = \false; } elseif (null === $this->user) { $changed = \false; } elseif ($this->user instanceof UserInterface) { if (!$user instanceof UserInterface) { $changed = \true; } else { $changed = $this->hasUserChanged($user); } } elseif ($user instanceof UserInterface) { $changed = \true; } else { $changed = (string) $this->user !== (string) $user; } // @deprecated since Symfony 5.4 if ($changed) { $this->setAuthenticated(\false, \false); } $this->user = $user; } /** * {@inheritdoc} * * @deprecated since Symfony 5.4 */ public function isAuthenticated() { if (1 > \func_num_args() || \func_get_arg(0)) { \trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated, return null from "getUser()" instead when a token is not authenticated.', __METHOD__); } return $this->authenticated; } /** * {@inheritdoc} */ public function setAuthenticated(bool $authenticated) { if (2 > \func_num_args() || \func_get_arg(1)) { \trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated', __METHOD__); } $this->authenticated = $authenticated; } /** * {@inheritdoc} */ public function eraseCredentials() { if ($this->getUser() instanceof UserInterface) { $this->getUser()->eraseCredentials(); } } /** * Returns all the necessary state of the object for serialization purposes. * * There is no need to serialize any entry, they should be returned as-is. * If you extend this method, keep in mind you MUST guarantee parent data is present in the state. * Here is an example of how to extend this method: * * public function __serialize(): array * { * return [$this->childAttribute, parent::__serialize()]; * } * * * @see __unserialize() */ public function __serialize() : array { return [$this->user, $this->authenticated, null, $this->attributes, $this->roleNames]; } /** * Restores the object state from an array given by __serialize(). * * There is no need to unserialize any entry in $data, they are already ready-to-use. * If you extend this method, keep in mind you MUST pass the parent data to its respective class. * Here is an example of how to extend this method: * * public function __unserialize(array $data): void * { * [$this->childAttribute, $parentData] = $data; * parent::__unserialize($parentData); * } * * * @see __serialize() */ public function __unserialize(array $data) : void { [$this->user, $this->authenticated, , $this->attributes, $this->roleNames] = $data; } /** * {@inheritdoc} */ public function getAttributes() { return $this->attributes; } /** * {@inheritdoc} */ public function setAttributes(array $attributes) { $this->attributes = $attributes; } /** * {@inheritdoc} */ public function hasAttribute(string $name) { return \array_key_exists($name, $this->attributes); } /** * {@inheritdoc} */ public function getAttribute(string $name) { if (!\array_key_exists($name, $this->attributes)) { throw new \InvalidArgumentException(\sprintf('This token has no "%s" attribute.', $name)); } return $this->attributes[$name]; } /** * {@inheritdoc} */ public function setAttribute(string $name, $value) { $this->attributes[$name] = $value; } /** * {@inheritdoc} */ public function __toString() { $class = static::class; $class = \substr($class, \strrpos($class, '\\') + 1); $roles = []; foreach ($this->roleNames as $role) { $roles[] = $role; } return \sprintf('%s(user="%s", authenticated=%s, roles="%s")', $class, $this->getUserIdentifier(), \json_encode($this->authenticated), \implode(', ', $roles)); } /** * @internal */ public final function serialize() : string { return \serialize($this->__serialize()); } /** * @internal */ public final function unserialize($serialized) { $this->__unserialize(\is_array($serialized) ? $serialized : \unserialize($serialized)); } /** * @deprecated since Symfony 5.4 */ private function hasUserChanged(UserInterface $user) : bool { if (!$this->user instanceof UserInterface) { throw new \BadMethodCallException('Method "hasUserChanged" should be called when current user class is instance of "UserInterface".'); } if ($this->user instanceof EquatableInterface) { return !(bool) $this->user->isEqualTo($user); } // @deprecated since Symfony 5.3, check for PasswordAuthenticatedUserInterface on both user objects before comparing passwords if ($this->user->getPassword() !== $user->getPassword()) { return \true; } // @deprecated since Symfony 5.3, check for LegacyPasswordAuthenticatedUserInterface on both user objects before comparing salts if ($this->user->getSalt() !== $user->getSalt()) { return \true; } $userRoles = \array_map('strval', (array) $user->getRoles()); if ($this instanceof SwitchUserToken) { $userRoles[] = 'ROLE_PREVIOUS_ADMIN'; } if (\count($userRoles) !== \count($this->getRoleNames()) || \count($userRoles) !== \count(\array_intersect($userRoles, $this->getRoleNames()))) { return \true; } // @deprecated since Symfony 5.3, drop getUsername() in 6.0 $userIdentifier = function ($user) { return \method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); }; if ($userIdentifier($this->user) !== $userIdentifier($user)) { return \true; } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; /** * @author Wouter de Jong */ class NullToken implements TokenInterface { public function __toString() : string { return ''; } public function getRoleNames() : array { return []; } public function getCredentials() { return ''; } public function getUser() { return null; } public function setUser($user) { throw new \BadMethodCallException('Cannot set user on a NullToken.'); } public function getUsername() { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); return ''; } public function getUserIdentifier() : string { return ''; } /** * @deprecated since Symfony 5.4 */ public function isAuthenticated() { if (0 === \func_num_args() || \func_get_arg(0)) { \trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated, return null from "getUser()" instead when a token is not authenticated.', __METHOD__); } return \true; } /** * @deprecated since Symfony 5.4 */ public function setAuthenticated(bool $isAuthenticated) { throw new \BadMethodCallException('Cannot change authentication state of NullToken.'); } public function eraseCredentials() { } public function getAttributes() { return []; } public function setAttributes(array $attributes) { throw new \BadMethodCallException('Cannot set attributes of NullToken.'); } public function hasAttribute(string $name) { return \false; } public function getAttribute(string $name) { return null; } public function setAttribute(string $name, $value) { throw new \BadMethodCallException('Cannot add attribute to NullToken.'); } public function __serialize() : array { return []; } public function __unserialize(array $data) : void { } /** * @return string * * @internal in 5.3 * * @final in 5.3 */ public function serialize() { return ''; } /** * @return void * * @internal in 5.3 * * @final in 5.3 */ public function unserialize($serialized) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * AnonymousToken represents an anonymous token. * * @author Fabien Potencier * * @deprecated since 5.4, anonymous is now represented by the absence of a token */ class AnonymousToken extends AbstractToken { private $secret; /** * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client * @param string|\Stringable|UserInterface $user * @param string[] $roles */ public function __construct(string $secret, $user, array $roles = []) { \trigger_deprecation('symfony/security-core', '5.4', 'The "%s" class is deprecated.', __CLASS__); parent::__construct($roles); $this->secret = $secret; $this->setUser($user); // @deprecated since Symfony 5.4 $this->setAuthenticated(\true, \false); } /** * {@inheritdoc} */ public function getCredentials() { return ''; } /** * Returns the secret. * * @return string */ public function getSecret() { return $this->secret; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->secret, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->secret, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; /** * A token storage that increments the session usage index when the token is accessed. * * @author Nicolas Grekas */ final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceSubscriberInterface { private $storage; private $container; private $enableUsageTracking = \false; public function __construct(TokenStorageInterface $storage, ContainerInterface $container) { $this->storage = $storage; $this->container = $container; } /** * {@inheritdoc} */ public function getToken() : ?TokenInterface { if ($this->shouldTrackUsage()) { // increments the internal session usage index $this->getSession()->getMetadataBag(); } return $this->storage->getToken(); } /** * {@inheritdoc} */ public function setToken(?TokenInterface $token = null) : void { $this->storage->setToken($token); if ($token && $this->shouldTrackUsage()) { // increments the internal session usage index $this->getSession()->getMetadataBag(); } } public function enableUsageTracking() : void { $this->enableUsageTracking = \true; } public function disableUsageTracking() : void { $this->enableUsageTracking = \false; } public static function getSubscribedServices() : array { return ['request_stack' => RequestStack::class]; } private function getSession() : SessionInterface { // BC for symfony/security-bundle < 5.3 if ($this->container->has('session')) { \trigger_deprecation('symfony/security-core', '5.3', 'Injecting the "session" in "%s" is deprecated, inject the "request_stack" instead.', __CLASS__); return $this->container->get('session'); } return $this->container->get('request_stack')->getSession(); } private function shouldTrackUsage() : bool { if (!$this->enableUsageTracking) { return \false; } // BC for symfony/security-bundle < 5.3 if ($this->container->has('session')) { return \true; } if (!$this->container->get('request_stack')->getMainRequest()) { return \false; } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * TokenStorage contains a TokenInterface. * * It gives access to the token representing the current user authentication. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class TokenStorage implements TokenStorageInterface, ResetInterface { private $token; private $initializer; /** * {@inheritdoc} */ public function getToken() { if ($initializer = $this->initializer) { $this->initializer = null; $initializer(); } return $this->token; } /** * {@inheritdoc} */ public function setToken(?TokenInterface $token = null) { if ($token) { // ensure any initializer is called $this->getToken(); // @deprecated since Symfony 5.3 if (!\method_exists($token, 'getUserIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in token class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', \get_debug_type($token)); } } $this->initializer = null; $this->token = $token; } public function setInitializer(?callable $initializer) : void { $this->initializer = $initializer; } public function reset() { $this->setToken(null); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\Storage; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * The TokenStorageInterface. * * @author Johannes M. Schmitt */ interface TokenStorageInterface { /** * Returns the current security token. * * @return TokenInterface|null */ public function getToken(); /** * Sets the authentication token. * * @param TokenInterface|null $token A TokenInterface token, or null if no further authentication information should be stored */ public function setToken(?TokenInterface $token = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Authentication Token for "Remember-Me". * * @author Johannes M. Schmitt */ class RememberMeToken extends AbstractToken { private $secret; private $firewallName; /** * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client * * @throws \InvalidArgumentException */ public function __construct(UserInterface $user, string $firewallName, string $secret) { parent::__construct($user->getRoles()); if (empty($secret)) { throw new \InvalidArgumentException('$secret must not be empty.'); } if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); } $this->firewallName = $firewallName; $this->secret = $secret; $this->setUser($user); parent::setAuthenticated(\true, \false); } /** * {@inheritdoc} */ public function setAuthenticated(bool $authenticated) { if ($authenticated) { throw new \LogicException('You cannot set this token to authenticated after creation.'); } parent::setAuthenticated(\false, \false); } /** * Returns the provider secret. * * @return string The provider secret * * @deprecated since Symfony 5.2, use getFirewallName() instead */ public function getProviderKey() { if (1 !== \func_num_args() || \true !== \func_get_arg(0)) { \trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__); } return $this->firewallName; } public function getFirewallName() : string { return $this->getProviderKey(\true); } /** * @return string */ public function getSecret() { return $this->secret; } /** * {@inheritdoc} */ public function getCredentials() { \trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated.', __METHOD__); return ''; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->secret, $this->firewallName, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->secret, $this->firewallName, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Token representing a user who temporarily impersonates another one. * * @author Christian Flothmann */ class SwitchUserToken extends UsernamePasswordToken { private $originalToken; private $originatedFromUri; /** * @param UserInterface $user * @param string|null $originatedFromUri The URI where was the user at the switch * * @throws \InvalidArgumentException */ public function __construct( $user, /* string */ $firewallName, /* array */ $roles, /* TokenInterface */ $originalToken, /* string */ $originatedFromUri = null ) { if (\is_string($roles)) { // @deprecated since 5.4, deprecation is triggered by UsernamePasswordToken::__construct() $credentials = $firewallName; $firewallName = $roles; $roles = $originalToken; $originalToken = $originatedFromUri; $originatedFromUri = \func_num_args() > 5 ? \func_get_arg(5) : null; parent::__construct($user, $credentials, $firewallName, $roles); } else { parent::__construct($user, $firewallName, $roles); } if (!$originalToken instanceof TokenInterface) { throw new \TypeError(\sprintf('Argument $originalToken of "%s" must be an instance of "%s", "%s" given.', __METHOD__, TokenInterface::class, \get_debug_type($originalToken))); } $this->originalToken = $originalToken; $this->originatedFromUri = $originatedFromUri; } public function getOriginalToken() : TokenInterface { return $this->originalToken; } public function getOriginatedFromUri() : ?string { return $this->originatedFromUri; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->originalToken, $this->originatedFromUri, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { if (3 > \count($data)) { // Support for tokens serialized with version 5.1 or lower of symfony/security-core. [$this->originalToken, $parentData] = $data; } else { [$this->originalToken, $this->originatedFromUri, $parentData] = $data; } $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * PreAuthenticatedToken implements a pre-authenticated token. * * @author Fabien Potencier */ class PreAuthenticatedToken extends AbstractToken { private $credentials; private $firewallName; /** * @param UserInterface $user * @param string $firewallName * @param string[] $roles */ public function __construct( $user, /* string */ $firewallName, /* array */ $roles = [] ) { if (\is_string($roles)) { \trigger_deprecation('symfony/security-core', '5.4', 'Argument $credentials of "%s()" is deprecated.', __METHOD__); $credentials = $firewallName; $firewallName = $roles; $roles = \func_num_args() > 3 ? \func_get_arg(3) : []; } parent::__construct($roles); if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); } $this->setUser($user); $this->credentials = $credentials ?? null; $this->firewallName = $firewallName; if ($roles) { $this->setAuthenticated(\true, \false); } } /** * Returns the provider key. * * @return string The provider key * * @deprecated since Symfony 5.2, use getFirewallName() instead */ public function getProviderKey() { if (1 !== \func_num_args() || \true !== \func_get_arg(0)) { \trigger_deprecation('symfony/security-core', '5.2', 'Method "%s()" is deprecated, use "getFirewallName()" instead.', __METHOD__); } return $this->firewallName; } public function getFirewallName() : string { return $this->getProviderKey(\true); } /** * {@inheritdoc} */ public function getCredentials() { \trigger_deprecation('symfony/security-core', '5.4', 'Method "%s()" is deprecated.', __METHOD__); return $this->credentials; } /** * {@inheritdoc} */ public function eraseCredentials() { parent::eraseCredentials(); $this->credentials = null; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->credentials, $this->firewallName, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->credentials, $this->firewallName, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * TokenInterface is the interface for the user authentication information. * * @method string getUserIdentifier() returns the user identifier used during authentication (e.g. a user's email address or username) * * @author Fabien Potencier * @author Johannes M. Schmitt */ interface TokenInterface extends \Serializable { /** * Returns a string representation of the Token. * * This is only to be used for debugging purposes. * * @return string */ public function __toString(); /** * Returns the user roles. * * @return string[] */ public function getRoleNames() : array; /** * Returns the user credentials. * * @return mixed * * @deprecated since Symfony 5.4 */ public function getCredentials(); /** * Returns a user representation. * * @return UserInterface|null * * @see AbstractToken::setUser() */ public function getUser(); /** * Sets the authenticated user in the token. * * @param UserInterface $user * * @throws \InvalidArgumentException */ public function setUser($user); /** * Returns whether the user is authenticated or not. * * @return bool true if the token has been authenticated, false otherwise * * @deprecated since Symfony 5.4, return null from "getUser()" instead when a token is not authenticated */ public function isAuthenticated(); /** * Sets the authenticated flag. * * @deprecated since Symfony 5.4 */ public function setAuthenticated(bool $isAuthenticated); /** * Removes sensitive information from the token. */ public function eraseCredentials(); /** * @return array */ public function getAttributes(); /** * @param array $attributes The token attributes */ public function setAttributes(array $attributes); /** * @return bool */ public function hasAttribute(string $name); /** * @return mixed * * @throws \InvalidArgumentException When attribute doesn't exist for this token */ public function getAttribute(string $name); /** * @param mixed $value The attribute value */ public function setAttribute(string $name, $value); /** * Returns all the necessary state of the object for serialization purposes. */ public function __serialize() : array; /** * Restores the object state from an array given by __serialize(). */ public function __unserialize(array $data) : void; /** * @return string * * @deprecated since Symfony 5.3, use getUserIdentifier() instead */ public function getUsername(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Token; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * UsernamePasswordToken implements a username and password token. * * @author Fabien Potencier */ class UsernamePasswordToken extends AbstractToken { private $credentials; private $firewallName; /** * @param UserInterface $user * @param string[] $roles * * @throws \InvalidArgumentException */ public function __construct( $user, /* string */ $firewallName, /* array */ $roles = [] ) { if (\is_string($roles)) { \trigger_deprecation('symfony/security-core', '5.4', 'The $credentials argument of "%s" is deprecated.', static::class . '::__construct'); $credentials = $firewallName; $firewallName = $roles; $roles = \func_num_args() > 3 ? \func_get_arg(3) : []; } parent::__construct($roles); if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); } $this->setUser($user); $this->credentials = $credentials ?? null; $this->firewallName = $firewallName; parent::setAuthenticated(\count($roles) > 0, \false); } /** * {@inheritdoc} */ public function setAuthenticated(bool $isAuthenticated) { if ($isAuthenticated) { throw new \LogicException('Cannot set this token to trusted after instantiation.'); } parent::setAuthenticated(\false, \false); } /** * {@inheritdoc} */ public function getCredentials() { \trigger_deprecation('symfony/security-core', '5.4', 'Method "%s" is deprecated.', __METHOD__); return $this->credentials; } /** * Returns the provider key. * * @return string The provider key * * @deprecated since Symfony 5.2, use getFirewallName() instead */ public function getProviderKey() { if (1 !== \func_num_args() || \true !== \func_get_arg(0)) { \trigger_deprecation('symfony/security-core', '5.2', 'Method "%s" is deprecated, use "getFirewallName()" instead.', __METHOD__); } return $this->firewallName; } public function getFirewallName() : string { return $this->getProviderKey(\true); } /** * {@inheritdoc} */ public function eraseCredentials() { parent::eraseCredentials(); $this->credentials = null; } /** * {@inheritdoc} */ public function __serialize() : array { return [$this->credentials, $this->firewallName, parent::__serialize()]; } /** * {@inheritdoc} */ public function __unserialize(array $data) : void { [$this->credentials, $this->firewallName, $parentData] = $data; $parentData = \is_array($parentData) ? $parentData : \unserialize($parentData); parent::__unserialize($parentData); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * Interface for resolving the authentication status of a given token. * * @author Johannes M. Schmitt * * @method bool isAuthenticated(TokenInterface $token = null) */ interface AuthenticationTrustResolverInterface { /** * Resolves whether the passed token implementation is authenticated * anonymously. * * If null is passed, the method must return false. * * @return bool * * @deprecated since Symfony 5.4, use !isAuthenticated() instead */ public function isAnonymous(?TokenInterface $token = null); /** * Resolves whether the passed token implementation is authenticated * using remember-me capabilities. * * @return bool */ public function isRememberMe(?TokenInterface $token = null); /** * Resolves whether the passed token implementation is fully authenticated. * * @return bool */ public function isFullFledged(?TokenInterface $token = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** * The default implementation of the authentication trust resolver. * * @author Johannes M. Schmitt */ class AuthenticationTrustResolver implements AuthenticationTrustResolverInterface { public function isAuthenticated(?TokenInterface $token = null) : bool { return $token && $token->getUser() && !$token instanceof AnonymousToken && (!\method_exists($token, 'isAuthenticated') || $token->isAuthenticated(\false)); } /** * {@inheritdoc} */ public function isAnonymous(?TokenInterface $token = null) { if (1 === \func_num_args() || \false !== \func_get_arg(1)) { \trigger_deprecation('symfony/security-core', '5.4', 'The "%s()" method is deprecated, use "isAuthenticated()" or "isFullFledged()" if you want to check if the request is (fully) authenticated.', __METHOD__); } return $token instanceof AnonymousToken || $token && !$token->getUser(); } /** * {@inheritdoc} */ public function isRememberMe(?TokenInterface $token = null) { return $token && $token instanceof RememberMeToken; } /** * {@inheritdoc} */ public function isFullFledged(?TokenInterface $token = null) { return $token && !$this->isAnonymous($token, \false) && !$this->isRememberMe($token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', UserAuthenticationProvider::class); /** * UserProviderInterface retrieves users for UsernamePasswordToken tokens. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ abstract class UserAuthenticationProvider implements AuthenticationProviderInterface { private $hideUserNotFoundExceptions; private $userChecker; private $providerKey; /** * @throws \InvalidArgumentException */ public function __construct(UserCheckerInterface $userChecker, string $providerKey, bool $hideUserNotFoundExceptions = \true) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); } $this->userChecker = $userChecker; $this->providerKey = $providerKey; $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; } /** * {@inheritdoc} */ public function authenticate(TokenInterface $token) { if (!$this->supports($token)) { throw new AuthenticationException('The token is not supported by this authentication provider.'); } $username = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); if ('' === $username || null === $username) { $username = AuthenticationProviderInterface::USERNAME_NONE_PROVIDED; } try { $user = $this->retrieveUser($username, $token); } catch (UserNotFoundException $e) { if ($this->hideUserNotFoundExceptions) { throw new BadCredentialsException('Bad credentials.', 0, $e); } $e->setUserIdentifier($username); throw $e; } if (!$user instanceof UserInterface) { throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.'); } try { $this->userChecker->checkPreAuth($user); $this->checkAuthentication($user, $token); $this->userChecker->checkPostAuth($user); } catch (AccountStatusException|BadCredentialsException $e) { if ($this->hideUserNotFoundExceptions && !$e instanceof CustomUserMessageAccountStatusException) { throw new BadCredentialsException('Bad credentials.', 0, $e); } throw $e; } if ($token instanceof SwitchUserToken) { $roles = $user->getRoles(); $roles[] = 'ROLE_PREVIOUS_ADMIN'; $authenticatedToken = new SwitchUserToken($user, $token->getCredentials(), $this->providerKey, $roles, $token->getOriginalToken()); } else { $authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles()); } $authenticatedToken->setAttributes($token->getAttributes()); return $authenticatedToken; } /** * {@inheritdoc} */ public function supports(TokenInterface $token) { return $token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName(); } /** * Retrieves the user from an implementation-specific location. * * @return UserInterface * * @throws AuthenticationException if the credentials could not be validated */ protected abstract function retrieveUser(string $username, UsernamePasswordToken $token); /** * Does additional checks on the user and token (like validating the * credentials). * * @throws AuthenticationException if the credentials could not be validated */ protected abstract function checkAuthentication(UserInterface $user, UsernamePasswordToken $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', DaoAuthenticationProvider::class); /** * DaoAuthenticationProvider uses a UserProviderInterface to retrieve the user * for a UsernamePasswordToken. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class DaoAuthenticationProvider extends UserAuthenticationProvider { private $hasherFactory; private $userProvider; /** * @param PasswordHasherFactoryInterface $hasherFactory */ public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, $hasherFactory, bool $hideUserNotFoundExceptions = \true) { parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); if ($hasherFactory instanceof EncoderFactoryInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Passing a "%s" instance to the "%s" constructor is deprecated, use "%s" instead.', EncoderFactoryInterface::class, __CLASS__, PasswordHasherFactoryInterface::class); } $this->hasherFactory = $hasherFactory; $this->userProvider = $userProvider; } /** * {@inheritdoc} */ protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) { $currentUser = $token->getUser(); if ($currentUser instanceof UserInterface) { if ($currentUser->getPassword() !== $user->getPassword()) { throw new BadCredentialsException('The credentials were changed from another session.'); } } else { if ('' === ($presentedPassword = $token->getCredentials())) { throw new BadCredentialsException('The presented password cannot be empty.'); } if (null === $user->getPassword()) { throw new BadCredentialsException('The presented password is invalid.'); } if (!$user instanceof PasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Using password-based authentication listeners while not implementing "%s" interface from class "%s" is deprecated.', PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $salt = $user->getSalt(); if ($salt && !$user instanceof LegacyPasswordAuthenticatedUserInterface) { \trigger_deprecation('symfony/security-core', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } // deprecated since Symfony 5.3 if ($this->hasherFactory instanceof EncoderFactoryInterface) { $encoder = $this->hasherFactory->getEncoder($user); if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $salt)) { throw new BadCredentialsException('The presented password is invalid.'); } if ($this->userProvider instanceof PasswordUpgraderInterface && \method_exists($encoder, 'needsRehash') && $encoder->needsRehash($user->getPassword())) { $this->userProvider->upgradePassword($user, $encoder->encodePassword($presentedPassword, $user->getSalt())); } return; } $hasher = $this->hasherFactory->getPasswordHasher($user); if (!$hasher->verify($user->getPassword(), $presentedPassword, $salt)) { throw new BadCredentialsException('The presented password is invalid.'); } if ($this->userProvider instanceof PasswordUpgraderInterface && $hasher->needsRehash($user->getPassword())) { $this->userProvider->upgradePassword($user, $hasher->hash($presentedPassword, $salt)); } } } /** * {@inheritdoc} */ protected function retrieveUser(string $userIdentifier, UsernamePasswordToken $token) { $user = $token->getUser(); if ($user instanceof UserInterface) { return $user; } try { // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($this->userProvider, 'loadUserByIdentifier')) { $user = $this->userProvider->loadUserByIdentifier($userIdentifier); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $user = $this->userProvider->loadUserByUsername($userIdentifier); } if (!$user instanceof UserInterface) { throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); } return $user; } catch (UserNotFoundException $e) { $e->setUserIdentifier($userIdentifier); throw $e; } catch (\Exception $e) { $e = new AuthenticationServiceException($e->getMessage(), 0, $e); $e->setToken($token); throw $e; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\Ldap\Exception\ConnectionException; use _ContaoManager\Symfony\Component\Ldap\LdapInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Core\Exception\UserNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', LdapBindAuthenticationProvider::class); /** * LdapBindAuthenticationProvider authenticates a user against an LDAP server. * * The only way to check user credentials is to try to connect the user with its * credentials to the ldap. * * @author Charles Sarrazin * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class LdapBindAuthenticationProvider extends UserAuthenticationProvider { private $userProvider; private $ldap; private $dnString; private $queryString; private $searchDn; private $searchPassword; public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, LdapInterface $ldap, string $dnString = '{user_identifier}', bool $hideUserNotFoundExceptions = \true, string $searchDn = '', string $searchPassword = '') { parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); $this->userProvider = $userProvider; $this->ldap = $ldap; $this->dnString = $dnString; $this->searchDn = $searchDn; $this->searchPassword = $searchPassword; } /** * Set a query string to use in order to find a DN for the user identifier. */ public function setQueryString(string $queryString) { $this->queryString = $queryString; } /** * {@inheritdoc} */ protected function retrieveUser(string $userIdentifier, UsernamePasswordToken $token) { if (AuthenticationProviderInterface::USERNAME_NONE_PROVIDED === $userIdentifier) { throw new UserNotFoundException('User identifier cannot be null.'); } // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($this->userProvider, 'loadUserByIdentifier')) { return $this->userProvider->loadUserByIdentifier($userIdentifier); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); return $this->userProvider->loadUserByUsername($userIdentifier); } } /** * {@inheritdoc} */ protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) { // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0 $userIdentifier = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $password = $token->getCredentials(); if ('' === (string) $password) { throw new BadCredentialsException('The presented password must not be empty.'); } try { if ($this->queryString) { if ('' !== $this->searchDn && '' !== $this->searchPassword) { $this->ldap->bind($this->searchDn, $this->searchPassword); } else { throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } $userIdentifier = $this->ldap->escape($userIdentifier, '', LdapInterface::ESCAPE_FILTER); $query = \str_replace(['{username}', '{user_identifier}'], $userIdentifier, $this->queryString); $result = $this->ldap->query($this->dnString, $query)->execute(); if (1 !== $result->count()) { throw new BadCredentialsException('The presented username is invalid.'); } $dn = $result[0]->getDn(); } else { $userIdentifier = $this->ldap->escape($userIdentifier, '', LdapInterface::ESCAPE_DN); $dn = \str_replace(['{username}', '{user_identifier}'], $userIdentifier, $this->dnString); } $this->ldap->bind($dn, $password); } catch (ConnectionException $e) { throw new BadCredentialsException('The presented password is invalid.'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" interface is deprecated, use the new authenticator system instead.', AuthenticationProviderInterface::class); /** * AuthenticationProviderInterface is the interface for all authentication * providers. * * Concrete implementations processes specific Token instances. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ interface AuthenticationProviderInterface extends AuthenticationManagerInterface { /** * Use this constant for not provided username. * * @var string */ public const USERNAME_NONE_PROVIDED = 'NONE_PROVIDED'; /** * Checks whether this provider supports the given token. * * @return bool */ public function supports(TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserProviderInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', PreAuthenticatedAuthenticationProvider::class); /** * Processes a pre-authenticated authentication request. * * This authentication provider will not perform any checks on authentication * requests, as they should already be pre-authenticated. However, the * UserProviderInterface implementation may still throw a * UserNotFoundException, for example. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class PreAuthenticatedAuthenticationProvider implements AuthenticationProviderInterface { private $userProvider; private $userChecker; private $providerKey; public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey) { $this->userProvider = $userProvider; $this->userChecker = $userChecker; $this->providerKey = $providerKey; } /** * {@inheritdoc} */ public function authenticate(TokenInterface $token) { if (!$this->supports($token)) { throw new AuthenticationException('The token is not supported by this authentication provider.'); } if (!($user = $token->getUser())) { throw new BadCredentialsException('No pre-authenticated principal found in request.'); } $userIdentifier = \method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); // @deprecated since Symfony 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 if (\method_exists($this->userProvider, 'loadUserByIdentifier')) { $user = $this->userProvider->loadUserByIdentifier($userIdentifier); } else { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($this->userProvider)); $user = $this->userProvider->loadUserByUsername($userIdentifier); } $this->userChecker->checkPostAuth($user); $authenticatedToken = new PreAuthenticatedToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles()); $authenticatedToken->setAttributes($token->getAttributes()); return $authenticatedToken; } /** * {@inheritdoc} */ public function supports(TokenInterface $token) { return $token instanceof PreAuthenticatedToken && $this->providerKey === $token->getFirewallName(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\LogicException; use _ContaoManager\Symfony\Component\Security\Core\User\UserCheckerInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', RememberMeAuthenticationProvider::class); /** * @deprecated since Symfony 5.3, use the new authenticator system instead */ class RememberMeAuthenticationProvider implements AuthenticationProviderInterface { private $userChecker; private $secret; private $providerKey; /** * @param string $secret A secret * @param string $providerKey A provider secret */ public function __construct(UserCheckerInterface $userChecker, string $secret, string $providerKey) { $this->userChecker = $userChecker; $this->secret = $secret; $this->providerKey = $providerKey; } /** * {@inheritdoc} */ public function authenticate(TokenInterface $token) { if (!$this->supports($token)) { throw new AuthenticationException('The token is not supported by this authentication provider.'); } if ($this->secret !== $token->getSecret()) { throw new BadCredentialsException('The presented secret does not match.'); } $user = $token->getUser(); if (!$user instanceof UserInterface) { throw new LogicException(\sprintf('Method "%s::getUser()" must return a "%s" instance, "%s" returned.', \get_debug_type($token), UserInterface::class, \get_debug_type($user))); } $this->userChecker->checkPreAuth($user); $this->userChecker->checkPostAuth($user); $authenticatedToken = new RememberMeToken($user, $this->providerKey, $this->secret); $authenticatedToken->setAttributes($token->getAttributes()); return $authenticatedToken; } /** * {@inheritdoc} */ public function supports(TokenInterface $token) { return $token instanceof RememberMeToken && $token->getFirewallName() === $this->providerKey; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AnonymousAuthenticationProvider::class); /** * AnonymousAuthenticationProvider validates AnonymousToken instances. * * @author Fabien Potencier * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class AnonymousAuthenticationProvider implements AuthenticationProviderInterface { /** * Used to determine if the token is created by the application * instead of a malicious client. * * @var string */ private $secret; /** * @param string $secret The secret shared with the AnonymousToken */ public function __construct(string $secret) { $this->secret = $secret; } /** * {@inheritdoc} */ public function authenticate(TokenInterface $token) { if (!$this->supports($token)) { throw new AuthenticationException('The token is not supported by this authentication provider.'); } if ($this->secret !== $token->getSecret()) { throw new BadCredentialsException('The Token does not contain the expected key.'); } return $token; } /** * {@inheritdoc} */ public function supports(TokenInterface $token) { return $token instanceof AnonymousToken; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\AuthenticationEvents; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use _ContaoManager\Symfony\Component\Security\Core\Exception\AccountStatusException; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Core\Exception\BadCredentialsException; use _ContaoManager\Symfony\Component\Security\Core\Exception\ProviderNotFoundException; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AuthenticationProviderManager::class); // Help opcache.preload discover always-needed symbols \class_exists(AuthenticationEvents::class); \class_exists(AuthenticationFailureEvent::class); \class_exists(AuthenticationSuccessEvent::class); /** * AuthenticationProviderManager uses a list of AuthenticationProviderInterface * instances to authenticate a Token. * * @author Fabien Potencier * @author Johannes M. Schmitt * * @deprecated since Symfony 5.3, use the new authenticator system instead */ class AuthenticationProviderManager implements AuthenticationManagerInterface { private $providers; private $eraseCredentials; private $eventDispatcher; /** * @param iterable $providers An iterable with AuthenticationProviderInterface instances as values * @param bool $eraseCredentials Whether to erase credentials after authentication or not * * @throws \InvalidArgumentException */ public function __construct(iterable $providers, bool $eraseCredentials = \true) { if (!$providers) { throw new \InvalidArgumentException('You must at least add one authentication provider.'); } $this->providers = $providers; $this->eraseCredentials = $eraseCredentials; } public function setEventDispatcher(EventDispatcherInterface $dispatcher) { $this->eventDispatcher = $dispatcher; } /** * {@inheritdoc} */ public function authenticate(TokenInterface $token) { $lastException = null; $result = null; foreach ($this->providers as $provider) { if (!$provider instanceof AuthenticationProviderInterface) { throw new \InvalidArgumentException(\sprintf('Provider "%s" must implement the AuthenticationProviderInterface.', \get_debug_type($provider))); } if (!$provider->supports($token)) { continue; } try { $result = $provider->authenticate($token); if (null !== $result) { break; } } catch (AccountStatusException $e) { $lastException = $e; break; } catch (AuthenticationException $e) { $lastException = $e; } catch (InvalidPasswordException $e) { $lastException = new BadCredentialsException('Bad credentials.', 0, $e); } } if (null !== $result) { if (\true === $this->eraseCredentials) { $result->eraseCredentials(); } if (null !== $this->eventDispatcher) { $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($result), AuthenticationEvents::AUTHENTICATION_SUCCESS); } // @deprecated since Symfony 5.3 if ($result->getUser() instanceof UserInterface && !\method_exists($result->getUser(), 'getUserIdentifier')) { \trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in user class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', \get_debug_type($result->getUser())); } return $result; } if (null === $lastException) { $lastException = new ProviderNotFoundException(\sprintf('No Authentication Provider found for token of class "%s".', \get_class($token))); } if (null !== $this->eventDispatcher) { $this->eventDispatcher->dispatch(new AuthenticationFailureEvent($token, $lastException), AuthenticationEvents::AUTHENTICATION_FAILURE); } $lastException->setToken($token); throw $lastException; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe; /** * Interface to be implemented by persistent token classes (such as * Doctrine entities representing a remember-me token). * * @method string getUserIdentifier() returns the identifier used to authenticate (e.g. their email address or username) * * @author Johannes M. Schmitt */ interface PersistentTokenInterface { /** * Returns the class of the user. * * @return string */ public function getClass(); /** * Returns the series. * * @return string */ public function getSeries(); /** * Returns the token value. * * @return string */ public function getTokenValue(); /** * Returns the time the token was last used. * * @return \DateTime */ public function getLastUsed(); /** * @return string * * @deprecated since Symfony 5.3, use getUserIdentifier() instead */ public function getUsername(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe; use _ContaoManager\Symfony\Component\Security\Core\Exception\TokenNotFoundException; /** * This class is used for testing purposes, and is not really suited for production. * * @author Johannes M. Schmitt */ class InMemoryTokenProvider implements TokenProviderInterface { private $tokens = []; /** * {@inheritdoc} */ public function loadTokenBySeries(string $series) { if (!isset($this->tokens[$series])) { throw new TokenNotFoundException('No token found.'); } return $this->tokens[$series]; } /** * {@inheritdoc} */ public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) { if (!isset($this->tokens[$series])) { throw new TokenNotFoundException('No token found.'); } $token = new PersistentToken($this->tokens[$series]->getClass(), \method_exists($this->tokens[$series], 'getUserIdentifier') ? $this->tokens[$series]->getUserIdentifier() : $this->tokens[$series]->getUsername(), $series, $tokenValue, $lastUsed); $this->tokens[$series] = $token; } /** * {@inheritdoc} */ public function deleteTokenBySeries(string $series) { unset($this->tokens[$series]); } /** * {@inheritdoc} */ public function createNewToken(PersistentTokenInterface $token) { $this->tokens[$token->getSeries()] = $token; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; /** * @author Jordi Boggiano */ class CacheTokenVerifier implements TokenVerifierInterface { private $cache; private $outdatedTokenTtl; private $cacheKeyPrefix; /** * @param int $outdatedTokenTtl How long the outdated token should still be considered valid. Defaults * to 60, which matches how often the PersistentRememberMeHandler will at * most refresh tokens. Increasing to more than that is not recommended, * but you may use a lower value. */ public function __construct(CacheItemPoolInterface $cache, int $outdatedTokenTtl = 60, string $cacheKeyPrefix = 'rememberme-stale-') { $this->cache = $cache; $this->outdatedTokenTtl = $outdatedTokenTtl; $this->cacheKeyPrefix = $cacheKeyPrefix; } /** * {@inheritdoc} */ public function verifyToken(PersistentTokenInterface $token, string $tokenValue) : bool { if (\hash_equals($token->getTokenValue(), $tokenValue)) { return \true; } $cacheKey = $this->getCacheKey($token); $item = $this->cache->getItem($cacheKey); if (!$item->isHit()) { return \false; } $outdatedToken = $item->get(); return \hash_equals($outdatedToken, $tokenValue); } /** * {@inheritdoc} */ public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed) : void { // When a token gets updated, persist the outdated token for $outdatedTokenTtl seconds so we can // still accept it as valid in verifyToken $item = $this->cache->getItem($this->getCacheKey($token)); $item->set($token->getTokenValue()); $item->expiresAfter($this->outdatedTokenTtl); $this->cache->save($item); } private function getCacheKey(PersistentTokenInterface $token) : string { return $this->cacheKeyPrefix . \rawurlencode($token->getSeries()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe; /** * @author Johannes M. Schmitt * * @internal */ final class PersistentToken implements PersistentTokenInterface { private $class; private $userIdentifier; private $series; private $tokenValue; private $lastUsed; public function __construct(string $class, string $userIdentifier, string $series, string $tokenValue, \DateTime $lastUsed) { if (empty($class)) { throw new \InvalidArgumentException('$class must not be empty.'); } if ('' === $userIdentifier) { throw new \InvalidArgumentException('$userIdentifier must not be empty.'); } if (empty($series)) { throw new \InvalidArgumentException('$series must not be empty.'); } if (empty($tokenValue)) { throw new \InvalidArgumentException('$tokenValue must not be empty.'); } $this->class = $class; $this->userIdentifier = $userIdentifier; $this->series = $series; $this->tokenValue = $tokenValue; $this->lastUsed = $lastUsed; } /** * {@inheritdoc} */ public function getClass() : string { return $this->class; } /** * {@inheritdoc} */ public function getUsername() : string { \trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); return $this->userIdentifier; } public function getUserIdentifier() : string { return $this->userIdentifier; } /** * {@inheritdoc} */ public function getSeries() : string { return $this->series; } /** * {@inheritdoc} */ public function getTokenValue() : string { return $this->tokenValue; } /** * {@inheritdoc} */ public function getLastUsed() : \DateTime { return $this->lastUsed; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe; /** * @author Jordi Boggiano */ interface TokenVerifierInterface { /** * Verifies that the given $token is valid. * * This lets you override the token check logic to for example accept slightly outdated tokens. * * Do not forget to implement token comparisons using hash_equals for a secure implementation. */ public function verifyToken(PersistentTokenInterface $token, string $tokenValue) : bool; /** * Updates an existing token with a new token value and lastUsed time. */ public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed) : void; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication\RememberMe; use _ContaoManager\Symfony\Component\Security\Core\Exception\TokenNotFoundException; /** * Interface for TokenProviders. * * @author Johannes M. Schmitt */ interface TokenProviderInterface { /** * Loads the active token for the given series. * * @return PersistentTokenInterface * * @throws TokenNotFoundException if the token is not found */ public function loadTokenBySeries(string $series); /** * Deletes all tokens belonging to series. */ public function deleteTokenBySeries(string $series); /** * Updates the token according to this data. * * @throws TokenNotFoundException if the token is not found */ public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed); /** * Creates a new token. */ public function createNewToken(PersistentTokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Authentication; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; /** * AuthenticationManagerInterface is the interface for authentication managers, * which process Token authentication. * * @author Fabien Potencier * * @internal since Symfony 5.3 */ interface AuthenticationManagerInterface { /** * Attempts to authenticate a TokenInterface object. * * @return TokenInterface * * @throws AuthenticationException if the authentication fails */ public function authenticate(TokenInterface $token); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Event; final class AuthenticationSuccessEvent extends AuthenticationEvent { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Event; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * This is a general purpose authentication event. * * @author Johannes M. Schmitt */ class AuthenticationEvent extends Event { private $authenticationToken; public function __construct(TokenInterface $token) { $this->authenticationToken = $token; } public function getAuthenticationToken() { return $this->authenticationToken; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Event; use _ContaoManager\Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use _ContaoManager\Symfony\Component\Security\Core\Exception\AuthenticationException; use _ContaoManager\Symfony\Component\Security\Http\Event\LoginFailureEvent; \trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" with the new authenticator system instead.', AuthenticationFailureEvent::class, LoginFailureEvent::class); /** * This event is dispatched on authentication failure. * * @author Johannes M. Schmitt * * @deprecated since Symfony 5.3, use LoginFailureEvent with the new authenticator system instead */ final class AuthenticationFailureEvent extends AuthenticationEvent { private $authenticationException; public function __construct(TokenInterface $token, AuthenticationException $ex) { parent::__construct($token); $this->authenticationException = $ex; } public function getAuthenticationException() : AuthenticationException { return $this->authenticationException; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core\Event; use _ContaoManager\Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * This event is dispatched on voter vote. * * @author Laurent VOULLEMIER * * @internal */ final class VoteEvent extends Event { private $voter; private $subject; private $attributes; private $vote; public function __construct(VoterInterface $voter, $subject, array $attributes, int $vote) { $this->voter = $voter; $this->subject = $subject; $this->attributes = $attributes; $this->vote = $vote; } public function getVoter() : VoterInterface { return $this->voter; } public function getSubject() { return $this->subject; } public function getAttributes() : array { return $this->attributes; } public function getVote() : int { return $this->vote; } } { "name": "symfony\/security-core", "type": "library", "description": "Symfony Security Component - Core Library", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/event-dispatcher-contracts": "^1.1|^2|^3", "symfony\/polyfill-php80": "^1.16", "symfony\/service-contracts": "^1.1.6|^2|^3", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/password-hasher": "^5.3|^6.0" }, "require-dev": { "psr\/container": "^1.0|^2.0", "psr\/cache": "^1.0|^2.0|^3.0", "symfony\/cache": "^4.4|^5.0|^6.0", "symfony\/event-dispatcher": "^4.4|^5.0|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/http-foundation": "^5.3|^6.0", "symfony\/ldap": "^4.4|^5.0|^6.0", "symfony\/translation": "^5.4.35|~6.3.12|^6.4.3", "symfony\/validator": "^5.2|^6.0", "psr\/log": "^1|^2|^3" }, "conflict": { "symfony\/event-dispatcher": "<4.4", "symfony\/http-foundation": "<5.3", "symfony\/security-guard": "<4.4", "symfony\/ldap": "<4.4", "symfony\/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3", "symfony\/validator": "<5.2" }, "suggest": { "psr\/container-implementation": "To instantiate the Security class", "symfony\/event-dispatcher": "", "symfony\/http-foundation": "", "symfony\/validator": "For using the user password constraint", "symfony\/expression-language": "For using the expression voter", "symfony\/ldap": "For using LDAP integration" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Security\\Core\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Security\Core; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; use _ContaoManager\Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; final class AuthenticationEvents { /** * The AUTHENTICATION_SUCCESS event occurs after a user is authenticated * by one provider. * * @Event("Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent") */ public const AUTHENTICATION_SUCCESS = 'security.authentication.success'; /** * The AUTHENTICATION_FAILURE event occurs after a user cannot be * authenticated by any of the providers. * * @Event("Symfony\Component\Security\Core\Event\AuthenticationFailureEvent") * * @deprecated since Symfony 5.4, use {@see Event\LoginFailureEvent} instead */ public const AUTHENTICATION_FAILURE = 'security.authentication.failure'; /** * Event aliases. * * These aliases can be consumed by RegisterListenersPass. */ public const ALIASES = [AuthenticationSuccessEvent::class => self::AUTHENTICATION_SUCCESS, AuthenticationFailureEvent::class => self::AUTHENTICATION_FAILURE]; } Copyright (c) 2020-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= The changelog is maintained for all Symfony contracts at the following URL: https://github.com/symfony/contracts/blob/main/CHANGELOG.md * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!function_exists('trigger_deprecation')) { /** * Triggers a silenced deprecation notice. * * @param string $package The name of the Composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The message of the deprecation * @param mixed ...$args Values to insert in the message using printf() formatting * * @author Nicolas Grekas */ function trigger_deprecation(string $package, string $version, string $message, ...$args): void { @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); } } Symfony Deprecation Contracts ============================= A generic function and convention to trigger deprecation notices. This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. The function requires at least 3 arguments: - the name of the Composer package that is triggering the deprecation - the version of the package that introduced the deprecation - the message of the deprecation - more arguments can be provided: they will be inserted in the message using `printf()` formatting Example: ```php trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); ``` This will generate the following message: `Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty `function trigger_deprecation() {}` in your application. { "name": "symfony\/deprecation-contracts", "type": "library", "description": "A generic function and convention to trigger deprecation notices", "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "autoload": { "files": [ "function.php" ] }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-main": "2.5-dev" }, "thanks": { "name": "symfony\/contracts", "url": "https:\/\/github.com\/symfony\/contracts" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; /** * CompiledRoutes are returned by the RouteCompiler class. * * @author Fabien Potencier */ class CompiledRoute implements \Serializable { private $variables; private $tokens; private $staticPrefix; private $regex; private $pathVariables; private $hostVariables; private $hostRegex; private $hostTokens; /** * @param string $staticPrefix The static prefix of the compiled route * @param string $regex The regular expression to use to match this route * @param array $tokens An array of tokens to use to generate URL for this route * @param array $pathVariables An array of path variables * @param string|null $hostRegex Host regex * @param array $hostTokens Host tokens * @param array $hostVariables An array of host variables * @param array $variables An array of variables (variables defined in the path and in the host patterns) */ public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, ?string $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = []) { $this->staticPrefix = $staticPrefix; $this->regex = $regex; $this->tokens = $tokens; $this->pathVariables = $pathVariables; $this->hostRegex = $hostRegex; $this->hostTokens = $hostTokens; $this->hostVariables = $hostVariables; $this->variables = $variables; } public function __serialize() : array { return ['vars' => $this->variables, 'path_prefix' => $this->staticPrefix, 'path_regex' => $this->regex, 'path_tokens' => $this->tokens, 'path_vars' => $this->pathVariables, 'host_regex' => $this->hostRegex, 'host_tokens' => $this->hostTokens, 'host_vars' => $this->hostVariables]; } /** * @internal */ public final function serialize() : string { return \serialize($this->__serialize()); } public function __unserialize(array $data) : void { $this->variables = $data['vars']; $this->staticPrefix = $data['path_prefix']; $this->regex = $data['path_regex']; $this->tokens = $data['path_tokens']; $this->pathVariables = $data['path_vars']; $this->hostRegex = $data['host_regex']; $this->hostTokens = $data['host_tokens']; $this->hostVariables = $data['host_vars']; } /** * @internal */ public final function unserialize($serialized) { $this->__unserialize(\unserialize($serialized, ['allowed_classes' => \false])); } /** * Returns the static prefix. * * @return string */ public function getStaticPrefix() { return $this->staticPrefix; } /** * Returns the regex. * * @return string */ public function getRegex() { return $this->regex; } /** * Returns the host regex. * * @return string|null */ public function getHostRegex() { return $this->hostRegex; } /** * Returns the tokens. * * @return array */ public function getTokens() { return $this->tokens; } /** * Returns the host tokens. * * @return array */ public function getHostTokens() { return $this->hostTokens; } /** * Returns the variables. * * @return array */ public function getVariables() { return $this->variables; } /** * Returns the path variables. * * @return array */ public function getPathVariables() { return $this->pathVariables; } /** * Returns the host variables. * * @return array */ public function getHostVariables() { return $this->hostVariables; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; /** * RouteCompiler compiles Route instances to CompiledRoute instances. * * @author Fabien Potencier * @author Tobias Schultze */ class RouteCompiler implements RouteCompilerInterface { /** * @deprecated since Symfony 5.1, to be removed in 6.0 */ public const REGEX_DELIMITER = '#'; /** * This string defines the characters that are automatically considered separators in front of * optional placeholders (with default and no static text following). Such a single separator * can be left out together with the optional placeholder from matching and generating URLs. */ public const SEPARATORS = '/,;.:-_~+*=@|'; /** * The maximum supported length of a PCRE subpattern name * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16. * * @internal */ public const VARIABLE_MAXIMUM_LENGTH = 32; /** * {@inheritdoc} * * @throws \InvalidArgumentException if a path variable is named _fragment * @throws \LogicException if a variable is referenced more than once * @throws \DomainException if a variable name starts with a digit or if it is too long to be successfully used as * a PCRE subpattern */ public static function compile(Route $route) { $hostVariables = []; $variables = []; $hostRegex = null; $hostTokens = []; if ('' !== ($host = $route->getHost())) { $result = self::compilePattern($route, $host, \true); $hostVariables = $result['variables']; $variables = $hostVariables; $hostTokens = $result['tokens']; $hostRegex = $result['regex']; } $locale = $route->getDefault('_locale'); if (null !== $locale && null !== $route->getDefault('_canonical_route') && \preg_quote($locale) === $route->getRequirement('_locale')) { $requirements = $route->getRequirements(); unset($requirements['_locale']); $route->setRequirements($requirements); $route->setPath(\str_replace('{_locale}', $locale, $route->getPath())); } $path = $route->getPath(); $result = self::compilePattern($route, $path, \false); $staticPrefix = $result['staticPrefix']; $pathVariables = $result['variables']; foreach ($pathVariables as $pathParam) { if ('_fragment' === $pathParam) { throw new \InvalidArgumentException(\sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath())); } } $variables = \array_merge($variables, $pathVariables); $tokens = $result['tokens']; $regex = $result['regex']; return new CompiledRoute($staticPrefix, $regex, $tokens, $pathVariables, $hostRegex, $hostTokens, $hostVariables, \array_unique($variables)); } private static function compilePattern(Route $route, string $pattern, bool $isHost) : array { $tokens = []; $variables = []; $matches = []; $pos = 0; $defaultSeparator = $isHost ? '.' : '/'; $useUtf8 = \preg_match('//u', $pattern); $needsUtf8 = $route->getOption('utf8'); if (!$needsUtf8 && $useUtf8 && \preg_match('/[\\x80-\\xFF]/', $pattern)) { throw new \LogicException(\sprintf('Cannot use UTF-8 route patterns without setting the "utf8" option for route "%s".', $route->getPath())); } if (!$useUtf8 && $needsUtf8) { throw new \LogicException(\sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern)); } // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. \preg_match_all('#\\{(!)?(\\w+)\\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER); foreach ($matches as $match) { $important = $match[1][1] >= 0; $varName = $match[2][0]; // get all static text preceding the current variable $precedingText = \substr($pattern, $pos, $match[0][1] - $pos); $pos = $match[0][1] + \strlen($match[0][0]); if (!\strlen($precedingText)) { $precedingChar = ''; } elseif ($useUtf8) { \preg_match('/.$/u', $precedingText, $precedingChar); $precedingChar = $precedingChar[0]; } else { $precedingChar = \substr($precedingText, -1); } $isSeparator = '' !== $precedingChar && \str_contains(static::SEPARATORS, $precedingChar); // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the // variable would not be usable as a Controller action argument. if (\preg_match('/^\\d/', $varName)) { throw new \DomainException(\sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern)); } if (\in_array($varName, $variables)) { throw new \LogicException(\sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); } if (\strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) { throw new \DomainException(\sprintf('Variable name "%s" cannot be longer than %d characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern)); } if ($isSeparator && $precedingText !== $precedingChar) { $tokens[] = ['text', \substr($precedingText, 0, -\strlen($precedingChar))]; } elseif (!$isSeparator && '' !== $precedingText) { $tokens[] = ['text', $precedingText]; } $regexp = $route->getRequirement($varName); if (null === $regexp) { $followingPattern = (string) \substr($pattern, $pos); // Find the next static character after the variable that functions as a separator. By default, this separator and '/' // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are // the same that will be matched. Example: new Route('/{page}.{_format}', ['_format' => 'html']) // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8); $regexp = \sprintf('[^%s%s]+', \preg_quote($defaultSeparator), $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? \preg_quote($nextSeparator) : ''); if ('' !== $nextSeparator && !\preg_match('#^\\{\\w+\\}#', $followingPattern) || '' === $followingPattern) { // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is // directly adjacent, e.g. '/{x}{y}'. $regexp .= '+'; } } else { if (!\preg_match('//u', $regexp)) { $useUtf8 = \false; } elseif (!$needsUtf8 && \preg_match('/[\\x80-\\xFF]|(?= 0; --$i) { $token = $tokens[$i]; // variable is optional when it is not important and has a default value if ('variable' === $token[0] && !($token[5] ?? \false) && $route->hasDefault($token[3])) { $firstOptional = $i; } else { break; } } } // compute the matching regexp $regexp = ''; for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { $regexp .= self::computeRegexp($tokens, $i, $firstOptional); } $regexp = '{^' . $regexp . '$}sD' . ($isHost ? 'i' : ''); // enable Utf8 matching if really required if ($needsUtf8) { $regexp .= 'u'; for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { if ('variable' === $tokens[$i][0]) { $tokens[$i][4] = \true; } } } return ['staticPrefix' => self::determineStaticPrefix($route, $tokens), 'regex' => $regexp, 'tokens' => \array_reverse($tokens), 'variables' => $variables]; } /** * Determines the longest static prefix possible for a route. */ private static function determineStaticPrefix(Route $route, array $tokens) : string { if ('text' !== $tokens[0][0]) { return $route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1] ? '' : $tokens[0][1]; } $prefix = $tokens[0][1]; if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && \false === $route->hasDefault($tokens[1][3])) { $prefix .= $tokens[1][1]; } return $prefix; } /** * Returns the next static character in the Route pattern that will serve as a separator (or the empty string when none available). */ private static function findNextSeparator(string $pattern, bool $useUtf8) : string { if ('' == $pattern) { // return empty string if pattern is empty or false (false which can be returned by substr) return ''; } // first remove all placeholders from the pattern so we can find the next real static character if ('' === ($pattern = \preg_replace('#\\{\\w+\\}#', '', $pattern))) { return ''; } if ($useUtf8) { \preg_match('/^./u', $pattern, $pattern); } return \str_contains(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; } /** * Computes the regexp used to match a specific token. It can be static text or a subpattern. * * @param array $tokens The route tokens * @param int $index The index of the current token * @param int $firstOptional The index of the first optional token */ private static function computeRegexp(array $tokens, int $index, int $firstOptional) : string { $token = $tokens[$index]; if ('text' === $token[0]) { // Text tokens return \preg_quote($token[1]); } else { // Variable tokens if (0 === $index && 0 === $firstOptional) { // When the only token is an optional variable token, the separator is required return \sprintf('%s(?P<%s>%s)?', \preg_quote($token[1]), $token[3], $token[2]); } else { $regexp = \sprintf('%s(?P<%s>%s)', \preg_quote($token[1]), $token[3], $token[2]); if ($index >= $firstOptional) { // Enclose each optional token in a subpattern to make it optional. // "?:" means it is non-capturing, i.e. the portion of the subject string that // matched the optional subpattern is not passed back. $regexp = "(?:{$regexp}"; $nbTokens = \count($tokens); if ($nbTokens - 1 == $index) { // Close the optional subpatterns $regexp .= \str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); } } return $regexp; } } } private static function transformCapturingGroupsToNonCapturings(string $regexp) : string { for ($i = 0; $i < \strlen($regexp); ++$i) { if ('\\' === $regexp[$i]) { ++$i; continue; } if ('(' !== $regexp[$i] || !isset($regexp[$i + 2])) { continue; } if ('*' === $regexp[++$i] || '?' === $regexp[$i]) { ++$i; continue; } $regexp = \substr_replace($regexp, '?:', $i, 0); ++$i; } return $regexp; } } CHANGELOG ========= 5.3 --- * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs * Add support for per-env configuration in XML and Yaml loaders * Deprecate creating instances of the `Route` annotation class by passing an array of parameters * Add `RoutingConfigurator::env()` to get the current environment 5.2.0 ----- * Added support for inline definition of requirements and defaults for host * Added support for `\A` and `\z` as regex start and end for route requirement * Added support for `#[Route]` attributes 5.1.0 ----- * added the protected method `PhpFileLoader::callConfigurator()` as extension point to ease custom routing configuration * deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`. * added "priority" option to annotated routes * added argument `$priority` to `RouteCollection::add()` * deprecated the `RouteCompiler::REGEX_DELIMITER` constant * added `ExpressionLanguageProvider` to expose extra functions to route conditions * added support for a `stateless` keyword for configuring route stateless in PHP, YAML and XML configurations. * added the "hosts" option to be able to configure the host per locale. * added `RequestContext::fromUri()` to ease building the default context 5.0.0 ----- * removed `PhpGeneratorDumper` and `PhpMatcherDumper` * removed `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options * `Serializable` implementing methods for `Route` and `CompiledRoute` are final * removed referencing service route loaders with a single colon * Removed `ServiceRouterLoader` and `ObjectRouteLoader`. 4.4.0 ----- * Deprecated `ServiceRouterLoader` in favor of `ContainerLoader`. * Deprecated `ObjectRouteLoader` in favor of `ObjectLoader`. * Added a way to exclude patterns of resources from being imported by the `import()` method 4.3.0 ----- * added `CompiledUrlMatcher` and `CompiledUrlMatcherDumper` * added `CompiledUrlGenerator` and `CompiledUrlGeneratorDumper` * deprecated `PhpGeneratorDumper` and `PhpMatcherDumper` * deprecated `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options * `Serializable` implementing methods for `Route` and `CompiledRoute` are marked as `@internal` and `@final`. Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible with the new serialization methods in PHP 7.4. * exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators * added support for invokable service route loaders 4.2.0 ----- * added fallback to cultureless locale for internationalized routes 4.0.0 ----- * dropped support for using UTF-8 route patterns without using the `utf8` option * dropped support for using UTF-8 route requirements without using the `utf8` option 3.4.0 ----- * Added `NoConfigurationException`. * Added the possibility to define a prefix for all routes of a controller via @Route(name="prefix_") * Added support for prioritized routing loaders. * Add matched and default parameters to redirect responses * Added support for a `controller` keyword for configuring route controllers in YAML and XML configurations. 3.3.0 ----- * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. * router.options.generator_class * router.options.generator_base_class * router.options.generator_dumper_class * router.options.matcher_class * router.options.matcher_base_class * router.options.matcher_dumper_class * router.options.matcher.cache_class * router.options.generator.cache_class 3.2.0 ----- * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations. * Added support for UTF-8 requirements 2.8.0 ----- * allowed specifying a directory to recursively load all routing configuration files it contains * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded by calling a method on an object/service. * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. Use the constants defined in the `UrlGeneratorInterface` instead. Before: ```php $router->generate('blog_show', ['slug' => 'my-blog-post'], true); ``` After: ```php use Symfony\Component\Routing\Generator\UrlGeneratorInterface; $router->generate('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL); ``` 2.5.0 ----- * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and will be removed in Symfony 3.0, since the performance gains were minimal and it's hard to replicate the behavior of PHP implementation. 2.3.0 ----- * added RequestContext::getQueryString() 2.2.0 ----- * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0): * The `pattern` setting for a route has been deprecated in favor of `path` * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings Before: ```yaml article_edit: pattern: /article/{id} requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' } ``` ```xml POST|PUT https \d+ ``` ```php $route = new Route(); $route->setPattern('/article/{id}'); $route->setRequirement('_method', 'POST|PUT'); $route->setRequirement('_scheme', 'https'); ``` After: ```yaml article_edit: path: /article/{id} methods: [POST, PUT] schemes: https requirements: { 'id': '\d+' } ``` ```xml \d+ ``` ```php $route = new Route(); $route->setPath('/article/{id}'); $route->setMethods(['POST', 'PUT']); $route->setSchemes('https'); ``` * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as a flat array of Routes. So when using PHP to build the RouteCollection, you must make sure to add routes to the sub-collection before adding it to the parent collection (this is not relevant when using YAML or XML for Route definitions). Before: ```php $rootCollection = new RouteCollection(); $subCollection = new RouteCollection(); $rootCollection->addCollection($subCollection); $subCollection->add('foo', new Route('/foo')); ``` After: ```php $rootCollection = new RouteCollection(); $subCollection = new RouteCollection(); $subCollection->add('foo', new Route('/foo')); $rootCollection->addCollection($subCollection); ``` Also one must call `addCollection` from the bottom to the top hierarchy. So the correct sequence is the following (and not the reverse): ```php $childCollection->addCollection($grandchildCollection); $rootCollection->addCollection($childCollection); ``` * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` have been deprecated and will be removed in Symfony 2.3. * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements or options without adding a prefix is not supported anymore. So if you called `addPrefix` with an empty prefix or `/` only (both have no relevance), like `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` you need to use the new dedicated methods `addDefaults($defaultsArray)`, `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated because adding options has nothing to do with adding a path prefix. If you want to add options to all child routes of a RouteCollection, you can use `addOptions()`. * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated because it suggested that all routes in the collection would have this prefix, which is not necessarily true. On top of that, since there is no tree structure anymore, this method is also useless. Don't worry about performance, prefix optimization for matching is still done in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities. * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` will still work, but have been deprecated. The `addPrefix` method should be used for this use-case instead. Before: `$parentCollection->addCollection($collection, '/prefix', [...], [...])` After: ```php $collection->addPrefix('/prefix', [...], [...]); $parentCollection->addCollection($collection); ``` * added support for the method default argument values when defining a @Route * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`. * Characters that function as separator between placeholders are now whitelisted to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`. * [BC BREAK] The default requirement of a variable has been changed slightly. Previously it disallowed the previous and the next char around a variable. Now it disallows the slash (`/`) and the next char. Using the previous char added no value and was problematic because the route `/index.{_format}` would be matched by `/index.ht/ml`. * The default requirement now uses possessive quantifiers when possible which improves matching performance by up to 20% because it prevents backtracking when it's not needed. * The ConfigurableRequirementsInterface can now also be used to disable the requirements check on URL generation completely by calling `setStrictRequirements(null)`. It improves performance in production environment as you should know that params always pass the requirements (otherwise it would break your link anyway). * There is no restriction on the route name anymore. So non-alphanumeric characters are now also allowed. * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static (only relevant if you implemented your own RouteCompiler). * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g. "../parent-file" and "//example.com/dir/file". The third parameter in `UrlGeneratorInterface::generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)` now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for claritiy. The old method calls with a Boolean parameter will continue to work because they equal the signature using the constants. 2.1.0 ----- * added RequestMatcherInterface * added RequestContext::fromRequest() * the UrlMatcher does not throw a \LogicException anymore when the required scheme is not the current one * added TraceableUrlMatcher * added the possibility to define options, default values and requirements for placeholders in prefix, including imported routes * added RouterInterface::getRouteCollection * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they were decoded twice before. Note that the `urldecode()` calls have been changed for a single `rawurldecode()` in order to support `+` for input paths. * added RouteCollection::getRoot method to retrieve the root of a RouteCollection tree * [BC BREAK] made RouteCollection::setParent private which could not have been used anyway without creating inconsistencies * [BC BREAK] RouteCollection::remove also removes a route from parent collections (not only from its children) * added ConfigurableRequirementsInterface that allows to disable exceptions (and generate empty URLs instead) when generating a route with an invalid parameter value * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * Holds information about the current request. * * This class implements a fluent interface. * * @author Fabien Potencier * @author Tobias Schultze */ class RequestContext { private $baseUrl; private $pathInfo; private $method; private $host; private $scheme; private $httpPort; private $httpsPort; private $queryString; private $parameters = []; public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '') { $this->setBaseUrl($baseUrl); $this->setMethod($method); $this->setHost($host); $this->setScheme($scheme); $this->setHttpPort($httpPort); $this->setHttpsPort($httpsPort); $this->setPathInfo($path); $this->setQueryString($queryString); } public static function fromUri(string $uri, string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443) : self { $uri = \parse_url($uri); $scheme = $uri['scheme'] ?? $scheme; $host = $uri['host'] ?? $host; if (isset($uri['port'])) { if ('http' === $scheme) { $httpPort = $uri['port']; } elseif ('https' === $scheme) { $httpsPort = $uri['port']; } } return new self($uri['path'] ?? '', 'GET', $host, $scheme, $httpPort, $httpsPort); } /** * Updates the RequestContext information based on a HttpFoundation Request. * * @return $this */ public function fromRequest(Request $request) { $this->setBaseUrl($request->getBaseUrl()); $this->setPathInfo($request->getPathInfo()); $this->setMethod($request->getMethod()); $this->setHost($request->getHost()); $this->setScheme($request->getScheme()); $this->setHttpPort($request->isSecure() || null === $request->getPort() ? $this->httpPort : $request->getPort()); $this->setHttpsPort($request->isSecure() && null !== $request->getPort() ? $request->getPort() : $this->httpsPort); $this->setQueryString($request->server->get('QUERY_STRING', '')); return $this; } /** * Gets the base URL. * * @return string */ public function getBaseUrl() { return $this->baseUrl; } /** * Sets the base URL. * * @return $this */ public function setBaseUrl(string $baseUrl) { $this->baseUrl = \rtrim($baseUrl, '/'); return $this; } /** * Gets the path info. * * @return string */ public function getPathInfo() { return $this->pathInfo; } /** * Sets the path info. * * @return $this */ public function setPathInfo(string $pathInfo) { $this->pathInfo = $pathInfo; return $this; } /** * Gets the HTTP method. * * The method is always an uppercased string. * * @return string */ public function getMethod() { return $this->method; } /** * Sets the HTTP method. * * @return $this */ public function setMethod(string $method) { $this->method = \strtoupper($method); return $this; } /** * Gets the HTTP host. * * The host is always lowercased because it must be treated case-insensitive. * * @return string */ public function getHost() { return $this->host; } /** * Sets the HTTP host. * * @return $this */ public function setHost(string $host) { $this->host = \strtolower($host); return $this; } /** * Gets the HTTP scheme. * * @return string */ public function getScheme() { return $this->scheme; } /** * Sets the HTTP scheme. * * @return $this */ public function setScheme(string $scheme) { $this->scheme = \strtolower($scheme); return $this; } /** * Gets the HTTP port. * * @return int */ public function getHttpPort() { return $this->httpPort; } /** * Sets the HTTP port. * * @return $this */ public function setHttpPort(int $httpPort) { $this->httpPort = $httpPort; return $this; } /** * Gets the HTTPS port. * * @return int */ public function getHttpsPort() { return $this->httpsPort; } /** * Sets the HTTPS port. * * @return $this */ public function setHttpsPort(int $httpsPort) { $this->httpsPort = $httpsPort; return $this; } /** * Gets the query string without the "?". * * @return string */ public function getQueryString() { return $this->queryString; } /** * Sets the query string. * * @return $this */ public function setQueryString(?string $queryString) { // string cast to be fault-tolerant, accepting null $this->queryString = (string) $queryString; return $this; } /** * Returns the parameters. * * @return array */ public function getParameters() { return $this->parameters; } /** * Sets the parameters. * * @param array $parameters The parameters * * @return $this */ public function setParameters(array $parameters) { $this->parameters = $parameters; return $this; } /** * Gets a parameter value. * * @return mixed */ public function getParameter(string $name) { return $this->parameters[$name] ?? null; } /** * Checks if a parameter value is set for the given parameter. * * @return bool */ public function hasParameter(string $name) { return \array_key_exists($name, $this->parameters); } /** * Sets a parameter value. * * @param mixed $parameter The parameter value * * @return $this */ public function setParameter(string $name, $parameter) { $this->parameters[$name] = $parameter; return $this; } public function isSecure() : bool { return 'https' === $this->scheme; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Annotation; /** * Annotation class for @Route(). * * @Annotation * @NamedArgumentConstructor * @Target({"CLASS", "METHOD"}) * * @author Fabien Potencier * @author Alexander M. Turek */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route { private $path; private $localizedPaths = []; private $name; private $requirements = []; private $options = []; private $defaults = []; private $host; private $methods = []; private $schemes = []; private $condition; private $priority; private $env; /** * @param array|string $data data array managed by the Doctrine Annotations library or the path * @param array|string|null $path * @param string[] $requirements * @param string[]|string $methods * @param string[]|string $schemes * * @throws \BadMethodCallException */ public function __construct($data = [], $path = null, ?string $name = null, array $requirements = [], array $options = [], array $defaults = [], ?string $host = null, $methods = [], $schemes = [], ?string $condition = null, ?int $priority = null, ?string $locale = null, ?string $format = null, ?bool $utf8 = null, ?bool $stateless = null, ?string $env = null) { if (\is_string($data)) { $data = ['path' => $data]; } elseif (!\is_array($data)) { throw new \TypeError(\sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, \get_debug_type($data))); } elseif ([] !== $data) { $deprecation = \false; foreach ($data as $key => $val) { if (\in_array($key, ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env', 'value'])) { $deprecation = \true; } } if ($deprecation) { \trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); } else { $localizedPaths = $data; $data = ['path' => $localizedPaths]; } } if (null !== $path && !\is_string($path) && !\is_array($path)) { throw new \TypeError(\sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, \get_debug_type($path))); } $data['path'] = $data['path'] ?? $path; $data['name'] = $data['name'] ?? $name; $data['requirements'] = $data['requirements'] ?? $requirements; $data['options'] = $data['options'] ?? $options; $data['defaults'] = $data['defaults'] ?? $defaults; $data['host'] = $data['host'] ?? $host; $data['methods'] = $data['methods'] ?? $methods; $data['schemes'] = $data['schemes'] ?? $schemes; $data['condition'] = $data['condition'] ?? $condition; $data['priority'] = $data['priority'] ?? $priority; $data['locale'] = $data['locale'] ?? $locale; $data['format'] = $data['format'] ?? $format; $data['utf8'] = $data['utf8'] ?? $utf8; $data['stateless'] = $data['stateless'] ?? $stateless; $data['env'] = $data['env'] ?? $env; $data = \array_filter($data, static function ($value) : bool { return null !== $value; }); if (isset($data['localized_paths'])) { throw new \BadMethodCallException(\sprintf('Unknown property "localized_paths" on annotation "%s".', static::class)); } if (isset($data['value'])) { $data[\is_array($data['value']) ? 'localized_paths' : 'path'] = $data['value']; unset($data['value']); } if (isset($data['path']) && \is_array($data['path'])) { $data['localized_paths'] = $data['path']; unset($data['path']); } if (isset($data['locale'])) { $data['defaults']['_locale'] = $data['locale']; unset($data['locale']); } if (isset($data['format'])) { $data['defaults']['_format'] = $data['format']; unset($data['format']); } if (isset($data['utf8'])) { $data['options']['utf8'] = \filter_var($data['utf8'], \FILTER_VALIDATE_BOOLEAN) ?: \false; unset($data['utf8']); } if (isset($data['stateless'])) { $data['defaults']['_stateless'] = \filter_var($data['stateless'], \FILTER_VALIDATE_BOOLEAN) ?: \false; unset($data['stateless']); } foreach ($data as $key => $value) { $method = 'set' . \str_replace('_', '', $key); if (!\method_exists($this, $method)) { throw new \BadMethodCallException(\sprintf('Unknown property "%s" on annotation "%s".', $key, static::class)); } $this->{$method}($value); } } public function setPath(string $path) { $this->path = $path; } public function getPath() { return $this->path; } public function setLocalizedPaths(array $localizedPaths) { $this->localizedPaths = $localizedPaths; } public function getLocalizedPaths() : array { return $this->localizedPaths; } public function setHost(string $pattern) { $this->host = $pattern; } public function getHost() { return $this->host; } public function setName(string $name) { $this->name = $name; } public function getName() { return $this->name; } public function setRequirements(array $requirements) { $this->requirements = $requirements; } public function getRequirements() { return $this->requirements; } public function setOptions(array $options) { $this->options = $options; } public function getOptions() { return $this->options; } public function setDefaults(array $defaults) { $this->defaults = $defaults; } public function getDefaults() { return $this->defaults; } public function setSchemes($schemes) { $this->schemes = \is_array($schemes) ? $schemes : [$schemes]; } public function getSchemes() { return $this->schemes; } public function setMethods($methods) { $this->methods = \is_array($methods) ? $methods : [$methods]; } public function getMethods() { return $this->methods; } public function setCondition(?string $condition) { $this->condition = $condition; } public function getCondition() { return $this->condition; } public function setPriority(int $priority) : void { $this->priority = $priority; } public function getPriority() : ?int { return $this->priority; } public function setEnv(?string $env) : void { $this->env = $env; } public function getEnv() : ?string { return $this->env; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Routing\Exception\MethodNotAllowedException; use _ContaoManager\Symfony\Component\Routing\Exception\NoConfigurationException; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; /** * RequestMatcherInterface is the interface that all request matcher classes must implement. * * @author Fabien Potencier */ interface RequestMatcherInterface { /** * Tries to match a request with a set of routes. * * If the matcher cannot find information, it must throw one of the exceptions documented * below. * * @return array * * @throws NoConfigurationException If no routing configuration could be found * @throws ResourceNotFoundException If no matching resource could be found * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed */ public function matchRequest(Request $request); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunction; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceProviderInterface; /** * Exposes functions defined in the request context to route conditions. * * @author Ahmed TAILOULOUTE */ class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface { private $functions; public function __construct(ServiceProviderInterface $functions) { $this->functions = $functions; } /** * {@inheritdoc} */ public function getFunctions() { $functions = []; foreach ($this->functions->getProvidedServices() as $function => $type) { $functions[] = new ExpressionFunction($function, static function (...$args) use($function) { return \sprintf('($context->getParameter(\'_functions\')->get(%s)(%s))', \var_export($function, \true), \implode(', ', $args)); }, function ($values, ...$args) use($function) { return $values['context']->getParameter('_functions')->get($function)(...$args); }); } return $functions; } public function get(string $function) : callable { return $this->functions->get($function); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Routing\Exception\MethodNotAllowedException; use _ContaoManager\Symfony\Component\Routing\Exception\NoConfigurationException; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * UrlMatcher matches URL based on a set of routes. * * @author Fabien Potencier */ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface { public const REQUIREMENT_MATCH = 0; public const REQUIREMENT_MISMATCH = 1; public const ROUTE_MATCH = 2; /** @var RequestContext */ protected $context; /** * Collects HTTP methods that would be allowed for the request. */ protected $allow = []; /** * Collects URI schemes that would be allowed for the request. * * @internal */ protected $allowSchemes = []; protected $routes; protected $request; protected $expressionLanguage; /** * @var ExpressionFunctionProviderInterface[] */ protected $expressionLanguageProviders = []; public function __construct(RouteCollection $routes, RequestContext $context) { $this->routes = $routes; $this->context = $context; } /** * {@inheritdoc} */ public function setContext(RequestContext $context) { $this->context = $context; } /** * {@inheritdoc} */ public function getContext() { return $this->context; } /** * {@inheritdoc} */ public function match(string $pathinfo) { $this->allow = $this->allowSchemes = []; if ($ret = $this->matchCollection(\rawurldecode($pathinfo) ?: '/', $this->routes)) { return $ret; } if ('/' === $pathinfo && !$this->allow && !$this->allowSchemes) { throw new NoConfigurationException(); } throw 0 < \count($this->allow) ? new MethodNotAllowedException(\array_unique($this->allow)) : new ResourceNotFoundException(\sprintf('No routes found for "%s".', $pathinfo)); } /** * {@inheritdoc} */ public function matchRequest(Request $request) { $this->request = $request; $ret = $this->match($request->getPathInfo()); $this->request = null; return $ret; } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) { $this->expressionLanguageProviders[] = $provider; } /** * Tries to match a URL with a set of routes. * * @param string $pathinfo The path info to be parsed * * @return array * * @throws NoConfigurationException If no routing configuration could be found * @throws ResourceNotFoundException If the resource could not be found * @throws MethodNotAllowedException If the resource was found but the request method is not allowed */ protected function matchCollection(string $pathinfo, RouteCollection $routes) { // HEAD and GET are equivalent as per RFC if ('HEAD' === ($method = $this->context->getMethod())) { $method = 'GET'; } $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface; $trimmedPathinfo = \rtrim($pathinfo, '/') ?: '/'; foreach ($routes as $name => $route) { $compiledRoute = $route->compile(); $staticPrefix = \rtrim($compiledRoute->getStaticPrefix(), '/'); $requiredMethods = $route->getMethods(); // check the static prefix of the URL first. Only use the more expensive preg_match when it matches if ('' !== $staticPrefix && !\str_starts_with($trimmedPathinfo, $staticPrefix)) { continue; } $regex = $compiledRoute->getRegex(); $pos = \strrpos($regex, '$'); $hasTrailingSlash = '/' === $regex[$pos - 1]; $regex = \substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); if (!\preg_match($regex, $pathinfo, $matches)) { continue; } $hasTrailingVar = $trimmedPathinfo !== $pathinfo && \preg_match('#\\{\\w+\\}/?$#', $route->getPath()); if ($hasTrailingVar && ($hasTrailingSlash || null === ($m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && \preg_match($regex, $trimmedPathinfo, $m)) { if ($hasTrailingSlash) { $matches = $m; } else { $hasTrailingVar = \false; } } $hostMatches = []; if ($compiledRoute->getHostRegex() && !\preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { continue; } $status = $this->handleRouteRequirements($pathinfo, $name, $route); if (self::REQUIREMENT_MISMATCH === $status[0]) { continue; } if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) { return $this->allow = $this->allowSchemes = []; } continue; } if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) { $this->allowSchemes = \array_merge($this->allowSchemes, $route->getSchemes()); continue; } if ($requiredMethods && !\in_array($method, $requiredMethods)) { $this->allow = \array_merge($this->allow, $requiredMethods); continue; } return $this->getAttributes($route, $name, \array_replace($matches, $hostMatches, $status[1] ?? [])); } return []; } /** * Returns an array of values to use as request attributes. * * As this method requires the Route object, it is not available * in matchers that do not have access to the matched Route instance * (like the PHP and Apache matcher dumpers). * * @return array */ protected function getAttributes(Route $route, string $name, array $attributes) { $defaults = $route->getDefaults(); if (isset($defaults['_canonical_route'])) { $name = $defaults['_canonical_route']; unset($defaults['_canonical_route']); } $attributes['_route'] = $name; return $this->mergeDefaults($attributes, $defaults); } /** * Handles specific route requirements. * * @return array The first element represents the status, the second contains additional information */ protected function handleRouteRequirements(string $pathinfo, string $name, Route $route) { // expression condition if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) { return [self::REQUIREMENT_MISMATCH, null]; } return [self::REQUIREMENT_MATCH, null]; } /** * Get merged default parameters. * * @return array */ protected function mergeDefaults(array $params, array $defaults) { foreach ($params as $key => $value) { if (!\is_int($key) && null !== $value) { $defaults[$key] = $value; } } return $defaults; } protected function getExpressionLanguage() { if (null === $this->expressionLanguage) { if (!\class_exists(ExpressionLanguage::class)) { throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); } return $this->expressionLanguage; } /** * @internal */ protected function createRequest(string $pathinfo) : ?Request { if (!\class_exists(Request::class)) { return null; } return Request::create($this->context->getScheme() . '://' . $this->context->getHost() . $this->context->getBaseUrl() . $pathinfo, $this->context->getMethod(), $this->context->getParameters(), [], [], ['SCRIPT_FILENAME' => $this->context->getBaseUrl(), 'SCRIPT_NAME' => $this->context->getBaseUrl()]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; /** * RedirectableUrlMatcherInterface knows how to redirect the user. * * @author Fabien Potencier */ interface RedirectableUrlMatcherInterface { /** * Redirects the user to another URL and returns the parameters for the redirection. * * @param string $path The path info to redirect to * @param string $route The route name that matched * @param string|null $scheme The URL scheme (null to keep the current one) * * @return array */ public function redirect(string $path, string $route, ?string $scheme = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\Routing\Exception\MethodNotAllowedException; use _ContaoManager\Symfony\Component\Routing\Exception\NoConfigurationException; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; use _ContaoManager\Symfony\Component\Routing\RequestContextAwareInterface; /** * UrlMatcherInterface is the interface that all URL matcher classes must implement. * * @author Fabien Potencier */ interface UrlMatcherInterface extends RequestContextAwareInterface { /** * Tries to match a URL path with a set of routes. * * If the matcher cannot find information, it must throw one of the exceptions documented * below. * * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) * * @return array * * @throws NoConfigurationException If no routing configuration could be found * @throws ResourceNotFoundException If the resource could not be found * @throws MethodNotAllowedException If the resource was found but the request method is not allowed */ public function match(string $pathinfo); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherTrait; use _ContaoManager\Symfony\Component\Routing\RequestContext; /** * Matches URLs based on rules dumped by CompiledUrlMatcherDumper. * * @author Nicolas Grekas */ class CompiledUrlMatcher extends UrlMatcher { use CompiledUrlMatcherTrait; public function __construct(array $compiledRoutes, RequestContext $context) { $this->context = $context; [$this->matchHost, $this->staticRoutes, $this->regexpList, $this->dynamicRoutes, $this->checkCondition] = $compiledRoutes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Routing\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * TraceableUrlMatcher helps debug path info matching by tracing the match. * * @author Fabien Potencier */ class TraceableUrlMatcher extends UrlMatcher { public const ROUTE_DOES_NOT_MATCH = 0; public const ROUTE_ALMOST_MATCHES = 1; public const ROUTE_MATCHES = 2; protected $traces; public function getTraces(string $pathinfo) { $this->traces = []; try { $this->match($pathinfo); } catch (ExceptionInterface $e) { } return $this->traces; } public function getTracesForRequest(Request $request) { $this->request = $request; $traces = $this->getTraces($request->getPathInfo()); $this->request = null; return $traces; } protected function matchCollection(string $pathinfo, RouteCollection $routes) { // HEAD and GET are equivalent as per RFC if ('HEAD' === ($method = $this->context->getMethod())) { $method = 'GET'; } $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface; $trimmedPathinfo = \rtrim($pathinfo, '/') ?: '/'; foreach ($routes as $name => $route) { $compiledRoute = $route->compile(); $staticPrefix = \rtrim($compiledRoute->getStaticPrefix(), '/'); $requiredMethods = $route->getMethods(); // check the static prefix of the URL first. Only use the more expensive preg_match when it matches if ('' !== $staticPrefix && !\str_starts_with($trimmedPathinfo, $staticPrefix)) { $this->addTrace(\sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); continue; } $regex = $compiledRoute->getRegex(); $pos = \strrpos($regex, '$'); $hasTrailingSlash = '/' === $regex[$pos - 1]; $regex = \substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); if (!\preg_match($regex, $pathinfo, $matches)) { // does it match without any requirements? $r = new Route($route->getPath(), $route->getDefaults(), [], $route->getOptions()); $cr = $r->compile(); if (!\preg_match($cr->getRegex(), $pathinfo)) { $this->addTrace(\sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); continue; } foreach ($route->getRequirements() as $n => $regex) { $r = new Route($route->getPath(), $route->getDefaults(), [$n => $regex], $route->getOptions()); $cr = $r->compile(); if (\in_array($n, $cr->getVariables()) && !\preg_match($cr->getRegex(), $pathinfo)) { $this->addTrace(\sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); continue 2; } } continue; } $hasTrailingVar = $trimmedPathinfo !== $pathinfo && \preg_match('#\\{\\w+\\}/?$#', $route->getPath()); if ($hasTrailingVar && ($hasTrailingSlash || null === ($m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && \preg_match($regex, $trimmedPathinfo, $m)) { if ($hasTrailingSlash) { $matches = $m; } else { $hasTrailingVar = \false; } } $hostMatches = []; if ($compiledRoute->getHostRegex() && !\preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { $this->addTrace(\sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); continue; } $status = $this->handleRouteRequirements($pathinfo, $name, $route); if (self::REQUIREMENT_MISMATCH === $status[0]) { $this->addTrace(\sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route); continue; } if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) { $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); return $this->allow = $this->allowSchemes = []; } $this->addTrace(\sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); continue; } if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) { $this->allowSchemes = \array_merge($this->allowSchemes, $route->getSchemes()); $this->addTrace(\sprintf('Scheme "%s" does not match any of the required schemes (%s)', $this->context->getScheme(), \implode(', ', $route->getSchemes())), self::ROUTE_ALMOST_MATCHES, $name, $route); continue; } if ($requiredMethods && !\in_array($method, $requiredMethods)) { $this->allow = \array_merge($this->allow, $requiredMethods); $this->addTrace(\sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), \implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); continue; } $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); return $this->getAttributes($route, $name, \array_replace($matches, $hostMatches, $status[1] ?? [])); } return []; } private function addTrace(string $log, int $level = self::ROUTE_DOES_NOT_MATCH, ?string $name = null, ?Route $route = null) { $this->traces[] = ['log' => $log, 'name' => $name, 'level' => $level, 'path' => null !== $route ? $route->getPath() : null]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher\Dumper; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * CompiledUrlMatcherDumper creates PHP arrays to be used with CompiledUrlMatcher. * * @author Fabien Potencier * @author Tobias Schultze * @author Arnaud Le Blanc * @author Nicolas Grekas */ class CompiledUrlMatcherDumper extends MatcherDumper { private $expressionLanguage; private $signalingException; /** * @var ExpressionFunctionProviderInterface[] */ private $expressionLanguageProviders = []; /** * {@inheritdoc} */ public function dump(array $options = []) { return <<generateCompiledRoutes()}]; EOF; } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) { $this->expressionLanguageProviders[] = $provider; } /** * Generates the arrays for CompiledUrlMatcher's constructor. */ public function getCompiledRoutes(bool $forDump = \false) : array { // Group hosts by same-suffix, re-order when possible $matchHost = \false; $routes = new StaticPrefixCollection(); foreach ($this->getRoutes()->all() as $name => $route) { if ($host = $route->getHost()) { $matchHost = \true; $host = '/' . \strtr(\strrev($host), '}.{', '(/)'); } $routes->addRoute($host ?: '/(.*)', [$name, $route]); } if ($matchHost) { $compiledRoutes = [\true]; $routes = $routes->populateCollection(new RouteCollection()); } else { $compiledRoutes = [\false]; $routes = $this->getRoutes(); } [$staticRoutes, $dynamicRoutes] = $this->groupStaticRoutes($routes); $conditions = [null]; $compiledRoutes[] = $this->compileStaticRoutes($staticRoutes, $conditions); $chunkLimit = \count($dynamicRoutes); while (\true) { try { $this->signalingException = new \RuntimeException('Compilation failed: regular expression is too large'); $compiledRoutes = \array_merge($compiledRoutes, $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit, $conditions)); break; } catch (\Exception $e) { if (1 < $chunkLimit && $this->signalingException === $e) { $chunkLimit = 1 + ($chunkLimit >> 1); continue; } throw $e; } } if ($forDump) { $compiledRoutes[2] = $compiledRoutes[4]; } unset($conditions[0]); if ($conditions) { foreach ($conditions as $expression => $condition) { $conditions[$expression] = "case {$condition}: return {$expression};"; } $checkConditionCode = <<indent(\implode("\n", $conditions), 3)} } } EOF; $compiledRoutes[4] = $forDump ? $checkConditionCode . ",\n" : eval('return ' . $checkConditionCode . ';'); } else { $compiledRoutes[4] = $forDump ? " null, // \$checkCondition\n" : null; } return $compiledRoutes; } private function generateCompiledRoutes() : string { [$matchHost, $staticRoutes, $regexpCode, $dynamicRoutes, $checkConditionCode] = $this->getCompiledRoutes(\true); $code = self::export($matchHost) . ', // $matchHost' . "\n"; $code .= '[ // $staticRoutes' . "\n"; foreach ($staticRoutes as $path => $routes) { $code .= \sprintf(" %s => [\n", self::export($path)); foreach ($routes as $route) { $code .= \vsprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", \array_map([__CLASS__, 'export'], $route)); } $code .= " ],\n"; } $code .= "],\n"; $code .= \sprintf("[ // \$regexpList%s\n],\n", $regexpCode); $code .= '[ // $dynamicRoutes' . "\n"; foreach ($dynamicRoutes as $path => $routes) { $code .= \sprintf(" %s => [\n", self::export($path)); foreach ($routes as $route) { $code .= \vsprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", \array_map([__CLASS__, 'export'], $route)); } $code .= " ],\n"; } $code .= "],\n"; $code = \preg_replace('/ => \\[\\n (\\[.+?),\\n \\],/', ' => [$1],', $code); return $this->indent($code, 1) . $checkConditionCode; } /** * Splits static routes from dynamic routes, so that they can be matched first, using a simple switch. */ private function groupStaticRoutes(RouteCollection $collection) : array { $staticRoutes = $dynamicRegex = []; $dynamicRoutes = new RouteCollection(); foreach ($collection->all() as $name => $route) { $compiledRoute = $route->compile(); $staticPrefix = \rtrim($compiledRoute->getStaticPrefix(), '/'); $hostRegex = $compiledRoute->getHostRegex(); $regex = $compiledRoute->getRegex(); if ($hasTrailingSlash = '/' !== $route->getPath()) { $pos = \strrpos($regex, '$'); $hasTrailingSlash = '/' === $regex[$pos - 1]; $regex = \substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); } if (!$compiledRoute->getPathVariables()) { $host = !$compiledRoute->getHostVariables() ? $route->getHost() : ''; $url = $route->getPath(); if ($hasTrailingSlash) { $url = \substr($url, 0, -1); } foreach ($dynamicRegex as [$hostRx, $rx, $prefix]) { if (('' === $prefix || \str_starts_with($url, $prefix)) && (\preg_match($rx, $url) || \preg_match($rx, $url . '/')) && (!$host || !$hostRx || \preg_match($hostRx, $host))) { $dynamicRegex[] = [$hostRegex, $regex, $staticPrefix]; $dynamicRoutes->add($name, $route); continue 2; } } $staticRoutes[$url][$name] = [$route, $hasTrailingSlash]; } else { $dynamicRegex[] = [$hostRegex, $regex, $staticPrefix]; $dynamicRoutes->add($name, $route); } } return [$staticRoutes, $dynamicRoutes]; } /** * Compiles static routes in a switch statement. * * Condition-less paths are put in a static array in the switch's default, with generic matching logic. * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. * * @throws \LogicException */ private function compileStaticRoutes(array $staticRoutes, array &$conditions) : array { if (!$staticRoutes) { return []; } $compiledRoutes = []; foreach ($staticRoutes as $url => $routes) { $compiledRoutes[$url] = []; foreach ($routes as $name => [$route, $hasTrailingSlash]) { $compiledRoutes[$url][] = $this->compileRoute($route, $name, (!$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex()) ?: null, $hasTrailingSlash, \false, $conditions); } } return $compiledRoutes; } /** * Compiles a regular expression followed by a switch statement to match dynamic routes. * * The regular expression matches both the host and the pathinfo at the same time. For stellar performance, * it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible. * * Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23). * This name is used to "switch" to the additional logic required to match the final route. * * Condition-less paths are put in a static array in the switch's default, with generic matching logic. * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. * * Last but not least: * - Because it is not possible to mix unicode/non-unicode patterns in a single regexp, several of them can be generated. * - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the * matching-but-failing subpattern is excluded by replacing its name by "(*F)", which forces a failure-to-match. * To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur. */ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit, array &$conditions) : array { if (!$collection->all()) { return [[], [], '']; } $regexpList = []; $code = ''; $state = (object) ['regexMark' => 0, 'regex' => [], 'routes' => [], 'mark' => 0, 'markTail' => 0, 'hostVars' => [], 'vars' => []]; $state->getVars = static function ($m) use($state) { if ('_route' === $m[1]) { return '?:'; } $state->vars[] = $m[1]; return ''; }; $chunkSize = 0; $prev = null; $perModifiers = []; foreach ($collection->all() as $name => $route) { \preg_match('#[a-zA-Z]*$#', $route->compile()->getRegex(), $rx); if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()->getPathVariables()) { $chunkSize = 1; $routes = new RouteCollection(); $perModifiers[] = [$rx[0], $routes]; $prev = $rx[0]; } $routes->add($name, $route); } foreach ($perModifiers as [$modifiers, $routes]) { $prev = \false; $perHost = []; foreach ($routes->all() as $name => $route) { $regex = $route->compile()->getHostRegex(); if ($prev !== $regex) { $routes = new RouteCollection(); $perHost[] = [$regex, $routes]; $prev = $regex; } $routes->add($name, $route); } $prev = \false; $rx = '{^(?'; $code .= "\n {$state->mark} => " . self::export($rx); $startingMark = $state->mark; $state->mark += \strlen($rx); $state->regex = $rx; foreach ($perHost as [$hostRegex, $routes]) { if ($matchHost) { if ($hostRegex) { \preg_match('#^.\\^(.*)\\$.[a-zA-Z]*$#', $hostRegex, $rx); $state->vars = []; $hostRegex = '(?i:' . \preg_replace_callback('#\\?P<([^>]++)>#', $state->getVars, $rx[1]) . ')\\.'; $state->hostVars = $state->vars; } else { $hostRegex = '(?:(?:[^./]*+\\.)++)'; $state->hostVars = []; } $state->mark += \strlen($rx = ($prev ? ')' : '') . "|{$hostRegex}(?"); $code .= "\n ." . self::export($rx); $state->regex .= $rx; $prev = \true; } $tree = new StaticPrefixCollection(); foreach ($routes->all() as $name => $route) { \preg_match('#^.\\^(.*)\\$.[a-zA-Z]*$#', $route->compile()->getRegex(), $rx); $state->vars = []; $regex = \preg_replace_callback('#\\?P<([^>]++)>#', $state->getVars, $rx[1]); if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) { $regex = \substr($regex, 0, -1); } $hasTrailingVar = (bool) \preg_match('#\\{\\w+\\}/?$#', $route->getPath()); $tree->addRoute($regex, [$name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar]); } $code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions); } if ($matchHost) { $code .= "\n .')'"; $state->regex .= ')'; } $rx = ")/?\$}{$modifiers}"; $code .= "\n .'{$rx}',"; $state->regex .= $rx; $state->markTail = 0; // if the regex is too large, throw a signaling exception to recompute with smaller chunk size \set_error_handler(function ($type, $message) { throw \str_contains($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); }); try { \preg_match($state->regex, ''); } finally { \restore_error_handler(); } $regexpList[$startingMark] = $state->regex; } $state->routes[$state->mark][] = [null, null, null, null, \false, \false, 0]; unset($state->getVars); return [$regexpList, $state->routes, $code]; } /** * Compiles a regexp tree of subpatterns that matches nested same-prefix routes. * * @param \stdClass $state A simple state object that keeps track of the progress of the compilation, * and gathers the generated switch's "case" and "default" statements */ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \stdClass $state, int $prefixLen, array &$conditions) : string { $code = ''; $prevRegex = null; $routes = $tree->getRoutes(); foreach ($routes as $i => $route) { if ($route instanceof StaticPrefixCollection) { $prevRegex = null; $prefix = \substr($route->getPrefix(), $prefixLen); $state->mark += \strlen($rx = "|{$prefix}(?"); $code .= "\n ." . self::export($rx); $state->regex .= $rx; $code .= $this->indent($this->compileStaticPrefixCollection($route, $state, $prefixLen + \strlen($prefix), $conditions)); $code .= "\n .')'"; $state->regex .= ')'; ++$state->markTail; continue; } [$name, $regex, $vars, $route, $hasTrailingSlash, $hasTrailingVar] = $route; $compiledRoute = $route->compile(); $vars = \array_merge($state->hostVars, $vars); if ($compiledRoute->getRegex() === $prevRegex) { $state->routes[$state->mark][] = $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions); continue; } $state->mark += 3 + $state->markTail + \strlen($regex) - $prefixLen; $state->markTail = 2 + \strlen($state->mark); $rx = \sprintf('|%s(*:%s)', \substr($regex, $prefixLen), $state->mark); $code .= "\n ." . self::export($rx); $state->regex .= $rx; $prevRegex = $compiledRoute->getRegex(); $state->routes[$state->mark] = [$this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions)]; } return $code; } /** * Compiles a single Route to PHP code used to match it against the path info. */ private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions) : array { $defaults = $route->getDefaults(); if (isset($defaults['_canonical_route'])) { $name = $defaults['_canonical_route']; unset($defaults['_canonical_route']); } if ($condition = $route->getCondition()) { $condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request']); $condition = $conditions[$condition] ?? ($conditions[$condition] = (\str_contains($condition, '$request') ? 1 : -1) * \count($conditions)); } else { $condition = null; } return [['_route' => $name] + $defaults, $vars, \array_flip($route->getMethods()) ?: null, \array_flip($route->getSchemes()) ?: null, $hasTrailingSlash, $hasTrailingVar, $condition]; } private function getExpressionLanguage() : ExpressionLanguage { if (null === $this->expressionLanguage) { if (!\class_exists(ExpressionLanguage::class)) { throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); } return $this->expressionLanguage; } private function indent(string $code, int $level = 1) : string { return \preg_replace('/^./m', \str_repeat(' ', $level) . '$0', $code); } /** * @internal */ public static function export($value) : string { if (null === $value) { return 'null'; } if (!\is_array($value)) { if (\is_object($value)) { throw new \InvalidArgumentException('Symfony\\Component\\Routing\\Route cannot contain objects.'); } return \str_replace("\n", '\'."\\n".\'', \var_export($value, \true)); } if (!$value) { return '[]'; } $i = 0; $export = '['; foreach ($value as $k => $v) { if ($i === $k) { ++$i; } else { $export .= self::export($k) . ' => '; if (\is_int($k) && $i < $k) { $i = 1 + $k; } } $export .= self::export($v) . ', '; } return \substr_replace($export, ']', -2); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher\Dumper; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * MatcherDumper is the abstract class for all built-in matcher dumpers. * * @author Fabien Potencier */ abstract class MatcherDumper implements MatcherDumperInterface { private $routes; public function __construct(RouteCollection $routes) { $this->routes = $routes; } /** * {@inheritdoc} */ public function getRoutes() { return $this->routes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher\Dumper; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * MatcherDumperInterface is the interface that all matcher dumper classes must implement. * * @author Fabien Potencier */ interface MatcherDumperInterface { /** * Dumps a set of routes to a string representation of executable code * that can then be used to match a request against these routes. * * @return string */ public function dump(array $options = []); /** * Gets the routes to dump. * * @return RouteCollection */ public function getRoutes(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher\Dumper; use _ContaoManager\Symfony\Component\Routing\Exception\MethodNotAllowedException; use _ContaoManager\Symfony\Component\Routing\Exception\NoConfigurationException; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; use _ContaoManager\Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; use _ContaoManager\Symfony\Component\Routing\RequestContext; /** * @author Nicolas Grekas * * @internal * * @property RequestContext $context */ trait CompiledUrlMatcherTrait { private $matchHost = \false; private $staticRoutes = []; private $regexpList = []; private $dynamicRoutes = []; /** * @var callable|null */ private $checkCondition; public function match(string $pathinfo) : array { $allow = $allowSchemes = []; if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) { return $ret; } if ($allow) { throw new MethodNotAllowedException(\array_keys($allow)); } if (!$this instanceof RedirectableUrlMatcherInterface) { throw new ResourceNotFoundException(\sprintf('No routes found for "%s".', $pathinfo)); } if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], \true)) { // no-op } elseif ($allowSchemes) { redirect_scheme: $scheme = $this->context->getScheme(); $this->context->setScheme(\key($allowSchemes)); try { if ($ret = $this->doMatch($pathinfo)) { return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret; } } finally { $this->context->setScheme($scheme); } } elseif ('/' !== ($trimmedPathinfo = \rtrim($pathinfo, '/') ?: '/')) { $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo . '/' : $trimmedPathinfo; if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) { return $this->redirect($pathinfo, $ret['_route']) + $ret; } if ($allowSchemes) { goto redirect_scheme; } } throw new ResourceNotFoundException(\sprintf('No routes found for "%s".', $pathinfo)); } private function doMatch(string $pathinfo, array &$allow = [], array &$allowSchemes = []) : array { $allow = $allowSchemes = []; $pathinfo = \rawurldecode($pathinfo) ?: '/'; $trimmedPathinfo = \rtrim($pathinfo, '/') ?: '/'; $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); if ($this->matchHost) { $host = \strtolower($context->getHost()); } if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } $supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface; foreach ($this->staticRoutes[$trimmedPathinfo] ?? [] as [$ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition]) { if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? ($request = $this->request ?: $this->createRequest($pathinfo)) : null)) { continue; } if ($requiredHost) { if ('{' !== $requiredHost[0] ? $requiredHost !== $host : !\preg_match($requiredHost, $host, $hostMatches)) { continue; } if ('{' === $requiredHost[0] && $hostMatches) { $hostMatches['_route'] = $ret['_route']; $ret = $this->mergeDefaults($hostMatches, $ret); } } if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) { return $allow = $allowSchemes = []; } continue; } $hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]); if ($hasRequiredScheme && $requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { $allow += $requiredMethods; continue; } if (!$hasRequiredScheme) { $allowSchemes += $requiredSchemes; continue; } return $ret; } $matchedPathinfo = $this->matchHost ? $host . '.' . $pathinfo : $pathinfo; foreach ($this->regexpList as $offset => $regex) { while (\preg_match($regex, $matchedPathinfo, $matches)) { foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as [$ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition]) { if (null !== $condition) { if (0 === $condition) { // marks the last route in the regexp continue 3; } if (!($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? ($request = $this->request ?: $this->createRequest($pathinfo)) : null)) { continue; } } $hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar; if ($hasTrailingVar && ($hasTrailingSlash || null === ($n = $matches[\count($vars)] ?? null) || '/' !== ($n[-1] ?? '/')) && \preg_match($regex, $this->matchHost ? $host . '.' . $trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) { if ($hasTrailingSlash) { $matches = $n; } else { $hasTrailingVar = \false; } } if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) { return $allow = $allowSchemes = []; } continue; } foreach ($vars as $i => $v) { if (isset($matches[1 + $i])) { $ret[$v] = $matches[1 + $i]; } } if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { $allowSchemes += $requiredSchemes; continue; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { $allow += $requiredMethods; continue; } return $ret; } $regex = \substr_replace($regex, 'F', $m - $offset, 1 + \strlen($m)); $offset += \strlen($m); } } if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new NoConfigurationException(); } return []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher\Dumper; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * Prefix tree of routes preserving routes order. * * @author Frank de Jonge * @author Nicolas Grekas * * @internal */ class StaticPrefixCollection { private $prefix; /** * @var string[] */ private $staticPrefixes = []; /** * @var string[] */ private $prefixes = []; /** * @var array[]|self[] */ private $items = []; public function __construct(string $prefix = '/') { $this->prefix = $prefix; } public function getPrefix() : string { return $this->prefix; } /** * @return array[]|self[] */ public function getRoutes() : array { return $this->items; } /** * Adds a route to a group. * * @param array|self $route */ public function addRoute(string $prefix, $route) { [$prefix, $staticPrefix] = $this->getCommonPrefix($prefix, $prefix); for ($i = \count($this->items) - 1; 0 <= $i; --$i) { $item = $this->items[$i]; [$commonPrefix, $commonStaticPrefix] = $this->getCommonPrefix($prefix, $this->prefixes[$i]); if ($this->prefix === $commonPrefix) { // the new route and a previous one have no common prefix, let's see if they are exclusive to each others if ($this->prefix !== $staticPrefix && $this->prefix !== $this->staticPrefixes[$i]) { // the new route and the previous one have exclusive static prefixes continue; } if ($this->prefix === $staticPrefix && $this->prefix === $this->staticPrefixes[$i]) { // the new route and the previous one have no static prefix break; } if ($this->prefixes[$i] !== $this->staticPrefixes[$i] && $this->prefix === $this->staticPrefixes[$i]) { // the previous route is non-static and has no static prefix break; } if ($prefix !== $staticPrefix && $this->prefix === $staticPrefix) { // the new route is non-static and has no static prefix break; } continue; } if ($item instanceof self && $this->prefixes[$i] === $commonPrefix) { // the new route is a child of a previous one, let's nest it $item->addRoute($prefix, $route); } else { // the new route and a previous one have a common prefix, let's merge them $child = new self($commonPrefix); [$child->prefixes[0], $child->staticPrefixes[0]] = $child->getCommonPrefix($this->prefixes[$i], $this->prefixes[$i]); [$child->prefixes[1], $child->staticPrefixes[1]] = $child->getCommonPrefix($prefix, $prefix); $child->items = [$this->items[$i], $route]; $this->staticPrefixes[$i] = $commonStaticPrefix; $this->prefixes[$i] = $commonPrefix; $this->items[$i] = $child; } return; } // No optimised case was found, in this case we simple add the route for possible // grouping when new routes are added. $this->staticPrefixes[] = $staticPrefix; $this->prefixes[] = $prefix; $this->items[] = $route; } /** * Linearizes back a set of nested routes into a collection. */ public function populateCollection(RouteCollection $routes) : RouteCollection { foreach ($this->items as $route) { if ($route instanceof self) { $route->populateCollection($routes); } else { $routes->add(...$route); } } return $routes; } /** * Gets the full and static common prefixes between two route patterns. * * The static prefix stops at last at the first opening bracket. */ private function getCommonPrefix(string $prefix, string $anotherPrefix) : array { $baseLength = \strlen($this->prefix); $end = \min(\strlen($prefix), \strlen($anotherPrefix)); $staticLength = null; \set_error_handler([__CLASS__, 'handleError']); try { for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) { if ('(' === $prefix[$i]) { $staticLength = $staticLength ?? $i; for ($j = 1 + $i, $n = 1; $j < $end && 0 < $n; ++$j) { if ($prefix[$j] !== $anotherPrefix[$j]) { break 2; } if ('(' === $prefix[$j]) { ++$n; } elseif (')' === $prefix[$j]) { --$n; } elseif ('\\' === $prefix[$j] && (++$j === $end || $prefix[$j] !== $anotherPrefix[$j])) { --$j; break; } } if (0 < $n) { break; } if (('?' === ($prefix[$j] ?? '') || '?' === ($anotherPrefix[$j] ?? '')) && ($prefix[$j] ?? '') !== ($anotherPrefix[$j] ?? '')) { break; } $subPattern = \substr($prefix, $i, $j - $i); if ($prefix !== $anotherPrefix && !\preg_match('/^\\(\\[[^\\]]++\\]\\+\\+\\)$/', $subPattern) && !\preg_match('{(?> 6 && \preg_match('//u', $prefix . ' ' . $anotherPrefix)) { do { // Prevent cutting in the middle of an UTF-8 characters --$i; } while (0b10 === \ord($prefix[$i]) >> 6); } return [\substr($prefix, 0, $i), \substr($prefix, 0, $staticLength ?? $i)]; } public static function handleError(int $type, string $msg) { return \str_contains($msg, 'Compilation failed: lookbehind assertion is not fixed length'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Matcher; use _ContaoManager\Symfony\Component\Routing\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\Routing\Exception\ResourceNotFoundException; /** * @author Fabien Potencier */ abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface { /** * {@inheritdoc} */ public function match(string $pathinfo) { try { return parent::match($pathinfo); } catch (ResourceNotFoundException $e) { if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], \true)) { throw $e; } if ($this->allowSchemes) { redirect_scheme: $scheme = $this->context->getScheme(); $this->context->setScheme(\current($this->allowSchemes)); try { $ret = parent::match($pathinfo); return $this->redirect($pathinfo, $ret['_route'] ?? null, $this->context->getScheme()) + $ret; } catch (ExceptionInterface $e2) { throw $e; } finally { $this->context->setScheme($scheme); } } elseif ('/' === ($trimmedPathinfo = \rtrim($pathinfo, '/') ?: '/')) { throw $e; } else { try { $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo . '/' : $trimmedPathinfo; $ret = parent::match($pathinfo); return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret; } catch (ExceptionInterface $e2) { if ($this->allowSchemes) { goto redirect_scheme; } throw $e; } } } } } Routing Component ================= The Routing component maps an HTTP request to a set of configuration variables. Getting Started --------------- ``` $ composer require symfony/routing ``` ```php use App\Controller\BlogController; use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; $route = new Route('/blog/{slug}', ['_controller' => BlogController::class]); $routes = new RouteCollection(); $routes->add('blog_show', $route); $context = new RequestContext(); // Routing can match routes with incoming requests $matcher = new UrlMatcher($routes, $context); $parameters = $matcher->match('/blog/lorem-ipsum'); // $parameters = [ // '_controller' => 'App\Controller\BlogController', // 'slug' => 'lorem-ipsum', // '_route' => 'blog_show' // ] // Routing can also generate URLs for a given route $generator = new UrlGenerator($routes, $context); $url = $generator->generate('blog_show', [ 'slug' => 'my-blog-post', ]); // $url = '/blog/my-blog-post' ``` Resources --------- * [Documentation](https://symfony.com/doc/current/routing.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Routing\Exception\RouteNotFoundException; use _ContaoManager\Symfony\Component\Routing\RequestContext; /** * Generates URLs based on rules dumped by CompiledUrlGeneratorDumper. */ class CompiledUrlGenerator extends UrlGenerator { private $compiledRoutes = []; private $defaultLocale; public function __construct(array $compiledRoutes, RequestContext $context, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->compiledRoutes = $compiledRoutes; $this->context = $context; $this->logger = $logger; $this->defaultLocale = $defaultLocale; } public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) { $locale = $parameters['_locale'] ?? $this->context->getParameter('_locale') ?: $this->defaultLocale; if (null !== $locale) { do { if (($this->compiledRoutes[$name . '.' . $locale][1]['_canonical_route'] ?? null) === $name) { $name .= '.' . $locale; break; } } while (\false !== ($locale = \strstr($locale, '_', \true))); } if (!isset($this->compiledRoutes[$name])) { throw new RouteNotFoundException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); } [$variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes, $deprecations] = $this->compiledRoutes[$name] + [6 => []]; foreach ($deprecations as $deprecation) { \trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { if (!\in_array('_locale', $variables, \true)) { unset($parameters['_locale']); } elseif (!isset($parameters['_locale'])) { $parameters['_locale'] = $defaults['_locale']; } } return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Routing\Exception\InvalidParameterException; use _ContaoManager\Symfony\Component\Routing\Exception\MissingMandatoryParametersException; use _ContaoManager\Symfony\Component\Routing\Exception\RouteNotFoundException; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * UrlGenerator can generate a URL or a path for any route in the RouteCollection * based on the passed parameters. * * @author Fabien Potencier * @author Tobias Schultze */ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface { private const QUERY_FRAGMENT_DECODED = [ // RFC 3986 explicitly allows those in the query/fragment to reference other URIs unencoded '%2F' => '/', '%3F' => '?', // reserved chars that have no special meaning for HTTP URIs in a query or fragment // this excludes esp. "&", "=" and also "+" because PHP would treat it as a space (form-encoded) '%40' => '@', '%3A' => ':', '%21' => '!', '%3B' => ';', '%2C' => ',', '%2A' => '*', ]; protected $routes; protected $context; /** * @var bool|null */ protected $strictRequirements = \true; protected $logger; private $defaultLocale; /** * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. * * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. * "?" and "#" (would be interpreted wrongly as query and fragment identifier), * "'" and """ (are used as delimiters in HTML). */ protected $decodedChars = [ // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning // some webservers don't allow the slash in encoded form in the path for security reasons anyway // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss '%2F' => '/', '%252F' => '%2F', // the following chars are general delimiters in the URI specification but have only special meaning in the authority component // so they can safely be used in the path in unencoded form '%40' => '@', '%3A' => ':', // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability '%3B' => ';', '%2C' => ',', '%3D' => '=', '%2B' => '+', '%21' => '!', '%2A' => '*', '%7C' => '|', ]; public function __construct(RouteCollection $routes, RequestContext $context, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->routes = $routes; $this->context = $context; $this->logger = $logger; $this->defaultLocale = $defaultLocale; } /** * {@inheritdoc} */ public function setContext(RequestContext $context) { $this->context = $context; } /** * {@inheritdoc} */ public function getContext() { return $this->context; } /** * {@inheritdoc} */ public function setStrictRequirements(?bool $enabled) { $this->strictRequirements = $enabled; } /** * {@inheritdoc} */ public function isStrictRequirements() { return $this->strictRequirements; } /** * {@inheritdoc} */ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) { $route = null; $locale = $parameters['_locale'] ?? $this->context->getParameter('_locale') ?: $this->defaultLocale; if (null !== $locale) { do { if (null !== ($route = $this->routes->get($name . '.' . $locale)) && $route->getDefault('_canonical_route') === $name) { break; } } while (\false !== ($locale = \strstr($locale, '_', \true))); } if (null === ($route = $route ?? $this->routes->get($name))) { throw new RouteNotFoundException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); } // the Route has a cache of its own and is not recompiled as long as it does not get modified $compiledRoute = $route->compile(); $defaults = $route->getDefaults(); $variables = $compiledRoute->getVariables(); if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { if (!\in_array('_locale', $variables, \true)) { unset($parameters['_locale']); } elseif (!isset($parameters['_locale'])) { $parameters['_locale'] = $defaults['_locale']; } } return $this->doGenerate($variables, $defaults, $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); } /** * @return string * * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route * @throws InvalidParameterException When a parameter value for a placeholder is not correct because * it does not match the requirement */ protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = []) { $variables = \array_flip($variables); $mergedParams = \array_replace($defaults, $this->context->getParameters(), $parameters); // all params must be given if ($diff = \array_diff_key($variables, $mergedParams)) { throw new MissingMandatoryParametersException(\sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', \implode('", "', \array_keys($diff)), $name)); } $url = ''; $optional = \true; $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; foreach ($tokens as $token) { if ('variable' === $token[0]) { $varName = $token[3]; // variable is not important by default $important = $token[5] ?? \false; if (!$optional || $important || !\array_key_exists($varName, $defaults) || null !== $mergedParams[$varName] && (string) $mergedParams[$varName] !== (string) $defaults[$varName]) { // check requirement (while ignoring look-around patterns) if (null !== $this->strictRequirements && !\preg_match('#^' . \preg_replace('/\\(\\?(?:=|<=|!|strictRequirements) { throw new InvalidParameterException(\strtr($message, ['{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]])); } if ($this->logger) { $this->logger->error($message, ['parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName]]); } return ''; } $url = $token[1] . $mergedParams[$varName] . $url; $optional = \false; } } else { // static text $url = $token[1] . $url; $optional = \false; } } if ('' === $url) { $url = '/'; } // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) $url = \strtr(\rawurlencode($url), $this->decodedChars); // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 // so we need to encode them as they are not used for this purpose here // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route $url = \strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/']); if (\str_ends_with($url, '/..')) { $url = \substr($url, 0, -2) . '%2E%2E'; } elseif (\str_ends_with($url, '/.')) { $url = \substr($url, 0, -1) . '%2E'; } $schemeAuthority = ''; $host = $this->context->getHost(); $scheme = $this->context->getScheme(); if ($requiredSchemes) { if (!\in_array($scheme, $requiredSchemes, \true)) { $referenceType = self::ABSOLUTE_URL; $scheme = \current($requiredSchemes); } } if ($hostTokens) { $routeHost = ''; foreach ($hostTokens as $token) { if ('variable' === $token[0]) { // check requirement (while ignoring look-around patterns) if (null !== $this->strictRequirements && !\preg_match('#^' . \preg_replace('/\\(\\?(?:=|<=|!|strictRequirements) { throw new InvalidParameterException(\strtr($message, ['{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]])); } if ($this->logger) { $this->logger->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]); } return ''; } $routeHost = $token[1] . $mergedParams[$token[3]] . $routeHost; } else { $routeHost = $token[1] . $routeHost; } } if ($routeHost !== $host) { $host = $routeHost; if (self::ABSOLUTE_URL !== $referenceType) { $referenceType = self::NETWORK_PATH; } } } if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { if ('' !== $host || '' !== $scheme && 'http' !== $scheme && 'https' !== $scheme) { $port = ''; if ('http' === $scheme && 80 !== $this->context->getHttpPort()) { $port = ':' . $this->context->getHttpPort(); } elseif ('https' === $scheme && 443 !== $this->context->getHttpsPort()) { $port = ':' . $this->context->getHttpsPort(); } $schemeAuthority = self::NETWORK_PATH === $referenceType || '' === $scheme ? '//' : "{$scheme}://"; $schemeAuthority .= $host . $port; } } if (self::RELATIVE_PATH === $referenceType) { $url = self::getRelativePath($this->context->getPathInfo(), $url); } else { $url = $schemeAuthority . $this->context->getBaseUrl() . $url; } // add a query string if needed $extra = \array_udiff_assoc(\array_diff_key($parameters, $variables), $defaults, function ($a, $b) { return $a == $b ? 0 : 1; }); \array_walk_recursive($extra, $caster = static function (&$v) use(&$caster) { if (\is_object($v)) { if ($vars = \get_object_vars($v)) { \array_walk_recursive($vars, $caster); $v = $vars; } elseif (\method_exists($v, '__toString')) { $v = (string) $v; } } }); // extract fragment $fragment = $defaults['_fragment'] ?? ''; if (isset($extra['_fragment'])) { $fragment = $extra['_fragment']; unset($extra['_fragment']); } if ($extra && ($query = \http_build_query($extra, '', '&', \PHP_QUERY_RFC3986))) { $url .= '?' . \strtr($query, self::QUERY_FRAGMENT_DECODED); } if ('' !== $fragment) { $url .= '#' . \strtr(\rawurlencode($fragment), self::QUERY_FRAGMENT_DECODED); } return $url; } /** * Returns the target path as relative reference from the base path. * * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. * Both paths must be absolute and not contain relative parts. * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. * Furthermore, they can be used to reduce the link size in documents. * * Example target paths, given a base path of "/a/b/c/d": * - "/a/b/c/d" -> "" * - "/a/b/c/" -> "./" * - "/a/b/" -> "../" * - "/a/b/c/other" -> "other" * - "/a/x/y" -> "../../x/y" * * @param string $basePath The base path * @param string $targetPath The target path * * @return string */ public static function getRelativePath(string $basePath, string $targetPath) { if ($basePath === $targetPath) { return ''; } $sourceDirs = \explode('/', isset($basePath[0]) && '/' === $basePath[0] ? \substr($basePath, 1) : $basePath); $targetDirs = \explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? \substr($targetPath, 1) : $targetPath); \array_pop($sourceDirs); $targetFile = \array_pop($targetDirs); foreach ($sourceDirs as $i => $dir) { if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { unset($sourceDirs[$i], $targetDirs[$i]); } else { break; } } $targetDirs[] = $targetFile; $path = \str_repeat('../', \count($sourceDirs)) . \implode('/', $targetDirs); // A reference to the same base directory or an empty subdirectory must be prefixed with "./". // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used // as the first segment of a relative-path reference, as it would be mistaken for a scheme name // (see http://tools.ietf.org/html/rfc3986#section-4.2). return '' === $path || '/' === $path[0] || \false !== ($colonPos = \strpos($path, ':')) && ($colonPos < ($slashPos = \strpos($path, '/')) || \false === $slashPos) ? "./{$path}" : $path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator; /** * ConfigurableRequirementsInterface must be implemented by URL generators that * can be configured whether an exception should be generated when the parameters * do not match the requirements. It is also possible to disable the requirements * check for URL generation completely. * * The possible configurations and use-cases: * - setStrictRequirements(true): Throw an exception for mismatching requirements. This * is mostly useful in development environment. * - setStrictRequirements(false): Don't throw an exception but return an empty string as URL for * mismatching requirements and log the problem. Useful when you cannot control all * params because they come from third party libs but don't want to have a 404 in * production environment. It should log the mismatch so one can review it. * - setStrictRequirements(null): Return the URL with the given parameters without * checking the requirements at all. When generating a URL you should either trust * your params or you validated them beforehand because otherwise it would break your * link anyway. So in production environment you should know that params always pass * the requirements. Thus this option allows to disable the check on URL generation for * performance reasons (saving a preg_match for each requirement every time a URL is * generated). * * @author Fabien Potencier * @author Tobias Schultze */ interface ConfigurableRequirementsInterface { /** * Enables or disables the exception on incorrect parameters. * Passing null will deactivate the requirements check completely. */ public function setStrictRequirements(?bool $enabled); /** * Returns whether to throw an exception on incorrect parameters. * Null means the requirements check is deactivated completely. * * @return bool|null */ public function isStrictRequirements(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator; use _ContaoManager\Symfony\Component\Routing\Exception\InvalidParameterException; use _ContaoManager\Symfony\Component\Routing\Exception\MissingMandatoryParametersException; use _ContaoManager\Symfony\Component\Routing\Exception\RouteNotFoundException; use _ContaoManager\Symfony\Component\Routing\RequestContextAwareInterface; /** * UrlGeneratorInterface is the interface that all URL generator classes must implement. * * The constants in this interface define the different types of resource references that * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 * We are using the term "URL" instead of "URI" as this is more common in web applications * and we do not need to distinguish them as the difference is mostly semantical and * less technical. Generating URIs, i.e. representation-independent resource identifiers, * is also possible. * * @author Fabien Potencier * @author Tobias Schultze */ interface UrlGeneratorInterface extends RequestContextAwareInterface { /** * Generates an absolute URL, e.g. "http://example.com/dir/file". */ public const ABSOLUTE_URL = 0; /** * Generates an absolute path, e.g. "/dir/file". */ public const ABSOLUTE_PATH = 1; /** * Generates a relative path based on the current request path, e.g. "../parent-file". * * @see UrlGenerator::getRelativePath() */ public const RELATIVE_PATH = 2; /** * Generates a network path, e.g. "//example.com/dir/file". * Such reference reuses the current scheme but specifies the host. */ public const NETWORK_PATH = 3; /** * Generates a URL or path for a specific route based on the given parameters. * * Parameters that reference placeholders in the route pattern will substitute them in the * path or host. Extra params are added as query string to the URL. * * When the passed reference type cannot be generated for the route because it requires a different * host or scheme than the current one, the method will return a more comprehensive reference * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH * but the route requires the https scheme whereas the current scheme is http, it will instead return an * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches * the route in any case. * * If there is no route with the given name, the generator must throw the RouteNotFoundException. * * The special parameter _fragment will be used as the document fragment suffixed to the final URL. * * @return string * * @throws RouteNotFoundException If the named route doesn't exist * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route * @throws InvalidParameterException When a parameter value for a placeholder is not correct because * it does not match the requirement */ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator\Dumper; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * GeneratorDumperInterface is the interface that all generator dumper classes must implement. * * @author Fabien Potencier */ interface GeneratorDumperInterface { /** * Dumps a set of routes to a string representation of executable code * that can then be used to generate a URL of such a route. * * @return string */ public function dump(array $options = []); /** * Gets the routes to dump. * * @return RouteCollection */ public function getRoutes(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator\Dumper; use _ContaoManager\Symfony\Component\Routing\Exception\RouteCircularReferenceException; use _ContaoManager\Symfony\Component\Routing\Exception\RouteNotFoundException; use _ContaoManager\Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; /** * CompiledUrlGeneratorDumper creates a PHP array to be used with CompiledUrlGenerator. * * @author Fabien Potencier * @author Tobias Schultze * @author Nicolas Grekas */ class CompiledUrlGeneratorDumper extends GeneratorDumper { public function getCompiledRoutes() : array { $compiledRoutes = []; foreach ($this->getRoutes()->all() as $name => $route) { $compiledRoute = $route->compile(); $compiledRoutes[$name] = [$compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $compiledRoute->getHostTokens(), $route->getSchemes(), []]; } return $compiledRoutes; } public function getCompiledAliases() : array { $routes = $this->getRoutes(); $compiledAliases = []; foreach ($routes->getAliases() as $name => $alias) { $deprecations = $alias->isDeprecated() ? [$alias->getDeprecation($name)] : []; $currentId = $alias->getId(); $visited = []; while (null !== ($alias = $routes->getAlias($currentId) ?? null)) { if (\false !== ($searchKey = \array_search($currentId, $visited))) { $visited[] = $currentId; throw new RouteCircularReferenceException($currentId, \array_slice($visited, $searchKey)); } if ($alias->isDeprecated()) { $deprecations[] = $deprecation = $alias->getDeprecation($currentId); \trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } $visited[] = $currentId; $currentId = $alias->getId(); } if (null === ($target = $routes->get($currentId))) { throw new RouteNotFoundException(\sprintf('Target route "%s" for alias "%s" does not exist.', $currentId, $name)); } $compiledTarget = $target->compile(); $compiledAliases[$name] = [$compiledTarget->getVariables(), $target->getDefaults(), $target->getRequirements(), $compiledTarget->getTokens(), $compiledTarget->getHostTokens(), $target->getSchemes(), $deprecations]; } return $compiledAliases; } /** * {@inheritdoc} */ public function dump(array $options = []) { return <<generateDeclaredRoutes()} ]; EOF; } /** * Generates PHP code representing an array of defined routes * together with the routes properties (e.g. requirements). */ private function generateDeclaredRoutes() : string { $routes = ''; foreach ($this->getCompiledRoutes() as $name => $properties) { $routes .= \sprintf("\n '%s' => %s,", $name, CompiledUrlMatcherDumper::export($properties)); } foreach ($this->getCompiledAliases() as $alias => $properties) { $routes .= \sprintf("\n '%s' => %s,", $alias, CompiledUrlMatcherDumper::export($properties)); } return $routes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Generator\Dumper; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * GeneratorDumper is the base class for all built-in generator dumpers. * * @author Fabien Potencier */ abstract class GeneratorDumper implements GeneratorDumperInterface { private $routes; public function __construct(RouteCollection $routes) { $this->routes = $routes; } /** * {@inheritdoc} */ public function getRoutes() { return $this->routes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; use _ContaoManager\Symfony\Component\Routing\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Routing\Exception\RouteCircularReferenceException; /** * A RouteCollection represents a set of Route instances. * * When adding a route at the end of the collection, an existing route * with the same name is removed first. So there can only be one route * with a given name. * * @author Fabien Potencier * @author Tobias Schultze * * @implements \IteratorAggregate */ class RouteCollection implements \IteratorAggregate, \Countable { /** * @var array */ private $routes = []; /** * @var array */ private $aliases = []; /** * @var array */ private $resources = []; /** * @var array */ private $priorities = []; public function __clone() { foreach ($this->routes as $name => $route) { $this->routes[$name] = clone $route; } foreach ($this->aliases as $name => $alias) { $this->aliases[$name] = clone $alias; } } /** * Gets the current RouteCollection as an Iterator that includes all routes. * * It implements \IteratorAggregate. * * @see all() * * @return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->all()); } /** * Gets the number of Routes in this collection. * * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->routes); } /** * @param int $priority */ public function add(string $name, Route $route) { if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \_ContaoManager\PHPUnit\Framework\MockObject\MockObject && !$this instanceof \_ContaoManager\Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \_ContaoManager\Mockery\MockInterface) { \trigger_deprecation('symfony/routing', '5.1', 'The "%s()" method will have a new "int $priority = 0" argument in version 6.0, not defining it is deprecated.', __METHOD__); } unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); $this->routes[$name] = $route; if ($priority = 3 <= \func_num_args() ? \func_get_arg(2) : 0) { $this->priorities[$name] = $priority; } } /** * Returns all routes in this collection. * * @return array */ public function all() { if ($this->priorities) { $priorities = $this->priorities; $keysOrder = \array_flip(\array_keys($this->routes)); \uksort($this->routes, static function ($n1, $n2) use($priorities, $keysOrder) { return ($priorities[$n2] ?? 0) <=> ($priorities[$n1] ?? 0) ?: $keysOrder[$n1] <=> $keysOrder[$n2]; }); } return $this->routes; } /** * Gets a route by name. * * @return Route|null */ public function get(string $name) { $visited = []; while (null !== ($alias = $this->aliases[$name] ?? null)) { if (\false !== ($searchKey = \array_search($name, $visited))) { $visited[] = $name; throw new RouteCircularReferenceException($name, \array_slice($visited, $searchKey)); } if ($alias->isDeprecated()) { $deprecation = $alias->getDeprecation($name); \trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } $visited[] = $name; $name = $alias->getId(); } return $this->routes[$name] ?? null; } /** * Removes a route or an array of routes by name from the collection. * * @param string|string[] $name The route name or an array of route names */ public function remove($name) { $routes = []; foreach ((array) $name as $n) { if (isset($this->routes[$n])) { $routes[] = $n; } unset($this->routes[$n], $this->priorities[$n], $this->aliases[$n]); } if (!$routes) { return; } foreach ($this->aliases as $k => $alias) { if (\in_array($alias->getId(), $routes, \true)) { unset($this->aliases[$k]); } } } /** * Adds a route collection at the end of the current set by appending all * routes of the added collection. */ public function addCollection(self $collection) { // we need to remove all routes with the same names first because just replacing them // would not place the new route at the end of the merged array foreach ($collection->all() as $name => $route) { unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); $this->routes[$name] = $route; if (isset($collection->priorities[$name])) { $this->priorities[$name] = $collection->priorities[$name]; } } foreach ($collection->getAliases() as $name => $alias) { unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); $this->aliases[$name] = $alias; } foreach ($collection->getResources() as $resource) { $this->addResource($resource); } } /** * Adds a prefix to the path of all child routes. */ public function addPrefix(string $prefix, array $defaults = [], array $requirements = []) { $prefix = \trim(\trim($prefix), '/'); if ('' === $prefix) { return; } foreach ($this->routes as $route) { $route->setPath('/' . $prefix . $route->getPath()); $route->addDefaults($defaults); $route->addRequirements($requirements); } } /** * Adds a prefix to the name of all the routes within in the collection. */ public function addNamePrefix(string $prefix) { $prefixedRoutes = []; $prefixedPriorities = []; $prefixedAliases = []; foreach ($this->routes as $name => $route) { $prefixedRoutes[$prefix . $name] = $route; if (null !== ($canonicalName = $route->getDefault('_canonical_route'))) { $route->setDefault('_canonical_route', $prefix . $canonicalName); } if (isset($this->priorities[$name])) { $prefixedPriorities[$prefix . $name] = $this->priorities[$name]; } } foreach ($this->aliases as $name => $alias) { $prefixedAliases[$prefix . $name] = $alias->withId($prefix . $alias->getId()); } $this->routes = $prefixedRoutes; $this->priorities = $prefixedPriorities; $this->aliases = $prefixedAliases; } /** * Sets the host pattern on all routes. */ public function setHost(?string $pattern, array $defaults = [], array $requirements = []) { foreach ($this->routes as $route) { $route->setHost($pattern); $route->addDefaults($defaults); $route->addRequirements($requirements); } } /** * Sets a condition on all routes. * * Existing conditions will be overridden. */ public function setCondition(?string $condition) { foreach ($this->routes as $route) { $route->setCondition($condition); } } /** * Adds defaults to all routes. * * An existing default value under the same name in a route will be overridden. */ public function addDefaults(array $defaults) { if ($defaults) { foreach ($this->routes as $route) { $route->addDefaults($defaults); } } } /** * Adds requirements to all routes. * * An existing requirement under the same name in a route will be overridden. */ public function addRequirements(array $requirements) { if ($requirements) { foreach ($this->routes as $route) { $route->addRequirements($requirements); } } } /** * Adds options to all routes. * * An existing option value under the same name in a route will be overridden. */ public function addOptions(array $options) { if ($options) { foreach ($this->routes as $route) { $route->addOptions($options); } } } /** * Sets the schemes (e.g. 'https') all child routes are restricted to. * * @param string|string[] $schemes The scheme or an array of schemes */ public function setSchemes($schemes) { foreach ($this->routes as $route) { $route->setSchemes($schemes); } } /** * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. * * @param string|string[] $methods The method or an array of methods */ public function setMethods($methods) { foreach ($this->routes as $route) { $route->setMethods($methods); } } /** * Returns an array of resources loaded to build this collection. * * @return ResourceInterface[] */ public function getResources() { return \array_values($this->resources); } /** * Adds a resource for this collection. If the resource already exists * it is not added. */ public function addResource(ResourceInterface $resource) { $key = (string) $resource; if (!isset($this->resources[$key])) { $this->resources[$key] = $resource; } } /** * Sets an alias for an existing route. * * @param string $name The alias to create * @param string $alias The route to alias * * @throws InvalidArgumentException if the alias is for itself */ public function addAlias(string $name, string $alias) : Alias { if ($name === $alias) { throw new InvalidArgumentException(\sprintf('Route alias "%s" can not reference itself.', $name)); } unset($this->routes[$name], $this->priorities[$name]); return $this->aliases[$name] = new Alias($alias); } /** * @return array */ public function getAliases() : array { return $this->aliases; } public function getAlias(string $name) : ?Alias { return $this->aliases[$name] ?? null; } public function getPriority(string $name) : ?int { return $this->priorities[$name] ?? null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; interface RequestContextAwareInterface { /** * Sets the request context. */ public function setContext(RequestContext $context); /** * Gets the request context. * * @return RequestContext */ public function getContext(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Doctrine\Common\Annotations\Reader; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\Config\Loader\LoaderResolverInterface; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Routing\Annotation\Route as RouteAnnotation; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * AnnotationClassLoader loads routing information from a PHP class and its methods. * * You need to define an implementation for the configureRoute() method. Most of the * time, this method should define some PHP callable to be called for the route * (a controller in MVC speak). * * The @Route annotation can be set on the class (for global parameters), * and on each method. * * The @Route annotation main value is the route path. The annotation also * recognizes several parameters: requirements, options, defaults, schemes, * methods, host, and name. The name parameter is mandatory. * Here is an example of how you should be able to use it: * /** * * @Route("/Blog") * * / * class Blog * { * /** * * @Route("/", name="blog_index") * * / * public function index() * { * } * /** * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) * * / * public function show() * { * } * } * * On PHP 8, the annotation class can be used as an attribute as well: * #[Route('/Blog')] * class Blog * { * #[Route('/', name: 'blog_index')] * public function index() * { * } * #[Route('/{id}', name: 'blog_post', requirements: ["id" => '\d+'])] * public function show() * { * } * } * * @author Fabien Potencier * @author Alexander M. Turek */ abstract class AnnotationClassLoader implements LoaderInterface { protected $reader; protected $env; /** * @var string */ protected $routeAnnotationClass = RouteAnnotation::class; /** * @var int */ protected $defaultRouteIndex = 0; public function __construct(?Reader $reader = null, ?string $env = null) { $this->reader = $reader; $this->env = $env; } /** * Sets the annotation class to read route properties from. */ public function setRouteAnnotationClass(string $class) { $this->routeAnnotationClass = $class; } /** * Loads from annotations from a class. * * @param string $class A class name * * @return RouteCollection * * @throws \InvalidArgumentException When route can't be parsed */ public function load($class, ?string $type = null) { if (!\class_exists($class)) { throw new \InvalidArgumentException(\sprintf('Class "%s" does not exist.', $class)); } $class = new \ReflectionClass($class); if ($class->isAbstract()) { throw new \InvalidArgumentException(\sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName())); } $globals = $this->getGlobals($class); $collection = new RouteCollection(); $collection->addResource(new FileResource($class->getFileName())); if ($globals['env'] && $this->env !== $globals['env']) { return $collection; } foreach ($class->getMethods() as $method) { $this->defaultRouteIndex = 0; foreach ($this->getAnnotations($method) as $annot) { $this->addRoute($collection, $annot, $globals, $class, $method); } } if (0 === $collection->count() && $class->hasMethod('__invoke')) { $globals = $this->resetGlobals(); foreach ($this->getAnnotations($class) as $annot) { $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); } } return $collection; } /** * @param RouteAnnotation $annot or an object that exposes a similar interface */ protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method) { if ($annot->getEnv() && $annot->getEnv() !== $this->env) { return; } $name = $annot->getName(); if (null === $name) { $name = $this->getDefaultRouteName($class, $method); } $name = $globals['name'] . $name; $requirements = $annot->getRequirements(); foreach ($requirements as $placeholder => $requirement) { if (\is_int($placeholder)) { throw new \InvalidArgumentException(\sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName())); } } $defaults = \array_replace($globals['defaults'], $annot->getDefaults()); $requirements = \array_replace($globals['requirements'], $requirements); $options = \array_replace($globals['options'], $annot->getOptions()); $schemes = \array_merge($globals['schemes'], $annot->getSchemes()); $methods = \array_merge($globals['methods'], $annot->getMethods()); $host = $annot->getHost(); if (null === $host) { $host = $globals['host']; } $condition = $annot->getCondition() ?? $globals['condition']; $priority = $annot->getPriority() ?? $globals['priority']; $path = $annot->getLocalizedPaths() ?: $annot->getPath(); $prefix = $globals['localized_paths'] ?: $globals['path']; $paths = []; if (\is_array($path)) { if (!\is_array($prefix)) { foreach ($path as $locale => $localePath) { $paths[$locale] = $prefix . $localePath; } } elseif ($missing = \array_diff_key($prefix, $path)) { throw new \LogicException(\sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name . '::' . $method->name, \implode('", "', \array_keys($missing)))); } else { foreach ($path as $locale => $localePath) { if (!isset($prefix[$locale])) { throw new \LogicException(\sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name)); } $paths[$locale] = $prefix[$locale] . $localePath; } } } elseif (\is_array($prefix)) { foreach ($prefix as $locale => $localePrefix) { $paths[$locale] = $localePrefix . $path; } } else { $paths[] = $prefix . $path; } foreach ($method->getParameters() as $param) { if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) { continue; } foreach ($paths as $locale => $path) { if (\preg_match(\sprintf('/\\{%s(?:<.*?>)?\\}/', \preg_quote($param->name)), $path)) { $defaults[$param->name] = $param->getDefaultValue(); break; } } } foreach ($paths as $locale => $path) { $route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); $this->configureRoute($route, $class, $method, $annot); if (0 !== $locale) { $route->setDefault('_locale', $locale); $route->setRequirement('_locale', \preg_quote($locale)); $route->setDefault('_canonical_route', $name); $collection->add($name . '.' . $locale, $route, $priority); } else { $collection->add($name, $route, $priority); } } } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return \is_string($resource) && \preg_match('/^(?:\\\\?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); } /** * {@inheritdoc} */ public function setResolver(LoaderResolverInterface $resolver) { } /** * {@inheritdoc} */ public function getResolver() { } /** * Gets the default route name for a class method. * * @return string */ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) { $name = \str_replace('\\', '_', $class->name) . '_' . $method->name; $name = \function_exists('mb_strtolower') && \preg_match('//u', $name) ? \mb_strtolower($name, 'UTF-8') : \strtolower($name); if ($this->defaultRouteIndex > 0) { $name .= '_' . $this->defaultRouteIndex; } ++$this->defaultRouteIndex; return $name; } protected function getGlobals(\ReflectionClass $class) { $globals = $this->resetGlobals(); $annot = null; if (\PHP_VERSION_ID >= 80000 && ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)) { $annot = $attribute->newInstance(); } if (!$annot && $this->reader) { $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass); } if ($annot) { if (null !== $annot->getName()) { $globals['name'] = $annot->getName(); } if (null !== $annot->getPath()) { $globals['path'] = $annot->getPath(); } $globals['localized_paths'] = $annot->getLocalizedPaths(); if (null !== $annot->getRequirements()) { $globals['requirements'] = $annot->getRequirements(); } if (null !== $annot->getOptions()) { $globals['options'] = $annot->getOptions(); } if (null !== $annot->getDefaults()) { $globals['defaults'] = $annot->getDefaults(); } if (null !== $annot->getSchemes()) { $globals['schemes'] = $annot->getSchemes(); } if (null !== $annot->getMethods()) { $globals['methods'] = $annot->getMethods(); } if (null !== $annot->getHost()) { $globals['host'] = $annot->getHost(); } if (null !== $annot->getCondition()) { $globals['condition'] = $annot->getCondition(); } $globals['priority'] = $annot->getPriority() ?? 0; $globals['env'] = $annot->getEnv(); foreach ($globals['requirements'] as $placeholder => $requirement) { if (\is_int($placeholder)) { throw new \InvalidArgumentException(\sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?', $placeholder, $requirement, $class->getName())); } } } return $globals; } private function resetGlobals() : array { return ['path' => null, 'localized_paths' => [], 'requirements' => [], 'options' => [], 'defaults' => [], 'schemes' => [], 'methods' => [], 'host' => '', 'condition' => '', 'name' => '', 'priority' => 0, 'env' => null]; } protected function createRoute(string $path, array $defaults, array $requirements, array $options, ?string $host, array $schemes, array $methods, ?string $condition) { return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); } protected abstract function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot); /** * @param \ReflectionClass|\ReflectionMethod $reflection * * @return iterable */ private function getAnnotations(object $reflection) : iterable { if (\PHP_VERSION_ID >= 80000) { foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { (yield $attribute->newInstance()); } } if (!$this->reader) { return; } $anntotations = $reflection instanceof \ReflectionClass ? $this->reader->getClassAnnotations($reflection) : $this->reader->getMethodAnnotations($reflection); foreach ($anntotations as $annotation) { if ($annotation instanceof $this->routeAnnotationClass) { (yield $annotation); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; use _ContaoManager\Symfony\Component\Routing\RouteCollection; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; use _ContaoManager\Symfony\Component\Yaml\Parser as YamlParser; use _ContaoManager\Symfony\Component\Yaml\Yaml; /** * YamlFileLoader loads Yaml routing files. * * @author Fabien Potencier * @author Tobias Schultze */ class YamlFileLoader extends FileLoader { use HostTrait; use LocalizedRouteTrait; use PrefixTrait; private const AVAILABLE_KEYS = ['resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless']; private $yamlParser; /** * Loads a Yaml file. * * @param string $file A Yaml file path * @param string|null $type The resource type * * @return RouteCollection * * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid */ public function load($file, ?string $type = null) { $path = $this->locator->locate($file); if (!\stream_is_local($path)) { throw new \InvalidArgumentException(\sprintf('This is not a local file "%s".', $path)); } if (!\file_exists($path)) { throw new \InvalidArgumentException(\sprintf('File "%s" not found.', $path)); } if (null === $this->yamlParser) { $this->yamlParser = new YamlParser(); } try { $parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT); } catch (ParseException $e) { throw new \InvalidArgumentException(\sprintf('The file "%s" does not contain valid YAML: ', $path) . $e->getMessage(), 0, $e); } $collection = new RouteCollection(); $collection->addResource(new FileResource($path)); // empty file if (null === $parsedConfig) { return $collection; } // not an array if (!\is_array($parsedConfig)) { throw new \InvalidArgumentException(\sprintf('The file "%s" must contain a YAML array.', $path)); } foreach ($parsedConfig as $name => $config) { if (0 === \strpos($name, 'when@')) { if (!$this->env || 'when@' . $this->env !== $name) { continue; } foreach ($config as $name => $config) { $this->validate($config, $name . '" when "@' . $this->env, $path); if (isset($config['resource'])) { $this->parseImport($collection, $config, $path, $file); } else { $this->parseRoute($collection, $name, $config, $path); } } continue; } $this->validate($config, $name, $path); if (isset($config['resource'])) { $this->parseImport($collection, $config, $path, $file); } else { $this->parseRoute($collection, $name, $config, $path); } } return $collection; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return \is_string($resource) && \in_array(\pathinfo($resource, \PATHINFO_EXTENSION), ['yml', 'yaml'], \true) && (!$type || 'yaml' === $type); } /** * Parses a route and adds it to the RouteCollection. */ protected function parseRoute(RouteCollection $collection, string $name, array $config, string $path) { if (isset($config['alias'])) { $alias = $collection->addAlias($name, $config['alias']); $deprecation = $config['deprecated'] ?? null; if (null !== $deprecation) { $alias->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message'] ?? ''); } return; } $defaults = $config['defaults'] ?? []; $requirements = $config['requirements'] ?? []; $options = $config['options'] ?? []; foreach ($requirements as $placeholder => $requirement) { if (\is_int($placeholder)) { throw new \InvalidArgumentException(\sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s"?', $placeholder, $requirement, $name, $path)); } } if (isset($config['controller'])) { $defaults['_controller'] = $config['controller']; } if (isset($config['locale'])) { $defaults['_locale'] = $config['locale']; } if (isset($config['format'])) { $defaults['_format'] = $config['format']; } if (isset($config['utf8'])) { $options['utf8'] = $config['utf8']; } if (isset($config['stateless'])) { $defaults['_stateless'] = $config['stateless']; } $routes = $this->createLocalizedRoute($collection, $name, $config['path']); $routes->addDefaults($defaults); $routes->addRequirements($requirements); $routes->addOptions($options); $routes->setSchemes($config['schemes'] ?? []); $routes->setMethods($config['methods'] ?? []); $routes->setCondition($config['condition'] ?? null); if (isset($config['host'])) { $this->addHost($routes, $config['host']); } } /** * Parses an import and adds the routes in the resource to the RouteCollection. */ protected function parseImport(RouteCollection $collection, array $config, string $path, string $file) { $type = $config['type'] ?? null; $prefix = $config['prefix'] ?? ''; $defaults = $config['defaults'] ?? []; $requirements = $config['requirements'] ?? []; $options = $config['options'] ?? []; $host = $config['host'] ?? null; $condition = $config['condition'] ?? null; $schemes = $config['schemes'] ?? null; $methods = $config['methods'] ?? null; $trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? \true; $namePrefix = $config['name_prefix'] ?? null; $exclude = $config['exclude'] ?? null; if (isset($config['controller'])) { $defaults['_controller'] = $config['controller']; } if (isset($config['locale'])) { $defaults['_locale'] = $config['locale']; } if (isset($config['format'])) { $defaults['_format'] = $config['format']; } if (isset($config['utf8'])) { $options['utf8'] = $config['utf8']; } if (isset($config['stateless'])) { $defaults['_stateless'] = $config['stateless']; } $this->setCurrentDir(\dirname($path)); /** @var RouteCollection[] $imported */ $imported = $this->import($config['resource'], $type, \false, $file, $exclude) ?: []; if (!\is_array($imported)) { $imported = [$imported]; } foreach ($imported as $subCollection) { $this->addPrefix($subCollection, $prefix, $trailingSlashOnRoot); if (null !== $host) { $this->addHost($subCollection, $host); } if (null !== $condition) { $subCollection->setCondition($condition); } if (null !== $schemes) { $subCollection->setSchemes($schemes); } if (null !== $methods) { $subCollection->setMethods($methods); } if (null !== $namePrefix) { $subCollection->addNamePrefix($namePrefix); } $subCollection->addDefaults($defaults); $subCollection->addRequirements($requirements); $subCollection->addOptions($options); $collection->addCollection($subCollection); } } /** * Validates the route configuration. * * @param array $config A resource config * @param string $name The config key * @param string $path The loaded file path * * @throws \InvalidArgumentException If one of the provided config keys is not supported, * something is missing or the combination is nonsense */ protected function validate($config, string $name, string $path) { if (!\is_array($config)) { throw new \InvalidArgumentException(\sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); } if (isset($config['alias'])) { $this->validateAlias($config, $name, $path); return; } if ($extraKeys = \array_diff(\array_keys($config), self::AVAILABLE_KEYS)) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, \implode('", "', $extraKeys), \implode('", "', self::AVAILABLE_KEYS))); } if (isset($config['resource']) && isset($config['path'])) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', $path, $name)); } if (!isset($config['resource']) && isset($config['type'])) { throw new \InvalidArgumentException(\sprintf('The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', $name, $path)); } if (!isset($config['resource']) && !isset($config['path'])) { throw new \InvalidArgumentException(\sprintf('You must define a "path" for the route "%s" in file "%s".', $name, $path)); } if (isset($config['controller']) && isset($config['defaults']['_controller'])) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name)); } if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".', $path, $name)); } } /** * @throws \InvalidArgumentException If one of the provided config keys is not supported, * something is missing or the combination is nonsense */ private function validateAlias(array $config, string $name, string $path) : void { foreach ($config as $key => $value) { if (!\in_array($key, ['alias', 'deprecated'], \true)) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" must not specify other keys than "alias" and "deprecated" for "%s".', $path, $name)); } if ('deprecated' === $key) { if (!isset($value['package'])) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" must specify the attribute "package" of the "deprecated" option for "%s".', $path, $name)); } if (!isset($value['version'])) { throw new \InvalidArgumentException(\sprintf('The routing file "%s" must specify the attribute "version" of the "deprecated" option for "%s".', $path, $name)); } } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\FileLocatorInterface; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * AnnotationFileLoader loads routing information from annotations set * on a PHP class and its methods. * * @author Fabien Potencier */ class AnnotationFileLoader extends FileLoader { protected $loader; public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader) { if (!\function_exists('token_get_all')) { throw new \LogicException('The Tokenizer extension is required for the routing annotation loaders.'); } parent::__construct($locator); $this->loader = $loader; } /** * Loads from annotations from a file. * * @param string $file A PHP file path * @param string|null $type The resource type * * @return RouteCollection|null * * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed */ public function load($file, ?string $type = null) { $path = $this->locator->locate($file); $collection = new RouteCollection(); if ($class = $this->findClass($path)) { $refl = new \ReflectionClass($class); if ($refl->isAbstract()) { return null; } $collection->addResource(new FileResource($path)); $collection->addCollection($this->loader->load($class, $type)); } \gc_mem_caches(); return $collection; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return \is_string($resource) && 'php' === \pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); } /** * Returns the full class name for the first class in the file. * * @return string|false */ protected function findClass(string $file) { $class = \false; $namespace = \false; $tokens = \token_get_all(\file_get_contents($file)); if (1 === \count($tokens) && \T_INLINE_HTML === $tokens[0][0]) { throw new \InvalidArgumentException(\sprintf('The file "%s" does not contain PHP code. Did you forgot to add the " \true, \T_STRING => \true]; if (\defined('T_NAME_QUALIFIED')) { $nsTokens[\T_NAME_QUALIFIED] = \true; } for ($i = 0; isset($tokens[$i]); ++$i) { $token = $tokens[$i]; if (!isset($token[1])) { continue; } if (\true === $class && \T_STRING === $token[0]) { return $namespace . '\\' . $token[1]; } if (\true === $namespace && isset($nsTokens[$token[0]])) { $namespace = $token[1]; while (isset($tokens[++$i][1], $nsTokens[$tokens[$i][0]])) { $namespace .= $tokens[$i][1]; } $token = $tokens[$i]; } if (\T_CLASS === $token[0]) { // Skip usage of ::class constant and anonymous classes $skipClassToken = \false; for ($j = $i - 1; $j > 0; --$j) { if (!isset($tokens[$j][1])) { if ('(' === $tokens[$j] || ',' === $tokens[$j]) { $skipClassToken = \true; } break; } if (\T_DOUBLE_COLON === $tokens[$j][0] || \T_NEW === $tokens[$j][0]) { $skipClassToken = \true; break; } elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) { break; } } if (!$skipClassToken) { $class = \true; } } if (\T_NAMESPACE === $token[0]) { $namespace = \true; } } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * PhpFileLoader loads routes from a PHP file. * * The file must return a RouteCollection instance. * * @author Fabien Potencier * @author Nicolas grekas * @author Jules Pietri */ class PhpFileLoader extends FileLoader { /** * Loads a PHP file. * * @param string $file A PHP file path * @param string|null $type The resource type * * @return RouteCollection */ public function load($file, ?string $type = null) { $path = $this->locator->locate($file); $this->setCurrentDir(\dirname($path)); // the closure forbids access to the private scope in the included file $loader = $this; $load = \Closure::bind(static function ($file) use($loader) { return include $file; }, null, ProtectedPhpFileLoader::class); $result = $load($path); if (\is_object($result) && \is_callable($result)) { $collection = $this->callConfigurator($result, $path, $file); } else { $collection = $result; } $collection->addResource(new FileResource($path)); return $collection; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return \is_string($resource) && 'php' === \pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type); } protected function callConfigurator(callable $result, string $path, string $file) : RouteCollection { $collection = new RouteCollection(); $result(new RoutingConfigurator($collection, $this, $path, $file, $this->env)); return $collection; } } /** * @internal */ final class ProtectedPhpFileLoader extends PhpFileLoader { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Config\Util\XmlUtils; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * XmlFileLoader loads XML routing files. * * @author Fabien Potencier * @author Tobias Schultze */ class XmlFileLoader extends FileLoader { use HostTrait; use LocalizedRouteTrait; use PrefixTrait; public const NAMESPACE_URI = 'http://symfony.com/schema/routing'; public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; /** * Loads an XML file. * * @param string $file An XML file path * @param string|null $type The resource type * * @return RouteCollection * * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be * parsed because it does not validate against the scheme */ public function load($file, ?string $type = null) { $path = $this->locator->locate($file); $xml = $this->loadFile($path); $collection = new RouteCollection(); $collection->addResource(new FileResource($path)); // process routes and imports foreach ($xml->documentElement->childNodes as $node) { if (!$node instanceof \DOMElement) { continue; } $this->parseNode($collection, $node, $path, $file); } return $collection; } /** * Parses a node from a loaded XML file. * * @throws \InvalidArgumentException When the XML is invalid */ protected function parseNode(RouteCollection $collection, \DOMElement $node, string $path, string $file) { if (self::NAMESPACE_URI !== $node->namespaceURI) { return; } switch ($node->localName) { case 'route': $this->parseRoute($collection, $node, $path); break; case 'import': $this->parseImport($collection, $node, $path, $file); break; case 'when': if (!$this->env || $node->getAttribute('env') !== $this->env) { break; } foreach ($node->childNodes as $node) { if ($node instanceof \DOMElement) { $this->parseNode($collection, $node, $path, $file); } } break; default: throw new \InvalidArgumentException(\sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); } } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return \is_string($resource) && 'xml' === \pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type); } /** * Parses a route and adds it to the RouteCollection. * * @throws \InvalidArgumentException When the XML is invalid */ protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $path) { if ('' === ($id = $node->getAttribute('id'))) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must have an "id" attribute.', $path)); } if ('' !== ($alias = $node->getAttribute('alias'))) { $alias = $collection->addAlias($id, $alias); if ($deprecationInfo = $this->parseDeprecation($node, $path)) { $alias->setDeprecated($deprecationInfo['package'], $deprecationInfo['version'], $deprecationInfo['message']); } return; } $schemes = \preg_split('/[\\s,\\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY); $methods = \preg_split('/[\\s,\\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY); [$defaults, $requirements, $options, $condition, $paths, , $hosts] = $this->parseConfigs($node, $path); if (!$paths && '' === $node->getAttribute('path')) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must have a "path" attribute or child nodes.', $path)); } if ($paths && '' !== $node->getAttribute('path')) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must not have both a "path" attribute and child nodes.', $path)); } $routes = $this->createLocalizedRoute($collection, $id, $paths ?: $node->getAttribute('path')); $routes->addDefaults($defaults); $routes->addRequirements($requirements); $routes->addOptions($options); $routes->setSchemes($schemes); $routes->setMethods($methods); $routes->setCondition($condition); if (null !== $hosts) { $this->addHost($routes, $hosts); } } /** * Parses an import and adds the routes in the resource to the RouteCollection. * * @throws \InvalidArgumentException When the XML is invalid */ protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file) { if ('' === ($resource = $node->getAttribute('resource'))) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must have a "resource" attribute.', $path)); } $type = $node->getAttribute('type'); $prefix = $node->getAttribute('prefix'); $schemes = $node->hasAttribute('schemes') ? \preg_split('/[\\s,\\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY) : null; $methods = $node->hasAttribute('methods') ? \preg_split('/[\\s,\\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY) : null; $trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : \true; $namePrefix = $node->getAttribute('name-prefix') ?: null; [$defaults, $requirements, $options, $condition, , $prefixes, $hosts] = $this->parseConfigs($node, $path); if ('' !== $prefix && $prefixes) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must not have both a "prefix" attribute and child nodes.', $path)); } $exclude = []; foreach ($node->childNodes as $child) { if ($child instanceof \DOMElement && $child->localName === $exclude && self::NAMESPACE_URI === $child->namespaceURI) { $exclude[] = $child->nodeValue; } } if ($node->hasAttribute('exclude')) { if ($exclude) { throw new \InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); } $exclude = [$node->getAttribute('exclude')]; } $this->setCurrentDir(\dirname($path)); /** @var RouteCollection[] $imported */ $imported = $this->import($resource, '' !== $type ? $type : null, \false, $file, $exclude) ?: []; if (!\is_array($imported)) { $imported = [$imported]; } foreach ($imported as $subCollection) { $this->addPrefix($subCollection, $prefixes ?: $prefix, $trailingSlashOnRoot); if (null !== $hosts) { $this->addHost($subCollection, $hosts); } if (null !== $condition) { $subCollection->setCondition($condition); } if (null !== $schemes) { $subCollection->setSchemes($schemes); } if (null !== $methods) { $subCollection->setMethods($methods); } if (null !== $namePrefix) { $subCollection->addNamePrefix($namePrefix); } $subCollection->addDefaults($defaults); $subCollection->addRequirements($requirements); $subCollection->addOptions($options); $collection->addCollection($subCollection); } } /** * @return \DOMDocument * * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors * or when the XML structure is not as expected by the scheme - * see validate() */ protected function loadFile(string $file) { return XmlUtils::loadFile($file, __DIR__ . static::SCHEME_PATH); } /** * Parses the config elements (default, requirement, option). * * @throws \InvalidArgumentException When the XML is invalid */ private function parseConfigs(\DOMElement $node, string $path) : array { $defaults = []; $requirements = []; $options = []; $condition = null; $prefixes = []; $paths = []; $hosts = []; /** @var \DOMElement $n */ foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { if ($node !== $n->parentNode) { continue; } switch ($n->localName) { case 'path': $paths[$n->getAttribute('locale')] = \trim($n->textContent); break; case 'host': $hosts[$n->getAttribute('locale')] = \trim($n->textContent); break; case 'prefix': $prefixes[$n->getAttribute('locale')] = \trim($n->textContent); break; case 'default': if ($this->isElementValueNull($n)) { $defaults[$n->getAttribute('key')] = null; } else { $defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path); } break; case 'requirement': $requirements[$n->getAttribute('key')] = \trim($n->textContent); break; case 'option': $options[$n->getAttribute('key')] = XmlUtils::phpize(\trim($n->textContent)); break; case 'condition': $condition = \trim($n->textContent); break; default: throw new \InvalidArgumentException(\sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path)); } } if ($controller = $node->getAttribute('controller')) { if (isset($defaults['_controller'])) { $name = $node->hasAttribute('id') ? \sprintf('"%s".', $node->getAttribute('id')) : \sprintf('the "%s" tag.', $node->tagName); throw new \InvalidArgumentException(\sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for ', $path) . $name); } $defaults['_controller'] = $controller; } if ($node->hasAttribute('locale')) { $defaults['_locale'] = $node->getAttribute('locale'); } if ($node->hasAttribute('format')) { $defaults['_format'] = $node->getAttribute('format'); } if ($node->hasAttribute('utf8')) { $options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8')); } if ($stateless = $node->getAttribute('stateless')) { if (isset($defaults['_stateless'])) { $name = $node->hasAttribute('id') ? \sprintf('"%s".', $node->getAttribute('id')) : \sprintf('the "%s" tag.', $node->tagName); throw new \InvalidArgumentException(\sprintf('The routing file "%s" must not specify both the "stateless" attribute and the defaults key "_stateless" for ', $path) . $name); } $defaults['_stateless'] = XmlUtils::phpize($stateless); } if (!$hosts) { $hosts = $node->hasAttribute('host') ? $node->getAttribute('host') : null; } return [$defaults, $requirements, $options, $condition, $paths, $prefixes, $hosts]; } /** * Parses the "default" elements. * * @return array|bool|float|int|string|null */ private function parseDefaultsConfig(\DOMElement $element, string $path) { if ($this->isElementValueNull($element)) { return null; } // Check for existing element nodes in the default element. There can // only be a single element inside a default element. So this element // (if one was found) can safely be returned. foreach ($element->childNodes as $child) { if (!$child instanceof \DOMElement) { continue; } if (self::NAMESPACE_URI !== $child->namespaceURI) { continue; } return $this->parseDefaultNode($child, $path); } // If the default element doesn't contain a nested "bool", "int", "float", // "string", "list", or "map" element, the element contents will be treated // as the string value of the associated default option. return \trim($element->textContent); } /** * Recursively parses the value of a "default" element. * * @return array|bool|float|int|string|null * * @throws \InvalidArgumentException when the XML is invalid */ private function parseDefaultNode(\DOMElement $node, string $path) { if ($this->isElementValueNull($node)) { return null; } switch ($node->localName) { case 'bool': return 'true' === \trim($node->nodeValue) || '1' === \trim($node->nodeValue); case 'int': return (int) \trim($node->nodeValue); case 'float': return (float) \trim($node->nodeValue); case 'string': return \trim($node->nodeValue); case 'list': $list = []; foreach ($node->childNodes as $element) { if (!$element instanceof \DOMElement) { continue; } if (self::NAMESPACE_URI !== $element->namespaceURI) { continue; } $list[] = $this->parseDefaultNode($element, $path); } return $list; case 'map': $map = []; foreach ($node->childNodes as $element) { if (!$element instanceof \DOMElement) { continue; } if (self::NAMESPACE_URI !== $element->namespaceURI) { continue; } $map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path); } return $map; default: throw new \InvalidArgumentException(\sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path)); } } private function isElementValueNull(\DOMElement $element) : bool { $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; if (!$element->hasAttributeNS($namespaceUri, 'nil')) { return \false; } return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); } /** * Parses the deprecation elements. * * @throws \InvalidArgumentException When the XML is invalid */ private function parseDeprecation(\DOMElement $node, string $path) : array { $deprecatedNode = null; foreach ($node->childNodes as $child) { if (!$child instanceof \DOMElement || self::NAMESPACE_URI !== $child->namespaceURI) { continue; } if ('deprecated' !== $child->localName) { throw new \InvalidArgumentException(\sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $node->getAttribute('id'), $path)); } $deprecatedNode = $child; } if (null === $deprecatedNode) { return []; } if (!$deprecatedNode->hasAttribute('package')) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must have a "package" attribute.', $path)); } if (!$deprecatedNode->hasAttribute('version')) { throw new \InvalidArgumentException(\sprintf('The element in file "%s" must have a "version" attribute.', $path)); } return ['package' => $deprecatedNode->getAttribute('package'), 'version' => $deprecatedNode->getAttribute('version'), 'message' => \trim($deprecatedNode->nodeValue)]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\Loader; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * A route loader that calls a method on an object to load the routes. * * @author Ryan Weaver */ abstract class ObjectLoader extends Loader { /** * Returns the object that the method will be called on to load routes. * * For example, if your application uses a service container, * the $id may be a service id. * * @return object */ protected abstract function getObject(string $id); /** * Calls the object method that will load the routes. * * @param string $resource object_id::method * @param string|null $type The resource type * * @return RouteCollection */ public function load($resource, ?string $type = null) { if (!\preg_match('/^[^\\:]+(?:::(?:[^\\:]+))?$/', $resource)) { throw new \InvalidArgumentException(\sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"' . $type . '"' : 'object')); } $parts = \explode('::', $resource); $method = $parts[1] ?? '__invoke'; $loaderObject = $this->getObject($parts[0]); if (!\is_object($loaderObject)) { throw new \TypeError(\sprintf('"%s:getObject()" must return an object: "%s" returned.', static::class, \get_debug_type($loaderObject))); } if (!\is_callable([$loaderObject, $method])) { throw new \BadMethodCallException(\sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, \get_debug_type($loaderObject), $resource)); } $routeCollection = $loaderObject->{$method}($this, $this->env); if (!$routeCollection instanceof RouteCollection) { $type = \get_debug_type($routeCollection); throw new \LogicException(\sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', \get_debug_type($loaderObject), $method, $type)); } // make the object file tracked so that if it changes, the cache rebuilds $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); return $routeCollection; } private function addClassResource(\ReflectionClass $class, RouteCollection $collection) { do { if (\is_file($class->getFileName())) { $collection->addResource(new FileResource($class->getFileName())); } } while ($class = $class->getParentClass()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @internal */ trait HostTrait { protected final function addHost(RouteCollection $routes, $hosts) { if (!$hosts || !\is_array($hosts)) { $routes->setHost($hosts ?: ''); return; } foreach ($routes->all() as $name => $route) { if (null === ($locale = $route->getDefault('_locale'))) { $routes->remove($name); foreach ($hosts as $locale => $host) { $localizedRoute = clone $route; $localizedRoute->setDefault('_locale', $locale); $localizedRoute->setRequirement('_locale', \preg_quote($locale)); $localizedRoute->setDefault('_canonical_route', $name); $localizedRoute->setHost($host); $routes->add($name . '.' . $locale, $localizedRoute); } } elseif (!isset($hosts[$locale])) { throw new \InvalidArgumentException(\sprintf('Route "%s" with locale "%s" is missing a corresponding host in its parent collection.', $name, $locale)); } else { $route->setHost($hosts[$locale]); $route->setRequirement('_locale', \preg_quote($locale)); $routes->add($name, $route); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @internal * * @author Nicolas Grekas */ trait PrefixTrait { protected final function addPrefix(RouteCollection $routes, $prefix, bool $trailingSlashOnRoot) { if (\is_array($prefix)) { foreach ($prefix as $locale => $localePrefix) { $prefix[$locale] = \trim(\trim($localePrefix), '/'); } foreach ($routes->all() as $name => $route) { if (null === ($locale = $route->getDefault('_locale'))) { $priority = $routes->getPriority($name) ?? 0; $routes->remove($name); foreach ($prefix as $locale => $localePrefix) { $localizedRoute = clone $route; $localizedRoute->setDefault('_locale', $locale); $localizedRoute->setRequirement('_locale', \preg_quote($locale)); $localizedRoute->setDefault('_canonical_route', $name); $localizedRoute->setPath($localePrefix . (!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); $routes->add($name . '.' . $locale, $localizedRoute, $priority); } } elseif (!isset($prefix[$locale])) { throw new \InvalidArgumentException(\sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); } else { $route->setPath($prefix[$locale] . (!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); $routes->add($name, $route, $routes->getPriority($name) ?? 0); } } return; } $routes->addPrefix($prefix); if (!$trailingSlashOnRoot) { $rootPath = (new Route(\trim(\trim($prefix), '/') . '/'))->getPath(); foreach ($routes->all() as $route) { if ($route->getPath() === $rootPath) { $route->setPath(\rtrim($rootPath, '/')); } } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @internal * * @author Nicolas Grekas * @author Jules Pietri */ trait LocalizedRouteTrait { /** * Creates one or many routes. * * @param string|array $path the path, or the localized paths of the route */ protected final function createLocalizedRoute(RouteCollection $collection, string $name, $path, string $namePrefix = '', ?array $prefixes = null) : RouteCollection { $paths = []; $routes = new RouteCollection(); if (\is_array($path)) { if (null === $prefixes) { $paths = $path; } elseif ($missing = \array_diff_key($prefixes, $path)) { throw new \LogicException(\sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, \implode('", "', \array_keys($missing)))); } else { foreach ($path as $locale => $localePath) { if (!isset($prefixes[$locale])) { throw new \LogicException(\sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); } $paths[$locale] = $prefixes[$locale] . $localePath; } } } elseif (null !== $prefixes) { foreach ($prefixes as $locale => $prefix) { $paths[$locale] = $prefix . $path; } } else { $routes->add($namePrefix . $name, $route = $this->createRoute($path)); $collection->add($namePrefix . $name, $route); return $routes; } foreach ($paths as $locale => $path) { $routes->add($name . '.' . $locale, $route = $this->createRoute($path)); $collection->add($namePrefix . $name . '.' . $locale, $route); $route->setDefault('_locale', $locale); $route->setRequirement('_locale', \preg_quote($locale)); $route->setDefault('_canonical_route', $namePrefix . $name); } return $routes; } private function createRoute(string $path) : Route { return new Route($path); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; trait RouteTrait { /** * @var RouteCollection|Route */ protected $route; /** * Adds defaults. * * @return $this */ public final function defaults(array $defaults) : self { $this->route->addDefaults($defaults); return $this; } /** * Adds requirements. * * @return $this */ public final function requirements(array $requirements) : self { $this->route->addRequirements($requirements); return $this; } /** * Adds options. * * @return $this */ public final function options(array $options) : self { $this->route->addOptions($options); return $this; } /** * Whether paths should accept utf8 encoding. * * @return $this */ public final function utf8(bool $utf8 = \true) : self { $this->route->addOptions(['utf8' => $utf8]); return $this; } /** * Sets the condition. * * @return $this */ public final function condition(string $condition) : self { $this->route->setCondition($condition); return $this; } /** * Sets the pattern for the host. * * @return $this */ public final function host(string $pattern) : self { $this->route->setHost($pattern); return $this; } /** * Sets the schemes (e.g. 'https') this route is restricted to. * So an empty array means that any scheme is allowed. * * @param string[] $schemes * * @return $this */ public final function schemes(array $schemes) : self { $this->route->setSchemes($schemes); return $this; } /** * Sets the HTTP methods (e.g. 'POST') this route is restricted to. * So an empty array means that any method is allowed. * * @param string[] $methods * * @return $this */ public final function methods(array $methods) : self { $this->route->setMethods($methods); return $this; } /** * Adds the "_controller" entry to defaults. * * @param callable|string|array $controller a callable or parseable pseudo-callable * * @return $this */ public final function controller($controller) : self { $this->route->addDefaults(['_controller' => $controller]); return $this; } /** * Adds the "_locale" entry to defaults. * * @return $this */ public final function locale(string $locale) : self { $this->route->addDefaults(['_locale' => $locale]); return $this; } /** * Adds the "_format" entry to defaults. * * @return $this */ public final function format(string $format) : self { $this->route->addDefaults(['_format' => $format]); return $this; } /** * Adds the "_stateless" entry to defaults. * * @return $this */ public final function stateless(bool $stateless = \true) : self { $this->route->addDefaults(['_stateless' => $stateless]); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\AliasConfigurator; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Nicolas Grekas */ trait AddTrait { use LocalizedRouteTrait; /** * @var RouteCollection */ protected $collection; protected $name = ''; protected $prefixes; /** * Adds a route. * * @param string|array $path the path, or the localized paths of the route */ public function add(string $name, $path) : RouteConfigurator { $parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null); $route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes); return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes); } public function alias(string $name, string $alias) : AliasConfigurator { return new AliasConfigurator($this->collection->addAlias($name, $alias)); } /** * Adds a route. * * @param string|array $path the path, or the localized paths of the route */ public function __invoke(string $name, $path) : RouteConfigurator { return $this->add($name, $path); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Nicolas Grekas */ class RouteConfigurator { use Traits\AddTrait; use Traits\HostTrait; use Traits\RouteTrait; protected $parentConfigurator; public function __construct(RouteCollection $collection, RouteCollection $route, string $name = '', ?CollectionConfigurator $parentConfigurator = null, ?array $prefixes = null) { $this->collection = $collection; $this->route = $route; $this->name = $name; $this->parentConfigurator = $parentConfigurator; // for GC control $this->prefixes = $prefixes; } /** * Sets the host to use for all child routes. * * @param string|array $host the host, or the localized hosts * * @return $this */ public final function host($host) : self { $this->addHost($this->route, $host); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator; use _ContaoManager\Symfony\Component\Routing\Route; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Nicolas Grekas */ class CollectionConfigurator { use Traits\AddTrait; use Traits\HostTrait; use Traits\RouteTrait; private $parent; private $parentConfigurator; private $parentPrefixes; private $host; public function __construct(RouteCollection $parent, string $name, ?self $parentConfigurator = null, ?array $parentPrefixes = null) { $this->parent = $parent; $this->name = $name; $this->collection = new RouteCollection(); $this->route = new Route(''); $this->parentConfigurator = $parentConfigurator; // for GC control $this->parentPrefixes = $parentPrefixes; } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { if (null === $this->prefixes) { $this->collection->addPrefix($this->route->getPath()); } if (null !== $this->host) { $this->addHost($this->collection, $this->host); } $this->parent->addCollection($this->collection); } /** * Creates a sub-collection. */ public final function collection(string $name = '') : self { return new self($this->collection, $this->name . $name, $this, $this->prefixes); } /** * Sets the prefix to add to the path of all child routes. * * @param string|array $prefix the prefix, or the localized prefixes * * @return $this */ public final function prefix($prefix) : self { if (\is_array($prefix)) { if (null === $this->parentPrefixes) { // no-op } elseif ($missing = \array_diff_key($this->parentPrefixes, $prefix)) { throw new \LogicException(\sprintf('Collection "%s" is missing prefixes for locale(s) "%s".', $this->name, \implode('", "', \array_keys($missing)))); } else { foreach ($prefix as $locale => $localePrefix) { if (!isset($this->parentPrefixes[$locale])) { throw new \LogicException(\sprintf('Collection "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $this->name, $locale)); } $prefix[$locale] = $this->parentPrefixes[$locale] . $localePrefix; } } $this->prefixes = $prefix; $this->route->setPath('/'); } else { $this->prefixes = null; $this->route->setPath($prefix); } return $this; } /** * Sets the host to use for all child routes. * * @param string|array $host the host, or the localized hosts * * @return $this */ public final function host($host) : self { $this->host = $host; return $this; } private function createRoute(string $path) : Route { return (clone $this->route)->setPath($path); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Routing\Alias; class AliasConfigurator { private $alias; public function __construct(Alias $alias) { $this->alias = $alias; } /** * Whether this alias is deprecated, that means it should not be called anymore. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ public function deprecate(string $package, string $version, string $message) : self { $this->alias->setDeprecated($package, $version, $message); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator; use _ContaoManager\Symfony\Component\Routing\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Nicolas Grekas */ class RoutingConfigurator { use Traits\AddTrait; private $loader; private $path; private $file; private $env; public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, ?string $env = null) { $this->collection = $collection; $this->loader = $loader; $this->path = $path; $this->file = $file; $this->env = $env; } /** * @param string|string[]|null $exclude Glob patterns to exclude from the import */ public final function import($resource, ?string $type = null, bool $ignoreErrors = \false, $exclude = null) : ImportConfigurator { $this->loader->setCurrentDir(\dirname($this->path)); $imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file, $exclude) ?: []; if (!\is_array($imported)) { return new ImportConfigurator($this->collection, $imported); } $mergedCollection = new RouteCollection(); foreach ($imported as $subCollection) { $mergedCollection->addCollection($subCollection); } return new ImportConfigurator($this->collection, $mergedCollection); } public final function collection(string $name = '') : CollectionConfigurator { return new CollectionConfigurator($this->collection, $name); } /** * Get the current environment to be able to write conditional configuration. */ public final function env() : ?string { return $this->env; } /** * @return static */ public final function withPath(string $path) : self { $clone = clone $this; $clone->path = $clone->file = $path; return $clone; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader\Configurator; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * @author Nicolas Grekas */ class ImportConfigurator { use Traits\HostTrait; use Traits\PrefixTrait; use Traits\RouteTrait; private $parent; public function __construct(RouteCollection $parent, RouteCollection $route) { $this->parent = $parent; $this->route = $route; } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { $this->parent->addCollection($this->route); } /** * Sets the prefix to add to the path of all child routes. * * @param string|array $prefix the prefix, or the localized prefixes * * @return $this */ public final function prefix($prefix, bool $trailingSlashOnRoot = \true) : self { $this->addPrefix($this->route, $prefix, $trailingSlashOnRoot); return $this; } /** * Sets the prefix to add to the name of all child routes. * * @return $this */ public final function namePrefix(string $namePrefix) : self { $this->route->addNamePrefix($namePrefix); return $this; } /** * Sets the host to use for all child routes. * * @param string|array $host the host, or the localized hosts * * @return $this */ public final function host($host) : self { $this->addHost($this->route, $host); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Psr\Container\ContainerInterface; /** * A route loader that executes a service from a PSR-11 container to load the routes. * * @author Ryan Weaver */ class ContainerLoader extends ObjectLoader { private $container; public function __construct(ContainerInterface $container, ?string $env = null) { $this->container = $container; parent::__construct($env); } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return 'service' === $type && \is_string($resource); } /** * {@inheritdoc} */ protected function getObject(string $id) { return $this->container->get($id); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader; use _ContaoManager\Symfony\Component\Config\Resource\DirectoryResource; use _ContaoManager\Symfony\Component\Routing\RouteCollection; class DirectoryLoader extends FileLoader { /** * {@inheritdoc} */ public function load($file, ?string $type = null) { $path = $this->locator->locate($file); $collection = new RouteCollection(); $collection->addResource(new DirectoryResource($path)); foreach (\scandir($path) as $dir) { if ('.' !== $dir[0]) { $this->setCurrentDir($path); $subPath = $path . '/' . $dir; $subType = null; if (\is_dir($subPath)) { $subPath .= '/'; $subType = 'directory'; } $subCollection = $this->import($subPath, $subType, \false, $path); $collection->addCollection($subCollection); } } return $collection; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { // only when type is forced to directory, not to conflict with AnnotationLoader return 'directory' === $type; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * GlobFileLoader loads files from a glob pattern. * * @author Nicolas Grekas */ class GlobFileLoader extends FileLoader { /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { $collection = new RouteCollection(); foreach ($this->glob($resource, \false, $globResource) as $path => $info) { $collection->addCollection($this->import($path)); } $collection->addResource($globResource); return $collection; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return 'glob' === $type; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Loader\Loader; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * ClosureLoader loads routes from a PHP closure. * * The Closure must return a RouteCollection instance. * * @author Fabien Potencier */ class ClosureLoader extends Loader { /** * Loads a Closure. * * @param \Closure $closure A Closure * @param string|null $type The resource type * * @return RouteCollection */ public function load($closure, ?string $type = null) { return $closure($this->env); } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return $resource instanceof \Closure && (!$type || 'closure' === $type); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Loader; use _ContaoManager\Symfony\Component\Config\Resource\DirectoryResource; use _ContaoManager\Symfony\Component\Routing\RouteCollection; /** * AnnotationDirectoryLoader loads routing information from annotations set * on PHP classes and methods. * * @author Fabien Potencier */ class AnnotationDirectoryLoader extends AnnotationFileLoader { /** * Loads from annotations from a directory. * * @param string $path A directory path * @param string|null $type The resource type * * @return RouteCollection * * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed */ public function load($path, ?string $type = null) { if (!\is_dir($dir = $this->locator->locate($path))) { return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection(); } $collection = new RouteCollection(); $collection->addResource(new DirectoryResource($dir, '/\\.php$/')); $files = \iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveCallbackFilterIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), function (\SplFileInfo $current) { return '.' !== \substr($current->getBasename(), 0, 1); }), \RecursiveIteratorIterator::LEAVES_ONLY)); \usort($files, function (\SplFileInfo $a, \SplFileInfo $b) { return (string) $a > (string) $b ? 1 : -1; }); foreach ($files as $file) { if (!$file->isFile() || !\str_ends_with($file->getFilename(), '.php')) { continue; } if ($class = $this->findClass($file)) { $refl = new \ReflectionClass($class); if ($refl->isAbstract()) { continue; } $collection->addCollection($this->loader->load($class, $type)); } } return $collection; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { if ('annotation' === $type) { return \true; } if ($type || !\is_string($resource)) { return \false; } try { return \is_dir($this->locator->locate($resource)); } catch (\Exception $e) { return \false; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; /** * A Route describes a route and its parameters. * * @author Fabien Potencier * @author Tobias Schultze */ class Route implements \Serializable { private $path = '/'; private $host = ''; private $schemes = []; private $methods = []; private $defaults = []; private $requirements = []; private $options = []; private $condition = ''; /** * @var CompiledRoute|null */ private $compiled; /** * Constructor. * * Available options: * * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) * * utf8: Whether UTF-8 matching is enforced ot not * * @param string $path The path pattern to match * @param array $defaults An array of default parameter values * @param array $requirements An array of requirements for parameters (regexes) * @param array $options An array of options * @param string|null $host The host pattern to match * @param string|string[] $schemes A required URI scheme or an array of restricted schemes * @param string|string[] $methods A required HTTP method or an array of restricted methods * @param string|null $condition A condition that should evaluate to true for the route to match */ public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', $schemes = [], $methods = [], ?string $condition = '') { $this->setPath($path); $this->addDefaults($defaults); $this->addRequirements($requirements); $this->setOptions($options); $this->setHost($host); $this->setSchemes($schemes); $this->setMethods($methods); $this->setCondition($condition); } public function __serialize() : array { return ['path' => $this->path, 'host' => $this->host, 'defaults' => $this->defaults, 'requirements' => $this->requirements, 'options' => $this->options, 'schemes' => $this->schemes, 'methods' => $this->methods, 'condition' => $this->condition, 'compiled' => $this->compiled]; } /** * @internal */ public final function serialize() : string { return \serialize($this->__serialize()); } public function __unserialize(array $data) : void { $this->path = $data['path']; $this->host = $data['host']; $this->defaults = $data['defaults']; $this->requirements = $data['requirements']; $this->options = $data['options']; $this->schemes = $data['schemes']; $this->methods = $data['methods']; if (isset($data['condition'])) { $this->condition = $data['condition']; } if (isset($data['compiled'])) { $this->compiled = $data['compiled']; } } /** * @internal */ public final function unserialize($serialized) { $this->__unserialize(\unserialize($serialized)); } /** * @return string */ public function getPath() { return $this->path; } /** * @return $this */ public function setPath(string $pattern) { $pattern = $this->extractInlineDefaultsAndRequirements($pattern); // A pattern must start with a slash and must not have multiple slashes at the beginning because the // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. $this->path = '/' . \ltrim(\trim($pattern), '/'); $this->compiled = null; return $this; } /** * @return string */ public function getHost() { return $this->host; } /** * @return $this */ public function setHost(?string $pattern) { $this->host = $this->extractInlineDefaultsAndRequirements((string) $pattern); $this->compiled = null; return $this; } /** * Returns the lowercased schemes this route is restricted to. * So an empty array means that any scheme is allowed. * * @return string[] */ public function getSchemes() { return $this->schemes; } /** * Sets the schemes (e.g. 'https') this route is restricted to. * So an empty array means that any scheme is allowed. * * @param string|string[] $schemes The scheme or an array of schemes * * @return $this */ public function setSchemes($schemes) { $this->schemes = \array_map('strtolower', (array) $schemes); $this->compiled = null; return $this; } /** * Checks if a scheme requirement has been set. * * @return bool */ public function hasScheme(string $scheme) { return \in_array(\strtolower($scheme), $this->schemes, \true); } /** * Returns the uppercased HTTP methods this route is restricted to. * So an empty array means that any method is allowed. * * @return string[] */ public function getMethods() { return $this->methods; } /** * Sets the HTTP methods (e.g. 'POST') this route is restricted to. * So an empty array means that any method is allowed. * * @param string|string[] $methods The method or an array of methods * * @return $this */ public function setMethods($methods) { $this->methods = \array_map('strtoupper', (array) $methods); $this->compiled = null; return $this; } /** * @return array */ public function getOptions() { return $this->options; } /** * @return $this */ public function setOptions(array $options) { $this->options = ['compiler_class' => '_ContaoManager\\Symfony\\Component\\Routing\\RouteCompiler']; return $this->addOptions($options); } /** * @return $this */ public function addOptions(array $options) { foreach ($options as $name => $option) { $this->options[$name] = $option; } $this->compiled = null; return $this; } /** * Sets an option value. * * @param mixed $value The option value * * @return $this */ public function setOption(string $name, $value) { $this->options[$name] = $value; $this->compiled = null; return $this; } /** * Returns the option value or null when not found. * * @return mixed */ public function getOption(string $name) { return $this->options[$name] ?? null; } /** * @return bool */ public function hasOption(string $name) { return \array_key_exists($name, $this->options); } /** * @return array */ public function getDefaults() { return $this->defaults; } /** * @return $this */ public function setDefaults(array $defaults) { $this->defaults = []; return $this->addDefaults($defaults); } /** * @return $this */ public function addDefaults(array $defaults) { if (isset($defaults['_locale']) && $this->isLocalized()) { unset($defaults['_locale']); } foreach ($defaults as $name => $default) { $this->defaults[$name] = $default; } $this->compiled = null; return $this; } /** * @return mixed */ public function getDefault(string $name) { return $this->defaults[$name] ?? null; } /** * @return bool */ public function hasDefault(string $name) { return \array_key_exists($name, $this->defaults); } /** * Sets a default value. * * @param mixed $default The default value * * @return $this */ public function setDefault(string $name, $default) { if ('_locale' === $name && $this->isLocalized()) { return $this; } $this->defaults[$name] = $default; $this->compiled = null; return $this; } /** * @return array */ public function getRequirements() { return $this->requirements; } /** * @return $this */ public function setRequirements(array $requirements) { $this->requirements = []; return $this->addRequirements($requirements); } /** * @return $this */ public function addRequirements(array $requirements) { if (isset($requirements['_locale']) && $this->isLocalized()) { unset($requirements['_locale']); } foreach ($requirements as $key => $regex) { $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); } $this->compiled = null; return $this; } /** * @return string|null */ public function getRequirement(string $key) { return $this->requirements[$key] ?? null; } /** * @return bool */ public function hasRequirement(string $key) { return \array_key_exists($key, $this->requirements); } /** * @return $this */ public function setRequirement(string $key, string $regex) { if ('_locale' === $key && $this->isLocalized()) { return $this; } $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); $this->compiled = null; return $this; } /** * @return string */ public function getCondition() { return $this->condition; } /** * @return $this */ public function setCondition(?string $condition) { $this->condition = (string) $condition; $this->compiled = null; return $this; } /** * Compiles the route. * * @return CompiledRoute * * @throws \LogicException If the Route cannot be compiled because the * path or host pattern is invalid * * @see RouteCompiler which is responsible for the compilation process */ public function compile() { if (null !== $this->compiled) { return $this->compiled; } $class = $this->getOption('compiler_class'); return $this->compiled = $class::compile($this); } private function extractInlineDefaultsAndRequirements(string $pattern) : string { if (\false === \strpbrk($pattern, '?<')) { return $pattern; } return \preg_replace_callback('#\\{(!?)(\\w++)(<.*?>)?(\\?[^\\}]*+)?\\}#', function ($m) { if (isset($m[4][0])) { $this->setDefault($m[2], '?' !== $m[4] ? \substr($m[4], 1) : null); } if (isset($m[3][0])) { $this->setRequirement($m[2], \substr($m[3], 1, -1)); } return '{' . $m[1] . $m[2] . '}'; }, $pattern); } private function sanitizeRequirement(string $key, string $regex) { if ('' !== $regex) { if ('^' === $regex[0]) { $regex = \substr($regex, 1); } elseif (0 === \strpos($regex, '\\A')) { $regex = \substr($regex, 2); } } if (\str_ends_with($regex, '$')) { $regex = \substr($regex, 0, -1); } elseif (\strlen($regex) - 2 === \strpos($regex, '\\z')) { $regex = \substr($regex, 0, -2); } if ('' === $regex) { throw new \InvalidArgumentException(\sprintf('Routing requirement for "%s" cannot be empty.', $key)); } return $regex; } private function isLocalized() : bool { return isset($this->defaults['_locale']) && isset($this->defaults['_canonical_route']) && ($this->requirements['_locale'] ?? null) === \preg_quote($this->defaults['_locale']); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; use _ContaoManager\Symfony\Component\Config\Exception\LoaderLoadException; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; use _ContaoManager\Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; \trigger_deprecation('symfony/routing', '5.1', 'The "%s" class is deprecated, use "%s" instead.', RouteCollectionBuilder::class, RoutingConfigurator::class); /** * Helps add and import routes into a RouteCollection. * * @author Ryan Weaver * * @deprecated since Symfony 5.1, use RoutingConfigurator instead */ class RouteCollectionBuilder { /** * @var Route[]|RouteCollectionBuilder[] */ private $routes = []; private $loader; private $defaults = []; private $prefix; private $host; private $condition; private $requirements = []; private $options = []; private $schemes; private $methods; private $resources = []; public function __construct(?LoaderInterface $loader = null) { $this->loader = $loader; } /** * Import an external routing resource and returns the RouteCollectionBuilder. * * $routes->import('blog.yml', '/blog'); * * @param mixed $resource * * @return self * * @throws LoaderLoadException */ public function import($resource, string $prefix = '/', ?string $type = null) { /** @var RouteCollection[] $collections */ $collections = $this->load($resource, $type); // create a builder from the RouteCollection $builder = $this->createBuilder(); foreach ($collections as $collection) { if (null === $collection) { continue; } foreach ($collection->all() as $name => $route) { $builder->addRoute($route, $name); } foreach ($collection->getResources() as $resource) { $builder->addResource($resource); } } // mount into this builder $this->mount($prefix, $builder); return $builder; } /** * Adds a route and returns it for future modification. * * @return Route */ public function add(string $path, string $controller, ?string $name = null) { $route = new Route($path); $route->setDefault('_controller', $controller); $this->addRoute($route, $name); return $route; } /** * Returns a RouteCollectionBuilder that can be configured and then added with mount(). * * @return self */ public function createBuilder() { return new self($this->loader); } /** * Add a RouteCollectionBuilder. */ public function mount(string $prefix, self $builder) { $builder->prefix = \trim(\trim($prefix), '/'); $this->routes[] = $builder; } /** * Adds a Route object to the builder. * * @return $this */ public function addRoute(Route $route, ?string $name = null) { if (null === $name) { // used as a flag to know which routes will need a name later $name = '_unnamed_route_' . \spl_object_hash($route); } $this->routes[$name] = $route; return $this; } /** * Sets the host on all embedded routes (unless already set). * * @return $this */ public function setHost(?string $pattern) { $this->host = $pattern; return $this; } /** * Sets a condition on all embedded routes (unless already set). * * @return $this */ public function setCondition(?string $condition) { $this->condition = $condition; return $this; } /** * Sets a default value that will be added to all embedded routes (unless that * default value is already set). * * @param mixed $value * * @return $this */ public function setDefault(string $key, $value) { $this->defaults[$key] = $value; return $this; } /** * Sets a requirement that will be added to all embedded routes (unless that * requirement is already set). * * @param mixed $regex * * @return $this */ public function setRequirement(string $key, $regex) { $this->requirements[$key] = $regex; return $this; } /** * Sets an option that will be added to all embedded routes (unless that * option is already set). * * @param mixed $value * * @return $this */ public function setOption(string $key, $value) { $this->options[$key] = $value; return $this; } /** * Sets the schemes on all embedded routes (unless already set). * * @param array|string $schemes * * @return $this */ public function setSchemes($schemes) { $this->schemes = $schemes; return $this; } /** * Sets the methods on all embedded routes (unless already set). * * @param array|string $methods * * @return $this */ public function setMethods($methods) { $this->methods = $methods; return $this; } /** * Adds a resource for this collection. * * @return $this */ private function addResource(ResourceInterface $resource) : self { $this->resources[] = $resource; return $this; } /** * Creates the final RouteCollection and returns it. * * @return RouteCollection */ public function build() { $routeCollection = new RouteCollection(); foreach ($this->routes as $name => $route) { if ($route instanceof Route) { $route->setDefaults(\array_merge($this->defaults, $route->getDefaults())); $route->setOptions(\array_merge($this->options, $route->getOptions())); foreach ($this->requirements as $key => $val) { if (!$route->hasRequirement($key)) { $route->setRequirement($key, $val); } } if (null !== $this->prefix) { $route->setPath('/' . $this->prefix . $route->getPath()); } if (!$route->getHost()) { $route->setHost($this->host); } if (!$route->getCondition()) { $route->setCondition($this->condition); } if (!$route->getSchemes()) { $route->setSchemes($this->schemes); } if (!$route->getMethods()) { $route->setMethods($this->methods); } // auto-generate the route name if it's been marked if ('_unnamed_route_' === \substr($name, 0, 15)) { $name = $this->generateRouteName($route); } $routeCollection->add($name, $route); } else { /* @var self $route */ $subCollection = $route->build(); if (null !== $this->prefix) { $subCollection->addPrefix($this->prefix); } $routeCollection->addCollection($subCollection); } } foreach ($this->resources as $resource) { $routeCollection->addResource($resource); } return $routeCollection; } /** * Generates a route name based on details of this route. */ private function generateRouteName(Route $route) : string { $methods = \implode('_', $route->getMethods()) . '_'; $routeName = $methods . $route->getPath(); $routeName = \str_replace(['/', ':', '|', '-'], '_', $routeName); $routeName = \preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); // Collapse consecutive underscores down into a single underscore. $routeName = \preg_replace('/_+/', '_', $routeName); return $routeName; } /** * Finds a loader able to load an imported resource and loads it. * * @param mixed $resource A resource * @param string|null $type The resource type or null if unknown * * @return RouteCollection[] * * @throws LoaderLoadException If no loader is found */ private function load($resource, ?string $type = null) : array { if (null === $this->loader) { throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); } if ($this->loader->supports($resource, $type)) { $collections = $this->loader->load($resource, $type); return \is_array($collections) ? $collections : [$collections]; } if (null === ($resolver = $this->loader->getResolver())) { throw new LoaderLoadException($resource, null, 0, null, $type); } if (\false === ($loader = $resolver->resolve($resource, $type))) { throw new LoaderLoadException($resource, null, 0, null, $type); } $collections = $loader->load($resource, $type); return \is_array($collections) ? $collections : [$collections]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\UrlMatcherInterface; /** * RouterInterface is the interface that all Router classes must implement. * * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. * * @author Fabien Potencier */ interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface { /** * Gets the RouteCollection instance associated with this Router. * * WARNING: This method should never be used at runtime as it is SLOW. * You might use it in a cache warmer though. * * @return RouteCollection */ public function getRouteCollection(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Config\ConfigCacheFactory; use _ContaoManager\Symfony\Component\Config\ConfigCacheFactoryInterface; use _ContaoManager\Symfony\Component\Config\ConfigCacheInterface; use _ContaoManager\Symfony\Component\Config\Loader\LoaderInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\Routing\Generator\CompiledUrlGenerator; use _ContaoManager\Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; use _ContaoManager\Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; use _ContaoManager\Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; use _ContaoManager\Symfony\Component\Routing\Generator\UrlGeneratorInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\CompiledUrlMatcher; use _ContaoManager\Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; use _ContaoManager\Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\RequestMatcherInterface; use _ContaoManager\Symfony\Component\Routing\Matcher\UrlMatcherInterface; /** * The Router class is an example of the integration of all pieces of the * routing system for easier use. * * @author Fabien Potencier */ class Router implements RouterInterface, RequestMatcherInterface { /** * @var UrlMatcherInterface|null */ protected $matcher; /** * @var UrlGeneratorInterface|null */ protected $generator; /** * @var RequestContext */ protected $context; /** * @var LoaderInterface */ protected $loader; /** * @var RouteCollection|null */ protected $collection; /** * @var mixed */ protected $resource; /** * @var array */ protected $options = []; /** * @var LoggerInterface|null */ protected $logger; /** * @var string|null */ protected $defaultLocale; /** * @var ConfigCacheFactoryInterface|null */ private $configCacheFactory; /** * @var ExpressionFunctionProviderInterface[] */ private $expressionLanguageProviders = []; private static $cache = []; /** * @param mixed $resource The main resource to load */ public function __construct(LoaderInterface $loader, $resource, array $options = [], ?RequestContext $context = null, ?LoggerInterface $logger = null, ?string $defaultLocale = null) { $this->loader = $loader; $this->resource = $resource; $this->logger = $logger; $this->context = $context ?? new RequestContext(); $this->setOptions($options); $this->defaultLocale = $defaultLocale; } /** * Sets options. * * Available options: * * * cache_dir: The cache directory (or null to disable caching) * * debug: Whether to enable debugging or not (false by default) * * generator_class: The name of a UrlGeneratorInterface implementation * * generator_dumper_class: The name of a GeneratorDumperInterface implementation * * matcher_class: The name of a UrlMatcherInterface implementation * * matcher_dumper_class: The name of a MatcherDumperInterface implementation * * resource_type: Type hint for the main resource (optional) * * strict_requirements: Configure strict requirement checking for generators * implementing ConfigurableRequirementsInterface (default is true) * * @throws \InvalidArgumentException When unsupported option is provided */ public function setOptions(array $options) { $this->options = ['cache_dir' => null, 'debug' => \false, 'generator_class' => CompiledUrlGenerator::class, 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, 'matcher_class' => CompiledUrlMatcher::class, 'matcher_dumper_class' => CompiledUrlMatcherDumper::class, 'resource_type' => null, 'strict_requirements' => \true]; // check option names and live merge, if errors are encountered Exception will be thrown $invalid = []; foreach ($options as $key => $value) { if (\array_key_exists($key, $this->options)) { $this->options[$key] = $value; } else { $invalid[] = $key; } } if ($invalid) { throw new \InvalidArgumentException(\sprintf('The Router does not support the following options: "%s".', \implode('", "', $invalid))); } } /** * Sets an option. * * @param mixed $value The value * * @throws \InvalidArgumentException */ public function setOption(string $key, $value) { if (!\array_key_exists($key, $this->options)) { throw new \InvalidArgumentException(\sprintf('The Router does not support the "%s" option.', $key)); } $this->options[$key] = $value; } /** * Gets an option value. * * @return mixed * * @throws \InvalidArgumentException */ public function getOption(string $key) { if (!\array_key_exists($key, $this->options)) { throw new \InvalidArgumentException(\sprintf('The Router does not support the "%s" option.', $key)); } return $this->options[$key]; } /** * {@inheritdoc} */ public function getRouteCollection() { if (null === $this->collection) { $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); } return $this->collection; } /** * {@inheritdoc} */ public function setContext(RequestContext $context) { $this->context = $context; if (null !== $this->matcher) { $this->getMatcher()->setContext($context); } if (null !== $this->generator) { $this->getGenerator()->setContext($context); } } /** * {@inheritdoc} */ public function getContext() { return $this->context; } /** * Sets the ConfigCache factory to use. */ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) { $this->configCacheFactory = $configCacheFactory; } /** * {@inheritdoc} */ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) { return $this->getGenerator()->generate($name, $parameters, $referenceType); } /** * {@inheritdoc} */ public function match(string $pathinfo) { return $this->getMatcher()->match($pathinfo); } /** * {@inheritdoc} */ public function matchRequest(Request $request) { $matcher = $this->getMatcher(); if (!$matcher instanceof RequestMatcherInterface) { // fallback to the default UrlMatcherInterface return $matcher->match($request->getPathInfo()); } return $matcher->matchRequest($request); } /** * Gets the UrlMatcher or RequestMatcher instance associated with this Router. * * @return UrlMatcherInterface|RequestMatcherInterface */ public function getMatcher() { if (null !== $this->matcher) { return $this->matcher; } if (null === $this->options['cache_dir']) { $routes = $this->getRouteCollection(); $compiled = \is_a($this->options['matcher_class'], CompiledUrlMatcher::class, \true); if ($compiled) { $routes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes(); } $this->matcher = new $this->options['matcher_class']($routes, $this->context); if (\method_exists($this->matcher, 'addExpressionLanguageProvider')) { foreach ($this->expressionLanguageProviders as $provider) { $this->matcher->addExpressionLanguageProvider($provider); } } return $this->matcher; } $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'] . '/url_matching_routes.php', function (ConfigCacheInterface $cache) { $dumper = $this->getMatcherDumperInstance(); if (\method_exists($dumper, 'addExpressionLanguageProvider')) { foreach ($this->expressionLanguageProviders as $provider) { $dumper->addExpressionLanguageProvider($provider); } } $cache->write($dumper->dump(), $this->getRouteCollection()->getResources()); }); return $this->matcher = new $this->options['matcher_class'](self::getCompiledRoutes($cache->getPath()), $this->context); } /** * Gets the UrlGenerator instance associated with this Router. * * @return UrlGeneratorInterface */ public function getGenerator() { if (null !== $this->generator) { return $this->generator; } if (null === $this->options['cache_dir']) { $routes = $this->getRouteCollection(); $compiled = \is_a($this->options['generator_class'], CompiledUrlGenerator::class, \true); if ($compiled) { $generatorDumper = new CompiledUrlGeneratorDumper($routes); $routes = \array_merge($generatorDumper->getCompiledRoutes(), $generatorDumper->getCompiledAliases()); } $this->generator = new $this->options['generator_class']($routes, $this->context, $this->logger, $this->defaultLocale); } else { $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'] . '/url_generating_routes.php', function (ConfigCacheInterface $cache) { $dumper = $this->getGeneratorDumperInstance(); $cache->write($dumper->dump(), $this->getRouteCollection()->getResources()); }); $this->generator = new $this->options['generator_class'](self::getCompiledRoutes($cache->getPath()), $this->context, $this->logger, $this->defaultLocale); } if ($this->generator instanceof ConfigurableRequirementsInterface) { $this->generator->setStrictRequirements($this->options['strict_requirements']); } return $this->generator; } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) { $this->expressionLanguageProviders[] = $provider; } /** * @return GeneratorDumperInterface */ protected function getGeneratorDumperInstance() { return new $this->options['generator_dumper_class']($this->getRouteCollection()); } /** * @return MatcherDumperInterface */ protected function getMatcherDumperInstance() { return new $this->options['matcher_dumper_class']($this->getRouteCollection()); } /** * Provides the ConfigCache factory implementation, falling back to a * default implementation if necessary. */ private function getConfigCacheFactory() : ConfigCacheFactoryInterface { if (null === $this->configCacheFactory) { $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); } return $this->configCacheFactory; } private static function getCompiledRoutes(string $path) : array { if ([] === self::$cache && \function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) || \filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) { self::$cache = null; } if (null === self::$cache) { return require $path; } if (isset(self::$cache[$path])) { return self::$cache[$path]; } return self::$cache[$path] = (require $path); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; /** * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. * * @author Fabien Potencier */ interface RouteCompilerInterface { /** * Compiles the current route instance. * * @return CompiledRoute * * @throws \LogicException If the Route cannot be compiled because the * path or host pattern is invalid */ public static function compile(Route $route); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing; use _ContaoManager\Symfony\Component\Routing\Exception\InvalidArgumentException; class Alias { private $id; private $deprecation = []; public function __construct(string $id) { $this->id = $id; } /** * @return static */ public function withId(string $id) : self { $new = clone $this; $new->id = $id; return $new; } /** * Returns the target name of this alias. * * @return string The target name */ public function getId() : string { return $this->id; } /** * Whether this alias is deprecated, that means it should not be referenced anymore. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ public function setDeprecated(string $package, string $version, string $message) : self { if ('' !== $message) { if (\preg_match('#[\\r\\n]|\\*/#', $message)) { throw new InvalidArgumentException('Invalid characters found in deprecation template.'); } if (!\str_contains($message, '%alias_id%')) { throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.'); } } $this->deprecation = ['package' => $package, 'version' => $version, 'message' => $message ?: 'The "%alias_id%" route alias is deprecated. You should stop using it, as it will be removed in the future.']; return $this; } public function isDeprecated() : bool { return (bool) $this->deprecation; } /** * @param string $name Route name relying on this alias */ public function getDeprecation(string $name) : array { return ['package' => $this->deprecation['package'], 'version' => $this->deprecation['version'], 'message' => \str_replace('%alias_id%', $name, $this->deprecation['message'])]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Adds tagged routing.loader services to routing.resolver service. * * @author Fabien Potencier */ class RoutingResolverPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; private $resolverServiceId; private $loaderTag; public function __construct(string $resolverServiceId = 'routing.resolver', string $loaderTag = 'routing.loader') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/routing', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->resolverServiceId = $resolverServiceId; $this->loaderTag = $loaderTag; } public function process(ContainerBuilder $container) { if (\false === $container->hasDefinition($this->resolverServiceId)) { return; } $definition = $container->getDefinition($this->resolverServiceId); foreach ($this->findAndSortTaggedServices($this->loaderTag, $container) as $id) { $definition->addMethodCall('addLoader', [new Reference($id)]); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * The resource was not found. * * This exception should trigger an HTTP 404 response in your application code. * * @author Kris Wallsmith */ class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * ExceptionInterface. * * @author Alexandre Salomé */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * Exception thrown when a route does not exist. * * @author Alexandre Salomé */ class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * Exception thrown when a route cannot be generated because of missing * mandatory parameters. * * @author Alexandre Salomé */ class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * The resource was found but the request method is not allowed. * * This exception should trigger an HTTP 405 response in your application code. * * @author Kris Wallsmith */ class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface { protected $allowedMethods = []; /** * @param string[] $allowedMethods */ public function __construct(array $allowedMethods, ?string $message = '', int $code = 0, ?\Throwable $previous = null) { if (null === $message) { \trigger_deprecation('symfony/routing', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); $message = ''; } $this->allowedMethods = \array_map('strtoupper', $allowedMethods); parent::__construct($message, $code, $previous); } /** * Gets the allowed HTTP methods. * * @return string[] */ public function getAllowedMethods() { return $this->allowedMethods; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * Exception thrown when no routes are configured. * * @author Yonel Ceruto */ class NoConfigurationException extends ResourceNotFoundException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; class RouteCircularReferenceException extends RuntimeException { public function __construct(string $routeId, array $path) { parent::__construct(\sprintf('Circular reference detected for route "%s", path: "%s".', $routeId, \implode(' -> ', $path))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Routing\Exception; /** * Exception thrown when a parameter is not valid. * * @author Alexandre Salomé */ class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface { } { "name": "symfony\/routing", "type": "library", "description": "Maps an HTTP request to a set of configuration variables", "keywords": [ "routing", "router", "url", "uri" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16" }, "require-dev": { "symfony\/config": "^5.3|^6.0", "symfony\/http-foundation": "^4.4|^5.0|^6.0", "symfony\/yaml": "^4.4|^5.0|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^4.4|^5.0|^6.0", "doctrine\/annotations": "^1.12|^2", "psr\/log": "^1|^2|^3" }, "conflict": { "doctrine\/annotations": "<1.12", "symfony\/config": "<5.3", "symfony\/dependency-injection": "<4.4", "symfony\/yaml": "<4.4" }, "suggest": { "symfony\/http-foundation": "For using a Symfony Request object", "symfony\/config": "For using the all-in-one router or any loader", "symfony\/yaml": "For using the YAML loader", "symfony\/expression-language": "For using expression matching" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Routing\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service; /** * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. * * The getSubscribedServices method returns an array of service types required by such instances, * optionally keyed by the service names used internally. Service types that start with an interrogation * mark "?" are optional, while the other ones are mandatory service dependencies. * * The injected service locators SHOULD NOT allow access to any other services not specified by the method. * * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. * This interface does not dictate any injection method for these service locators, although constructor * injection is recommended. * * @author Nicolas Grekas */ interface ServiceSubscriberInterface { /** * Returns an array of service types required by such instances, optionally keyed by the service names used internally. * * For mandatory dependencies: * * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name * internally to fetch a service which must implement Psr\Log\LoggerInterface. * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name * internally to fetch an iterable of Psr\Log\LoggerInterface instances. * * ['Psr\Log\LoggerInterface'] is a shortcut for * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] * * otherwise: * * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency * * ['?Psr\Log\LoggerInterface'] is a shortcut for * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] * * @return string[] The required service types, optionally keyed by service names */ public static function getSubscribedServices(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service\Attribute; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberTrait; /** * Use with {@see ServiceSubscriberTrait} to mark a method's return type * as a subscribed service. * * @author Kevin Bond */ #[\Attribute(\Attribute::TARGET_METHOD)] final class SubscribedService { /** * @param string|null $key The key to use for the service * If null, use "ClassName::methodName" */ public function __construct(public ?string $key = null) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service\Attribute; /** * A required dependency. * * This attribute indicates that a property holds a required dependency. The annotated property or method should be * considered during the instantiation process of the containing class. * * @author Alexander M. Turek */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] final class Required { } Copyright (c) 2018-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service\Test; use _ContaoManager\PHPUnit\Framework\TestCase; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceLocatorTrait; abstract class ServiceLocatorTest extends TestCase { /** * @return ContainerInterface */ protected function getServiceLocator(array $factories) { return new class($factories) implements ContainerInterface { use ServiceLocatorTrait; }; } public function testHas() { $locator = $this->getServiceLocator(['foo' => function () { return 'bar'; }, 'bar' => function () { return 'baz'; }, function () { return 'dummy'; }]); $this->assertTrue($locator->has('foo')); $this->assertTrue($locator->has('bar')); $this->assertFalse($locator->has('dummy')); } public function testGet() { $locator = $this->getServiceLocator(['foo' => function () { return 'bar'; }, 'bar' => function () { return 'baz'; }]); $this->assertSame('bar', $locator->get('foo')); $this->assertSame('baz', $locator->get('bar')); } public function testGetDoesNotMemoize() { $i = 0; $locator = $this->getServiceLocator(['foo' => function () use(&$i) { ++$i; return 'bar'; }]); $this->assertSame('bar', $locator->get('foo')); $this->assertSame('bar', $locator->get('foo')); $this->assertSame(2, $i); } public function testThrowsOnUndefinedInternalService() { if (!$this->getExpectedException()) { $this->expectException(\_ContaoManager\Psr\Container\NotFoundExceptionInterface::class); $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); } $locator = $this->getServiceLocator(['foo' => function () use(&$locator) { return $locator->get('bar'); }]); $locator->get('foo'); } public function testThrowsOnCircularReference() { $this->expectException(\_ContaoManager\Psr\Container\ContainerExceptionInterface::class); $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); $locator = $this->getServiceLocator(['foo' => function () use(&$locator) { return $locator->get('bar'); }, 'bar' => function () use(&$locator) { return $locator->get('baz'); }, 'baz' => function () use(&$locator) { return $locator->get('bar'); }]); $locator->get('foo'); } } CHANGELOG ========= The changelog is maintained for all Symfony contracts at the following URL: https://github.com/symfony/contracts/blob/main/CHANGELOG.md Symfony Service Contracts ========================= A set of abstractions extracted out of the Symfony components. Can be used to build on semantics that the Symfony components proved useful - and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service; /** * Provides a way to reset an object to its initial state. * * When calling the "reset()" method on an object, it should be put back to its * initial state. This usually means clearing any internal buffers and forwarding * the call to internal dependencies. All properties of the object should be put * back to the same state it had when it was first ready to use. * * This method could be called, for example, to recycle objects that are used as * services, so that they can be used to handle several requests in the same * process loop (note that we advise making your services stateless instead of * implementing this interface when possible.) */ interface ResetInterface { public function reset(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service; use _ContaoManager\Psr\Container\ContainerInterface; /** * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. * * @author Nicolas Grekas * @author Mateusz Sip */ interface ServiceProviderInterface extends ContainerInterface { /** * Returns an associative array of service types keyed by the identifiers provided by the current container. * * Examples: * * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface * * ['foo' => '?'] means the container provides service name "foo" of unspecified type * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null * * @return string[] The provided service types, keyed by service names */ public function getProvidedServices() : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Contracts\Service\Attribute\SubscribedService; /** * Implementation of ServiceSubscriberInterface that determines subscribed services from * method return types. Service ids are available as "ClassName::methodName". * * @author Kevin Bond */ trait ServiceSubscriberTrait { /** @var ContainerInterface */ protected $container; /** * {@inheritdoc} */ public static function getSubscribedServices() : array { $services = \method_exists(\get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; $attributeOptIn = \false; if (\PHP_VERSION_ID >= 80000) { foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { if (self::class !== $method->getDeclaringClass()->name) { continue; } if (!($attribute = $method->getAttributes(SubscribedService::class)[0] ?? null)) { continue; } if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { throw new \LogicException(\sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); } if (!($returnType = $method->getReturnType())) { throw new \LogicException(\sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); } $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; if ($returnType->allowsNull()) { $serviceId = '?' . $serviceId; } $services[$attribute->newInstance()->key ?? self::class . '::' . $method->name] = $serviceId; $attributeOptIn = \true; } } if (!$attributeOptIn) { foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { continue; } if (self::class !== $method->getDeclaringClass()->name) { continue; } if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { continue; } if ($returnType->isBuiltin()) { continue; } if (\PHP_VERSION_ID >= 80000) { \trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); } $services[self::class . '::' . $method->name] = '?' . ($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); } } return $services; } /** * @required * * @return ContainerInterface|null */ public function setContainer(ContainerInterface $container) { $this->container = $container; if (\method_exists(\get_parent_class(self::class) ?: '', __FUNCTION__)) { return parent::setContainer($container); } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Service; use _ContaoManager\Psr\Container\ContainerExceptionInterface; use _ContaoManager\Psr\Container\NotFoundExceptionInterface; // Help opcache.preload discover always-needed symbols \class_exists(ContainerExceptionInterface::class); \class_exists(NotFoundExceptionInterface::class); /** * A trait to help implement ServiceProviderInterface. * * @author Robin Chalas * @author Nicolas Grekas */ trait ServiceLocatorTrait { private $factories; private $loading = []; private $providedTypes; /** * @param callable[] $factories */ public function __construct(array $factories) { $this->factories = $factories; } /** * {@inheritdoc} * * @return bool */ public function has(string $id) { return isset($this->factories[$id]); } /** * {@inheritdoc} * * @return mixed */ public function get(string $id) { if (!isset($this->factories[$id])) { throw $this->createNotFoundException($id); } if (isset($this->loading[$id])) { $ids = \array_values($this->loading); $ids = \array_slice($this->loading, \array_search($id, $ids)); $ids[] = $id; throw $this->createCircularReferenceException($id, $ids); } $this->loading[$id] = $id; try { return $this->factories[$id]($this); } finally { unset($this->loading[$id]); } } /** * {@inheritdoc} */ public function getProvidedServices() : array { if (null === $this->providedTypes) { $this->providedTypes = []; foreach ($this->factories as $name => $factory) { if (!\is_callable($factory)) { $this->providedTypes[$name] = '?'; } else { $type = (new \ReflectionFunction($factory))->getReturnType(); $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '') . ($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; } } } return $this->providedTypes; } private function createNotFoundException(string $id) : NotFoundExceptionInterface { if (!($alternatives = \array_keys($this->factories))) { $message = 'is empty...'; } else { $last = \array_pop($alternatives); if ($alternatives) { $message = \sprintf('only knows about the "%s" and "%s" services.', \implode('", "', $alternatives), $last); } else { $message = \sprintf('only knows about the "%s" service.', $last); } } if ($this->loading) { $message = \sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', \end($this->loading), $id, $message); } else { $message = \sprintf('Service "%s" not found: the current service locator %s', $id, $message); } return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { }; } private function createCircularReferenceException(string $id, array $path) : ContainerExceptionInterface { return new class(\sprintf('Circular reference detected for service "%s", path: "%s".', $id, \implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { }; } } { "name": "symfony\/service-contracts", "type": "library", "description": "Generic abstractions related to writing services", "keywords": [ "abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "psr\/container": "^1.1", "symfony\/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, "suggest": { "symfony\/service-implementation": "" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Contracts\\Service\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-main": "2.5-dev" }, "thanks": { "name": "symfony\/contracts", "url": "https:\/\/github.com\/symfony\/contracts" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\PhpStan; use _ContaoManager\phpDocumentor\Reflection\Types\ContextFactory; /** * @author Baptiste Leduc * * @internal */ final class NameScopeFactory { public function create(string $calledClassName, ?string $declaringClassName = null) : NameScope { $declaringClassName = $declaringClassName ?? $calledClassName; $path = \explode('\\', $calledClassName); $calledClassName = \array_pop($path); $declaringReflection = new \ReflectionClass($declaringClassName); [$declaringNamespace, $declaringUses] = $this->extractFromFullClassName($declaringReflection); $declaringUses = \array_merge($declaringUses, $this->collectUses($declaringReflection)); return new NameScope($calledClassName, $declaringNamespace, $declaringUses); } private function collectUses(\ReflectionClass $reflection) : array { $uses = [$this->extractFromFullClassName($reflection)[1]]; foreach ($reflection->getTraits() as $traitReflection) { $uses[] = $this->extractFromFullClassName($traitReflection)[1]; } if (\false !== ($parentClass = $reflection->getParentClass())) { $uses[] = $this->collectUses($parentClass); } return $uses ? \array_merge(...$uses) : []; } private function extractFromFullClassName(\ReflectionClass $reflection) : array { $namespace = \trim($reflection->getNamespaceName(), '\\'); $fileName = $reflection->getFileName(); if (\is_string($fileName) && \is_file($fileName)) { if (\false === ($contents = \file_get_contents($fileName))) { throw new \RuntimeException(\sprintf('Unable to read file "%s".', $fileName)); } $factory = new ContextFactory(); $context = $factory->createForNamespace($namespace, $contents); return [$namespace, $context->getNamespaceAliases()]; } return [$namespace, []]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\PhpStan; /** * NameScope class adapted from PHPStan code. * * @copyright Copyright (c) 2016, PHPStan https://github.com/phpstan/phpstan-src * @copyright Copyright (c) 2016, Ondřej Mirtes * @author Baptiste Leduc * * @internal */ final class NameScope { private $calledClassName; private $namespace; /** @var array alias(string) => fullName(string) */ private $uses; public function __construct(string $calledClassName, string $namespace, array $uses = []) { $this->calledClassName = $calledClassName; $this->namespace = $namespace; $this->uses = $uses; } public function resolveStringName(string $name) : string { if (0 === \strpos($name, '\\')) { return \ltrim($name, '\\'); } $nameParts = \explode('\\', $name); $firstNamePart = $nameParts[0]; if (isset($this->uses[$firstNamePart])) { if (1 === \count($nameParts)) { return $this->uses[$firstNamePart]; } \array_shift($nameParts); return \sprintf('%s\\%s', $this->uses[$firstNamePart], \implode('\\', $nameParts)); } if (null !== $this->namespace) { return \sprintf('%s\\%s', $this->namespace, $name); } return $name; } public function resolveRootClass() : string { return $this->resolveStringName($this->calledClassName); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Gets info about PHP class properties. * * A convenient interface inheriting all specific info interfaces. * * @author Kévin Dunglas */ interface PropertyInfoExtractorInterface extends PropertyTypeExtractorInterface, PropertyDescriptionExtractorInterface, PropertyAccessExtractorInterface, PropertyListExtractorInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Type value object (immutable). * * @author Kévin Dunglas * * @final */ class Type { public const BUILTIN_TYPE_INT = 'int'; public const BUILTIN_TYPE_FLOAT = 'float'; public const BUILTIN_TYPE_STRING = 'string'; public const BUILTIN_TYPE_BOOL = 'bool'; public const BUILTIN_TYPE_RESOURCE = 'resource'; public const BUILTIN_TYPE_OBJECT = 'object'; public const BUILTIN_TYPE_ARRAY = 'array'; public const BUILTIN_TYPE_NULL = 'null'; public const BUILTIN_TYPE_FALSE = 'false'; public const BUILTIN_TYPE_TRUE = 'true'; public const BUILTIN_TYPE_CALLABLE = 'callable'; public const BUILTIN_TYPE_ITERABLE = 'iterable'; /** * List of PHP builtin types. * * @var string[] */ public static $builtinTypes = [self::BUILTIN_TYPE_INT, self::BUILTIN_TYPE_FLOAT, self::BUILTIN_TYPE_STRING, self::BUILTIN_TYPE_BOOL, self::BUILTIN_TYPE_RESOURCE, self::BUILTIN_TYPE_OBJECT, self::BUILTIN_TYPE_ARRAY, self::BUILTIN_TYPE_CALLABLE, self::BUILTIN_TYPE_FALSE, self::BUILTIN_TYPE_TRUE, self::BUILTIN_TYPE_NULL, self::BUILTIN_TYPE_ITERABLE]; /** * List of PHP builtin collection types. * * @var string[] */ public static $builtinCollectionTypes = [self::BUILTIN_TYPE_ARRAY, self::BUILTIN_TYPE_ITERABLE]; private $builtinType; private $nullable; private $class; private $collection; private $collectionKeyType; private $collectionValueType; /** * @param Type[]|Type|null $collectionKeyType * @param Type[]|Type|null $collectionValueType * * @throws \InvalidArgumentException */ public function __construct(string $builtinType, bool $nullable = \false, ?string $class = null, bool $collection = \false, $collectionKeyType = null, $collectionValueType = null) { if (!\in_array($builtinType, self::$builtinTypes)) { throw new \InvalidArgumentException(\sprintf('"%s" is not a valid PHP type.', $builtinType)); } $this->builtinType = $builtinType; $this->nullable = $nullable; $this->class = $class; $this->collection = $collection; $this->collectionKeyType = $this->validateCollectionArgument($collectionKeyType, 5, '$collectionKeyType') ?? []; $this->collectionValueType = $this->validateCollectionArgument($collectionValueType, 6, '$collectionValueType') ?? []; } private function validateCollectionArgument($collectionArgument, int $argumentIndex, string $argumentName) : ?array { if (null === $collectionArgument) { return null; } if (!\is_array($collectionArgument) && !$collectionArgument instanceof self) { throw new \TypeError(\sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, \get_debug_type($collectionArgument))); } if (\is_array($collectionArgument)) { foreach ($collectionArgument as $type) { if (!$type instanceof self) { throw new \TypeError(\sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", array value "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, \get_debug_type($collectionArgument))); } } return $collectionArgument; } return [$collectionArgument]; } /** * Gets built-in type. * * Can be bool, int, float, string, array, object, resource, null, callback or iterable. */ public function getBuiltinType() : string { return $this->builtinType; } public function isNullable() : bool { return $this->nullable; } /** * Gets the class name. * * Only applicable if the built-in type is object. */ public function getClassName() : ?string { return $this->class; } public function isCollection() : bool { return $this->collection; } /** * Gets collection key type. * * Only applicable for a collection type. * * @deprecated since Symfony 5.3, use "getCollectionKeyTypes()" instead */ public function getCollectionKeyType() : ?self { \trigger_deprecation('symfony/property-info', '5.3', 'The "%s()" method is deprecated, use "getCollectionKeyTypes()" instead.', __METHOD__); $type = $this->getCollectionKeyTypes(); if (0 === \count($type)) { return null; } if (\is_array($type)) { [$type] = $type; } return $type; } /** * Gets collection key types. * * Only applicable for a collection type. * * @return Type[] */ public function getCollectionKeyTypes() : array { return $this->collectionKeyType; } /** * Gets collection value type. * * Only applicable for a collection type. * * @deprecated since Symfony 5.3, use "getCollectionValueTypes()" instead */ public function getCollectionValueType() : ?self { \trigger_deprecation('symfony/property-info', '5.3', 'The "%s()" method is deprecated, use "getCollectionValueTypes()" instead.', __METHOD__); return $this->getCollectionValueTypes()[0] ?? null; } /** * Gets collection value types. * * Only applicable for a collection type. * * @return Type[] */ public function getCollectionValueTypes() : array { return $this->collectionValueType; } } Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Util; use _ContaoManager\phpDocumentor\Reflection\PseudoTypes\ConstExpression; use _ContaoManager\phpDocumentor\Reflection\PseudoTypes\List_; use _ContaoManager\phpDocumentor\Reflection\Type as DocType; use _ContaoManager\phpDocumentor\Reflection\Types\Array_; use _ContaoManager\phpDocumentor\Reflection\Types\Collection; use _ContaoManager\phpDocumentor\Reflection\Types\Compound; use _ContaoManager\phpDocumentor\Reflection\Types\Null_; use _ContaoManager\phpDocumentor\Reflection\Types\Nullable; use _ContaoManager\Symfony\Component\PropertyInfo\Type; // Workaround for phpdocumentor/type-resolver < 1.6 // We trigger the autoloader here, so we don't need to trigger it inside the loop later. \class_exists(List_::class); /** * Transforms a php doc type to a {@link Type} instance. * * @author Kévin Dunglas * @author Guilhem N. */ final class PhpDocTypeHelper { /** * Creates a {@see Type} from a PHPDoc type. * * @return Type[] */ public function getTypes(DocType $varType) : array { if ($varType instanceof ConstExpression) { // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment return []; } $types = []; $nullable = \false; if ($varType instanceof Nullable) { $nullable = \true; $varType = $varType->getActualType(); } if (!$varType instanceof Compound) { if ($varType instanceof Null_) { $nullable = \true; } $type = $this->createType($varType, $nullable); if (null !== $type) { $types[] = $type; } return $types; } $varTypes = []; for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) { $type = $varType->get($typeIndex); if ($type instanceof ConstExpression) { // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment return []; } // If null is present, all types are nullable if ($type instanceof Null_) { $nullable = \true; continue; } if ($type instanceof Nullable) { $nullable = \true; $type = $type->getActualType(); } $varTypes[] = $type; } foreach ($varTypes as $varType) { $type = $this->createType($varType, $nullable); if (null !== $type) { $types[] = $type; } } return $types; } /** * Creates a {@see Type} from a PHPDoc type. */ private function createType(DocType $type, bool $nullable, ?string $docType = null) : ?Type { $docType = $docType ?? (string) $type; if ($type instanceof Collection) { $fqsen = $type->getFqsen(); if ($fqsen && 'list' === $fqsen->getName() && !\class_exists(List_::class, \false) && !\class_exists((string) $fqsen)) { // Workaround for phpdocumentor/type-resolver < 1.6 return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, \true, new Type(Type::BUILTIN_TYPE_INT), $this->getTypes($type->getValueType())); } [$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen); $keys = $this->getTypes($type->getKeyType()); $values = $this->getTypes($type->getValueType()); return new Type($phpType, $nullable, $class, \true, $keys, $values); } // Cannot guess if (!$docType || 'mixed' === $docType) { return null; } if (\str_ends_with($docType, '[]') && $type instanceof Array_) { $collectionKeyTypes = new Type(Type::BUILTIN_TYPE_INT); $collectionValueTypes = $this->getTypes($type->getValueType()); return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, \true, $collectionKeyTypes, $collectionValueTypes); } if ((\str_starts_with($docType, 'list<') || \str_starts_with($docType, 'array<')) && $type instanceof Array_) { // array is converted to x[] which is handled above // so it's only necessary to handle array here $collectionKeyTypes = $this->getTypes($type->getKeyType()); $collectionValueTypes = $this->getTypes($type->getValueType()); return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, \true, $collectionKeyTypes, $collectionValueTypes); } $docType = $this->normalizeType($docType); [$phpType, $class] = $this->getPhpTypeAndClass($docType); if ('array' === $docType) { return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, \true, null, null); } return new Type($phpType, $nullable, $class); } private function normalizeType(string $docType) : string { switch ($docType) { case 'integer': return 'int'; case 'boolean': return 'bool'; // real is not part of the PHPDoc standard, so we ignore it case 'double': return 'float'; case 'callback': return 'callable'; case 'void': return 'null'; default: return $docType; } } private function getPhpTypeAndClass(string $docType) : array { if (\in_array($docType, Type::$builtinTypes)) { return [$docType, null]; } if (\in_array($docType, ['parent', 'self', 'static'], \true)) { return ['object', $docType]; } return ['object', \ltrim($docType, '\\')]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Util; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\TypeNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use _ContaoManager\Symfony\Component\PropertyInfo\PhpStan\NameScope; use _ContaoManager\Symfony\Component\PropertyInfo\Type; /** * Transforms a php doc tag value to a {@link Type} instance. * * @author Baptiste Leduc * * @internal */ final class PhpStanTypeHelper { /** * Creates a {@see Type} from a PhpDocTagValueNode type. * * @return Type[] */ public function getTypes(PhpDocTagValueNode $node, NameScope $nameScope) : array { if ($node instanceof ParamTagValueNode || $node instanceof ReturnTagValueNode || $node instanceof VarTagValueNode) { return $this->compressNullableType($this->extractTypes($node->type, $nameScope)); } return []; } /** * Because PhpStan extract null as a separated type when Symfony / PHP compress it in the first available type we * need this method to mimic how Symfony want null types. * * @param Type[] $types * * @return Type[] */ private function compressNullableType(array $types) : array { $firstTypeIndex = null; $nullableTypeIndex = null; foreach ($types as $k => $type) { if (null === $firstTypeIndex && Type::BUILTIN_TYPE_NULL !== $type->getBuiltinType() && !$type->isNullable()) { $firstTypeIndex = $k; } if (null === $nullableTypeIndex && Type::BUILTIN_TYPE_NULL === $type->getBuiltinType()) { $nullableTypeIndex = $k; } if (null !== $firstTypeIndex && null !== $nullableTypeIndex) { break; } } if (null !== $firstTypeIndex && null !== $nullableTypeIndex) { $firstType = $types[$firstTypeIndex]; $types[$firstTypeIndex] = new Type($firstType->getBuiltinType(), \true, $firstType->getClassName(), $firstType->isCollection(), $firstType->getCollectionKeyTypes(), $firstType->getCollectionValueTypes()); unset($types[$nullableTypeIndex]); } return \array_values($types); } /** * @return Type[] */ private function extractTypes(TypeNode $node, NameScope $nameScope) : array { if ($node instanceof UnionTypeNode) { $types = []; foreach ($node->types as $type) { if ($type instanceof ConstTypeNode) { // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment return []; } foreach ($this->extractTypes($type, $nameScope) as $subType) { $types[] = $subType; } } return $this->compressNullableType($types); } if ($node instanceof GenericTypeNode) { [$mainType] = $this->extractTypes($node->type, $nameScope); if (Type::BUILTIN_TYPE_INT === $mainType->getBuiltinType()) { return [$mainType]; } $collectionKeyTypes = $mainType->getCollectionKeyTypes(); $collectionKeyValues = []; if (1 === \count($node->genericTypes)) { foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $subType) { $collectionKeyValues[] = $subType; } } elseif (2 === \count($node->genericTypes)) { foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $keySubType) { $collectionKeyTypes[] = $keySubType; } foreach ($this->extractTypes($node->genericTypes[1], $nameScope) as $valueSubType) { $collectionKeyValues[] = $valueSubType; } } return [new Type($mainType->getBuiltinType(), $mainType->isNullable(), $mainType->getClassName(), \true, $collectionKeyTypes, $collectionKeyValues)]; } if ($node instanceof ArrayShapeNode) { return [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true)]; } if ($node instanceof ArrayTypeNode) { return [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true, [new Type(Type::BUILTIN_TYPE_INT)], $this->extractTypes($node->type, $nameScope))]; } if ($node instanceof CallableTypeNode || $node instanceof CallableTypeParameterNode) { return [new Type(Type::BUILTIN_TYPE_CALLABLE)]; } if ($node instanceof NullableTypeNode) { $subTypes = $this->extractTypes($node->type, $nameScope); if (\count($subTypes) > 1) { $subTypes[] = new Type(Type::BUILTIN_TYPE_NULL); return $subTypes; } return [new Type($subTypes[0]->getBuiltinType(), \true, $subTypes[0]->getClassName(), $subTypes[0]->isCollection(), $subTypes[0]->getCollectionKeyTypes(), $subTypes[0]->getCollectionValueTypes())]; } if ($node instanceof ThisTypeNode) { return [new Type(Type::BUILTIN_TYPE_OBJECT, \false, $nameScope->resolveRootClass())]; } if ($node instanceof IdentifierTypeNode) { if (\in_array($node->name, Type::$builtinTypes)) { return [new Type($node->name, \false, null, \in_array($node->name, Type::$builtinCollectionTypes))]; } switch ($node->name) { case 'integer': return [new Type(Type::BUILTIN_TYPE_INT)]; case 'list': case 'non-empty-list': return [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true, new Type(Type::BUILTIN_TYPE_INT))]; case 'non-empty-array': return [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true)]; case 'mixed': return []; // mixed seems to be ignored in all other extractors case 'parent': return [new Type(Type::BUILTIN_TYPE_OBJECT, \false, $node->name)]; case 'static': case 'self': return [new Type(Type::BUILTIN_TYPE_OBJECT, \false, $nameScope->resolveRootClass())]; case 'void': return [new Type(Type::BUILTIN_TYPE_NULL)]; } return [new Type(Type::BUILTIN_TYPE_OBJECT, \false, $nameScope->resolveStringName($node->name))]; } return []; } } CHANGELOG ========= 5.4 --- * Add PhpStanExtractor 5.3 --- * Add support for multiple types for collection keys & values * Deprecate the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead 5.2.0 ----- * deprecated the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()` in favor of `enable_magic_methods_extraction` 5.1.0 ----- * Add support for extracting accessor and mutator via PHP Reflection 4.3.0 ----- * Added the ability to extract private and protected properties and methods on `ReflectionExtractor` * Added the ability to extract property type based on its initial value 4.2.0 ----- * added `PropertyInitializableExtractorInterface` to test if a property can be initialized through the constructor (implemented by `ReflectionExtractor`) 3.3.0 ----- * Added `PropertyInfoPass` * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; /** * Adds a PSR-6 cache layer on top of an extractor. * * @author Kévin Dunglas * * @final */ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface { private $propertyInfoExtractor; private $cacheItemPool; private $arrayCache = []; public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, CacheItemPoolInterface $cacheItemPool) { $this->propertyInfoExtractor = $propertyInfoExtractor; $this->cacheItemPool = $cacheItemPool; } /** * {@inheritdoc} */ public function isReadable(string $class, string $property, array $context = []) : ?bool { return $this->extract('isReadable', [$class, $property, $context]); } /** * {@inheritdoc} */ public function isWritable(string $class, string $property, array $context = []) : ?bool { return $this->extract('isWritable', [$class, $property, $context]); } /** * {@inheritdoc} */ public function getShortDescription(string $class, string $property, array $context = []) : ?string { return $this->extract('getShortDescription', [$class, $property, $context]); } /** * {@inheritdoc} */ public function getLongDescription(string $class, string $property, array $context = []) : ?string { return $this->extract('getLongDescription', [$class, $property, $context]); } /** * {@inheritdoc} */ public function getProperties(string $class, array $context = []) : ?array { return $this->extract('getProperties', [$class, $context]); } /** * {@inheritdoc} */ public function getTypes(string $class, string $property, array $context = []) : ?array { return $this->extract('getTypes', [$class, $property, $context]); } /** * {@inheritdoc} */ public function isInitializable(string $class, string $property, array $context = []) : ?bool { return $this->extract('isInitializable', [$class, $property, $context]); } /** * Retrieves the cached data if applicable or delegates to the decorated extractor. * * @return mixed */ private function extract(string $method, array $arguments) { try { $serializedArguments = \serialize($arguments); } catch (\Exception $exception) { // If arguments are not serializable, skip the cache return $this->propertyInfoExtractor->{$method}(...$arguments); } // Calling rawurlencode escapes special characters not allowed in PSR-6's keys $key = \rawurlencode($method . '.' . $serializedArguments); if (\array_key_exists($key, $this->arrayCache)) { return $this->arrayCache[$key]; } $item = $this->cacheItemPool->getItem($key); if ($item->isHit()) { return $this->arrayCache[$key] = $item->get(); } $value = $this->propertyInfoExtractor->{$method}(...$arguments); $item->set($value); $this->cacheItemPool->save($item); return $this->arrayCache[$key] = $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Extract write information for the property of a class. * * @author Joel Wurtz */ interface PropertyWriteInfoExtractorInterface { /** * Get write information object for a given property of a class. */ public function getWriteInfo(string $class, string $property, array $context = []) : ?PropertyWriteInfo; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Guesses if the property can be accessed or mutated. * * @author Kévin Dunglas */ interface PropertyAccessExtractorInterface { /** * Is the property readable? * * @return bool|null */ public function isReadable(string $class, string $property, array $context = []); /** * Is the property writable? * * @return bool|null */ public function isWritable(string $class, string $property, array $context = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Type Extractor Interface. * * @author Kévin Dunglas */ interface PropertyTypeExtractorInterface { /** * Gets types of a property. * * @return Type[]|null */ public function getTypes(string $class, string $property, array $context = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * The property read info tells how a property can be read. * * @author Joel Wurtz * * @internal */ final class PropertyReadInfo { public const TYPE_METHOD = 'method'; public const TYPE_PROPERTY = 'property'; public const VISIBILITY_PUBLIC = 'public'; public const VISIBILITY_PROTECTED = 'protected'; public const VISIBILITY_PRIVATE = 'private'; private $type; private $name; private $visibility; private $static; private $byRef; public function __construct(string $type, string $name, string $visibility, bool $static, bool $byRef) { $this->type = $type; $this->name = $name; $this->visibility = $visibility; $this->static = $static; $this->byRef = $byRef; } /** * Get type of access. */ public function getType() : string { return $this->type; } /** * Get name of the access, which can be a method name or a property name, depending on the type. */ public function getName() : string { return $this->name; } public function getVisibility() : string { return $this->visibility; } public function isStatic() : bool { return $this->static; } /** * Whether this accessor can be accessed by reference. */ public function canBeReference() : bool { return $this->byRef; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Extractor; use _ContaoManager\phpDocumentor\Reflection\Types\ContextFactory; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use _ContaoManager\PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use _ContaoManager\PHPStan\PhpDocParser\Lexer\Lexer; use _ContaoManager\PHPStan\PhpDocParser\Parser\ConstExprParser; use _ContaoManager\PHPStan\PhpDocParser\Parser\PhpDocParser; use _ContaoManager\PHPStan\PhpDocParser\Parser\TokenIterator; use _ContaoManager\PHPStan\PhpDocParser\Parser\TypeParser; use _ContaoManager\Symfony\Component\PropertyInfo\PhpStan\NameScopeFactory; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\Type; use _ContaoManager\Symfony\Component\PropertyInfo\Util\PhpStanTypeHelper; /** * Extracts data using PHPStan parser. * * @author Baptiste Leduc */ final class PhpStanExtractor implements PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface { private const PROPERTY = 0; private const ACCESSOR = 1; private const MUTATOR = 2; /** @var PhpDocParser */ private $phpDocParser; /** @var Lexer */ private $lexer; /** @var NameScopeFactory */ private $nameScopeFactory; /** @var array */ private $docBlocks = []; private $phpStanTypeHelper; private $mutatorPrefixes; private $accessorPrefixes; private $arrayMutatorPrefixes; /** * @param list|null $mutatorPrefixes * @param list|null $accessorPrefixes * @param list|null $arrayMutatorPrefixes */ public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null) { if (!\class_exists(ContextFactory::class)) { throw new \LogicException(\sprintf('Unable to use the "%s" class as the "phpdocumentor/type-resolver" package is not installed. Try running composer require "phpdocumentor/type-resolver".', __CLASS__)); } if (!\class_exists(PhpDocParser::class)) { throw new \LogicException(\sprintf('Unable to use the "%s" class as the "phpstan/phpdoc-parser" package is not installed. Try running composer require "phpstan/phpdoc-parser".', __CLASS__)); } $this->phpStanTypeHelper = new PhpStanTypeHelper(); $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; $this->phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); $this->lexer = new Lexer(); $this->nameScopeFactory = new NameScopeFactory(); } public function getTypes(string $class, string $property, array $context = []) : ?array { /** @var PhpDocNode|null $docNode */ [$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property); $nameScope = $this->nameScopeFactory->create($class, $declaringClass); if (null === $docNode) { return null; } switch ($source) { case self::PROPERTY: $tag = '@var'; break; case self::ACCESSOR: $tag = '@return'; break; case self::MUTATOR: $tag = '@param'; break; } $parentClass = null; $types = []; foreach ($docNode->getTagsByName($tag) as $tagDocNode) { if ($tagDocNode->value instanceof InvalidTagValueNode) { continue; } foreach ($this->phpStanTypeHelper->getTypes($tagDocNode->value, $nameScope) as $type) { switch ($type->getClassName()) { case 'self': case 'static': $resolvedClass = $class; break; case 'parent': if (\false !== ($resolvedClass = $parentClass ?? ($parentClass = \get_parent_class($class)))) { break; } // no break default: $types[] = $type; continue 2; } $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); } } if (!isset($types[0])) { return null; } if (!\in_array($prefix, $this->arrayMutatorPrefixes, \true)) { return $types; } return [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true, new Type(Type::BUILTIN_TYPE_INT), $types[0])]; } public function getTypesFromConstructor(string $class, string $property) : ?array { if (null === ($tagDocNode = $this->getDocBlockFromConstructor($class, $property))) { return null; } $types = []; foreach ($this->phpStanTypeHelper->getTypes($tagDocNode, $this->nameScopeFactory->create($class)) as $type) { $types[] = $type; } if (!isset($types[0])) { return null; } return $types; } private function getDocBlockFromConstructor(string $class, string $property) : ?ParamTagValueNode { try { $reflectionClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } if (null === ($reflectionConstructor = $reflectionClass->getConstructor())) { return null; } if (!($rawDocNode = $reflectionConstructor->getDocComment())) { return null; } $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode)); $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); return $this->filterDocBlockParams($phpDocNode, $property); } private function filterDocBlockParams(PhpDocNode $docNode, string $allowedParam) : ?ParamTagValueNode { $tags = \array_values(\array_filter($docNode->getTagsByName('@param'), function ($tagNode) use($allowedParam) { return $tagNode instanceof PhpDocTagNode && '$' . $allowedParam === $tagNode->value->parameterName; })); if (!$tags) { return null; } return $tags[0]->value; } /** * @return array{PhpDocNode|null, int|null, string|null, string|null} */ private function getDocBlock(string $class, string $property) : array { $propertyHash = $class . '::' . $property; if (isset($this->docBlocks[$propertyHash])) { return $this->docBlocks[$propertyHash]; } $ucFirstProperty = \ucfirst($property); if ([$docBlock, $declaringClass] = $this->getDocBlockFromProperty($class, $property)) { $data = [$docBlock, self::PROPERTY, null, $declaringClass]; } elseif ([$docBlock, $_, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) { $data = [$docBlock, self::ACCESSOR, null, $declaringClass]; } elseif ([$docBlock, $prefix, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) { $data = [$docBlock, self::MUTATOR, $prefix, $declaringClass]; } else { $data = [null, null, null, null]; } return $this->docBlocks[$propertyHash] = $data; } /** * @return array{PhpDocNode, string}|null */ private function getDocBlockFromProperty(string $class, string $property) : ?array { // Use a ReflectionProperty instead of $class to get the parent class if applicable try { $reflectionProperty = new \ReflectionProperty($class, $property); } catch (\ReflectionException $e) { return null; } if (null === ($rawDocNode = $reflectionProperty->getDocComment() ?: null)) { return null; } $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode)); $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); return [$phpDocNode, $reflectionProperty->class]; } /** * @return array{PhpDocNode, string, string}|null */ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type) : ?array { $prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes; $prefix = null; foreach ($prefixes as $prefix) { $methodName = $prefix . $ucFirstProperty; try { $reflectionMethod = new \ReflectionMethod($class, $methodName); if ($reflectionMethod->isStatic()) { continue; } if (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters() || self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) { break; } } catch (\ReflectionException $e) { // Try the next prefix if the method doesn't exist } } if (!isset($reflectionMethod)) { return null; } if (null === ($rawDocNode = $reflectionMethod->getDocComment() ?: null)) { return null; } $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode)); $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); return [$phpDocNode, $prefix, $reflectionMethod->class]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Extractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use _ContaoManager\Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; /** * Lists available properties using Symfony Serializer Component metadata. * * @author Kévin Dunglas * * @final */ class SerializerExtractor implements PropertyListExtractorInterface { private $classMetadataFactory; public function __construct(ClassMetadataFactoryInterface $classMetadataFactory) { $this->classMetadataFactory = $classMetadataFactory; } /** * {@inheritdoc} */ public function getProperties(string $class, array $context = []) : ?array { if (!\array_key_exists('serializer_groups', $context) || null !== $context['serializer_groups'] && !\is_array($context['serializer_groups'])) { return null; } if (!$this->classMetadataFactory->getMetadataFor($class)) { return null; } $properties = []; $serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class); foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { $ignored = \method_exists($serializerAttributeMetadata, 'isIgnored') && $serializerAttributeMetadata->isIgnored(); if (!$ignored && (null === $context['serializer_groups'] || \array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups()))) { $properties[] = $serializerAttributeMetadata->getName(); } } return $properties; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Extractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; /** * Extracts the constructor argument type using ConstructorArgumentTypeExtractorInterface implementations. * * @author Dmitrii Poddubnyi */ final class ConstructorExtractor implements PropertyTypeExtractorInterface { private $extractors; /** * @param iterable $extractors */ public function __construct(iterable $extractors = []) { $this->extractors = $extractors; } /** * {@inheritdoc} */ public function getTypes(string $class, string $property, array $context = []) : ?array { foreach ($this->extractors as $extractor) { $value = $extractor->getTypesFromConstructor($class, $property); if (null !== $value) { return $value; } } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Extractor; use _ContaoManager\Symfony\Component\PropertyInfo\Type; /** * Infers the constructor argument type. * * @author Dmitrii Poddubnyi * * @internal */ interface ConstructorArgumentTypeExtractorInterface { /** * Gets types of an argument from constructor. * * @return Type[]|null * * @internal */ public function getTypesFromConstructor(string $class, string $property) : ?array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Extractor; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfo; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfo; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\Type; use _ContaoManager\Symfony\Component\String\Inflector\EnglishInflector; use _ContaoManager\Symfony\Component\String\Inflector\InflectorInterface; /** * Extracts data using the reflection API. * * @author Kévin Dunglas * * @final */ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, PropertyReadInfoExtractorInterface, PropertyWriteInfoExtractorInterface, ConstructorArgumentTypeExtractorInterface { /** * @internal */ public static $defaultMutatorPrefixes = ['add', 'remove', 'set']; /** * @internal */ public static $defaultAccessorPrefixes = ['get', 'is', 'has', 'can']; /** * @internal */ public static $defaultArrayMutatorPrefixes = ['add', 'remove']; public const ALLOW_PRIVATE = 1; public const ALLOW_PROTECTED = 2; public const ALLOW_PUBLIC = 4; /** @var int Allow none of the magic methods */ public const DISALLOW_MAGIC_METHODS = 0; /** @var int Allow magic __get methods */ public const ALLOW_MAGIC_GET = 1 << 0; /** @var int Allow magic __set methods */ public const ALLOW_MAGIC_SET = 1 << 1; /** @var int Allow magic __call methods */ public const ALLOW_MAGIC_CALL = 1 << 2; private const MAP_TYPES = ['integer' => Type::BUILTIN_TYPE_INT, 'boolean' => Type::BUILTIN_TYPE_BOOL, 'double' => Type::BUILTIN_TYPE_FLOAT]; private $mutatorPrefixes; private $accessorPrefixes; private $arrayMutatorPrefixes; private $enableConstructorExtraction; private $methodReflectionFlags; private $magicMethodsFlags; private $propertyReflectionFlags; private $inflector; private $arrayMutatorPrefixesFirst; private $arrayMutatorPrefixesLast; /** * @param string[]|null $mutatorPrefixes * @param string[]|null $accessorPrefixes * @param string[]|null $arrayMutatorPrefixes */ public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = \true, int $accessFlags = self::ALLOW_PUBLIC, ?InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET) { $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes; $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes; $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes; $this->enableConstructorExtraction = $enableConstructorExtraction; $this->methodReflectionFlags = $this->getMethodsFlags($accessFlags); $this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags); $this->magicMethodsFlags = $magicMethodsFlags; $this->inflector = $inflector ?? new EnglishInflector(); $this->arrayMutatorPrefixesFirst = \array_merge($this->arrayMutatorPrefixes, \array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes)); $this->arrayMutatorPrefixesLast = \array_reverse($this->arrayMutatorPrefixesFirst); } /** * {@inheritdoc} */ public function getProperties(string $class, array $context = []) : ?array { try { $reflectionClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } $reflectionProperties = $reflectionClass->getProperties(); $properties = []; foreach ($reflectionProperties as $reflectionProperty) { if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) { $properties[$reflectionProperty->name] = $reflectionProperty->name; } } foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) { if ($reflectionMethod->isStatic()) { continue; } $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties); if (!$propertyName || isset($properties[$propertyName])) { continue; } if ($reflectionClass->hasProperty($lowerCasedPropertyName = \lcfirst($propertyName)) || !$reflectionClass->hasProperty($propertyName) && !\preg_match('/^[A-Z]{2,}/', $propertyName)) { $propertyName = $lowerCasedPropertyName; } $properties[$propertyName] = $propertyName; } return $properties ? \array_values($properties) : null; } /** * {@inheritdoc} */ public function getTypes(string $class, string $property, array $context = []) : ?array { if ($fromMutator = $this->extractFromMutator($class, $property)) { return $fromMutator; } if ($fromAccessor = $this->extractFromAccessor($class, $property)) { return $fromAccessor; } if (($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) && ($fromConstructor = $this->extractFromConstructor($class, $property))) { return $fromConstructor; } if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) { return $fromPropertyDeclaration; } return null; } /** * {@inheritdoc} */ public function getTypesFromConstructor(string $class, string $property) : ?array { try { $reflection = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } if (!($reflectionConstructor = $reflection->getConstructor())) { return null; } if (!($reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor))) { return null; } if (!($reflectionType = $reflectionParameter->getType())) { return null; } if (!($types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass()))) { return null; } return $types; } private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor) : ?\ReflectionParameter { $reflectionParameter = null; foreach ($reflectionConstructor->getParameters() as $reflectionParameter) { if ($reflectionParameter->getName() === $property) { return $reflectionParameter; } } return null; } /** * {@inheritdoc} */ public function isReadable(string $class, string $property, array $context = []) : ?bool { if ($this->isAllowedProperty($class, $property)) { return \true; } return null !== $this->getReadInfo($class, $property, $context); } /** * {@inheritdoc} */ public function isWritable(string $class, string $property, array $context = []) : ?bool { if ($this->isAllowedProperty($class, $property, \true)) { return \true; } [$reflectionMethod] = $this->getMutatorMethod($class, $property); return null !== $reflectionMethod; } /** * {@inheritdoc} */ public function isInitializable(string $class, string $property, array $context = []) : ?bool { try { $reflectionClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } if (!$reflectionClass->isInstantiable()) { return \false; } if ($constructor = $reflectionClass->getConstructor()) { foreach ($constructor->getParameters() as $parameter) { if ($property === $parameter->name) { return \true; } } } elseif ($parentClass = $reflectionClass->getParentClass()) { return $this->isInitializable($parentClass->getName(), $property); } return \false; } /** * {@inheritdoc} */ public function getReadInfo(string $class, string $property, array $context = []) : ?PropertyReadInfo { try { $reflClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? \false; $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags; $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL); $allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET); if (isset($context['enable_magic_call_extraction'])) { \trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__); $allowMagicCall = $context['enable_magic_call_extraction'] ?? \false; } $hasProperty = $reflClass->hasProperty($property); $camelProp = $this->camelize($property); $getsetter = \lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item) foreach ($this->accessorPrefixes as $prefix) { $methodName = $prefix . $camelProp; if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) { $method = $reflClass->getMethod($methodName); return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), \false); } } if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags) { $method = $reflClass->getMethod($getsetter); return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), \false); } if ($allowMagicGet && $reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags) { return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, \false, \false); } if ($hasProperty && $reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags) { $reflProperty = $reflClass->getProperty($property); return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), \true); } if ($allowMagicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags) { return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get' . $camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, \false, \false); } return null; } /** * {@inheritdoc} */ public function getWriteInfo(string $class, string $property, array $context = []) : ?PropertyWriteInfo { try { $reflClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? \false; $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags; $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL); $allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET); if (isset($context['enable_magic_call_extraction'])) { \trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__); $allowMagicCall = $context['enable_magic_call_extraction'] ?? \false; } $allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction; $allowAdderRemover = $context['enable_adder_remover_extraction'] ?? \true; $camelized = $this->camelize($property); $constructor = $reflClass->getConstructor(); $singulars = $this->inflector->singularize($camelized); $errors = []; if (null !== $constructor && $allowConstruct) { foreach ($constructor->getParameters() as $parameter) { if ($parameter->getName() === $property) { return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property); } } } [$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars); if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) { $adderMethod = $reflClass->getMethod($adderAccessName); $removerMethod = $reflClass->getMethod($removerAccessName); $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER); $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic())); $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic())); return $mutator; } $errors[] = $adderAndRemoverErrors; foreach ($this->mutatorPrefixes as $mutatorPrefix) { $methodName = $mutatorPrefix . $camelized; [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1); if (!$accessible) { $errors[] = $methodAccessibleErrors; continue; } $method = $reflClass->getMethod($methodName); if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, \true)) { return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic()); } } $getsetter = \lcfirst($camelized); if ($allowGetterSetter) { [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1); if ($accessible) { $method = $reflClass->getMethod($getsetter); return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic()); } $errors[] = $methodAccessibleErrors; } if ($reflClass->hasProperty($property) && $reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags) { $reflProperty = $reflClass->getProperty($property); if (\PHP_VERSION_ID < 80100 || !$reflProperty->isReadOnly()) { return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic()); } $errors[] = [\sprintf('The property "%s" in class "%s" is a promoted readonly property.', $property, $reflClass->getName())]; $allowMagicSet = $allowMagicCall = \false; } if ($allowMagicSet) { [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2); if ($accessible) { return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, \false); } $errors[] = $methodAccessibleErrors; } if ($allowMagicCall) { [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2); if ($accessible) { return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set' . $camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, \false); } $errors[] = $methodAccessibleErrors; } if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) { $errors[] = [\sprintf('The property "%s" in class "%s" can be defined with the methods "%s()" but ' . 'the new value must be an array or an instance of \\Traversable', $property, $reflClass->getName(), \implode('()", "', [$adderAccessName, $removerAccessName]))]; } $noneProperty = new PropertyWriteInfo(); $noneProperty->setErrors(\array_merge([], ...$errors)); return $noneProperty; } /** * @return Type[]|null */ private function extractFromMutator(string $class, string $property) : ?array { [$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property); if (null === $reflectionMethod) { return null; } $reflectionParameters = $reflectionMethod->getParameters(); $reflectionParameter = $reflectionParameters[0]; if (!($reflectionType = $reflectionParameter->getType())) { return null; } $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) { $type = [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true, new Type(Type::BUILTIN_TYPE_INT), $type[0])]; } return $type; } /** * Tries to extract type information from accessors. * * @return Type[]|null */ private function extractFromAccessor(string $class, string $property) : ?array { [$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property); if (null === $reflectionMethod) { return null; } if ($reflectionType = $reflectionMethod->getReturnType()) { return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); } if (\in_array($prefix, ['is', 'can', 'has'])) { return [new Type(Type::BUILTIN_TYPE_BOOL)]; } return null; } /** * Tries to extract type information from constructor. * * @return Type[]|null */ private function extractFromConstructor(string $class, string $property) : ?array { try { $reflectionClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } $constructor = $reflectionClass->getConstructor(); if (!$constructor) { return null; } foreach ($constructor->getParameters() as $parameter) { if ($property !== $parameter->name) { continue; } $reflectionType = $parameter->getType(); return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null; } if ($parentClass = $reflectionClass->getParentClass()) { return $this->extractFromConstructor($parentClass->getName(), $property); } return null; } private function extractFromPropertyDeclaration(string $class, string $property) : ?array { try { $reflectionClass = new \ReflectionClass($class); if (\PHP_VERSION_ID >= 70400) { $reflectionProperty = $reflectionClass->getProperty($property); $reflectionPropertyType = $reflectionProperty->getType(); if (null !== $reflectionPropertyType && ($types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass()))) { return $types; } } } catch (\ReflectionException $e) { return null; } $defaultValue = $reflectionClass->getDefaultProperties()[$property] ?? null; if (null === $defaultValue) { return null; } $type = \gettype($defaultValue); $type = static::MAP_TYPES[$type] ?? $type; return [new Type($type, $this->isNullableProperty($class, $property), null, Type::BUILTIN_TYPE_ARRAY === $type)]; } private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass) : array { $types = []; $nullable = $reflectionType->allowsNull(); foreach ($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType ? $reflectionType->getTypes() : [$reflectionType] as $type) { if (!$type instanceof \ReflectionNamedType) { // Nested composite types are not supported yet. return []; } $phpTypeOrClass = $type->getName(); if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) { continue; } if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) { $types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, \true); } elseif ('void' === $phpTypeOrClass) { $types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable); } elseif ($type->isBuiltin()) { $types[] = new Type($phpTypeOrClass, $nullable); } else { $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass)); } } return $types; } private function resolveTypeName(string $name, \ReflectionClass $declaringClass) : string { if ('self' === ($lcName = \strtolower($name))) { return $declaringClass->name; } if ('parent' === $lcName && ($parent = $declaringClass->getParentClass())) { return $parent->name; } return $name; } private function isNullableProperty(string $class, string $property) : bool { try { $reflectionProperty = new \ReflectionProperty($class, $property); if (\PHP_VERSION_ID >= 70400) { $reflectionPropertyType = $reflectionProperty->getType(); return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull(); } return \false; } catch (\ReflectionException $e) { // Return false if the property doesn't exist } return \false; } private function isAllowedProperty(string $class, string $property, bool $writeAccessRequired = \false) : bool { try { $reflectionProperty = new \ReflectionProperty($class, $property); if (\PHP_VERSION_ID >= 80100 && $writeAccessRequired && $reflectionProperty->isReadOnly()) { return \false; } return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags); } catch (\ReflectionException $e) { // Return false if the property doesn't exist } return \false; } /** * Gets the accessor method. * * Returns an array with a the instance of \ReflectionMethod as first key * and the prefix of the method as second or null if not found. */ private function getAccessorMethod(string $class, string $property) : ?array { $ucProperty = \ucfirst($property); foreach ($this->accessorPrefixes as $prefix) { try { $reflectionMethod = new \ReflectionMethod($class, $prefix . $ucProperty); if ($reflectionMethod->isStatic()) { continue; } if (0 === $reflectionMethod->getNumberOfRequiredParameters()) { return [$reflectionMethod, $prefix]; } } catch (\ReflectionException $e) { // Return null if the property doesn't exist } } return null; } /** * Returns an array with a the instance of \ReflectionMethod as first key * and the prefix of the method as second or null if not found. */ private function getMutatorMethod(string $class, string $property) : ?array { $ucProperty = \ucfirst($property); $ucSingulars = $this->inflector->singularize($ucProperty); $mutatorPrefixes = \in_array($ucProperty, $ucSingulars, \true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst; foreach ($mutatorPrefixes as $prefix) { $names = [$ucProperty]; if (\in_array($prefix, $this->arrayMutatorPrefixes)) { $names = \array_merge($names, $ucSingulars); } foreach ($names as $name) { try { $reflectionMethod = new \ReflectionMethod($class, $prefix . $name); if ($reflectionMethod->isStatic()) { continue; } // Parameter can be optional to allow things like: method(array $foo = null) if ($reflectionMethod->getNumberOfParameters() >= 1) { return [$reflectionMethod, $prefix]; } } catch (\ReflectionException $e) { // Try the next prefix if the method doesn't exist } } } return null; } private function getPropertyName(string $methodName, array $reflectionProperties) : ?string { $pattern = \implode('|', \array_merge($this->accessorPrefixes, $this->mutatorPrefixes)); if ('' !== $pattern && \preg_match('/^(' . $pattern . ')(.+)$/i', $methodName, $matches)) { if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) { return $matches[2]; } foreach ($reflectionProperties as $reflectionProperty) { foreach ($this->inflector->singularize($reflectionProperty->name) as $name) { if (\strtolower($name) === \strtolower($matches[2])) { return $reflectionProperty->name; } } } return $matches[2]; } return null; } /** * Searches for add and remove methods. * * @param \ReflectionClass $reflClass The reflection class for the given object * @param array $singulars The singular form of the property name or null * * @return array An array containing the adder and remover when found and errors */ private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars) : array { if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) { return [null, null, []]; } [$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes; $errors = []; foreach ($singulars as $singular) { $addMethod = $addPrefix . $singular; $removeMethod = $removePrefix . $singular; [$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1); [$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1); $errors[] = $addMethodAccessibleErrors; $errors[] = $removeMethodAccessibleErrors; if ($addMethodFound && $removeMethodFound) { return [$addMethod, $removeMethod, []]; } if ($addMethodFound && !$removeMethodFound) { $errors[] = [\sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod)]; } elseif (!$addMethodFound && $removeMethodFound) { $errors[] = [\sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod)]; } } return [null, null, \array_merge([], ...$errors)]; } /** * Returns whether a method is public and has the number of required parameters and errors. */ private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters) : array { $errors = []; if ($class->hasMethod($methodName)) { $method = $class->getMethod($methodName); if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) { $errors[] = \sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName()); } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) { $errors[] = \sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters); } else { return [\true, $errors]; } } return [\false, $errors]; } /** * Camelizes a given string. */ private function camelize(string $string) : string { return \str_replace(' ', '', \ucwords(\str_replace('_', ' ', $string))); } /** * Return allowed reflection method flags. */ private function getMethodsFlags(int $accessFlags) : int { $methodFlags = 0; if ($accessFlags & self::ALLOW_PUBLIC) { $methodFlags |= \ReflectionMethod::IS_PUBLIC; } if ($accessFlags & self::ALLOW_PRIVATE) { $methodFlags |= \ReflectionMethod::IS_PRIVATE; } if ($accessFlags & self::ALLOW_PROTECTED) { $methodFlags |= \ReflectionMethod::IS_PROTECTED; } return $methodFlags; } /** * Return allowed reflection property flags. */ private function getPropertyFlags(int $accessFlags) : int { $propertyFlags = 0; if ($accessFlags & self::ALLOW_PUBLIC) { $propertyFlags |= \ReflectionProperty::IS_PUBLIC; } if ($accessFlags & self::ALLOW_PRIVATE) { $propertyFlags |= \ReflectionProperty::IS_PRIVATE; } if ($accessFlags & self::ALLOW_PROTECTED) { $propertyFlags |= \ReflectionProperty::IS_PROTECTED; } return $propertyFlags; } private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty) : string { if ($reflectionProperty->isPrivate()) { return PropertyReadInfo::VISIBILITY_PRIVATE; } if ($reflectionProperty->isProtected()) { return PropertyReadInfo::VISIBILITY_PROTECTED; } return PropertyReadInfo::VISIBILITY_PUBLIC; } private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod) : string { if ($reflectionMethod->isPrivate()) { return PropertyReadInfo::VISIBILITY_PRIVATE; } if ($reflectionMethod->isProtected()) { return PropertyReadInfo::VISIBILITY_PROTECTED; } return PropertyReadInfo::VISIBILITY_PUBLIC; } private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty) : string { if ($reflectionProperty->isPrivate()) { return PropertyWriteInfo::VISIBILITY_PRIVATE; } if ($reflectionProperty->isProtected()) { return PropertyWriteInfo::VISIBILITY_PROTECTED; } return PropertyWriteInfo::VISIBILITY_PUBLIC; } private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod) : string { if ($reflectionMethod->isPrivate()) { return PropertyWriteInfo::VISIBILITY_PRIVATE; } if ($reflectionMethod->isProtected()) { return PropertyWriteInfo::VISIBILITY_PROTECTED; } return PropertyWriteInfo::VISIBILITY_PUBLIC; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\Extractor; use _ContaoManager\phpDocumentor\Reflection\DocBlock; use _ContaoManager\phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; use _ContaoManager\phpDocumentor\Reflection\DocBlockFactory; use _ContaoManager\phpDocumentor\Reflection\DocBlockFactoryInterface; use _ContaoManager\phpDocumentor\Reflection\Types\Context; use _ContaoManager\phpDocumentor\Reflection\Types\ContextFactory; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use _ContaoManager\Symfony\Component\PropertyInfo\Type; use _ContaoManager\Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper; /** * Extracts data using a PHPDoc parser. * * @author Kévin Dunglas * * @final */ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface { public const PROPERTY = 0; public const ACCESSOR = 1; public const MUTATOR = 2; /** * @var array */ private $docBlocks = []; /** * @var Context[] */ private $contexts = []; private $docBlockFactory; private $contextFactory; private $phpDocTypeHelper; private $mutatorPrefixes; private $accessorPrefixes; private $arrayMutatorPrefixes; /** * @param string[]|null $mutatorPrefixes * @param string[]|null $accessorPrefixes * @param string[]|null $arrayMutatorPrefixes */ public function __construct(?DocBlockFactoryInterface $docBlockFactory = null, ?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null) { if (!\class_exists(DocBlockFactory::class)) { throw new \LogicException(\sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed. Try running composer require "phpdocumentor/reflection-docblock".', __CLASS__)); } $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); $this->contextFactory = new ContextFactory(); $this->phpDocTypeHelper = new PhpDocTypeHelper(); $this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes; $this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes; $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes; } /** * {@inheritdoc} */ public function getShortDescription(string $class, string $property, array $context = []) : ?string { /** @var $docBlock DocBlock */ [$docBlock] = $this->getDocBlock($class, $property); if (!$docBlock) { return null; } $shortDescription = $docBlock->getSummary(); if (!empty($shortDescription)) { return $shortDescription; } foreach ($docBlock->getTagsByName('var') as $var) { if ($var && !$var instanceof InvalidTag) { $varDescription = $var->getDescription()->render(); if (!empty($varDescription)) { return $varDescription; } } } return null; } /** * {@inheritdoc} */ public function getLongDescription(string $class, string $property, array $context = []) : ?string { /** @var $docBlock DocBlock */ [$docBlock] = $this->getDocBlock($class, $property); if (!$docBlock) { return null; } $contents = $docBlock->getDescription()->render(); return '' === $contents ? null : $contents; } /** * {@inheritdoc} */ public function getTypes(string $class, string $property, array $context = []) : ?array { /** @var $docBlock DocBlock */ [$docBlock, $source, $prefix] = $this->getDocBlock($class, $property); if (!$docBlock) { return null; } switch ($source) { case self::PROPERTY: $tag = 'var'; break; case self::ACCESSOR: $tag = 'return'; break; case self::MUTATOR: $tag = 'param'; break; } $parentClass = null; $types = []; /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ foreach ($docBlock->getTagsByName($tag) as $tag) { if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) { foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) { switch ($type->getClassName()) { case 'self': case 'static': $resolvedClass = $class; break; case 'parent': if (\false !== ($resolvedClass = $parentClass ?? ($parentClass = \get_parent_class($class)))) { break; } // no break default: $types[] = $type; continue 2; } $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); } } } if (!isset($types[0])) { return null; } if (!\in_array($prefix, $this->arrayMutatorPrefixes)) { return $types; } return [new Type(Type::BUILTIN_TYPE_ARRAY, \false, null, \true, new Type(Type::BUILTIN_TYPE_INT), $types[0])]; } /** * {@inheritdoc} */ public function getTypesFromConstructor(string $class, string $property) : ?array { $docBlock = $this->getDocBlockFromConstructor($class, $property); if (!$docBlock) { return null; } $types = []; /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ foreach ($docBlock->getTagsByName('param') as $tag) { if ($tag && null !== $tag->getType()) { $types[] = $this->phpDocTypeHelper->getTypes($tag->getType()); } } if (!isset($types[0]) || [] === $types[0]) { return null; } return \array_merge([], ...$types); } private function getDocBlockFromConstructor(string $class, string $property) : ?DocBlock { try { $reflectionClass = new \ReflectionClass($class); } catch (\ReflectionException $e) { return null; } $reflectionConstructor = $reflectionClass->getConstructor(); if (!$reflectionConstructor) { return null; } try { $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor)); return $this->filterDocBlockParams($docBlock, $property); } catch (\InvalidArgumentException $e) { return null; } } private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam) : DocBlock { $tags = \array_values(\array_filter($docBlock->getTagsByName('param'), function ($tag) use($allowedParam) { return $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName(); })); return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(), $docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd()); } /** * @return array{DocBlock|null, int|null, string|null} */ private function getDocBlock(string $class, string $property) : array { $propertyHash = \sprintf('%s::%s', $class, $property); if (isset($this->docBlocks[$propertyHash])) { return $this->docBlocks[$propertyHash]; } $ucFirstProperty = \ucfirst($property); switch (\true) { case $docBlock = $this->getDocBlockFromProperty($class, $property): $data = [$docBlock, self::PROPERTY, null]; break; case [$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR): $data = [$docBlock, self::ACCESSOR, null]; break; case [$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR): $data = [$docBlock, self::MUTATOR, $prefix]; break; default: $data = [null, null, null]; } return $this->docBlocks[$propertyHash] = $data; } private function getDocBlockFromProperty(string $class, string $property) : ?DocBlock { // Use a ReflectionProperty instead of $class to get the parent class if applicable try { $reflectionProperty = new \ReflectionProperty($class, $property); } catch (\ReflectionException $e) { return null; } $reflector = $reflectionProperty->getDeclaringClass(); foreach ($reflector->getTraits() as $trait) { if ($trait->hasProperty($property)) { return $this->getDocBlockFromProperty($trait->getName(), $property); } } try { return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector)); } catch (\InvalidArgumentException|\RuntimeException $e) { return null; } } /** * @return array{DocBlock, string}|null */ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type) : ?array { $prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes; $prefix = null; foreach ($prefixes as $prefix) { $methodName = $prefix . $ucFirstProperty; try { $reflectionMethod = new \ReflectionMethod($class, $methodName); if ($reflectionMethod->isStatic()) { continue; } if (self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters() || self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1) { break; } } catch (\ReflectionException $e) { // Try the next prefix if the method doesn't exist } } if (!isset($reflectionMethod)) { return null; } $reflector = $reflectionMethod->getDeclaringClass(); foreach ($reflector->getTraits() as $trait) { if ($trait->hasMethod($methodName)) { return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type); } } try { return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix]; } catch (\InvalidArgumentException|\RuntimeException $e) { return null; } } /** * Prevents a lot of redundant calls to ContextFactory::createForNamespace(). */ private function createFromReflector(\ReflectionClass $reflector) : Context { $cacheKey = $reflector->getNamespaceName() . ':' . $reflector->getFileName(); if (isset($this->contexts[$cacheKey])) { return $this->contexts[$cacheKey]; } $this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector); return $this->contexts[$cacheKey]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Guesses the property's human readable description. * * @author Kévin Dunglas */ interface PropertyDescriptionExtractorInterface { /** * Gets the short description of the property. * * @return string|null */ public function getShortDescription(string $class, string $property, array $context = []); /** * Gets the long description of the property. * * @return string|null */ public function getLongDescription(string $class, string $property, array $context = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * The write mutator defines how a property can be written. * * @author Joel Wurtz * * @internal */ final class PropertyWriteInfo { public const TYPE_NONE = 'none'; public const TYPE_METHOD = 'method'; public const TYPE_PROPERTY = 'property'; public const TYPE_ADDER_AND_REMOVER = 'adder_and_remover'; public const TYPE_CONSTRUCTOR = 'constructor'; public const VISIBILITY_PUBLIC = 'public'; public const VISIBILITY_PROTECTED = 'protected'; public const VISIBILITY_PRIVATE = 'private'; private $type; private $name; private $visibility; private $static; private $adderInfo; private $removerInfo; private $errors = []; public function __construct(string $type = self::TYPE_NONE, ?string $name = null, ?string $visibility = null, ?bool $static = null) { $this->type = $type; $this->name = $name; $this->visibility = $visibility; $this->static = $static; } public function getType() : string { return $this->type; } public function getName() : string { if (null === $this->name) { throw new \LogicException("Calling getName() when having a mutator of type {$this->type} is not tolerated."); } return $this->name; } public function setAdderInfo(self $adderInfo) : void { $this->adderInfo = $adderInfo; } public function getAdderInfo() : self { if (null === $this->adderInfo) { throw new \LogicException("Calling getAdderInfo() when having a mutator of type {$this->type} is not tolerated."); } return $this->adderInfo; } public function setRemoverInfo(self $removerInfo) : void { $this->removerInfo = $removerInfo; } public function getRemoverInfo() : self { if (null === $this->removerInfo) { throw new \LogicException("Calling getRemoverInfo() when having a mutator of type {$this->type} is not tolerated."); } return $this->removerInfo; } public function getVisibility() : string { if (null === $this->visibility) { throw new \LogicException("Calling getVisibility() when having a mutator of type {$this->type} is not tolerated."); } return $this->visibility; } public function isStatic() : bool { if (null === $this->static) { throw new \LogicException("Calling isStatic() when having a mutator of type {$this->type} is not tolerated."); } return $this->static; } public function setErrors(array $errors) : void { $this->errors = $errors; } public function getErrors() : array { return $this->errors; } public function hasErrors() : bool { return (bool) \count($this->errors); } } PropertyInfo Component ====================== The PropertyInfo component extracts information about PHP class' properties using metadata of popular sources. Resources --------- * [Documentation](https://symfony.com/doc/current/components/property_info.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Extract read information for the property of a class. * * @author Joel Wurtz */ interface PropertyReadInfoExtractorInterface { /** * Get read information object for a given property of a class. */ public function getReadInfo(string $class, string $property, array $context = []) : ?PropertyReadInfo; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Guesses if the property can be initialized through the constructor. * * @author Kévin Dunglas */ interface PropertyInitializableExtractorInterface { /** * Is the property initializable? Returns true if a constructor's parameter matches the given property name. */ public function isInitializable(string $class, string $property, array $context = []) : ?bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Default {@see PropertyInfoExtractorInterface} implementation. * * @author Kévin Dunglas * * @final */ class PropertyInfoExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface { private $listExtractors; private $typeExtractors; private $descriptionExtractors; private $accessExtractors; private $initializableExtractors; /** * @param iterable $listExtractors * @param iterable $typeExtractors * @param iterable $descriptionExtractors * @param iterable $accessExtractors * @param iterable $initializableExtractors */ public function __construct(iterable $listExtractors = [], iterable $typeExtractors = [], iterable $descriptionExtractors = [], iterable $accessExtractors = [], iterable $initializableExtractors = []) { $this->listExtractors = $listExtractors; $this->typeExtractors = $typeExtractors; $this->descriptionExtractors = $descriptionExtractors; $this->accessExtractors = $accessExtractors; $this->initializableExtractors = $initializableExtractors; } /** * {@inheritdoc} */ public function getProperties(string $class, array $context = []) : ?array { return $this->extract($this->listExtractors, 'getProperties', [$class, $context]); } /** * {@inheritdoc} */ public function getShortDescription(string $class, string $property, array $context = []) : ?string { return $this->extract($this->descriptionExtractors, 'getShortDescription', [$class, $property, $context]); } /** * {@inheritdoc} */ public function getLongDescription(string $class, string $property, array $context = []) : ?string { return $this->extract($this->descriptionExtractors, 'getLongDescription', [$class, $property, $context]); } /** * {@inheritdoc} */ public function getTypes(string $class, string $property, array $context = []) : ?array { return $this->extract($this->typeExtractors, 'getTypes', [$class, $property, $context]); } /** * {@inheritdoc} */ public function isReadable(string $class, string $property, array $context = []) : ?bool { return $this->extract($this->accessExtractors, 'isReadable', [$class, $property, $context]); } /** * {@inheritdoc} */ public function isWritable(string $class, string $property, array $context = []) : ?bool { return $this->extract($this->accessExtractors, 'isWritable', [$class, $property, $context]); } /** * {@inheritdoc} */ public function isInitializable(string $class, string $property, array $context = []) : ?bool { return $this->extract($this->initializableExtractors, 'isInitializable', [$class, $property, $context]); } /** * Iterates over registered extractors and return the first value found. * * @param iterable $extractors * @param list $arguments * * @return mixed */ private function extract(iterable $extractors, string $method, array $arguments) { foreach ($extractors as $extractor) { if (null !== ($value = $extractor->{$method}(...$arguments))) { return $value; } } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo; /** * Extracts the list of properties available for the given class. * * @author Kévin Dunglas */ interface PropertyListExtractorInterface { /** * Gets the list of properties available for the given class. * * @return string[]|null */ public function getProperties(string $class, array $context = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Adds extractors to the property_info.constructor_extractor service. * * @author Dmitrii Poddubnyi */ final class PropertyInfoConstructorPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; private $service; private $tag; public function __construct(string $service = 'property_info.constructor_extractor', string $tag = 'property_info.constructor_extractor') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/property-info', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->service = $service; $this->tag = $tag; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->service)) { return; } $definition = $container->getDefinition($this->service); $listExtractors = $this->findAndSortTaggedServices($this->tag, $container); $definition->replaceArgument(0, new IteratorArgument($listExtractors)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PropertyInfo\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Adds extractors to the property_info service. * * @author Kévin Dunglas */ class PropertyInfoPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; private $propertyInfoService; private $listExtractorTag; private $typeExtractorTag; private $descriptionExtractorTag; private $accessExtractorTag; private $initializableExtractorTag; public function __construct(string $propertyInfoService = 'property_info', string $listExtractorTag = 'property_info.list_extractor', string $typeExtractorTag = 'property_info.type_extractor', string $descriptionExtractorTag = 'property_info.description_extractor', string $accessExtractorTag = 'property_info.access_extractor', string $initializableExtractorTag = 'property_info.initializable_extractor') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/property-info', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->propertyInfoService = $propertyInfoService; $this->listExtractorTag = $listExtractorTag; $this->typeExtractorTag = $typeExtractorTag; $this->descriptionExtractorTag = $descriptionExtractorTag; $this->accessExtractorTag = $accessExtractorTag; $this->initializableExtractorTag = $initializableExtractorTag; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->propertyInfoService)) { return; } $definition = $container->getDefinition($this->propertyInfoService); $listExtractors = $this->findAndSortTaggedServices($this->listExtractorTag, $container); $definition->replaceArgument(0, new IteratorArgument($listExtractors)); $typeExtractors = $this->findAndSortTaggedServices($this->typeExtractorTag, $container); $definition->replaceArgument(1, new IteratorArgument($typeExtractors)); $descriptionExtractors = $this->findAndSortTaggedServices($this->descriptionExtractorTag, $container); $definition->replaceArgument(2, new IteratorArgument($descriptionExtractors)); $accessExtractors = $this->findAndSortTaggedServices($this->accessExtractorTag, $container); $definition->replaceArgument(3, new IteratorArgument($accessExtractors)); $initializableExtractors = $this->findAndSortTaggedServices($this->initializableExtractorTag, $container); $definition->setArgument(4, new IteratorArgument($initializableExtractors)); } } { "name": "symfony\/property-info", "type": "library", "description": "Extracts information about PHP class' properties using metadata of popular sources", "keywords": [ "property", "type", "phpdoc", "symfony", "validator", "doctrine" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "K\u00e9vin Dunglas", "email": "dunglas@gmail.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16", "symfony\/string": "^5.1|^6.0" }, "require-dev": { "symfony\/serializer": "^4.4|^5.0|^6.0", "symfony\/cache": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^4.4|^5.0|^6.0", "phpdocumentor\/reflection-docblock": "^3.0|^4.0|^5.0", "phpstan\/phpdoc-parser": "^1.0", "doctrine\/annotations": "^1.10.4|^2" }, "conflict": { "phpdocumentor\/reflection-docblock": "<3.2.2", "phpdocumentor\/type-resolver": "<1.4.0", "symfony\/dependency-injection": "<4.4" }, "suggest": { "psr\/cache-implementation": "To cache results", "symfony\/doctrine-bridge": "To use Doctrine metadata", "phpdocumentor\/reflection-docblock": "To use the PHPDoc", "symfony\/serializer": "To use Serializer metadata" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\PropertyInfo\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * StreamedResponse represents a streamed HTTP response. * * A StreamedResponse uses a callback for its content. * * The callback should use the standard PHP functions like echo * to stream the response back to the client. The flush() function * can also be used if needed. * * @see flush() * * @author Fabien Potencier */ class StreamedResponse extends Response { protected $callback; protected $streamed; private $headersSent; public function __construct(?callable $callback = null, int $status = 200, array $headers = []) { parent::__construct(null, $status, $headers); if (null !== $callback) { $this->setCallback($callback); } $this->streamed = \false; $this->headersSent = \false; } /** * Factory method for chainability. * * @param callable|null $callback A valid PHP callback or null to set it later * * @return static * * @deprecated since Symfony 5.1, use __construct() instead. */ public static function create($callback = null, int $status = 200, array $headers = []) { \trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); return new static($callback, $status, $headers); } /** * Sets the PHP callback associated with this Response. * * @return $this */ public function setCallback(callable $callback) { $this->callback = $callback; return $this; } /** * {@inheritdoc} * * This method only sends the headers once. * * @return $this */ public function sendHeaders() { if ($this->headersSent) { return $this; } $this->headersSent = \true; return parent::sendHeaders(); } /** * {@inheritdoc} * * This method only sends the content once. * * @return $this */ public function sendContent() { if ($this->streamed) { return $this; } $this->streamed = \true; if (null === $this->callback) { throw new \LogicException('The Response callback must not be null.'); } ($this->callback)(); return $this; } /** * {@inheritdoc} * * @return $this * * @throws \LogicException when the content is not null */ public function setContent(?string $content) { if (null !== $content) { throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); } $this->streamed = \true; return $this; } /** * {@inheritdoc} */ public function getContent() { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * RequestMatcherInterface is an interface for strategies to match a Request. * * @author Fabien Potencier */ interface RequestMatcherInterface { /** * Decides whether the rule(s) implemented by the strategy matches the supplied request. * * @return bool */ public function matches(Request $request); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\FileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\File; /** * BinaryFileResponse represents an HTTP response delivering a file. * * @author Niklas Fiekas * @author stealth35 * @author Igor Wiedler * @author Jordan Alliot * @author Sergey Linnik */ class BinaryFileResponse extends Response { protected static $trustXSendfileTypeHeader = \false; /** * @var File */ protected $file; protected $offset = 0; protected $maxlen = -1; protected $deleteFileAfterSend = \false; protected $chunkSize = 16 * 1024; /** * @param \SplFileInfo|string $file The file to stream * @param int $status The response status code * @param array $headers An array of response headers * @param bool $public Files are public by default * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename * @param bool $autoEtag Whether the ETag header should be automatically set * @param bool $autoLastModified Whether the Last-Modified header should be automatically set */ public function __construct($file, int $status = 200, array $headers = [], bool $public = \true, ?string $contentDisposition = null, bool $autoEtag = \false, bool $autoLastModified = \true) { parent::__construct(null, $status, $headers); $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); if ($public) { $this->setPublic(); } } /** * @param \SplFileInfo|string $file The file to stream * @param int $status The response status code * @param array $headers An array of response headers * @param bool $public Files are public by default * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename * @param bool $autoEtag Whether the ETag header should be automatically set * @param bool $autoLastModified Whether the Last-Modified header should be automatically set * * @return static * * @deprecated since Symfony 5.2, use __construct() instead. */ public static function create($file = null, int $status = 200, array $headers = [], bool $public = \true, ?string $contentDisposition = null, bool $autoEtag = \false, bool $autoLastModified = \true) { \trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); } /** * Sets the file to stream. * * @param \SplFileInfo|string $file The file to stream * * @return $this * * @throws FileException */ public function setFile($file, ?string $contentDisposition = null, bool $autoEtag = \false, bool $autoLastModified = \true) { if (!$file instanceof File) { if ($file instanceof \SplFileInfo) { $file = new File($file->getPathname()); } else { $file = new File((string) $file); } } if (!$file->isReadable()) { throw new FileException('File must be readable.'); } $this->file = $file; if ($autoEtag) { $this->setAutoEtag(); } if ($autoLastModified) { $this->setAutoLastModified(); } if ($contentDisposition) { $this->setContentDisposition($contentDisposition); } return $this; } /** * Gets the file. * * @return File */ public function getFile() { return $this->file; } /** * Sets the response stream chunk size. * * @return $this */ public function setChunkSize(int $chunkSize) : self { if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) { throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.'); } $this->chunkSize = $chunkSize; return $this; } /** * Automatically sets the Last-Modified header according the file modification date. * * @return $this */ public function setAutoLastModified() { $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); return $this; } /** * Automatically sets the ETag header according to the checksum of the file. * * @return $this */ public function setAutoEtag() { $this->setEtag(\base64_encode(\hash_file('sha256', $this->file->getPathname(), \true))); return $this; } /** * Sets the Content-Disposition header with the given filename. * * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename * * @return $this */ public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '') { if ('' === $filename) { $filename = $this->file->getFilename(); } if ('' === $filenameFallback && (!\preg_match('/^[\\x20-\\x7e]*$/', $filename) || \str_contains($filename, '%'))) { $encoding = \mb_detect_encoding($filename, null, \true) ?: '8bit'; for ($i = 0, $filenameLength = \mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { $char = \mb_substr($filename, $i, 1, $encoding); if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { $filenameFallback .= '_'; } else { $filenameFallback .= $char; } } } $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); $this->headers->set('Content-Disposition', $dispositionHeader); return $this; } /** * {@inheritdoc} */ public function prepare(Request $request) { if ($this->isInformational() || $this->isEmpty()) { parent::prepare($request); $this->maxlen = 0; return $this; } if (!$this->headers->has('Content-Type')) { $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); } parent::prepare($request); $this->offset = 0; $this->maxlen = -1; if (\false === ($fileSize = $this->file->getSize())) { return $this; } $this->headers->remove('Transfer-Encoding'); $this->headers->set('Content-Length', $fileSize); if (!$this->headers->has('Accept-Ranges')) { // Only accept ranges on safe HTTP methods $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); } if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { // Use X-Sendfile, do not send any content. $type = $request->headers->get('X-Sendfile-Type'); $path = $this->file->getRealPath(); // Fall back to scheme://path for stream wrapped locations. if (\false === $path) { $path = $this->file->getPathname(); } if ('x-accel-redirect' === \strtolower($type)) { // Do X-Accel-Mapping substitutions. // @link https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-redirect $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); foreach ($parts as $part) { [$pathPrefix, $location] = $part; if (\substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { $path = $location . \substr($path, \strlen($pathPrefix)); // Only set X-Accel-Redirect header if a valid URI can be produced // as nginx does not serve arbitrary file paths. $this->headers->set($type, $path); $this->maxlen = 0; break; } } } else { $this->headers->set($type, $path); $this->maxlen = 0; } } elseif ($request->headers->has('Range') && $request->isMethod('GET')) { // Process the range headers. if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { $range = $request->headers->get('Range'); if (\str_starts_with($range, 'bytes=')) { [$start, $end] = \explode('-', \substr($range, 6), 2) + [1 => 0]; $end = '' === $end ? $fileSize - 1 : (int) $end; if ('' === $start) { $start = $fileSize - $end; $end = $fileSize - 1; } else { $start = (int) $start; } if ($start <= $end) { $end = \min($end, $fileSize - 1); if ($start < 0 || $start > $end) { $this->setStatusCode(416); $this->headers->set('Content-Range', \sprintf('bytes */%s', $fileSize)); } elseif ($end - $start < $fileSize - 1) { $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; $this->offset = $start; $this->setStatusCode(206); $this->headers->set('Content-Range', \sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); $this->headers->set('Content-Length', $end - $start + 1); } } } } } if ($request->isMethod('HEAD')) { $this->maxlen = 0; } return $this; } private function hasValidIfRangeHeader(?string $header) : bool { if ($this->getEtag() === $header) { return \true; } if (null === ($lastModified = $this->getLastModified())) { return \false; } return $lastModified->format('D, d M Y H:i:s') . ' GMT' === $header; } /** * {@inheritdoc} */ public function sendContent() { try { if (!$this->isSuccessful()) { return parent::sendContent(); } if (0 === $this->maxlen) { return $this; } $out = \fopen('php://output', 'w'); $file = \fopen($this->file->getPathname(), 'r'); \ignore_user_abort(\true); if (0 !== $this->offset) { \fseek($file, $this->offset); } $length = $this->maxlen; while ($length && !\feof($file)) { $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length; if (\false === ($data = \fread($file, $read))) { break; } while ('' !== $data) { $read = \fwrite($out, $data); if (\false === $read || \connection_aborted()) { break 2; } if (0 < $length) { $length -= $read; } $data = \substr($data, $read); } } \fclose($out); \fclose($file); } finally { if ($this->deleteFileAfterSend && \is_file($this->file->getPathname())) { \unlink($this->file->getPathname()); } } return $this; } /** * {@inheritdoc} * * @throws \LogicException when the content is not null */ public function setContent(?string $content) { if (null !== $content) { throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); } return $this; } /** * {@inheritdoc} */ public function getContent() { return \false; } /** * Trust X-Sendfile-Type header. */ public static function trustXSendfileTypeHeader() { self::$trustXSendfileTypeHeader = \true; } /** * If this is set to true, the file will be unlinked after the request is sent * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. * * @return $this */ public function deleteFileAfterSend(bool $shouldDelete = \true) { $this->deleteFileAfterSend = $shouldDelete; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\BadRequestException; /** * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. * * @author Saif Eddin Gmati */ final class InputBag extends ParameterBag { /** * Returns a scalar input value by name. * * @param string|int|float|bool|null $default The default value if the input key does not exist * * @return string|int|float|bool|null */ public function get(string $key, $default = null) { if (null !== $default && !\is_scalar($default) && !(\is_object($default) && \method_exists($default, '__toString'))) { \trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__); } $value = parent::get($key, $this); if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && \method_exists($value, '__toString'))) { \trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); } return $this === $value ? $default : $value; } /** * {@inheritdoc} */ public function all(?string $key = null) : array { return parent::all($key); } /** * Replaces the current input values by a new set. */ public function replace(array $inputs = []) { $this->parameters = []; $this->add($inputs); } /** * Adds input values. */ public function add(array $inputs = []) { foreach ($inputs as $input => $value) { $this->set($input, $value); } } /** * Sets an input by name. * * @param string|int|float|bool|array|null $value */ public function set(string $key, $value) { if (null !== $value && !\is_scalar($value) && !\is_array($value) && !\method_exists($value, '__toString')) { \trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', \get_debug_type($value), __METHOD__); } $this->parameters[$key] = $value; } /** * {@inheritdoc} */ public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) { $value = $this->has($key) ? $this->all()[$key] : $default; // Always turn $options into an array - this allows filter_var option shortcuts. if (!\is_array($options) && $options) { $options = ['flags' => $options]; } if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) { \trigger_deprecation('symfony/http-foundation', '5.1', 'Filtering an array value with "%s()" without passing the FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY flag is deprecated', __METHOD__); if (!isset($options['flags'])) { $options['flags'] = \FILTER_REQUIRE_ARRAY; } } if (\FILTER_CALLBACK & $filter && !($options['options'] ?? null) instanceof \Closure) { \trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); } return \filter_var($value, $filter, $options); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\RateLimiter; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\RateLimiter\LimiterInterface; use _ContaoManager\Symfony\Component\RateLimiter\Policy\NoLimiter; use _ContaoManager\Symfony\Component\RateLimiter\RateLimit; /** * An implementation of RequestRateLimiterInterface that * fits most use-cases. * * @author Wouter de Jong */ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface { public function consume(Request $request) : RateLimit { $limiters = $this->getLimiters($request); if (0 === \count($limiters)) { $limiters = [new NoLimiter()]; } $minimalRateLimit = null; foreach ($limiters as $limiter) { $rateLimit = $limiter->consume(1); $minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit; } return $minimalRateLimit; } public function reset(Request $request) : void { foreach ($this->getLimiters($request) as $limiter) { $limiter->reset(); } } /** * @return LimiterInterface[] a set of limiters using keys extracted from the request */ protected abstract function getLimiters(Request $request) : array; private static function getMinimalRateLimit(RateLimit $first, RateLimit $second) : RateLimit { if ($first->isAccepted() !== $second->isAccepted()) { return $first->isAccepted() ? $second : $first; } $firstRemainingTokens = $first->getRemainingTokens(); $secondRemainingTokens = $second->getRemainingTokens(); if ($firstRemainingTokens === $secondRemainingTokens) { return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first; } return $firstRemainingTokens > $secondRemainingTokens ? $second : $first; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\RateLimiter; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\RateLimiter\RateLimit; /** * A special type of limiter that deals with requests. * * This allows to limit on different types of information * from the requests. * * @author Wouter de Jong */ interface RequestRateLimiterInterface { public function consume(Request $request) : RateLimit; public function reset(Request $request) : void; } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseCookieValueSame extends Constraint { private $name; private $value; private $path; private $domain; public function __construct(string $name, string $value, string $path = '/', ?string $domain = null) { $this->name = $name; $this->value = $value; $this->path = $path; $this->domain = $domain; } /** * {@inheritdoc} */ public function toString() : string { $str = \sprintf('has cookie "%s"', $this->name); if ('/' !== $this->path) { $str .= \sprintf(' with path "%s"', $this->path); } if ($this->domain) { $str .= \sprintf(' for domain "%s"', $this->domain); } $str .= \sprintf(' with value "%s"', $this->value); return $str; } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { $cookie = $this->getCookie($response); if (!$cookie) { return \false; } return $this->value === (string) $cookie->getValue(); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } protected function getCookie(Response $response) : ?Cookie { $cookies = $response->headers->getCookies(); $filteredCookies = \array_filter($cookies, function (Cookie $cookie) { return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; }); return \reset($filteredCookies) ?: null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Cookie; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseHasCookie extends Constraint { private $name; private $path; private $domain; public function __construct(string $name, string $path = '/', ?string $domain = null) { $this->name = $name; $this->path = $path; $this->domain = $domain; } /** * {@inheritdoc} */ public function toString() : string { $str = \sprintf('has cookie "%s"', $this->name); if ('/' !== $this->path) { $str .= \sprintf(' with path "%s"', $this->path); } if ($this->domain) { $str .= \sprintf(' for domain "%s"', $this->domain); } return $str; } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return null !== $this->getCookie($response); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } private function getCookie(Response $response) : ?Cookie { $cookies = $response->headers->getCookies(); $filteredCookies = \array_filter($cookies, function (Cookie $cookie) { return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; }); return \reset($filteredCookies) ?: null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseStatusCodeSame extends Constraint { private $statusCode; public function __construct(int $statusCode) { $this->statusCode = $statusCode; } /** * {@inheritdoc} */ public function toString() : string { return 'status code is ' . $this->statusCode; } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return $this->statusCode === $response->getStatusCode(); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } /** * @param Response $response * * {@inheritdoc} */ protected function additionalFailureDescription($response) : string { return (string) $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseIsSuccessful extends Constraint { /** * {@inheritdoc} */ public function toString() : string { return 'is successful'; } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return $response->isSuccessful(); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } /** * @param Response $response * * {@inheritdoc} */ protected function additionalFailureDescription($response) : string { return (string) $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseIsUnprocessable extends Constraint { /** * {@inheritdoc} */ public function toString() : string { return 'is unprocessable'; } /** * @param Response $other * * {@inheritdoc} */ protected function matches($other) : bool { return Response::HTTP_UNPROCESSABLE_ENTITY === $other->getStatusCode(); } /** * @param Response $other * * {@inheritdoc} */ protected function failureDescription($other) : string { return 'the Response ' . $this->toString(); } /** * @param Response $other * * {@inheritdoc} */ protected function additionalFailureDescription($other) : string { return (string) $other; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseIsRedirected extends Constraint { /** * {@inheritdoc} */ public function toString() : string { return 'is redirected'; } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return $response->isRedirect(); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } /** * @param Response $response * * {@inheritdoc} */ protected function additionalFailureDescription($response) : string { return (string) $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseHasHeader extends Constraint { private $headerName; public function __construct(string $headerName) { $this->headerName = $headerName; } /** * {@inheritdoc} */ public function toString() : string { return \sprintf('has header "%s"', $this->headerName); } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return $response->headers->has($this->headerName); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Request; final class RequestAttributeValueSame extends Constraint { private $name; private $value; public function __construct(string $name, string $value) { $this->name = $name; $this->value = $value; } /** * {@inheritdoc} */ public function toString() : string { return \sprintf('has attribute "%s" with value "%s"', $this->name, $this->value); } /** * @param Request $request * * {@inheritdoc} */ protected function matches($request) : bool { return $this->value === $request->attributes->get($this->name); } /** * @param Request $request * * {@inheritdoc} */ protected function failureDescription($request) : string { return 'the Request ' . $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\Response; /** * Asserts that the response is in the given format. * * @author Kévin Dunglas */ final class ResponseFormatSame extends Constraint { private $request; private $format; public function __construct(Request $request, ?string $format) { $this->request = $request; $this->format = $format; } /** * {@inheritdoc} */ public function toString() : string { return 'format is ' . ($this->format ?? 'null'); } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return $this->format === $this->request->getFormat($response->headers->get('Content-Type')); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } /** * @param Response $response * * {@inheritdoc} */ protected function additionalFailureDescription($response) : string { return (string) $response; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Test\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\HttpFoundation\Response; final class ResponseHeaderSame extends Constraint { private $headerName; private $expectedValue; public function __construct(string $headerName, string $expectedValue) { $this->headerName = $headerName; $this->expectedValue = $expectedValue; } /** * {@inheritdoc} */ public function toString() : string { return \sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); } /** * @param Response $response * * {@inheritdoc} */ protected function matches($response) : bool { return $this->expectedValue === $response->headers->get($this->headerName, null); } /** * @param Response $response * * {@inheritdoc} */ protected function failureDescription($response) : string { return 'the Response ' . $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * ServerBag is a container for HTTP headers from the $_SERVER variable. * * @author Fabien Potencier * @author Bulat Shakirzyanov * @author Robert Kiss */ class ServerBag extends ParameterBag { /** * Gets the HTTP headers. * * @return array */ public function getHeaders() { $headers = []; foreach ($this->parameters as $key => $value) { if (\str_starts_with($key, 'HTTP_')) { $headers[\substr($key, 5)] = $value; } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], \true) && '' !== $value) { $headers[$key] = $value; } } if (isset($this->parameters['PHP_AUTH_USER'])) { $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; $headers['PHP_AUTH_PW'] = $this->parameters['PHP_AUTH_PW'] ?? ''; } else { /* * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default * For this workaround to work, add these lines to your .htaccess file: * RewriteCond %{HTTP:Authorization} .+ * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] * * A sample .htaccess file: * RewriteEngine On * RewriteCond %{HTTP:Authorization} .+ * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] * RewriteCond %{REQUEST_FILENAME} !-f * RewriteRule ^(.*)$ index.php [QSA,L] */ $authorizationHeader = null; if (isset($this->parameters['HTTP_AUTHORIZATION'])) { $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; } if (null !== $authorizationHeader) { if (0 === \stripos($authorizationHeader, 'basic ')) { // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic $exploded = \explode(':', \base64_decode(\substr($authorizationHeader, 6)), 2); if (2 == \count($exploded)) { [$headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']] = $exploded; } } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && 0 === \stripos($authorizationHeader, 'digest ')) { // In some circumstances PHP_AUTH_DIGEST needs to be set $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; } elseif (0 === \stripos($authorizationHeader, 'bearer ')) { /* * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, * I'll just set $headers['AUTHORIZATION'] here. * https://php.net/reserved.variables.server */ $headers['AUTHORIZATION'] = $authorizationHeader; } } } if (isset($headers['AUTHORIZATION'])) { return $headers; } // PHP_AUTH_USER/PHP_AUTH_PW if (isset($headers['PHP_AUTH_USER'])) { $headers['AUTHORIZATION'] = 'Basic ' . \base64_encode($headers['PHP_AUTH_USER'] . ':' . ($headers['PHP_AUTH_PW'] ?? '')); } elseif (isset($headers['PHP_AUTH_DIGEST'])) { $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; } return $headers; } } CHANGELOG ========= 5.4 --- * Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead. * Add the `litespeed_finish_request` method to work with Litespeed * Deprecate `upload_progress.*` and `url_rewriter.tags` session options * Allow setting session options via DSN 5.3 --- * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException` * Add the `RequestStack::getSession` method * Deprecate the `NamespacedAttributeBag` class * Add `ResponseFormatSame` PHPUnit constraint * Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement 5.2.0 ----- * added support for `X-Forwarded-Prefix` header * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names * added `File::getContent()` * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()` * added `Request::toArray()` to parse a JSON request body to an array * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead. * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead 5.1.0 ----- * added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`, `Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`, `Cookie::withRaw`, `Cookie::withSameSite` * Deprecate `Response::create()`, `JsonResponse::create()`, `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use `__construct()` instead) * added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference according to [RFC 8674](https://tools.ietf.org/html/rfc8674) * made the Mime component an optional dependency * added `MarshallingSessionHandler`, `IdentityMarshaller` * made `Session` accept a callback to report when the session is being used * Add support for all core cache control directives * Added `Symfony\Component\HttpFoundation\InputBag` * Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values 5.0.0 ----- * made `Cookie` auto-secure and lax by default * removed classes in the `MimeType` namespace, use the Symfony Mime component instead * removed method `UploadedFile::getClientSize()` and the related constructor argument * made `Request::getSession()` throw if the session has not been set before * removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL` * passing a null url when instantiating a `RedirectResponse` is not allowed 4.4.0 ----- * passing arguments to `Request::isMethodSafe()` is deprecated. * `ApacheRequest` is deprecated, use the `Request` class instead. * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column, make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to update your database. * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database to speed up garbage collection of expired sessions. * added `SessionHandlerFactory` to create session handlers with a DSN * added `IpUtils::anonymize()` to help with GDPR compliance. 4.3.0 ----- * added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`, `ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame` * deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`. * deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`. * deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`. * deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`. * added `UrlHelper` that allows to get an absolute URL and a relative path for a given path 4.2.0 ----- * the default value of the "$secure" and "$samesite" arguments of Cookie's constructor will respectively change from "false" to "null" and from "null" to "lax" in Symfony 5.0, you should define their values explicitly or use "Cookie::create()" instead. * added `matchPort()` in RequestMatcher 4.1.3 ----- * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` HTTP headers has been dropped for security reasons. 4.1.0 ----- * Query string normalization uses `parse_str()` instead of custom parsing logic. * Passing the file size to the constructor of the `UploadedFile` class is deprecated. * The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead. * added `RedisSessionHandler` to use Redis as a session storage * The `get()` method of the `AcceptHeader` class now takes into account the `*` and `*/*` default values (if they are present in the Accept HTTP header) when looking for items. * deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead. * added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`, `IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to handle failed `UploadedFile`. * added `MigratingSessionHandler` for migrating between two session handlers without losing sessions * added `HeaderUtils`. 4.0.0 ----- * the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods have been removed * the `Request::HEADER_CLIENT_IP` constant has been removed, use `Request::HEADER_X_FORWARDED_FOR` instead * the `Request::HEADER_CLIENT_HOST` constant has been removed, use `Request::HEADER_X_FORWARDED_HOST` instead * the `Request::HEADER_CLIENT_PROTO` constant has been removed, use `Request::HEADER_X_FORWARDED_PROTO` instead * the `Request::HEADER_CLIENT_PORT` constant has been removed, use `Request::HEADER_X_FORWARDED_PORT` instead * checking for cacheable HTTP methods using the `Request::isMethodSafe()` method (by not passing `false` as its argument) is not supported anymore and throws a `\BadMethodCallException` * the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed * setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a `\TypeError` 3.4.0 ----- * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead 3.3.0 ----- * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, see https://symfony.com/doc/current/deployment/proxies.html for more info, * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, disabling `Range` and `Content-Length` handling, switching to chunked encoding instead * added the `Cookie::fromString()` method that allows to create a cookie from a raw header string 3.1.0 ----- * Added support for creating `JsonResponse` with a string of JSON data 3.0.0 ----- * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" 2.8.0 ----- * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and will be removed in 3.0. 2.6.0 ----- * PdoSessionHandler changes - implemented different session locking strategies to prevent loss of data by concurrent access to the same session - [BC BREAK] save session data in a binary column without base64_encode - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session - implemented lazy connections that are only opened when a session is used by either passing a dsn string explicitly or falling back to session.save_path ini setting - added a createTable method that initializes a correctly defined table depending on the database vendor 2.5.0 ----- * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation of the options used while encoding data to JSON format. 2.4.0 ----- * added RequestStack * added Request::getEncodings() * added accessors methods to session handlers 2.3.0 ----- * added support for ranges of IPs in trusted proxies * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases to verify that Exceptions are properly thrown when the PDO queries fail. 2.2.0 ----- * fixed the Request::create() precedence (URI information always take precedence now) * added Request::getTrustedProxies() * deprecated Request::isProxyTrusted() * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects * added a IpUtils class to check if an IP belongs to a CIDR * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 2.1.0 ----- * added Request::getSchemeAndHttpHost() and Request::getUserInfo() * added a fluent interface to the Response class * added Request::isProxyTrusted() * added JsonResponse * added a getTargetUrl method to RedirectResponse * added support for streamed responses * made Response::prepare() method the place to enforce HTTP specification * [BC BREAK] moved management of the locale from the Session class to the Request class * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() * made FileBinaryMimeTypeGuesser command configurable * added Request::getUser() and Request::getPassword() * added support for the PATCH method in Request * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) * made mimetype to extension conversion configurable * [BC BREAK] Moved all session related classes and interfaces into own namespace, as `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. * SessionHandlers must implement `\SessionHandlerInterface` or extend from the `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. * Added internal storage driver proxy mechanism for forward compatibility with PHP 5.4 `\SessionHandler` class. * Added session handlers for custom Memcache, Memcached and Null session save handlers. * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class is a mediator for the session storage internals including the session handlers which do the real work of participating in the internal PHP session workflow. * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit and functional testing without starting real PHP sessions. Removed `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` for functional tests. These do not interact with global session ini configuration values, session functions or `$_SESSION` superglobal. This means they can be configured directly allowing multiple instances to work without conflicting in the same PHP process. * [BC BREAK] Removed the `close()` method from the `Session` class, as this is now redundant. * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead which returns a `FlashBagInterface`. * `Session->clear()` now only clears session attributes as before it cleared flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. * Session data is now managed by `SessionBagInterface` to better encapsulate session data. * Refactored session attribute and flash messages system to their own `SessionBagInterface` implementations. * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This implementation is ESI compatible. * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire behavior of messages auto expiring after one page page load. Messages must be retrieved by `get()` or `all()`. * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate attributes storage behavior from 2.0.x (default). * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for namespace session attributes. * Flash API can stores messages in an array so there may be multiple messages per flash type. The old `Session` class API remains without BC break as it will allow single messages as before. * Added basic session meta-data to the session to record session create time, last updated time, and the lifetime of the session cookie that was provided to the client. * Request::getClientIp() method doesn't take a parameter anymore but bases itself on the trustProxy parameter. * Added isMethod() to Request object. * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of a `Request` now all return a raw value (vs a urldecoded value before). Any call to one of these methods must be checked and wrapped in a `rawurldecode()` if needed. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\FileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; use _ContaoManager\Symfony\Component\Mime\MimeTypes; /** * A file in the file system. * * @author Bernhard Schussek */ class File extends \SplFileInfo { /** * Constructs a new file from the given path. * * @param string $path The path to the file * @param bool $checkPath Whether to check the path or not * * @throws FileNotFoundException If the given path is not a file */ public function __construct(string $path, bool $checkPath = \true) { if ($checkPath && !\is_file($path)) { throw new FileNotFoundException($path); } parent::__construct($path); } /** * Returns the extension based on the mime type. * * If the mime type is unknown, returns null. * * This method uses the mime type as guessed by getMimeType() * to guess the file extension. * * @return string|null * * @see MimeTypes * @see getMimeType() */ public function guessExtension() { if (!\class_exists(MimeTypes::class)) { throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); } return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null; } /** * Returns the mime type of the file. * * The mime type is guessed using a MimeTypeGuesserInterface instance, * which uses finfo_file() then the "file" system binary, * depending on which of those are available. * * @return string|null * * @see MimeTypes */ public function getMimeType() { if (!\class_exists(MimeTypes::class)) { throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".'); } return MimeTypes::getDefault()->guessMimeType($this->getPathname()); } /** * Moves the file to a new location. * * @return self * * @throws FileException if the target file could not be created */ public function move(string $directory, ?string $name = null) { $target = $this->getTargetFile($directory, $name); \set_error_handler(function ($type, $msg) use(&$error) { $error = $msg; }); try { $renamed = \rename($this->getPathname(), $target); } finally { \restore_error_handler(); } if (!$renamed) { throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, \strip_tags($error))); } @\chmod($target, 0666 & ~\umask()); return $target; } public function getContent() : string { $content = \file_get_contents($this->getPathname()); if (\false === $content) { throw new FileException(\sprintf('Could not get the content of the file "%s".', $this->getPathname())); } return $content; } /** * @return self */ protected function getTargetFile(string $directory, ?string $name = null) { if (!\is_dir($directory)) { if (\false === @\mkdir($directory, 0777, \true) && !\is_dir($directory)) { throw new FileException(\sprintf('Unable to create the "%s" directory.', $directory)); } } elseif (!\is_writable($directory)) { throw new FileException(\sprintf('Unable to write in the "%s" directory.', $directory)); } $target = \rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name)); return new self($target, \false); } /** * Returns locale independent base name of the given path. * * @return string */ protected function getName(string $name) { $originalName = \str_replace('\\', '/', $name); $pos = \strrpos($originalName, '/'); $originalName = \false === $pos ? $originalName : \substr($originalName, $pos + 1); return $originalName; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File; /** * A PHP stream of unknown size. * * @author Nicolas Grekas */ class Stream extends File { /** * {@inheritdoc} * * @return int|false */ #[\ReturnTypeWillChange] public function getSize() { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\FileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\NoFileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException; use _ContaoManager\Symfony\Component\HttpFoundation\File\Exception\PartialFileException; use _ContaoManager\Symfony\Component\Mime\MimeTypes; /** * A file uploaded through a form. * * @author Bernhard Schussek * @author Florian Eckerstorfer * @author Fabien Potencier */ class UploadedFile extends File { private $test; private $originalName; private $mimeType; private $error; /** * Accepts the information of the uploaded file as provided by the PHP global $_FILES. * * The file object is only created when the uploaded file is valid (i.e. when the * isValid() method returns true). Otherwise the only methods that could be called * on an UploadedFile instance are: * * * getClientOriginalName, * * getClientMimeType, * * isValid, * * getError. * * Calling any other method on an non-valid instance will cause an unpredictable result. * * @param string $path The full temporary path to the file * @param string $originalName The original file name of the uploaded file * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK * @param bool $test Whether the test mode is active * Local files are used in test mode hence the code should not enforce HTTP uploads * * @throws FileException If file_uploads is disabled * @throws FileNotFoundException If the file does not exist */ public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null, bool $test = \false) { $this->originalName = $this->getName($originalName); $this->mimeType = $mimeType ?: 'application/octet-stream'; $this->error = $error ?: \UPLOAD_ERR_OK; $this->test = $test; parent::__construct($path, \UPLOAD_ERR_OK === $this->error); } /** * Returns the original file name. * * It is extracted from the request from which the file has been uploaded. * This should not be considered as a safe value to use for a file name on your servers. * * @return string */ public function getClientOriginalName() { return $this->originalName; } /** * Returns the original file extension. * * It is extracted from the original file name that was uploaded. * This should not be considered as a safe value to use for a file name on your servers. * * @return string */ public function getClientOriginalExtension() { return \pathinfo($this->originalName, \PATHINFO_EXTENSION); } /** * Returns the file mime type. * * The client mime type is extracted from the request from which the file * was uploaded, so it should not be considered as a safe value. * * For a trusted mime type, use getMimeType() instead (which guesses the mime * type based on the file content). * * @return string * * @see getMimeType() */ public function getClientMimeType() { return $this->mimeType; } /** * Returns the extension based on the client mime type. * * If the mime type is unknown, returns null. * * This method uses the mime type as guessed by getClientMimeType() * to guess the file extension. As such, the extension returned * by this method cannot be trusted. * * For a trusted extension, use guessExtension() instead (which guesses * the extension based on the guessed mime type for the file). * * @return string|null * * @see guessExtension() * @see getClientMimeType() */ public function guessClientExtension() { if (!\class_exists(MimeTypes::class)) { throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); } return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null; } /** * Returns the upload error. * * If the upload was successful, the constant UPLOAD_ERR_OK is returned. * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. * * @return int */ public function getError() { return $this->error; } /** * Returns whether the file has been uploaded with HTTP and no error occurred. * * @return bool */ public function isValid() { $isOk = \UPLOAD_ERR_OK === $this->error; return $this->test ? $isOk : $isOk && \is_uploaded_file($this->getPathname()); } /** * Moves the file to a new location. * * @return File * * @throws FileException if, for any reason, the file could not have been moved */ public function move(string $directory, ?string $name = null) { if ($this->isValid()) { if ($this->test) { return parent::move($directory, $name); } $target = $this->getTargetFile($directory, $name); \set_error_handler(function ($type, $msg) use(&$error) { $error = $msg; }); try { $moved = \move_uploaded_file($this->getPathname(), $target); } finally { \restore_error_handler(); } if (!$moved) { throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, \strip_tags($error))); } @\chmod($target, 0666 & ~\umask()); return $target; } switch ($this->error) { case \UPLOAD_ERR_INI_SIZE: throw new IniSizeFileException($this->getErrorMessage()); case \UPLOAD_ERR_FORM_SIZE: throw new FormSizeFileException($this->getErrorMessage()); case \UPLOAD_ERR_PARTIAL: throw new PartialFileException($this->getErrorMessage()); case \UPLOAD_ERR_NO_FILE: throw new NoFileException($this->getErrorMessage()); case \UPLOAD_ERR_CANT_WRITE: throw new CannotWriteFileException($this->getErrorMessage()); case \UPLOAD_ERR_NO_TMP_DIR: throw new NoTmpDirFileException($this->getErrorMessage()); case \UPLOAD_ERR_EXTENSION: throw new ExtensionFileException($this->getErrorMessage()); } throw new FileException($this->getErrorMessage()); } /** * Returns the maximum size of an uploaded file as configured in php.ini. * * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX) */ public static function getMaxFilesize() { $sizePostMax = self::parseFilesize(\ini_get('post_max_size')); $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize')); return \min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); } /** * Returns the given size from an ini value in bytes. * * @return int|float Returns float if size > PHP_INT_MAX */ private static function parseFilesize(string $size) { if ('' === $size) { return 0; } $size = \strtolower($size); $max = \ltrim($size, '+'); if (\str_starts_with($max, '0x')) { $max = \intval($max, 16); } elseif (\str_starts_with($max, '0')) { $max = \intval($max, 8); } else { $max = (int) $max; } switch (\substr($size, -1)) { case 't': $max *= 1024; // no break case 'g': $max *= 1024; // no break case 'm': $max *= 1024; // no break case 'k': $max *= 1024; } return $max; } /** * Returns an informative upload error message. * * @return string */ public function getErrorMessage() { static $errors = [\UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', \UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', \UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', \UPLOAD_ERR_NO_FILE => 'No file was uploaded.', \UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', \UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', \UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.']; $errorCode = $this->error; $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.'; return \sprintf($message, $this->getClientOriginalName(), $maxFilesize); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when the access on a file was denied. * * @author Bernhard Schussek */ class AccessDeniedException extends FileException { public function __construct(string $path) { parent::__construct(\sprintf('The file %s could not be accessed', $path)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile. * * @author Florent Mata */ class NoFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile. * * @author Florent Mata */ class IniSizeFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile. * * @author Florent Mata */ class CannotWriteFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile. * * @author Florent Mata */ class ExtensionFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an error occurred in the component File. * * @author Bernhard Schussek */ class FileException extends \RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an error occurred during file upload. * * @author Bernhard Schussek */ class UploadException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; class UnexpectedTypeException extends FileException { public function __construct($value, string $expectedType) { parent::__construct(\sprintf('Expected argument of type %s, %s given', $expectedType, \get_debug_type($value))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile. * * @author Florent Mata */ class FormSizeFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when a file was not found. * * @author Bernhard Schussek */ class FileNotFoundException extends FileException { public function __construct(string $path) { parent::__construct(\sprintf('The file "%s" does not exist', $path)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile. * * @author Florent Mata */ class PartialFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\File\Exception; /** * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile. * * @author Florent Mata */ class NoTmpDirFileException extends FileException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; // Help opcache.preload discover always-needed symbols \class_exists(ResponseHeaderBag::class); /** * Response represents an HTTP response. * * @author Fabien Potencier */ class Response { public const HTTP_CONTINUE = 100; public const HTTP_SWITCHING_PROTOCOLS = 101; public const HTTP_PROCESSING = 102; // RFC2518 public const HTTP_EARLY_HINTS = 103; // RFC8297 public const HTTP_OK = 200; public const HTTP_CREATED = 201; public const HTTP_ACCEPTED = 202; public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; public const HTTP_NO_CONTENT = 204; public const HTTP_RESET_CONTENT = 205; public const HTTP_PARTIAL_CONTENT = 206; public const HTTP_MULTI_STATUS = 207; // RFC4918 public const HTTP_ALREADY_REPORTED = 208; // RFC5842 public const HTTP_IM_USED = 226; // RFC3229 public const HTTP_MULTIPLE_CHOICES = 300; public const HTTP_MOVED_PERMANENTLY = 301; public const HTTP_FOUND = 302; public const HTTP_SEE_OTHER = 303; public const HTTP_NOT_MODIFIED = 304; public const HTTP_USE_PROXY = 305; public const HTTP_RESERVED = 306; public const HTTP_TEMPORARY_REDIRECT = 307; public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 public const HTTP_BAD_REQUEST = 400; public const HTTP_UNAUTHORIZED = 401; public const HTTP_PAYMENT_REQUIRED = 402; public const HTTP_FORBIDDEN = 403; public const HTTP_NOT_FOUND = 404; public const HTTP_METHOD_NOT_ALLOWED = 405; public const HTTP_NOT_ACCEPTABLE = 406; public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; public const HTTP_REQUEST_TIMEOUT = 408; public const HTTP_CONFLICT = 409; public const HTTP_GONE = 410; public const HTTP_LENGTH_REQUIRED = 411; public const HTTP_PRECONDITION_FAILED = 412; public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; public const HTTP_REQUEST_URI_TOO_LONG = 414; public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; public const HTTP_EXPECTATION_FAILED = 417; public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 public const HTTP_LOCKED = 423; // RFC4918 public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725 public const HTTP_INTERNAL_SERVER_ERROR = 500; public const HTTP_NOT_IMPLEMENTED = 501; public const HTTP_BAD_GATEWAY = 502; public const HTTP_SERVICE_UNAVAILABLE = 503; public const HTTP_GATEWAY_TIMEOUT = 504; public const HTTP_VERSION_NOT_SUPPORTED = 505; public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 public const HTTP_LOOP_DETECTED = 508; // RFC5842 public const HTTP_NOT_EXTENDED = 510; // RFC2774 public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 /** * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control */ private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = ['must_revalidate' => \false, 'no_cache' => \false, 'no_store' => \false, 'no_transform' => \false, 'public' => \false, 'private' => \false, 'proxy_revalidate' => \false, 'max_age' => \true, 's_maxage' => \true, 'immutable' => \false, 'last_modified' => \true, 'etag' => \true]; /** * @var ResponseHeaderBag */ public $headers; /** * @var string */ protected $content; /** * @var string */ protected $version; /** * @var int */ protected $statusCode; /** * @var string */ protected $statusText; /** * @var string */ protected $charset; /** * Status codes translation table. * * The list of codes is complete according to the * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry} * (last updated 2021-10-01). * * Unless otherwise noted, the status code is defined in RFC2616. * * @var array */ public static $statusTexts = [ 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', // RFC2518 103 => 'Early Hints', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', // RFC4918 208 => 'Already Reported', // RFC5842 226 => 'IM Used', // RFC3229 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', // RFC7238 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Content Too Large', // RFC-ietf-httpbis-semantics 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', // RFC2324 421 => 'Misdirected Request', // RFC7540 422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics 423 => 'Locked', // RFC4918 424 => 'Failed Dependency', // RFC4918 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 426 => 'Upgrade Required', // RFC2817 428 => 'Precondition Required', // RFC6585 429 => 'Too Many Requests', // RFC6585 431 => 'Request Header Fields Too Large', // RFC6585 451 => 'Unavailable For Legal Reasons', // RFC7725 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', // RFC2295 507 => 'Insufficient Storage', // RFC4918 508 => 'Loop Detected', // RFC5842 510 => 'Not Extended', // RFC2774 511 => 'Network Authentication Required', ]; /** * @throws \InvalidArgumentException When the HTTP status code is not valid */ public function __construct(?string $content = '', int $status = 200, array $headers = []) { $this->headers = new ResponseHeaderBag($headers); $this->setContent($content); $this->setStatusCode($status); $this->setProtocolVersion('1.0'); } /** * Factory method for chainability. * * Example: * * return Response::create($body, 200) * ->setSharedMaxAge(300); * * @return static * * @deprecated since Symfony 5.1, use __construct() instead. */ public static function create(?string $content = '', int $status = 200, array $headers = []) { \trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); return new static($content, $status, $headers); } /** * Returns the Response as an HTTP string. * * The string representation of the Response is the same as the * one that will be sent to the client only if the prepare() method * has been called before. * * @return string * * @see prepare() */ public function __toString() { return \sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText) . "\r\n" . $this->headers . "\r\n" . $this->getContent(); } /** * Clones the current Response instance. */ public function __clone() { $this->headers = clone $this->headers; } /** * Prepares the Response before it is sent to the client. * * This method tweaks the Response to ensure that it is * compliant with RFC 2616. Most of the changes are based on * the Request that is "associated" with this Response. * * @return $this */ public function prepare(Request $request) { $headers = $this->headers; if ($this->isInformational() || $this->isEmpty()) { $this->setContent(null); $headers->remove('Content-Type'); $headers->remove('Content-Length'); // prevent PHP from sending the Content-Type header based on default_mimetype \ini_set('default_mimetype', ''); } else { // Content-type based on the Request if (!$headers->has('Content-Type')) { $format = $request->getRequestFormat(null); if (null !== $format && ($mimeType = $request->getMimeType($format))) { $headers->set('Content-Type', $mimeType); } } // Fix Content-Type $charset = $this->charset ?: 'UTF-8'; if (!$headers->has('Content-Type')) { $headers->set('Content-Type', 'text/html; charset=' . $charset); } elseif (0 === \stripos($headers->get('Content-Type') ?? '', 'text/') && \false === \stripos($headers->get('Content-Type') ?? '', 'charset')) { // add the charset $headers->set('Content-Type', $headers->get('Content-Type') . '; charset=' . $charset); } // Fix Content-Length if ($headers->has('Transfer-Encoding')) { $headers->remove('Content-Length'); } if ($request->isMethod('HEAD')) { // cf. RFC2616 14.13 $length = $headers->get('Content-Length'); $this->setContent(null); if ($length) { $headers->set('Content-Length', $length); } } } // Fix protocol if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { $this->setProtocolVersion('1.1'); } // Check if we need to send extra expire info headers if ('1.0' == $this->getProtocolVersion() && \str_contains($headers->get('Cache-Control', ''), 'no-cache')) { $headers->set('pragma', 'no-cache'); $headers->set('expires', -1); } $this->ensureIEOverSSLCompatibility($request); if ($request->isSecure()) { foreach ($headers->getCookies() as $cookie) { $cookie->setSecureDefault(\true); } } return $this; } /** * Sends HTTP headers. * * @return $this */ public function sendHeaders() { // headers have already been sent by the developer if (\headers_sent()) { return $this; } // headers foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { $replace = 0 === \strcasecmp($name, 'Content-Type'); foreach ($values as $value) { \header($name . ': ' . $value, $replace, $this->statusCode); } } // cookies foreach ($this->headers->getCookies() as $cookie) { \header('Set-Cookie: ' . $cookie, \false, $this->statusCode); } // status \header(\sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), \true, $this->statusCode); return $this; } /** * Sends content for the current web response. * * @return $this */ public function sendContent() { echo $this->content; return $this; } /** * Sends HTTP headers and content. * * @return $this */ public function send() { $this->sendHeaders(); $this->sendContent(); if (\function_exists('fastcgi_finish_request')) { \fastcgi_finish_request(); } elseif (\function_exists('_ContaoManager\\litespeed_finish_request')) { litespeed_finish_request(); } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true)) { static::closeOutputBuffers(0, \true); \flush(); } return $this; } /** * Sets the response content. * * @return $this */ public function setContent(?string $content) { $this->content = $content ?? ''; return $this; } /** * Gets the current response content. * * @return string|false */ public function getContent() { return $this->content; } /** * Sets the HTTP protocol version (1.0 or 1.1). * * @return $this * * @final */ public function setProtocolVersion(string $version) : object { $this->version = $version; return $this; } /** * Gets the HTTP protocol version. * * @final */ public function getProtocolVersion() : string { return $this->version; } /** * Sets the response status code. * * If the status text is null it will be automatically populated for the known * status codes and left empty otherwise. * * @return $this * * @throws \InvalidArgumentException When the HTTP status code is not valid * * @final */ public function setStatusCode(int $code, ?string $text = null) : object { $this->statusCode = $code; if ($this->isInvalid()) { throw new \InvalidArgumentException(\sprintf('The HTTP status code "%s" is not valid.', $code)); } if (null === $text) { $this->statusText = self::$statusTexts[$code] ?? 'unknown status'; return $this; } if (\false === $text) { $this->statusText = ''; return $this; } $this->statusText = $text; return $this; } /** * Retrieves the status code for the current web response. * * @final */ public function getStatusCode() : int { return $this->statusCode; } /** * Sets the response charset. * * @return $this * * @final */ public function setCharset(string $charset) : object { $this->charset = $charset; return $this; } /** * Retrieves the response charset. * * @final */ public function getCharset() : ?string { return $this->charset; } /** * Returns true if the response may safely be kept in a shared (surrogate) cache. * * Responses marked "private" with an explicit Cache-Control directive are * considered uncacheable. * * Responses with neither a freshness lifetime (Expires, max-age) nor cache * validator (Last-Modified, ETag) are considered uncacheable because there is * no way to tell when or how to remove them from the cache. * * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, * for example "status codes that are defined as cacheable by default [...] * can be reused by a cache with heuristic expiration unless otherwise indicated" * (https://tools.ietf.org/html/rfc7231#section-6.1) * * @final */ public function isCacheable() : bool { if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { return \false; } if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { return \false; } return $this->isValidateable() || $this->isFresh(); } /** * Returns true if the response is "fresh". * * Fresh responses may be served from cache without any interaction with the * origin. A response is considered fresh when it includes a Cache-Control/max-age * indicator or Expires header and the calculated age is less than the freshness lifetime. * * @final */ public function isFresh() : bool { return $this->getTtl() > 0; } /** * Returns true if the response includes headers that can be used to validate * the response with the origin server using a conditional GET request. * * @final */ public function isValidateable() : bool { return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); } /** * Marks the response as "private". * * It makes the response ineligible for serving other clients. * * @return $this * * @final */ public function setPrivate() : object { $this->headers->removeCacheControlDirective('public'); $this->headers->addCacheControlDirective('private'); return $this; } /** * Marks the response as "public". * * It makes the response eligible for serving other clients. * * @return $this * * @final */ public function setPublic() : object { $this->headers->addCacheControlDirective('public'); $this->headers->removeCacheControlDirective('private'); return $this; } /** * Marks the response as "immutable". * * @return $this * * @final */ public function setImmutable(bool $immutable = \true) : object { if ($immutable) { $this->headers->addCacheControlDirective('immutable'); } else { $this->headers->removeCacheControlDirective('immutable'); } return $this; } /** * Returns true if the response is marked as "immutable". * * @final */ public function isImmutable() : bool { return $this->headers->hasCacheControlDirective('immutable'); } /** * Returns true if the response must be revalidated by shared caches once it has become stale. * * This method indicates that the response must not be served stale by a * cache in any circumstance without first revalidating with the origin. * When present, the TTL of the response should not be overridden to be * greater than the value provided by the origin. * * @final */ public function mustRevalidate() : bool { return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); } /** * Returns the Date header as a DateTime instance. * * @throws \RuntimeException When the header is not parseable * * @final */ public function getDate() : ?\DateTimeInterface { return $this->headers->getDate('Date'); } /** * Sets the Date header. * * @return $this * * @final */ public function setDate(\DateTimeInterface $date) : object { if ($date instanceof \DateTime) { $date = \DateTimeImmutable::createFromMutable($date); } $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Date', $date->format('D, d M Y H:i:s') . ' GMT'); return $this; } /** * Returns the age of the response in seconds. * * @final */ public function getAge() : int { if (null !== ($age = $this->headers->get('Age'))) { return (int) $age; } return \max(\time() - (int) $this->getDate()->format('U'), 0); } /** * Marks the response stale by setting the Age header to be equal to the maximum age of the response. * * @return $this */ public function expire() { if ($this->isFresh()) { $this->headers->set('Age', $this->getMaxAge()); $this->headers->remove('Expires'); } return $this; } /** * Returns the value of the Expires header as a DateTime instance. * * @final */ public function getExpires() : ?\DateTimeInterface { try { return $this->headers->getDate('Expires'); } catch (\RuntimeException $e) { // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past return \DateTime::createFromFormat('U', \time() - 172800); } } /** * Sets the Expires HTTP header with a DateTime instance. * * Passing null as value will remove the header. * * @return $this * * @final */ public function setExpires(?\DateTimeInterface $date = null) : object { if (null === $date) { $this->headers->remove('Expires'); return $this; } if ($date instanceof \DateTime) { $date = \DateTimeImmutable::createFromMutable($date); } $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Expires', $date->format('D, d M Y H:i:s') . ' GMT'); return $this; } /** * Returns the number of seconds after the time specified in the response's Date * header when the response should no longer be considered fresh. * * First, it checks for a s-maxage directive, then a max-age directive, and then it falls * back on an expires header. It returns null when no maximum age can be established. * * @final */ public function getMaxAge() : ?int { if ($this->headers->hasCacheControlDirective('s-maxage')) { return (int) $this->headers->getCacheControlDirective('s-maxage'); } if ($this->headers->hasCacheControlDirective('max-age')) { return (int) $this->headers->getCacheControlDirective('max-age'); } if (null !== ($expires = $this->getExpires())) { $maxAge = (int) $expires->format('U') - (int) $this->getDate()->format('U'); return \max($maxAge, 0); } return null; } /** * Sets the number of seconds after which the response should no longer be considered fresh. * * This methods sets the Cache-Control max-age directive. * * @return $this * * @final */ public function setMaxAge(int $value) : object { $this->headers->addCacheControlDirective('max-age', $value); return $this; } /** * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. * * This methods sets the Cache-Control s-maxage directive. * * @return $this * * @final */ public function setSharedMaxAge(int $value) : object { $this->setPublic(); $this->headers->addCacheControlDirective('s-maxage', $value); return $this; } /** * Returns the response's time-to-live in seconds. * * It returns null when no freshness information is present in the response. * * When the response's TTL is 0, the response may not be served from cache without first * revalidating with the origin. * * @final */ public function getTtl() : ?int { $maxAge = $this->getMaxAge(); return null !== $maxAge ? \max($maxAge - $this->getAge(), 0) : null; } /** * Sets the response's time-to-live for shared caches in seconds. * * This method adjusts the Cache-Control/s-maxage directive. * * @return $this * * @final */ public function setTtl(int $seconds) : object { $this->setSharedMaxAge($this->getAge() + $seconds); return $this; } /** * Sets the response's time-to-live for private/client caches in seconds. * * This method adjusts the Cache-Control/max-age directive. * * @return $this * * @final */ public function setClientTtl(int $seconds) : object { $this->setMaxAge($this->getAge() + $seconds); return $this; } /** * Returns the Last-Modified HTTP header as a DateTime instance. * * @throws \RuntimeException When the HTTP header is not parseable * * @final */ public function getLastModified() : ?\DateTimeInterface { return $this->headers->getDate('Last-Modified'); } /** * Sets the Last-Modified HTTP header with a DateTime instance. * * Passing null as value will remove the header. * * @return $this * * @final */ public function setLastModified(?\DateTimeInterface $date = null) : object { if (null === $date) { $this->headers->remove('Last-Modified'); return $this; } if ($date instanceof \DateTime) { $date = \DateTimeImmutable::createFromMutable($date); } $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT'); return $this; } /** * Returns the literal value of the ETag HTTP header. * * @final */ public function getEtag() : ?string { return $this->headers->get('ETag'); } /** * Sets the ETag value. * * @param string|null $etag The ETag unique identifier or null to remove the header * @param bool $weak Whether you want a weak ETag or not * * @return $this * * @final */ public function setEtag(?string $etag = null, bool $weak = \false) : object { if (null === $etag) { $this->headers->remove('Etag'); } else { if (!\str_starts_with($etag, '"')) { $etag = '"' . $etag . '"'; } $this->headers->set('ETag', (\true === $weak ? 'W/' : '') . $etag); } return $this; } /** * Sets the response's cache headers (validation and/or expiration). * * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag. * * @return $this * * @throws \InvalidArgumentException * * @final */ public function setCache(array $options) : object { if ($diff = \array_diff(\array_keys($options), \array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) { throw new \InvalidArgumentException(\sprintf('Response does not support the following options: "%s".', \implode('", "', $diff))); } if (isset($options['etag'])) { $this->setEtag($options['etag']); } if (isset($options['last_modified'])) { $this->setLastModified($options['last_modified']); } if (isset($options['max_age'])) { $this->setMaxAge($options['max_age']); } if (isset($options['s_maxage'])) { $this->setSharedMaxAge($options['s_maxage']); } foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) { if (!$hasValue && isset($options[$directive])) { if ($options[$directive]) { $this->headers->addCacheControlDirective(\str_replace('_', '-', $directive)); } else { $this->headers->removeCacheControlDirective(\str_replace('_', '-', $directive)); } } } if (isset($options['public'])) { if ($options['public']) { $this->setPublic(); } else { $this->setPrivate(); } } if (isset($options['private'])) { if ($options['private']) { $this->setPrivate(); } else { $this->setPublic(); } } return $this; } /** * Modifies the response so that it conforms to the rules defined for a 304 status code. * * This sets the status, removes the body, and discards any headers * that MUST NOT be included in 304 responses. * * @return $this * * @see https://tools.ietf.org/html/rfc2616#section-10.3.5 * * @final */ public function setNotModified() : object { $this->setStatusCode(304); $this->setContent(null); // remove headers that MUST NOT be included with 304 Not Modified responses foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) { $this->headers->remove($header); } return $this; } /** * Returns true if the response includes a Vary header. * * @final */ public function hasVary() : bool { return null !== $this->headers->get('Vary'); } /** * Returns an array of header names given in the Vary header. * * @final */ public function getVary() : array { if (!($vary = $this->headers->all('Vary'))) { return []; } $ret = []; foreach ($vary as $item) { $ret[] = \preg_split('/[\\s,]+/', $item); } return \array_merge([], ...$ret); } /** * Sets the Vary header. * * @param string|array $headers * @param bool $replace Whether to replace the actual value or not (true by default) * * @return $this * * @final */ public function setVary($headers, bool $replace = \true) : object { $this->headers->set('Vary', $headers, $replace); return $this; } /** * Determines if the Response validators (ETag, Last-Modified) match * a conditional value specified in the Request. * * If the Response is not modified, it sets the status code to 304 and * removes the actual content by calling the setNotModified() method. * * @final */ public function isNotModified(Request $request) : bool { if (!$request->isMethodCacheable()) { return \false; } $notModified = \false; $lastModified = $this->headers->get('Last-Modified'); $modifiedSince = $request->headers->get('If-Modified-Since'); if (($ifNoneMatchEtags = $request->getETags()) && null !== ($etag = $this->getEtag())) { if (0 == \strncmp($etag, 'W/', 2)) { $etag = \substr($etag, 2); } // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2. foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) { if (0 == \strncmp($ifNoneMatchEtag, 'W/', 2)) { $ifNoneMatchEtag = \substr($ifNoneMatchEtag, 2); } if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) { $notModified = \true; break; } } } elseif ($modifiedSince && $lastModified) { $notModified = \strtotime($modifiedSince) >= \strtotime($lastModified); } if ($notModified) { $this->setNotModified(); } return $notModified; } /** * Is response invalid? * * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html * * @final */ public function isInvalid() : bool { return $this->statusCode < 100 || $this->statusCode >= 600; } /** * Is response informative? * * @final */ public function isInformational() : bool { return $this->statusCode >= 100 && $this->statusCode < 200; } /** * Is response successful? * * @final */ public function isSuccessful() : bool { return $this->statusCode >= 200 && $this->statusCode < 300; } /** * Is the response a redirect? * * @final */ public function isRedirection() : bool { return $this->statusCode >= 300 && $this->statusCode < 400; } /** * Is there a client error? * * @final */ public function isClientError() : bool { return $this->statusCode >= 400 && $this->statusCode < 500; } /** * Was there a server side error? * * @final */ public function isServerError() : bool { return $this->statusCode >= 500 && $this->statusCode < 600; } /** * Is the response OK? * * @final */ public function isOk() : bool { return 200 === $this->statusCode; } /** * Is the response forbidden? * * @final */ public function isForbidden() : bool { return 403 === $this->statusCode; } /** * Is the response a not found error? * * @final */ public function isNotFound() : bool { return 404 === $this->statusCode; } /** * Is the response a redirect of some form? * * @final */ public function isRedirect(?string $location = null) : bool { return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); } /** * Is the response empty? * * @final */ public function isEmpty() : bool { return \in_array($this->statusCode, [204, 304]); } /** * Cleans or flushes output buffers up to target level. * * Resulting level can be greater than target level if a non-removable buffer has been encountered. * * @final */ public static function closeOutputBuffers(int $targetLevel, bool $flush) : void { $status = \ob_get_status(\true); $level = \count($status); $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE); while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { if ($flush) { \ob_end_flush(); } else { \ob_end_clean(); } } } /** * Marks a response as safe according to RFC8674. * * @see https://tools.ietf.org/html/rfc8674 */ public function setContentSafe(bool $safe = \true) : void { if ($safe) { $this->headers->set('Preference-Applied', 'safe'); } elseif ('safe' === $this->headers->get('Preference-Applied')) { $this->headers->remove('Preference-Applied'); } $this->setVary('Prefer', \false); } /** * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. * * @see http://support.microsoft.com/kb/323308 * * @final */ protected function ensureIEOverSSLCompatibility(Request $request) : void { if (\false !== \stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == \preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && \true === $request->isSecure()) { if ((int) \preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { $this->headers->remove('Cache-Control'); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * Represents a cookie. * * @author Johannes M. Schmitt */ class Cookie { public const SAMESITE_NONE = 'none'; public const SAMESITE_LAX = 'lax'; public const SAMESITE_STRICT = 'strict'; protected $name; protected $value; protected $domain; protected $expire; protected $path; protected $secure; protected $httpOnly; private $raw; private $sameSite; private $secureDefault = \false; private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f"; private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; /** * Creates cookie from raw header string. * * @return static */ public static function fromString(string $cookie, bool $decode = \false) { $data = ['expires' => 0, 'path' => '/', 'domain' => null, 'secure' => \false, 'httponly' => \false, 'raw' => !$decode, 'samesite' => null]; $parts = HeaderUtils::split($cookie, ';='); $part = \array_shift($parts); $name = $decode ? \urldecode($part[0]) : $part[0]; $value = isset($part[1]) ? $decode ? \urldecode($part[1]) : $part[1] : null; $data = HeaderUtils::combine($parts) + $data; $data['expires'] = self::expiresTimestamp($data['expires']); if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > \time())) { $data['expires'] = \time() + (int) $data['max-age']; } return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); } public static function create(string $name, ?string $value = null, $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = \true, bool $raw = \false, ?string $sameSite = self::SAMESITE_LAX) : self { return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite); } /** * @param string $name The name of the cookie * @param string|null $value The value of the cookie * @param int|string|\DateTimeInterface $expire The time the cookie expires * @param string|null $path The path on the server in which the cookie will be available on * @param string|null $domain The domain that the cookie is available to * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol * @param bool $raw Whether the cookie value should be sent with no url encoding * @param string|null $sameSite Whether the cookie will be available for cross-site requests * * @throws \InvalidArgumentException */ public function __construct(string $name, ?string $value = null, $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = \true, bool $raw = \false, ?string $sameSite = 'lax') { // from PHP source code if ($raw && \false !== \strpbrk($name, self::RESERVED_CHARS_LIST)) { throw new \InvalidArgumentException(\sprintf('The cookie name "%s" contains invalid characters.', $name)); } if (empty($name)) { throw new \InvalidArgumentException('The cookie name cannot be empty.'); } $this->name = $name; $this->value = $value; $this->domain = $domain; $this->expire = self::expiresTimestamp($expire); $this->path = empty($path) ? '/' : $path; $this->secure = $secure; $this->httpOnly = $httpOnly; $this->raw = $raw; $this->sameSite = $this->withSameSite($sameSite)->sameSite; } /** * Creates a cookie copy with a new value. * * @return static */ public function withValue(?string $value) : self { $cookie = clone $this; $cookie->value = $value; return $cookie; } /** * Creates a cookie copy with a new domain that the cookie is available to. * * @return static */ public function withDomain(?string $domain) : self { $cookie = clone $this; $cookie->domain = $domain; return $cookie; } /** * Creates a cookie copy with a new time the cookie expires. * * @param int|string|\DateTimeInterface $expire * * @return static */ public function withExpires($expire = 0) : self { $cookie = clone $this; $cookie->expire = self::expiresTimestamp($expire); return $cookie; } /** * Converts expires formats to a unix timestamp. * * @param int|string|\DateTimeInterface $expire */ private static function expiresTimestamp($expire = 0) : int { // convert expiration time to a Unix timestamp if ($expire instanceof \DateTimeInterface) { $expire = $expire->format('U'); } elseif (!\is_numeric($expire)) { $expire = \strtotime($expire); if (\false === $expire) { throw new \InvalidArgumentException('The cookie expiration time is not valid.'); } } return 0 < $expire ? (int) $expire : 0; } /** * Creates a cookie copy with a new path on the server in which the cookie will be available on. * * @return static */ public function withPath(string $path) : self { $cookie = clone $this; $cookie->path = '' === $path ? '/' : $path; return $cookie; } /** * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client. * * @return static */ public function withSecure(bool $secure = \true) : self { $cookie = clone $this; $cookie->secure = $secure; return $cookie; } /** * Creates a cookie copy that be accessible only through the HTTP protocol. * * @return static */ public function withHttpOnly(bool $httpOnly = \true) : self { $cookie = clone $this; $cookie->httpOnly = $httpOnly; return $cookie; } /** * Creates a cookie copy that uses no url encoding. * * @return static */ public function withRaw(bool $raw = \true) : self { if ($raw && \false !== \strpbrk($this->name, self::RESERVED_CHARS_LIST)) { throw new \InvalidArgumentException(\sprintf('The cookie name "%s" contains invalid characters.', $this->name)); } $cookie = clone $this; $cookie->raw = $raw; return $cookie; } /** * Creates a cookie copy with SameSite attribute. * * @return static */ public function withSameSite(?string $sameSite) : self { if ('' === $sameSite) { $sameSite = null; } elseif (null !== $sameSite) { $sameSite = \strtolower($sameSite); } if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], \true)) { throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); } $cookie = clone $this; $cookie->sameSite = $sameSite; return $cookie; } /** * Returns the cookie as a string. * * @return string */ public function __toString() { if ($this->isRaw()) { $str = $this->getName(); } else { $str = \str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $this->getName()); } $str .= '='; if ('' === (string) $this->getValue()) { $str .= 'deleted; expires=' . \gmdate('D, d-M-Y H:i:s T', \time() - 31536001) . '; Max-Age=0'; } else { $str .= $this->isRaw() ? $this->getValue() : \rawurlencode($this->getValue()); if (0 !== $this->getExpiresTime()) { $str .= '; expires=' . \gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()) . '; Max-Age=' . $this->getMaxAge(); } } if ($this->getPath()) { $str .= '; path=' . $this->getPath(); } if ($this->getDomain()) { $str .= '; domain=' . $this->getDomain(); } if (\true === $this->isSecure()) { $str .= '; secure'; } if (\true === $this->isHttpOnly()) { $str .= '; httponly'; } if (null !== $this->getSameSite()) { $str .= '; samesite=' . $this->getSameSite(); } return $str; } /** * Gets the name of the cookie. * * @return string */ public function getName() { return $this->name; } /** * Gets the value of the cookie. * * @return string|null */ public function getValue() { return $this->value; } /** * Gets the domain that the cookie is available to. * * @return string|null */ public function getDomain() { return $this->domain; } /** * Gets the time the cookie expires. * * @return int */ public function getExpiresTime() { return $this->expire; } /** * Gets the max-age attribute. * * @return int */ public function getMaxAge() { $maxAge = $this->expire - \time(); return 0 >= $maxAge ? 0 : $maxAge; } /** * Gets the path on the server in which the cookie will be available on. * * @return string */ public function getPath() { return $this->path; } /** * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. * * @return bool */ public function isSecure() { return $this->secure ?? $this->secureDefault; } /** * Checks whether the cookie will be made accessible only through the HTTP protocol. * * @return bool */ public function isHttpOnly() { return $this->httpOnly; } /** * Whether this cookie is about to be cleared. * * @return bool */ public function isCleared() { return 0 !== $this->expire && $this->expire < \time(); } /** * Checks if the cookie value should be sent with no url encoding. * * @return bool */ public function isRaw() { return $this->raw; } /** * Gets the SameSite attribute. * * @return string|null */ public function getSameSite() { return $this->sameSite; } /** * @param bool $default The default value of the "secure" flag when it is set to null */ public function setSecureDefault(bool $default) : void { $this->secureDefault = $default; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; // Help opcache.preload discover always-needed symbols \class_exists(AcceptHeaderItem::class); /** * Represents an Accept-* header. * * An accept header is compound with a list of items, * sorted by descending quality. * * @author Jean-François Simon */ class AcceptHeader { /** * @var AcceptHeaderItem[] */ private $items = []; /** * @var bool */ private $sorted = \true; /** * @param AcceptHeaderItem[] $items */ public function __construct(array $items) { foreach ($items as $item) { $this->add($item); } } /** * Builds an AcceptHeader instance from a string. * * @return self */ public static function fromString(?string $headerValue) { $index = 0; $parts = HeaderUtils::split($headerValue ?? '', ',;='); return new self(\array_map(function ($subParts) use(&$index) { $part = \array_shift($subParts); $attributes = HeaderUtils::combine($subParts); $item = new AcceptHeaderItem($part[0], $attributes); $item->setIndex($index++); return $item; }, $parts)); } /** * Returns header value's string representation. * * @return string */ public function __toString() { return \implode(',', $this->items); } /** * Tests if header has given value. * * @return bool */ public function has(string $value) { return isset($this->items[$value]); } /** * Returns given value's item, if exists. * * @return AcceptHeaderItem|null */ public function get(string $value) { return $this->items[$value] ?? $this->items[\explode('/', $value)[0] . '/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; } /** * Adds an item. * * @return $this */ public function add(AcceptHeaderItem $item) { $this->items[$item->getValue()] = $item; $this->sorted = \false; return $this; } /** * Returns all items. * * @return AcceptHeaderItem[] */ public function all() { $this->sort(); return $this->items; } /** * Filters items on their value using given regex. * * @return self */ public function filter(string $pattern) { return new self(\array_filter($this->items, function (AcceptHeaderItem $item) use($pattern) { return \preg_match($pattern, $item->getValue()); })); } /** * Returns first item. * * @return AcceptHeaderItem|null */ public function first() { $this->sort(); return !empty($this->items) ? \reset($this->items) : null; } /** * Sorts items by descending quality. */ private function sort() : void { if (!$this->sorted) { \uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { $qA = $a->getQuality(); $qB = $b->getQuality(); if ($qA === $qB) { return $a->getIndex() > $b->getIndex() ? 1 : -1; } return $qA > $qB ? -1 : 1; }); $this->sorted = \true; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * RequestMatcher compares a pre-defined set of checks against a Request instance. * * @author Fabien Potencier */ class RequestMatcher implements RequestMatcherInterface { /** * @var string|null */ private $path; /** * @var string|null */ private $host; /** * @var int|null */ private $port; /** * @var string[] */ private $methods = []; /** * @var string[] */ private $ips = []; /** * @var array */ private $attributes = []; /** * @var string[] */ private $schemes = []; /** * @param string|string[]|null $methods * @param string|string[]|null $ips * @param string|string[]|null $schemes */ public function __construct(?string $path = null, ?string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, ?int $port = null) { $this->matchPath($path); $this->matchHost($host); $this->matchMethod($methods); $this->matchIps($ips); $this->matchScheme($schemes); $this->matchPort($port); foreach ($attributes as $k => $v) { $this->matchAttribute($k, $v); } } /** * Adds a check for the HTTP scheme. * * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes */ public function matchScheme($scheme) { $this->schemes = null !== $scheme ? \array_map('strtolower', (array) $scheme) : []; } /** * Adds a check for the URL host name. */ public function matchHost(?string $regexp) { $this->host = $regexp; } /** * Adds a check for the URL port. * * @param int|null $port The port number to connect to */ public function matchPort(?int $port) { $this->port = $port; } /** * Adds a check for the URL path info. */ public function matchPath(?string $regexp) { $this->path = $regexp; } /** * Adds a check for the client IP. * * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 */ public function matchIp(string $ip) { $this->matchIps($ip); } /** * Adds a check for the client IP. * * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 */ public function matchIps($ips) { $ips = null !== $ips ? (array) $ips : []; $this->ips = \array_reduce($ips, static function (array $ips, string $ip) { return \array_merge($ips, \preg_split('/\\s*,\\s*/', $ip)); }, []); } /** * Adds a check for the HTTP method. * * @param string|string[]|null $method An HTTP method or an array of HTTP methods */ public function matchMethod($method) { $this->methods = null !== $method ? \array_map('strtoupper', (array) $method) : []; } /** * Adds a check for request attribute. */ public function matchAttribute(string $key, string $regexp) { $this->attributes[$key] = $regexp; } /** * {@inheritdoc} */ public function matches(Request $request) { if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, \true)) { return \false; } if ($this->methods && !\in_array($request->getMethod(), $this->methods, \true)) { return \false; } foreach ($this->attributes as $key => $pattern) { $requestAttribute = $request->attributes->get($key); if (!\is_string($requestAttribute)) { return \false; } if (!\preg_match('{' . $pattern . '}', $requestAttribute)) { return \false; } } if (null !== $this->path && !\preg_match('{' . $this->path . '}', \rawurldecode($request->getPathInfo()))) { return \false; } if (null !== $this->host && !\preg_match('{' . $this->host . '}i', $request->getHost())) { return \false; } if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) { return \false; } if (IpUtils::checkIp($request->getClientIp() ?? '', $this->ips)) { return \true; } // Note to future implementors: add additional checks above the // foreach above or else your check might not be run! return 0 === \count($this->ips); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * ResponseHeaderBag is a container for Response HTTP headers. * * @author Fabien Potencier */ class ResponseHeaderBag extends HeaderBag { public const COOKIES_FLAT = 'flat'; public const COOKIES_ARRAY = 'array'; public const DISPOSITION_ATTACHMENT = 'attachment'; public const DISPOSITION_INLINE = 'inline'; protected $computedCacheControl = []; protected $cookies = []; protected $headerNames = []; public function __construct(array $headers = []) { parent::__construct($headers); if (!isset($this->headers['cache-control'])) { $this->set('Cache-Control', ''); } /* RFC2616 - 14.18 says all Responses need to have a Date */ if (!isset($this->headers['date'])) { $this->initDate(); } } /** * Returns the headers, with original capitalizations. * * @return array */ public function allPreserveCase() { $headers = []; foreach ($this->all() as $name => $value) { $headers[$this->headerNames[$name] ?? $name] = $value; } return $headers; } public function allPreserveCaseWithoutCookies() { $headers = $this->allPreserveCase(); if (isset($this->headerNames['set-cookie'])) { unset($headers[$this->headerNames['set-cookie']]); } return $headers; } /** * {@inheritdoc} */ public function replace(array $headers = []) { $this->headerNames = []; parent::replace($headers); if (!isset($this->headers['cache-control'])) { $this->set('Cache-Control', ''); } if (!isset($this->headers['date'])) { $this->initDate(); } } /** * {@inheritdoc} */ public function all(?string $key = null) { $headers = parent::all(); if (null !== $key) { $key = \strtr($key, self::UPPER, self::LOWER); return 'set-cookie' !== $key ? $headers[$key] ?? [] : \array_map('strval', $this->getCookies()); } foreach ($this->getCookies() as $cookie) { $headers['set-cookie'][] = (string) $cookie; } return $headers; } /** * {@inheritdoc} */ public function set(string $key, $values, bool $replace = \true) { $uniqueKey = \strtr($key, self::UPPER, self::LOWER); if ('set-cookie' === $uniqueKey) { if ($replace) { $this->cookies = []; } foreach ((array) $values as $cookie) { $this->setCookie(Cookie::fromString($cookie)); } $this->headerNames[$uniqueKey] = $key; return; } $this->headerNames[$uniqueKey] = $key; parent::set($key, $values, $replace); // ensure the cache-control header has sensible defaults if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], \true) && '' !== ($computed = $this->computeCacheControlValue())) { $this->headers['cache-control'] = [$computed]; $this->headerNames['cache-control'] = 'Cache-Control'; $this->computedCacheControl = $this->parseCacheControl($computed); } } /** * {@inheritdoc} */ public function remove(string $key) { $uniqueKey = \strtr($key, self::UPPER, self::LOWER); unset($this->headerNames[$uniqueKey]); if ('set-cookie' === $uniqueKey) { $this->cookies = []; return; } parent::remove($key); if ('cache-control' === $uniqueKey) { $this->computedCacheControl = []; } if ('date' === $uniqueKey) { $this->initDate(); } } /** * {@inheritdoc} */ public function hasCacheControlDirective(string $key) { return \array_key_exists($key, $this->computedCacheControl); } /** * {@inheritdoc} */ public function getCacheControlDirective(string $key) { return $this->computedCacheControl[$key] ?? null; } public function setCookie(Cookie $cookie) { $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; $this->headerNames['set-cookie'] = 'Set-Cookie'; } /** * Removes a cookie from the array, but does not unset it in the browser. */ public function removeCookie(string $name, ?string $path = '/', ?string $domain = null) { if (null === $path) { $path = '/'; } unset($this->cookies[$domain][$path][$name]); if (empty($this->cookies[$domain][$path])) { unset($this->cookies[$domain][$path]); if (empty($this->cookies[$domain])) { unset($this->cookies[$domain]); } } if (empty($this->cookies)) { unset($this->headerNames['set-cookie']); } } /** * Returns an array with all cookies. * * @return Cookie[] * * @throws \InvalidArgumentException When the $format is invalid */ public function getCookies(string $format = self::COOKIES_FLAT) { if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { throw new \InvalidArgumentException(\sprintf('Format "%s" invalid (%s).', $format, \implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); } if (self::COOKIES_ARRAY === $format) { return $this->cookies; } $flattenedCookies = []; foreach ($this->cookies as $path) { foreach ($path as $cookies) { foreach ($cookies as $cookie) { $flattenedCookies[] = $cookie; } } } return $flattenedCookies; } /** * Clears a cookie in the browser. */ public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = \false, bool $httpOnly = \true, ?string $sameSite = null) { $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, \false, $sameSite)); } /** * @see HeaderUtils::makeDisposition() */ public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') { return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); } /** * Returns the calculated value of the cache-control header. * * This considers several other headers and calculates or modifies the * cache-control header to a sensible, conservative value. * * @return string */ protected function computeCacheControlValue() { if (!$this->cacheControl) { if ($this->has('Last-Modified') || $this->has('Expires')) { return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified" } // conservative by default return 'no-cache, private'; } $header = $this->getCacheControlHeader(); if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { return $header; } // public if s-maxage is defined, private otherwise if (!isset($this->cacheControl['s-maxage'])) { return $header . ', private'; } return $header; } private function initDate() : void { $this->set('Date', \gmdate('D, d M Y H:i:s') . ' GMT'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * HTTP header utility functions. * * @author Christian Schmidt */ class HeaderUtils { public const DISPOSITION_ATTACHMENT = 'attachment'; public const DISPOSITION_INLINE = 'inline'; /** * This class should not be instantiated. */ private function __construct() { } /** * Splits an HTTP header by one or more separators. * * Example: * * HeaderUtils::split('da, en-gb;q=0.8', ',;') * // => ['da'], ['en-gb', 'q=0.8']] * * @param string $separators List of characters to split on, ordered by * precedence, e.g. ',', ';=', or ',;=' * * @return array Nested array with as many levels as there are characters in * $separators */ public static function split(string $header, string $separators) : array { if ('' === $separators) { throw new \InvalidArgumentException('At least one separator must be specified.'); } $quotedSeparators = \preg_quote($separators, '/'); \preg_match_all(' / (?!\\s) (?: # quoted-string "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) | # token [^"' . $quotedSeparators . ']+ )+ (?[' . $quotedSeparators . ']) \\s* /x', \trim($header), $matches, \PREG_SET_ORDER); return self::groupParts($matches, $separators); } /** * Combines an array of arrays into one associative array. * * Each of the nested arrays should have one or two elements. The first * value will be used as the keys in the associative array, and the second * will be used as the values, or true if the nested array only contains one * element. Array keys are lowercased. * * Example: * * HeaderUtils::combine([['foo', 'abc'], ['bar']]) * // => ['foo' => 'abc', 'bar' => true] */ public static function combine(array $parts) : array { $assoc = []; foreach ($parts as $part) { $name = \strtolower($part[0]); $value = $part[1] ?? \true; $assoc[$name] = $value; } return $assoc; } /** * Joins an associative array into a string for use in an HTTP header. * * The key and value of each entry are joined with '=', and all entries * are joined with the specified separator and an additional space (for * readability). Values are quoted if necessary. * * Example: * * HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',') * // => 'foo=abc, bar, baz="a b c"' */ public static function toString(array $assoc, string $separator) : string { $parts = []; foreach ($assoc as $name => $value) { if (\true === $value) { $parts[] = $name; } else { $parts[] = $name . '=' . self::quote($value); } } return \implode($separator . ' ', $parts); } /** * Encodes a string as a quoted string, if necessary. * * If a string contains characters not allowed by the "token" construct in * the HTTP specification, it is backslash-escaped and enclosed in quotes * to match the "quoted-string" construct. */ public static function quote(string $s) : string { if (\preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { return $s; } return '"' . \addcslashes($s, '"\\"') . '"'; } /** * Decodes a quoted string. * * If passed an unquoted string that matches the "token" construct (as * defined in the HTTP specification), it is passed through verbatim. */ public static function unquote(string $s) : string { return \preg_replace('/\\\\(.)|"/', '$1', $s); } /** * Generates an HTTP Content-Disposition field-value. * * @param string $disposition One of "inline" or "attachment" * @param string $filename A unicode string * @param string $filenameFallback A string containing only ASCII characters that * is semantically equivalent to $filename. If the filename is already ASCII, * it can be omitted, or just copied from $filename * * @throws \InvalidArgumentException * * @see RFC 6266 */ public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') : string { if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { throw new \InvalidArgumentException(\sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); } if ('' === $filenameFallback) { $filenameFallback = $filename; } // filenameFallback is not ASCII. if (!\preg_match('/^[\\x20-\\x7e]*$/', $filenameFallback)) { throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); } // percent characters aren't safe in fallback. if (\str_contains($filenameFallback, '%')) { throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); } // path separators aren't allowed in either. if (\str_contains($filename, '/') || \str_contains($filename, '\\') || \str_contains($filenameFallback, '/') || \str_contains($filenameFallback, '\\')) { throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); } $params = ['filename' => $filenameFallback]; if ($filename !== $filenameFallback) { $params['filename*'] = "utf-8''" . \rawurlencode($filename); } return $disposition . '; ' . self::toString($params, ';'); } /** * Like parse_str(), but preserves dots in variable names. */ public static function parseQuery(string $query, bool $ignoreBrackets = \false, string $separator = '&') : array { $q = []; foreach (\explode($separator, $query) as $v) { if (\false !== ($i = \strpos($v, "\x00"))) { $v = \substr($v, 0, $i); } if (\false === ($i = \strpos($v, '='))) { $k = \urldecode($v); $v = ''; } else { $k = \urldecode(\substr($v, 0, $i)); $v = \substr($v, $i); } if (\false !== ($i = \strpos($k, "\x00"))) { $k = \substr($k, 0, $i); } $k = \ltrim($k, ' '); if ($ignoreBrackets) { $q[$k][] = \urldecode(\substr($v, 1)); continue; } if (\false === ($i = \strpos($k, '['))) { $q[] = \bin2hex($k) . $v; } else { $q[] = \bin2hex(\substr($k, 0, $i)) . \rawurlencode(\substr($k, $i)) . $v; } } if ($ignoreBrackets) { return $q; } \parse_str(\implode('&', $q), $q); $query = []; foreach ($q as $k => $v) { if (\false !== ($i = \strpos($k, '_'))) { $query[\substr_replace($k, \hex2bin(\substr($k, 0, $i)) . '[', 0, 1 + $i)] = $v; } else { $query[\hex2bin($k)] = $v; } } return $query; } private static function groupParts(array $matches, string $separators, bool $first = \true) : array { $separator = $separators[0]; $separators = \substr($separators, 1) ?: ''; $i = 0; if ('' === $separators && !$first) { $parts = ['']; foreach ($matches as $match) { if (!$i && isset($match['separator'])) { $i = 1; $parts[1] = ''; } else { $parts[$i] .= self::unquote($match[0]); } } return $parts; } $parts = []; $partMatches = []; foreach ($matches as $match) { if (($match['separator'] ?? null) === $separator) { ++$i; } else { $partMatches[$i][] = $match; } } foreach ($partMatches as $matches) { $parts[] = '' === $separators ? self::unquote($matches[0][0]) : self::groupParts($matches, $separators, \false); } return $parts; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Request stack that controls the lifecycle of requests. * * @author Benjamin Eberlei */ class RequestStack { /** * @var Request[] */ private $requests = []; /** * Pushes a Request on the stack. * * This method should generally not be called directly as the stack * management should be taken care of by the application itself. */ public function push(Request $request) { $this->requests[] = $request; } /** * Pops the current request from the stack. * * This operation lets the current request go out of scope. * * This method should generally not be called directly as the stack * management should be taken care of by the application itself. * * @return Request|null */ public function pop() { if (!$this->requests) { return null; } return \array_pop($this->requests); } /** * @return Request|null */ public function getCurrentRequest() { return \end($this->requests) ?: null; } /** * Gets the main request. * * Be warned that making your code aware of the main request * might make it un-compatible with other features of your framework * like ESI support. */ public function getMainRequest() : ?Request { if (!$this->requests) { return null; } return $this->requests[0]; } /** * Gets the master request. * * @return Request|null * * @deprecated since symfony/http-foundation 5.3, use getMainRequest() instead */ public function getMasterRequest() { \trigger_deprecation('symfony/http-foundation', '5.3', '"%s()" is deprecated, use "getMainRequest()" instead.', __METHOD__); return $this->getMainRequest(); } /** * Returns the parent request of the current. * * Be warned that making your code aware of the parent request * might make it un-compatible with other features of your framework * like ESI support. * * If current Request is the main request, it returns null. * * @return Request|null */ public function getParentRequest() { $pos = \count($this->requests) - 2; return $this->requests[$pos] ?? null; } /** * Gets the current session. * * @throws SessionNotFoundException */ public function getSession() : SessionInterface { if (null !== ($request = \end($this->requests) ?: null) && $request->hasSession()) { return $request->getSession(); } throw new SessionNotFoundException(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * RedirectResponse represents an HTTP response doing a redirect. * * @author Fabien Potencier */ class RedirectResponse extends Response { protected $targetUrl; /** * Creates a redirect response so that it conforms to the rules defined for a redirect status code. * * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., * but practically every browser redirects on paths only as well * @param int $status The status code (302 by default) * @param array $headers The headers (Location is always set to the given URL) * * @throws \InvalidArgumentException * * @see https://tools.ietf.org/html/rfc2616#section-10.3 */ public function __construct(string $url, int $status = 302, array $headers = []) { parent::__construct('', $status, $headers); $this->setTargetUrl($url); if (!$this->isRedirect()) { throw new \InvalidArgumentException(\sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); } if (301 == $status && !\array_key_exists('cache-control', \array_change_key_case($headers, \CASE_LOWER))) { $this->headers->remove('cache-control'); } } /** * Factory method for chainability. * * @param string $url The URL to redirect to * * @return static * * @deprecated since Symfony 5.1, use __construct() instead. */ public static function create($url = '', int $status = 302, array $headers = []) { \trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); return new static($url, $status, $headers); } /** * Returns the target URL. * * @return string */ public function getTargetUrl() { return $this->targetUrl; } /** * Sets the redirect target of this response. * * @return $this * * @throws \InvalidArgumentException */ public function setTargetUrl(string $url) { if ('' === $url) { throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); } $this->targetUrl = $url; $this->setContent(\sprintf(' Redirecting to %1$s Redirecting to %1$s. ', \htmlspecialchars($url, \ENT_QUOTES, 'UTF-8'))); $this->headers->set('Location', $url); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * Response represents an HTTP response in JSON format. * * Note that this class does not force the returned JSON content to be an * object. It is however recommended that you do return an object as it * protects yourself against XSSI and JSON-JavaScript Hijacking. * * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside * * @author Igor Wiedler */ class JsonResponse extends Response { protected $data; protected $callback; // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT public const DEFAULT_ENCODING_OPTIONS = 15; protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; /** * @param mixed $data The response data * @param int $status The response status code * @param array $headers An array of response headers * @param bool $json If the data is already a JSON string */ public function __construct($data = null, int $status = 200, array $headers = [], bool $json = \false) { parent::__construct('', $status, $headers); if ($json && !\is_string($data) && !\is_numeric($data) && !\is_callable([$data, '__toString'])) { throw new \TypeError(\sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, \get_debug_type($data))); } if (null === $data) { $data = new \ArrayObject(); } $json ? $this->setJson($data) : $this->setData($data); } /** * Factory method for chainability. * * Example: * * return JsonResponse::create(['key' => 'value']) * ->setSharedMaxAge(300); * * @param mixed $data The JSON response data * @param int $status The response status code * @param array $headers An array of response headers * * @return static * * @deprecated since Symfony 5.1, use __construct() instead. */ public static function create($data = null, int $status = 200, array $headers = []) { \trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); return new static($data, $status, $headers); } /** * Factory method for chainability. * * Example: * * return JsonResponse::fromJsonString('{"key": "value"}') * ->setSharedMaxAge(300); * * @param string $data The JSON response string * @param int $status The response status code * @param array $headers An array of response headers * * @return static */ public static function fromJsonString(string $data, int $status = 200, array $headers = []) { return new static($data, $status, $headers, \true); } /** * Sets the JSONP callback. * * @param string|null $callback The JSONP callback or null to use none * * @return $this * * @throws \InvalidArgumentException When the callback name is not valid */ public function setCallback(?string $callback = null) { if (null !== $callback) { // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ // partially taken from https://github.com/willdurand/JsonpCallbackValidator // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. // (c) William Durand $pattern = '/^[$_\\p{L}][$_\\p{L}\\p{Mn}\\p{Mc}\\p{Nd}\\p{Pc}\\x{200C}\\x{200D}]*(?:\\[(?:"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'|\\d+)\\])*?$/u'; $reserved = ['break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false']; $parts = \explode('.', $callback); foreach ($parts as $part) { if (!\preg_match($pattern, $part) || \in_array($part, $reserved, \true)) { throw new \InvalidArgumentException('The callback name is not valid.'); } } } $this->callback = $callback; return $this->update(); } /** * Sets a raw string containing a JSON document to be sent. * * @return $this */ public function setJson(string $json) { $this->data = $json; return $this->update(); } /** * Sets the data to be sent as JSON. * * @param mixed $data * * @return $this * * @throws \InvalidArgumentException */ public function setData($data = []) { try { $data = \json_encode($data, $this->encodingOptions); } catch (\Exception $e) { if ('Exception' === \get_class($e) && \str_starts_with($e->getMessage(), 'Failed calling ')) { throw $e->getPrevious() ?: $e; } throw $e; } if (\PHP_VERSION_ID >= 70300 && \JSON_THROW_ON_ERROR & $this->encodingOptions) { return $this->setJson($data); } if (\JSON_ERROR_NONE !== \json_last_error()) { throw new \InvalidArgumentException(\json_last_error_msg()); } return $this->setJson($data); } /** * Returns options used while encoding data to JSON. * * @return int */ public function getEncodingOptions() { return $this->encodingOptions; } /** * Sets options used while encoding data to JSON. * * @return $this */ public function setEncodingOptions(int $encodingOptions) { $this->encodingOptions = $encodingOptions; return $this->setData(\json_decode($this->data)); } /** * Updates the content and headers according to the JSON data and callback. * * @return $this */ protected function update() { if (null !== $this->callback) { // Not using application/javascript for compatibility reasons with older browsers. $this->headers->set('Content-Type', 'text/javascript'); return $this->setContent(\sprintf('/**/%s(%s);', $this->callback, $this->data)); } // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) // in order to not overwrite a custom definition. if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { $this->headers->set('Content-Type', 'application/json'); } return $this->setContent($this->data); } } HttpFoundation Component ======================== The HttpFoundation component defines an object-oriented layer for the HTTP specification. Sponsor ------- The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2]. Laravel is a PHP web development framework that is passionate about maximum developer happiness. Laravel is built using a variety of bespoke and Symfony based components. Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/http_foundation.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://laravel.com/ [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\JsonException; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; // Help opcache.preload discover always-needed symbols \class_exists(AcceptHeader::class); \class_exists(FileBag::class); \class_exists(HeaderBag::class); \class_exists(HeaderUtils::class); \class_exists(InputBag::class); \class_exists(ParameterBag::class); \class_exists(ServerBag::class); /** * Request represents an HTTP request. * * The methods dealing with URL accept / return a raw path (% encoded): * * getBasePath * * getBaseUrl * * getPathInfo * * getRequestUri * * getUri * * getUriForPath * * @author Fabien Potencier */ class Request { public const HEADER_FORWARDED = 0b1; // When using RFC 7239 public const HEADER_X_FORWARDED_FOR = 0b10; public const HEADER_X_FORWARDED_HOST = 0b100; public const HEADER_X_FORWARDED_PROTO = 0b1000; public const HEADER_X_FORWARDED_PORT = 0b10000; public const HEADER_X_FORWARDED_PREFIX = 0b100000; /** @deprecated since Symfony 5.2, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead. */ public const HEADER_X_FORWARDED_ALL = 0b1011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy public const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host public const HEADER_X_FORWARDED_TRAEFIK = 0b111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy public const METHOD_HEAD = 'HEAD'; public const METHOD_GET = 'GET'; public const METHOD_POST = 'POST'; public const METHOD_PUT = 'PUT'; public const METHOD_PATCH = 'PATCH'; public const METHOD_DELETE = 'DELETE'; public const METHOD_PURGE = 'PURGE'; public const METHOD_OPTIONS = 'OPTIONS'; public const METHOD_TRACE = 'TRACE'; public const METHOD_CONNECT = 'CONNECT'; /** * @var string[] */ protected static $trustedProxies = []; /** * @var string[] */ protected static $trustedHostPatterns = []; /** * @var string[] */ protected static $trustedHosts = []; protected static $httpMethodParameterOverride = \false; /** * Custom parameters. * * @var ParameterBag */ public $attributes; /** * Request body parameters ($_POST). * * @var InputBag */ public $request; /** * Query string parameters ($_GET). * * @var InputBag */ public $query; /** * Server and execution environment parameters ($_SERVER). * * @var ServerBag */ public $server; /** * Uploaded files ($_FILES). * * @var FileBag */ public $files; /** * Cookies ($_COOKIE). * * @var InputBag */ public $cookies; /** * Headers (taken from the $_SERVER). * * @var HeaderBag */ public $headers; /** * @var string|resource|false|null */ protected $content; /** * @var array */ protected $languages; /** * @var array */ protected $charsets; /** * @var array */ protected $encodings; /** * @var array */ protected $acceptableContentTypes; /** * @var string */ protected $pathInfo; /** * @var string */ protected $requestUri; /** * @var string */ protected $baseUrl; /** * @var string */ protected $basePath; /** * @var string */ protected $method; /** * @var string */ protected $format; /** * @var SessionInterface|callable(): SessionInterface */ protected $session; /** * @var string|null */ protected $locale; /** * @var string */ protected $defaultLocale = 'en'; /** * @var array */ protected static $formats; protected static $requestFactory; /** * @var string|null */ private $preferredFormat; private $isHostValid = \true; private $isForwardedValid = \true; /** * @var bool|null */ private $isSafeContentPreferred; private static $trustedHeaderSet = -1; private const FORWARDED_PARAMS = [self::HEADER_X_FORWARDED_FOR => 'for', self::HEADER_X_FORWARDED_HOST => 'host', self::HEADER_X_FORWARDED_PROTO => 'proto', self::HEADER_X_FORWARDED_PORT => 'host']; /** * Names for headers that can be trusted when * using trusted proxies. * * The FORWARDED header is the standard as of rfc7239. * * The other headers are non-standard, but widely used * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). */ private const TRUSTED_HEADERS = [self::HEADER_FORWARDED => 'FORWARDED', self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX']; /** @var bool */ private $isIisRewrite = \false; /** * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data */ public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); } /** * Sets the parameters for this request. * * This method also re-initializes all properties. * * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data */ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $this->request = new InputBag($request); $this->query = new InputBag($query); $this->attributes = new ParameterBag($attributes); $this->cookies = new InputBag($cookies); $this->files = new FileBag($files); $this->server = new ServerBag($server); $this->headers = new HeaderBag($this->server->getHeaders()); $this->content = $content; $this->languages = null; $this->charsets = null; $this->encodings = null; $this->acceptableContentTypes = null; $this->pathInfo = null; $this->requestUri = null; $this->baseUrl = null; $this->basePath = null; $this->method = null; $this->format = null; } /** * Creates a new request with values from PHP's super globals. * * @return static */ public static function createFromGlobals() { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); if (\str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') && \in_array(\strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])) { \parse_str($request->getContent(), $data); $request->request = new InputBag($data); } return $request; } /** * Creates a Request based on a given URI and configuration. * * The information contained in the URI always take precedence * over the other information (server and parameters). * * @param string $uri The URI * @param string $method The HTTP method * @param array $parameters The query (GET) or request (POST) parameters * @param array $cookies The request cookies ($_COOKIE) * @param array $files The request files ($_FILES) * @param array $server The server parameters ($_SERVER) * @param string|resource|null $content The raw body data * * @return static */ public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $server = \array_replace(['SERVER_NAME' => 'localhost', 'SERVER_PORT' => 80, 'HTTP_HOST' => 'localhost', 'HTTP_USER_AGENT' => 'Symfony', 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'REMOTE_ADDR' => '127.0.0.1', 'SCRIPT_NAME' => '', 'SCRIPT_FILENAME' => '', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_TIME' => \time(), 'REQUEST_TIME_FLOAT' => \microtime(\true)], $server); $server['PATH_INFO'] = ''; $server['REQUEST_METHOD'] = \strtoupper($method); $components = \parse_url($uri); if (isset($components['host'])) { $server['SERVER_NAME'] = $components['host']; $server['HTTP_HOST'] = $components['host']; } if (isset($components['scheme'])) { if ('https' === $components['scheme']) { $server['HTTPS'] = 'on'; $server['SERVER_PORT'] = 443; } else { unset($server['HTTPS']); $server['SERVER_PORT'] = 80; } } if (isset($components['port'])) { $server['SERVER_PORT'] = $components['port']; $server['HTTP_HOST'] .= ':' . $components['port']; } if (isset($components['user'])) { $server['PHP_AUTH_USER'] = $components['user']; } if (isset($components['pass'])) { $server['PHP_AUTH_PW'] = $components['pass']; } if (!isset($components['path'])) { $components['path'] = '/'; } switch (\strtoupper($method)) { case 'POST': case 'PUT': case 'DELETE': if (!isset($server['CONTENT_TYPE'])) { $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; } // no break case 'PATCH': $request = $parameters; $query = []; break; default: $request = []; $query = $parameters; break; } $queryString = ''; if (isset($components['query'])) { \parse_str(\html_entity_decode($components['query']), $qs); if ($query) { $query = \array_replace($qs, $query); $queryString = \http_build_query($query, '', '&'); } else { $query = $qs; $queryString = $components['query']; } } elseif ($query) { $queryString = \http_build_query($query, '', '&'); } $server['REQUEST_URI'] = $components['path'] . ('' !== $queryString ? '?' . $queryString : ''); $server['QUERY_STRING'] = $queryString; return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content); } /** * Sets a callable able to create a Request instance. * * This is mainly useful when you need to override the Request class * to keep BC with an existing system. It should not be used for any * other purpose. */ public static function setFactory(?callable $callable) { self::$requestFactory = $callable; } /** * Clones a request and overrides some of its parameters. * * @param array|null $query The GET parameters * @param array|null $request The POST parameters * @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array|null $cookies The COOKIE parameters * @param array|null $files The FILES parameters * @param array|null $server The SERVER parameters * * @return static */ public function duplicate(?array $query = null, ?array $request = null, ?array $attributes = null, ?array $cookies = null, ?array $files = null, ?array $server = null) { $dup = clone $this; if (null !== $query) { $dup->query = new InputBag($query); } if (null !== $request) { $dup->request = new InputBag($request); } if (null !== $attributes) { $dup->attributes = new ParameterBag($attributes); } if (null !== $cookies) { $dup->cookies = new InputBag($cookies); } if (null !== $files) { $dup->files = new FileBag($files); } if (null !== $server) { $dup->server = new ServerBag($server); $dup->headers = new HeaderBag($dup->server->getHeaders()); } $dup->languages = null; $dup->charsets = null; $dup->encodings = null; $dup->acceptableContentTypes = null; $dup->pathInfo = null; $dup->requestUri = null; $dup->baseUrl = null; $dup->basePath = null; $dup->method = null; $dup->format = null; if (!$dup->get('_format') && $this->get('_format')) { $dup->attributes->set('_format', $this->get('_format')); } if (!$dup->getRequestFormat(null)) { $dup->setRequestFormat($this->getRequestFormat(null)); } return $dup; } /** * Clones the current request. * * Note that the session is not cloned as duplicated requests * are most of the time sub-requests of the main one. */ public function __clone() { $this->query = clone $this->query; $this->request = clone $this->request; $this->attributes = clone $this->attributes; $this->cookies = clone $this->cookies; $this->files = clone $this->files; $this->server = clone $this->server; $this->headers = clone $this->headers; } /** * Returns the request as a string. * * @return string */ public function __toString() { $content = $this->getContent(); $cookieHeader = ''; $cookies = []; foreach ($this->cookies as $k => $v) { $cookies[] = \is_array($v) ? \http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "{$k}={$v}"; } if ($cookies) { $cookieHeader = 'Cookie: ' . \implode('; ', $cookies) . "\r\n"; } return \sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL')) . "\r\n" . $this->headers . $cookieHeader . "\r\n" . $content; } /** * Overrides the PHP global variables according to this request instance. * * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. * $_FILES is never overridden, see rfc1867 */ public function overrideGlobals() { $this->server->set('QUERY_STRING', static::normalizeQueryString(\http_build_query($this->query->all(), '', '&'))); $_GET = $this->query->all(); $_POST = $this->request->all(); $_SERVER = $this->server->all(); $_COOKIE = $this->cookies->all(); foreach ($this->headers->all() as $key => $value) { $key = \strtoupper(\str_replace('-', '_', $key)); if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], \true)) { $_SERVER[$key] = \implode(', ', $value); } else { $_SERVER['HTTP_' . $key] = \implode(', ', $value); } } $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order'); $requestOrder = \preg_replace('#[^cgp]#', '', \strtolower($requestOrder)) ?: 'gp'; $_REQUEST = [[]]; foreach (\str_split($requestOrder) as $order) { $_REQUEST[] = $request[$order]; } $_REQUEST = \array_merge(...$_REQUEST); } /** * Sets a list of trusted proxies. * * You should only list the reverse proxies that you manage directly. * * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies */ public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) { if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) { \trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); } self::$trustedProxies = \array_reduce($proxies, function ($proxies, $proxy) { if ('REMOTE_ADDR' !== $proxy) { $proxies[] = $proxy; } elseif (isset($_SERVER['REMOTE_ADDR'])) { $proxies[] = $_SERVER['REMOTE_ADDR']; } return $proxies; }, []); self::$trustedHeaderSet = $trustedHeaderSet; } /** * Gets the list of trusted proxies. * * @return array */ public static function getTrustedProxies() { return self::$trustedProxies; } /** * Gets the set of trusted headers from trusted proxies. * * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies */ public static function getTrustedHeaderSet() { return self::$trustedHeaderSet; } /** * Sets a list of trusted host patterns. * * You should only list the hosts you manage using regexs. * * @param array $hostPatterns A list of trusted host patterns */ public static function setTrustedHosts(array $hostPatterns) { self::$trustedHostPatterns = \array_map(function ($hostPattern) { return \sprintf('{%s}i', $hostPattern); }, $hostPatterns); // we need to reset trusted hosts on trusted host patterns change self::$trustedHosts = []; } /** * Gets the list of trusted host patterns. * * @return array */ public static function getTrustedHosts() { return self::$trustedHostPatterns; } /** * Normalizes a query string. * * It builds a normalized query string, where keys/value pairs are alphabetized, * have consistent escaping and unneeded delimiters are removed. * * @return string */ public static function normalizeQueryString(?string $qs) { if ('' === ($qs ?? '')) { return ''; } $qs = HeaderUtils::parseQuery($qs); \ksort($qs); return \http_build_query($qs, '', '&', \PHP_QUERY_RFC3986); } /** * Enables support for the _method request parameter to determine the intended HTTP method. * * Be warned that enabling this feature might lead to CSRF issues in your code. * Check that you are using CSRF tokens when required. * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered * and used to send a "PUT" or "DELETE" request via the _method request parameter. * If these methods are not protected against CSRF, this presents a possible vulnerability. * * The HTTP method can only be overridden when the real HTTP method is POST. */ public static function enableHttpMethodParameterOverride() { self::$httpMethodParameterOverride = \true; } /** * Checks whether support for the _method request parameter is enabled. * * @return bool */ public static function getHttpMethodParameterOverride() { return self::$httpMethodParameterOverride; } /** * Gets a "parameter" value from any bag. * * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the * flexibility in controllers, it is better to explicitly get request parameters from the appropriate * public property instead (attributes, query, request). * * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST * * @param mixed $default The default value if the parameter key does not exist * * @return mixed * * @internal since Symfony 5.4, use explicit input sources instead */ public function get(string $key, $default = null) { if ($this !== ($result = $this->attributes->get($key, $this))) { return $result; } if ($this->query->has($key)) { return $this->query->all()[$key]; } if ($this->request->has($key)) { return $this->request->all()[$key]; } return $default; } /** * Gets the Session. * * @return SessionInterface */ public function getSession() { $session = $this->session; if (!$session instanceof SessionInterface && null !== $session) { $this->setSession($session = $session()); } if (null === $session) { throw new SessionNotFoundException('Session has not been set.'); } return $session; } /** * Whether the request contains a Session which was started in one of the * previous requests. * * @return bool */ public function hasPreviousSession() { // the check for $this->session avoids malicious users trying to fake a session cookie with proper name return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); } /** * Whether the request contains a Session object. * * This method does not give any information about the state of the session object, * like whether the session is started or not. It is just a way to check if this Request * is associated with a Session instance. * * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory` * * @return bool */ public function hasSession() { $skipIfUninitialized = \func_num_args() > 0 ? \func_get_arg(0) : \false; return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); } public function setSession(SessionInterface $session) { $this->session = $session; } /** * @internal * * @param callable(): SessionInterface $factory */ public function setSessionFactory(callable $factory) { $this->session = $factory; } /** * Returns the client IP addresses. * * In the returned array the most trusted IP address is first, and the * least trusted one last. The "real" client IP address is the last one, * but this is also the least trusted one. Trusted proxies are stripped. * * Use this method carefully; you should use getClientIp() instead. * * @return array * * @see getClientIp() */ public function getClientIps() { $ip = $this->server->get('REMOTE_ADDR'); if (!$this->isFromTrustedProxy()) { return [$ip]; } return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip]; } /** * Returns the client IP address. * * This method can read the client IP address from the "X-Forwarded-For" header * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" * header value is a comma+space separated list of IP addresses, the left-most * being the original client, and each successive proxy that passed the request * adding the IP address where it received the request from. * * If your reverse proxy uses a different header name than "X-Forwarded-For", * ("Client-Ip" for instance), configure it via the $trustedHeaderSet * argument of the Request::setTrustedProxies() method instead. * * @return string|null * * @see getClientIps() * @see https://wikipedia.org/wiki/X-Forwarded-For */ public function getClientIp() { $ipAddresses = $this->getClientIps(); return $ipAddresses[0]; } /** * Returns current script name. * * @return string */ public function getScriptName() { return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); } /** * Returns the path being requested relative to the executed script. * * The path info always starts with a /. * * Suppose this request is instantiated from /mysite on localhost: * * * http://localhost/mysite returns an empty string * * http://localhost/mysite/about returns '/about' * * http://localhost/mysite/enco%20ded returns '/enco%20ded' * * http://localhost/mysite/about?var=1 returns '/about' * * @return string The raw path (i.e. not urldecoded) */ public function getPathInfo() { if (null === $this->pathInfo) { $this->pathInfo = $this->preparePathInfo(); } return $this->pathInfo; } /** * Returns the root path from which this request is executed. * * Suppose that an index.php file instantiates this request object: * * * http://localhost/index.php returns an empty string * * http://localhost/index.php/page returns an empty string * * http://localhost/web/index.php returns '/web' * * http://localhost/we%20b/index.php returns '/we%20b' * * @return string The raw path (i.e. not urldecoded) */ public function getBasePath() { if (null === $this->basePath) { $this->basePath = $this->prepareBasePath(); } return $this->basePath; } /** * Returns the root URL from which this request is executed. * * The base URL never ends with a /. * * This is similar to getBasePath(), except that it also includes the * script filename (e.g. index.php) if one exists. * * @return string The raw URL (i.e. not urldecoded) */ public function getBaseUrl() { $trustedPrefix = ''; // the proxy prefix must be prepended to any prefix being needed at the webserver level if ($this->isFromTrustedProxy() && ($trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX))) { $trustedPrefix = \rtrim($trustedPrefixValues[0], '/'); } return $trustedPrefix . $this->getBaseUrlReal(); } /** * Returns the real base URL received by the webserver from which this request is executed. * The URL does not include trusted reverse proxy prefix. * * @return string The raw URL (i.e. not urldecoded) */ private function getBaseUrlReal() : string { if (null === $this->baseUrl) { $this->baseUrl = $this->prepareBaseUrl(); } return $this->baseUrl; } /** * Gets the request's scheme. * * @return string */ public function getScheme() { return $this->isSecure() ? 'https' : 'http'; } /** * Returns the port on which the request is made. * * This method can read the client port from the "X-Forwarded-Port" header * when trusted proxies were set via "setTrustedProxies()". * * The "X-Forwarded-Port" header must contain the client port. * * @return int|string|null Can be a string if fetched from the server bag */ public function getPort() { if ($this->isFromTrustedProxy() && ($host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT))) { $host = $host[0]; } elseif ($this->isFromTrustedProxy() && ($host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST))) { $host = $host[0]; } elseif (!($host = $this->headers->get('HOST'))) { return $this->server->get('SERVER_PORT'); } if ('[' === $host[0]) { $pos = \strpos($host, ':', \strrpos($host, ']')); } else { $pos = \strrpos($host, ':'); } if (\false !== $pos && ($port = \substr($host, $pos + 1))) { return (int) $port; } return 'https' === $this->getScheme() ? 443 : 80; } /** * Returns the user. * * @return string|null */ public function getUser() { return $this->headers->get('PHP_AUTH_USER'); } /** * Returns the password. * * @return string|null */ public function getPassword() { return $this->headers->get('PHP_AUTH_PW'); } /** * Gets the user info. * * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server */ public function getUserInfo() { $userinfo = $this->getUser(); $pass = $this->getPassword(); if ('' != $pass) { $userinfo .= ":{$pass}"; } return $userinfo; } /** * Returns the HTTP host being requested. * * The port name will be appended to the host if it's non-standard. * * @return string */ public function getHttpHost() { $scheme = $this->getScheme(); $port = $this->getPort(); if ('http' == $scheme && 80 == $port || 'https' == $scheme && 443 == $port) { return $this->getHost(); } return $this->getHost() . ':' . $port; } /** * Returns the requested URI (path and query string). * * @return string The raw URI (i.e. not URI decoded) */ public function getRequestUri() { if (null === $this->requestUri) { $this->requestUri = $this->prepareRequestUri(); } return $this->requestUri; } /** * Gets the scheme and HTTP host. * * If the URL was called with basic authentication, the user * and the password are not added to the generated string. * * @return string */ public function getSchemeAndHttpHost() { return $this->getScheme() . '://' . $this->getHttpHost(); } /** * Generates a normalized URI (URL) for the Request. * * @return string * * @see getQueryString() */ public function getUri() { if (null !== ($qs = $this->getQueryString())) { $qs = '?' . $qs; } return $this->getSchemeAndHttpHost() . $this->getBaseUrl() . $this->getPathInfo() . $qs; } /** * Generates a normalized URI for the given path. * * @param string $path A path to use instead of the current one * * @return string */ public function getUriForPath(string $path) { return $this->getSchemeAndHttpHost() . $this->getBaseUrl() . $path; } /** * Returns the path as relative reference from the current Request path. * * Only the URIs path component (no schema, host etc.) is relevant and must be given. * Both paths must be absolute and not contain relative parts. * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. * Furthermore, they can be used to reduce the link size in documents. * * Example target paths, given a base path of "/a/b/c/d": * - "/a/b/c/d" -> "" * - "/a/b/c/" -> "./" * - "/a/b/" -> "../" * - "/a/b/c/other" -> "other" * - "/a/x/y" -> "../../x/y" * * @return string */ public function getRelativeUriForPath(string $path) { // be sure that we are dealing with an absolute path if (!isset($path[0]) || '/' !== $path[0]) { return $path; } if ($path === ($basePath = $this->getPathInfo())) { return ''; } $sourceDirs = \explode('/', isset($basePath[0]) && '/' === $basePath[0] ? \substr($basePath, 1) : $basePath); $targetDirs = \explode('/', \substr($path, 1)); \array_pop($sourceDirs); $targetFile = \array_pop($targetDirs); foreach ($sourceDirs as $i => $dir) { if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { unset($sourceDirs[$i], $targetDirs[$i]); } else { break; } } $targetDirs[] = $targetFile; $path = \str_repeat('../', \count($sourceDirs)) . \implode('/', $targetDirs); // A reference to the same base directory or an empty subdirectory must be prefixed with "./". // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used // as the first segment of a relative-path reference, as it would be mistaken for a scheme name // (see https://tools.ietf.org/html/rfc3986#section-4.2). return !isset($path[0]) || '/' === $path[0] || \false !== ($colonPos = \strpos($path, ':')) && ($colonPos < ($slashPos = \strpos($path, '/')) || \false === $slashPos) ? "./{$path}" : $path; } /** * Generates the normalized query string for the Request. * * It builds a normalized query string, where keys/value pairs are alphabetized * and have consistent escaping. * * @return string|null */ public function getQueryString() { $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); return '' === $qs ? null : $qs; } /** * Checks whether the request is secure or not. * * This method can read the client protocol from the "X-Forwarded-Proto" header * when trusted proxies were set via "setTrustedProxies()". * * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". * * @return bool */ public function isSecure() { if ($this->isFromTrustedProxy() && ($proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO))) { return \in_array(\strtolower($proto[0]), ['https', 'on', 'ssl', '1'], \true); } $https = $this->server->get('HTTPS'); return !empty($https) && 'off' !== \strtolower($https); } /** * Returns the host name. * * This method can read the client host name from the "X-Forwarded-Host" header * when trusted proxies were set via "setTrustedProxies()". * * The "X-Forwarded-Host" header must contain the client host name. * * @return string * * @throws SuspiciousOperationException when the host name is invalid or not trusted */ public function getHost() { if ($this->isFromTrustedProxy() && ($host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST))) { $host = $host[0]; } elseif (!($host = $this->headers->get('HOST'))) { if (!($host = $this->server->get('SERVER_NAME'))) { $host = $this->server->get('SERVER_ADDR', ''); } } // trim and remove port number from host // host is lowercase as per RFC 952/2181 $host = \strtolower(\preg_replace('/:\\d+$/', '', \trim($host))); // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names if ($host && '' !== \preg_replace('/(?:^\\[)?[a-zA-Z0-9-:\\]_]+\\.?/', '', $host)) { if (!$this->isHostValid) { return ''; } $this->isHostValid = \false; throw new SuspiciousOperationException(\sprintf('Invalid Host "%s".', $host)); } if (\count(self::$trustedHostPatterns) > 0) { // to avoid host header injection attacks, you should provide a list of trusted host patterns if (\in_array($host, self::$trustedHosts)) { return $host; } foreach (self::$trustedHostPatterns as $pattern) { if (\preg_match($pattern, $host)) { self::$trustedHosts[] = $host; return $host; } } if (!$this->isHostValid) { return ''; } $this->isHostValid = \false; throw new SuspiciousOperationException(\sprintf('Untrusted Host "%s".', $host)); } return $host; } /** * Sets the request method. */ public function setMethod(string $method) { $this->method = null; $this->server->set('REQUEST_METHOD', $method); } /** * Gets the request "intended" method. * * If the X-HTTP-Method-Override header is set, and if the method is a POST, * then it is used to determine the "real" intended HTTP method. * * The _method request parameter can also be used to determine the HTTP method, * but only if enableHttpMethodParameterOverride() has been called. * * The method is always an uppercased string. * * @return string * * @see getRealMethod() */ public function getMethod() { if (null !== $this->method) { return $this->method; } $this->method = \strtoupper($this->server->get('REQUEST_METHOD', 'GET')); if ('POST' !== $this->method) { return $this->method; } $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE'); if (!$method && self::$httpMethodParameterOverride) { $method = $this->request->get('_method', $this->query->get('_method', 'POST')); } if (!\is_string($method)) { return $this->method; } $method = \strtoupper($method); if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], \true)) { return $this->method = $method; } if (!\preg_match('/^[A-Z]++$/D', $method)) { throw new SuspiciousOperationException(\sprintf('Invalid method override "%s".', $method)); } return $this->method = $method; } /** * Gets the "real" request method. * * @return string * * @see getMethod() */ public function getRealMethod() { return \strtoupper($this->server->get('REQUEST_METHOD', 'GET')); } /** * Gets the mime type associated with the format. * * @return string|null */ public function getMimeType(string $format) { if (null === static::$formats) { static::initializeFormats(); } return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; } /** * Gets the mime types associated with the format. * * @return array */ public static function getMimeTypes(string $format) { if (null === static::$formats) { static::initializeFormats(); } return static::$formats[$format] ?? []; } /** * Gets the format associated with the mime type. * * @return string|null */ public function getFormat(?string $mimeType) { $canonicalMimeType = null; if ($mimeType && \false !== ($pos = \strpos($mimeType, ';'))) { $canonicalMimeType = \trim(\substr($mimeType, 0, $pos)); } if (null === static::$formats) { static::initializeFormats(); } foreach (static::$formats as $format => $mimeTypes) { if (\in_array($mimeType, (array) $mimeTypes)) { return $format; } if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) { return $format; } } return null; } /** * Associates a format with mime types. * * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) */ public function setFormat(?string $format, $mimeTypes) { if (null === static::$formats) { static::initializeFormats(); } static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes]; } /** * Gets the request format. * * Here is the process to determine the format: * * * format defined by the user (with setRequestFormat()) * * _format request attribute * * $default * * @see getPreferredFormat * * @return string|null */ public function getRequestFormat(?string $default = 'html') { if (null === $this->format) { $this->format = $this->attributes->get('_format'); } return $this->format ?? $default; } /** * Sets the request format. */ public function setRequestFormat(?string $format) { $this->format = $format; } /** * Gets the format associated with the request. * * @return string|null */ public function getContentType() { return $this->getFormat($this->headers->get('CONTENT_TYPE', '')); } /** * Sets the default locale. */ public function setDefaultLocale(string $locale) { $this->defaultLocale = $locale; if (null === $this->locale) { $this->setPhpDefaultLocale($locale); } } /** * Get the default locale. * * @return string */ public function getDefaultLocale() { return $this->defaultLocale; } /** * Sets the locale. */ public function setLocale(string $locale) { $this->setPhpDefaultLocale($this->locale = $locale); } /** * Get the locale. * * @return string */ public function getLocale() { return $this->locale ?? $this->defaultLocale; } /** * Checks if the request method is of specified type. * * @param string $method Uppercase request method (GET, POST etc) * * @return bool */ public function isMethod(string $method) { return $this->getMethod() === \strtoupper($method); } /** * Checks whether or not the method is safe. * * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 * * @return bool */ public function isMethodSafe() { return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); } /** * Checks whether or not the method is idempotent. * * @return bool */ public function isMethodIdempotent() { return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); } /** * Checks whether the method is cacheable or not. * * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 * * @return bool */ public function isMethodCacheable() { return \in_array($this->getMethod(), ['GET', 'HEAD']); } /** * Returns the protocol version. * * If the application is behind a proxy, the protocol version used in the * requests between the client and the proxy and between the proxy and the * server might be different. This returns the former (from the "Via" header) * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns * the latter (from the "SERVER_PROTOCOL" server parameter). * * @return string|null */ public function getProtocolVersion() { if ($this->isFromTrustedProxy()) { \preg_match('~^(HTTP/)?([1-9]\\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches); if ($matches) { return 'HTTP/' . $matches[2]; } } return $this->server->get('SERVER_PROTOCOL'); } /** * Returns the request body content. * * @param bool $asResource If true, a resource will be returned * * @return string|resource */ public function getContent(bool $asResource = \false) { $currentContentIsResource = \is_resource($this->content); if (\true === $asResource) { if ($currentContentIsResource) { \rewind($this->content); return $this->content; } // Content passed in parameter (test) if (\is_string($this->content)) { $resource = \fopen('php://temp', 'r+'); \fwrite($resource, $this->content); \rewind($resource); return $resource; } $this->content = \false; return \fopen('php://input', 'r'); } if ($currentContentIsResource) { \rewind($this->content); return \stream_get_contents($this->content); } if (null === $this->content || \false === $this->content) { $this->content = \file_get_contents('php://input'); } return $this->content; } /** * Gets the request body decoded as array, typically from a JSON payload. * * @return array * * @throws JsonException When the body cannot be decoded to an array */ public function toArray() { if ('' === ($content = $this->getContent())) { throw new JsonException('Request body is empty.'); } try { $content = \json_decode($content, \true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); } catch (\JsonException $e) { throw new JsonException('Could not decode request body.', $e->getCode(), $e); } if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== \json_last_error()) { throw new JsonException('Could not decode request body: ' . \json_last_error_msg(), \json_last_error()); } if (!\is_array($content)) { throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', \get_debug_type($content))); } return $content; } /** * Gets the Etags. * * @return array */ public function getETags() { return \preg_split('/\\s*,\\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); } /** * @return bool */ public function isNoCache() { return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } /** * Gets the preferred format for the response by inspecting, in the following order: * * the request format set using setRequestFormat; * * the values of the Accept HTTP header. * * Note that if you use this method, you should send the "Vary: Accept" header * in the response to prevent any issues with intermediary HTTP caches. */ public function getPreferredFormat(?string $default = 'html') : ?string { if (null !== $this->preferredFormat || null !== ($this->preferredFormat = $this->getRequestFormat(null))) { return $this->preferredFormat; } foreach ($this->getAcceptableContentTypes() as $mimeType) { if ($this->preferredFormat = $this->getFormat($mimeType)) { return $this->preferredFormat; } } return $default; } /** * Returns the preferred language. * * @param string[] $locales An array of ordered available locales * * @return string|null */ public function getPreferredLanguage(?array $locales = null) { $preferredLanguages = $this->getLanguages(); if (empty($locales)) { return $preferredLanguages[0] ?? null; } if (!$preferredLanguages) { return $locales[0]; } $extendedPreferredLanguages = []; foreach ($preferredLanguages as $language) { $extendedPreferredLanguages[] = $language; if (\false !== ($position = \strpos($language, '_'))) { $superLanguage = \substr($language, 0, $position); if (!\in_array($superLanguage, $preferredLanguages)) { $extendedPreferredLanguages[] = $superLanguage; } } } $preferredLanguages = \array_values(\array_intersect($extendedPreferredLanguages, $locales)); return $preferredLanguages[0] ?? $locales[0]; } /** * Gets a list of languages acceptable by the client browser ordered in the user browser preferences. * * @return array */ public function getLanguages() { if (null !== $this->languages) { return $this->languages; } $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); $this->languages = []; foreach ($languages as $acceptHeaderItem) { $lang = $acceptHeaderItem->getValue(); if (\str_contains($lang, '-')) { $codes = \explode('-', $lang); if ('i' === $codes[0]) { // Language not listed in ISO 639 that are not variants // of any listed language, which can be registered with the // i-prefix, such as i-cherokee if (\count($codes) > 1) { $lang = $codes[1]; } } else { for ($i = 0, $max = \count($codes); $i < $max; ++$i) { if (0 === $i) { $lang = \strtolower($codes[0]); } else { $lang .= '_' . \strtoupper($codes[$i]); } } } } $this->languages[] = $lang; } return $this->languages; } /** * Gets a list of charsets acceptable by the client browser in preferable order. * * @return array */ public function getCharsets() { if (null !== $this->charsets) { return $this->charsets; } return $this->charsets = \array_map('strval', \array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all())); } /** * Gets a list of encodings acceptable by the client browser in preferable order. * * @return array */ public function getEncodings() { if (null !== $this->encodings) { return $this->encodings; } return $this->encodings = \array_map('strval', \array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all())); } /** * Gets a list of content types acceptable by the client browser in preferable order. * * @return array */ public function getAcceptableContentTypes() { if (null !== $this->acceptableContentTypes) { return $this->acceptableContentTypes; } return $this->acceptableContentTypes = \array_map('strval', \array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all())); } /** * Returns true if the request is an XMLHttpRequest. * * It works if your JavaScript library sets an X-Requested-With HTTP header. * It is known to work with common JavaScript frameworks: * * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript * * @return bool */ public function isXmlHttpRequest() { return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); } /** * Checks whether the client browser prefers safe content or not according to RFC8674. * * @see https://tools.ietf.org/html/rfc8674 */ public function preferSafeContent() : bool { if (null !== $this->isSafeContentPreferred) { return $this->isSafeContentPreferred; } if (!$this->isSecure()) { // see https://tools.ietf.org/html/rfc8674#section-3 return $this->isSafeContentPreferred = \false; } return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe'); } /* * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) * * Code subject to the new BSD license (https://framework.zend.com/license). * * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) */ protected function prepareRequestUri() { $requestUri = ''; if ($this->isIisRewrite() && '' != $this->server->get('UNENCODED_URL')) { // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) $requestUri = $this->server->get('UNENCODED_URL'); $this->server->remove('UNENCODED_URL'); } elseif ($this->server->has('REQUEST_URI')) { $requestUri = $this->server->get('REQUEST_URI'); if ('' !== $requestUri && '/' === $requestUri[0]) { // To only use path and query remove the fragment. if (\false !== ($pos = \strpos($requestUri, '#'))) { $requestUri = \substr($requestUri, 0, $pos); } } else { // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, // only use URL path. $uriComponents = \parse_url($requestUri); if (isset($uriComponents['path'])) { $requestUri = $uriComponents['path']; } if (isset($uriComponents['query'])) { $requestUri .= '?' . $uriComponents['query']; } } } elseif ($this->server->has('ORIG_PATH_INFO')) { // IIS 5.0, PHP as CGI $requestUri = $this->server->get('ORIG_PATH_INFO'); if ('' != $this->server->get('QUERY_STRING')) { $requestUri .= '?' . $this->server->get('QUERY_STRING'); } $this->server->remove('ORIG_PATH_INFO'); } // normalize the request URI to ease creating sub-requests from this request $this->server->set('REQUEST_URI', $requestUri); return $requestUri; } /** * Prepares the base URL. * * @return string */ protected function prepareBaseUrl() { $filename = \basename($this->server->get('SCRIPT_FILENAME', '')); if (\basename($this->server->get('SCRIPT_NAME', '')) === $filename) { $baseUrl = $this->server->get('SCRIPT_NAME'); } elseif (\basename($this->server->get('PHP_SELF', '')) === $filename) { $baseUrl = $this->server->get('PHP_SELF'); } elseif (\basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $filename) { $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility } else { // Backtrack up the script_filename to find the portion matching // php_self $path = $this->server->get('PHP_SELF', ''); $file = $this->server->get('SCRIPT_FILENAME', ''); $segs = \explode('/', \trim($file, '/')); $segs = \array_reverse($segs); $index = 0; $last = \count($segs); $baseUrl = ''; do { $seg = $segs[$index]; $baseUrl = '/' . $seg . $baseUrl; ++$index; } while ($last > $index && \false !== ($pos = \strpos($path, $baseUrl)) && 0 != $pos); } // Does the baseUrl have anything in common with the request_uri? $requestUri = $this->getRequestUri(); if ('' !== $requestUri && '/' !== $requestUri[0]) { $requestUri = '/' . $requestUri; } if ($baseUrl && null !== ($prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl))) { // full $baseUrl matches return $prefix; } if ($baseUrl && null !== ($prefix = $this->getUrlencodedPrefix($requestUri, \rtrim(\dirname($baseUrl), '/' . \DIRECTORY_SEPARATOR) . '/'))) { // directory portion of $baseUrl matches return \rtrim($prefix, '/' . \DIRECTORY_SEPARATOR); } $truncatedRequestUri = $requestUri; if (\false !== ($pos = \strpos($requestUri, '?'))) { $truncatedRequestUri = \substr($requestUri, 0, $pos); } $basename = \basename($baseUrl ?? ''); if (empty($basename) || !\strpos(\rawurldecode($truncatedRequestUri), $basename)) { // no match whatsoever; set it blank return ''; } // If using mod_rewrite or ISAPI_Rewrite strip the script filename // out of baseUrl. $pos !== 0 makes sure it is not matching a value // from PATH_INFO or QUERY_STRING if (\strlen($requestUri) >= \strlen($baseUrl) && \false !== ($pos = \strpos($requestUri, $baseUrl)) && 0 !== $pos) { $baseUrl = \substr($requestUri, 0, $pos + \strlen($baseUrl)); } return \rtrim($baseUrl, '/' . \DIRECTORY_SEPARATOR); } /** * Prepares the base path. * * @return string */ protected function prepareBasePath() { $baseUrl = $this->getBaseUrl(); if (empty($baseUrl)) { return ''; } $filename = \basename($this->server->get('SCRIPT_FILENAME')); if (\basename($baseUrl) === $filename) { $basePath = \dirname($baseUrl); } else { $basePath = $baseUrl; } if ('\\' === \DIRECTORY_SEPARATOR) { $basePath = \str_replace('\\', '/', $basePath); } return \rtrim($basePath, '/'); } /** * Prepares the path info. * * @return string */ protected function preparePathInfo() { if (null === ($requestUri = $this->getRequestUri())) { return '/'; } // Remove the query string from REQUEST_URI if (\false !== ($pos = \strpos($requestUri, '?'))) { $requestUri = \substr($requestUri, 0, $pos); } if ('' !== $requestUri && '/' !== $requestUri[0]) { $requestUri = '/' . $requestUri; } if (null === ($baseUrl = $this->getBaseUrlReal())) { return $requestUri; } $pathInfo = \substr($requestUri, \strlen($baseUrl)); if (\false === $pathInfo || '' === $pathInfo) { // If substr() returns false then PATH_INFO is set to an empty string return '/'; } return $pathInfo; } /** * Initializes HTTP request formats. */ protected static function initializeFormats() { static::$formats = ['html' => ['text/html', 'application/xhtml+xml'], 'txt' => ['text/plain'], 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'], 'css' => ['text/css'], 'json' => ['application/json', 'application/x-json'], 'jsonld' => ['application/ld+json'], 'xml' => ['text/xml', 'application/xml', 'application/x-xml'], 'rdf' => ['application/rdf+xml'], 'atom' => ['application/atom+xml'], 'rss' => ['application/rss+xml'], 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data']]; } private function setPhpDefaultLocale(string $locale) : void { // if either the class Locale doesn't exist, or an exception is thrown when // setting the default locale, the intl module is not installed, and // the call can be ignored: try { if (\class_exists(\Locale::class, \false)) { \Locale::setDefault($locale); } } catch (\Exception $e) { } } /** * Returns the prefix as encoded in the string when the string starts with * the given prefix, null otherwise. */ private function getUrlencodedPrefix(string $string, string $prefix) : ?string { if ($this->isIisRewrite()) { // ISS with UrlRewriteModule might report SCRIPT_NAME/PHP_SELF with wrong case // see https://github.com/php/php-src/issues/11981 if (0 !== \stripos(\rawurldecode($string), $prefix)) { return null; } } elseif (!\str_starts_with(\rawurldecode($string), $prefix)) { return null; } $len = \strlen($prefix); if (\preg_match(\sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { return $match[0]; } return null; } private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) : self { if (self::$requestFactory) { $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); if (!$request instanceof self) { throw new \LogicException('The Request factory must return an instance of Symfony\\Component\\HttpFoundation\\Request.'); } return $request; } return new static($query, $request, $attributes, $cookies, $files, $server, $content); } /** * Indicates whether this request originated from a trusted proxy. * * This can be useful to determine whether or not to trust the * contents of a proxy-specific header. * * @return bool */ public function isFromTrustedProxy() { return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies); } private function getTrustedValues(int $type, ?string $ip = null) : array { $clientValues = []; $forwardedValues = []; if (self::$trustedHeaderSet & $type && $this->headers->has(self::TRUSTED_HEADERS[$type])) { foreach (\explode(',', $this->headers->get(self::TRUSTED_HEADERS[$type])) as $v) { $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '') . \trim($v); } } if (self::$trustedHeaderSet & self::HEADER_FORWARDED && isset(self::FORWARDED_PARAMS[$type]) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) { $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); $parts = HeaderUtils::split($forwarded, ',;='); $forwardedValues = []; $param = self::FORWARDED_PARAMS[$type]; foreach ($parts as $subParts) { if (null === ($v = HeaderUtils::combine($subParts)[$param] ?? null)) { continue; } if (self::HEADER_X_FORWARDED_PORT === $type) { if (\str_ends_with($v, ']') || \false === ($v = \strrchr($v, ':'))) { $v = $this->isSecure() ? ':443' : ':80'; } $v = '0.0.0.0' . $v; } $forwardedValues[] = $v; } } if (null !== $ip) { $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); } if ($forwardedValues === $clientValues || !$clientValues) { return $forwardedValues; } if (!$forwardedValues) { return $clientValues; } if (!$this->isForwardedValid) { return null !== $ip ? ['0.0.0.0', $ip] : []; } $this->isForwardedValid = \false; throw new ConflictingHeadersException(\sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type])); } private function normalizeAndFilterClientIps(array $clientIps, string $ip) : array { if (!$clientIps) { return []; } $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from $firstTrustedIp = null; foreach ($clientIps as $key => $clientIp) { if (\strpos($clientIp, '.')) { // Strip :port from IPv4 addresses. This is allowed in Forwarded // and may occur in X-Forwarded-For. $i = \strpos($clientIp, ':'); if ($i) { $clientIps[$key] = $clientIp = \substr($clientIp, 0, $i); } } elseif (\str_starts_with($clientIp, '[')) { // Strip brackets and :port from IPv6 addresses. $i = \strpos($clientIp, ']', 1); $clientIps[$key] = $clientIp = \substr($clientIp, 1, $i - 1); } if (!\filter_var($clientIp, \FILTER_VALIDATE_IP)) { unset($clientIps[$key]); continue; } if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { unset($clientIps[$key]); // Fallback to this when the client IP falls into the range of trusted proxies if (null === $firstTrustedIp) { $firstTrustedIp = $clientIp; } } } // Now the IP chain contains only untrusted proxies and the client IP return $clientIps ? \array_reverse($clientIps) : [$firstTrustedIp]; } /** * Is this IIS with UrlRewriteModule? * * This method consumes, caches and removed the IIS_WasUrlRewritten env var, * so we don't inherit it to sub-requests. */ private function isIisRewrite() : bool { if (1 === $this->server->getInt('IIS_WasUrlRewritten')) { $this->isIisRewrite = \true; $this->server->remove('IIS_WasUrlRewritten'); } return $this->isIisRewrite; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage; /** * ExpressionRequestMatcher uses an expression to match a Request. * * @author Fabien Potencier */ class ExpressionRequestMatcher extends RequestMatcher { private $language; private $expression; public function setExpression(ExpressionLanguage $language, $expression) { $this->language = $language; $this->expression = $expression; } public function matches(Request $request) { if (!$this->language) { throw new \LogicException('Unable to match the request as the expression language is not available.'); } return $this->language->evaluate($this->expression, ['request' => $request, 'method' => $request->getMethod(), 'path' => \rawurldecode($request->getPathInfo()), 'host' => $request->getHost(), 'ip' => $request->getClientIp(), 'attributes' => $request->attributes->all()]) && parent::matches($request); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\BadRequestException; /** * ParameterBag is a container for key/value pairs. * * @author Fabien Potencier * * @implements \IteratorAggregate */ class ParameterBag implements \IteratorAggregate, \Countable { /** * Parameter storage. */ protected $parameters; public function __construct(array $parameters = []) { $this->parameters = $parameters; } /** * Returns the parameters. * * @param string|null $key The name of the parameter to return or null to get them all * * @return array */ public function all() { $key = \func_num_args() > 0 ? \func_get_arg(0) : null; if (null === $key) { return $this->parameters; } if (!\is_array($value = $this->parameters[$key] ?? [])) { throw new BadRequestException(\sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, \get_debug_type($value))); } return $value; } /** * Returns the parameter keys. * * @return array */ public function keys() { return \array_keys($this->parameters); } /** * Replaces the current parameters by a new set. */ public function replace(array $parameters = []) { $this->parameters = $parameters; } /** * Adds parameters. */ public function add(array $parameters = []) { $this->parameters = \array_replace($this->parameters, $parameters); } /** * Returns a parameter by name. * * @param mixed $default The default value if the parameter key does not exist * * @return mixed */ public function get(string $key, $default = null) { return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; } /** * Sets a parameter by name. * * @param mixed $value The value */ public function set(string $key, $value) { $this->parameters[$key] = $value; } /** * Returns true if the parameter is defined. * * @return bool */ public function has(string $key) { return \array_key_exists($key, $this->parameters); } /** * Removes a parameter. */ public function remove(string $key) { unset($this->parameters[$key]); } /** * Returns the alphabetic characters of the parameter value. * * @return string */ public function getAlpha(string $key, string $default = '') { return \preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); } /** * Returns the alphabetic characters and digits of the parameter value. * * @return string */ public function getAlnum(string $key, string $default = '') { return \preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); } /** * Returns the digits of the parameter value. * * @return string */ public function getDigits(string $key, string $default = '') { // we need to remove - and + because they're allowed in the filter return \str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT)); } /** * Returns the parameter value converted to integer. * * @return int */ public function getInt(string $key, int $default = 0) { return (int) $this->get($key, $default); } /** * Returns the parameter value converted to boolean. * * @return bool */ public function getBoolean(string $key, bool $default = \false) { return $this->filter($key, $default, \FILTER_VALIDATE_BOOLEAN); } /** * Filter key. * * @param mixed $default Default = null * @param int $filter FILTER_* constant * @param mixed $options Filter options * * @see https://php.net/filter-var * * @return mixed */ public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) { $value = $this->get($key, $default); // Always turn $options into an array - this allows filter_var option shortcuts. if (!\is_array($options) && $options) { $options = ['flags' => $options]; } // Add a convenience check for arrays. if (\is_array($value) && !isset($options['flags'])) { $options['flags'] = \FILTER_REQUIRE_ARRAY; } if (\FILTER_CALLBACK & $filter && !($options['options'] ?? null) instanceof \Closure) { \trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); } return \filter_var($value, $filter, $options); } /** * Returns an iterator for parameters. * * @return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->parameters); } /** * Returns the number of parameters. * * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->parameters); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * Http utility functions. * * @author Fabien Potencier */ class IpUtils { private static $checkedIps = []; /** * This class should not be instantiated. */ private function __construct() { } /** * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. * * @param string|array $ips List of IPs or subnets (can be a string if only a single one) * * @return bool */ public static function checkIp(?string $requestIp, $ips) { if (null === $requestIp) { \trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); return \false; } if (!\is_array($ips)) { $ips = [$ips]; } $method = \substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; foreach ($ips as $ip) { if (self::$method($requestIp, $ip)) { return \true; } } return \false; } /** * Compares two IPv4 addresses. * In case a subnet is given, it checks if it contains the request IP. * * @param string $ip IPv4 address or subnet in CIDR notation * * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet */ public static function checkIp4(?string $requestIp, string $ip) { if (null === $requestIp) { \trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); return \false; } $cacheKey = $requestIp . '-' . $ip . '-v4'; if (isset(self::$checkedIps[$cacheKey])) { return self::$checkedIps[$cacheKey]; } if (!\filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { return self::$checkedIps[$cacheKey] = \false; } if (\str_contains($ip, '/')) { [$address, $netmask] = \explode('/', $ip, 2); if ('0' === $netmask) { return self::$checkedIps[$cacheKey] = \false !== \filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); } if ($netmask < 0 || $netmask > 32) { return self::$checkedIps[$cacheKey] = \false; } } else { $address = $ip; $netmask = 32; } if (\false === \ip2long($address)) { return self::$checkedIps[$cacheKey] = \false; } return self::$checkedIps[$cacheKey] = 0 === \substr_compare(\sprintf('%032b', \ip2long($requestIp)), \sprintf('%032b', \ip2long($address)), 0, $netmask); } /** * Compares two IPv6 addresses. * In case a subnet is given, it checks if it contains the request IP. * * @author David Soria Parra * * @see https://github.com/dsp/v6tools * * @param string $ip IPv6 address or subnet in CIDR notation * * @return bool * * @throws \RuntimeException When IPV6 support is not enabled */ public static function checkIp6(?string $requestIp, string $ip) { if (null === $requestIp) { \trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); return \false; } $cacheKey = $requestIp . '-' . $ip . '-v6'; if (isset(self::$checkedIps[$cacheKey])) { return self::$checkedIps[$cacheKey]; } if (!(\extension_loaded('sockets') && \defined('AF_INET6') || @\inet_pton('::1'))) { throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); } // Check to see if we were given a IP4 $requestIp or $ip by mistake if (!\filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { return self::$checkedIps[$cacheKey] = \false; } if (\str_contains($ip, '/')) { [$address, $netmask] = \explode('/', $ip, 2); if (!\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { return self::$checkedIps[$cacheKey] = \false; } if ('0' === $netmask) { return (bool) \unpack('n*', @\inet_pton($address)); } if ($netmask < 1 || $netmask > 128) { return self::$checkedIps[$cacheKey] = \false; } } else { if (!\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { return self::$checkedIps[$cacheKey] = \false; } $address = $ip; $netmask = 128; } $bytesAddr = \unpack('n*', @\inet_pton($address)); $bytesTest = \unpack('n*', @\inet_pton($requestIp)); if (!$bytesAddr || !$bytesTest) { return self::$checkedIps[$cacheKey] = \false; } for ($i = 1, $ceil = \ceil($netmask / 16); $i <= $ceil; ++$i) { $left = $netmask - 16 * ($i - 1); $left = $left <= 16 ? $left : 16; $mask = ~(0xffff >> $left) & 0xffff; if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { return self::$checkedIps[$cacheKey] = \false; } } return self::$checkedIps[$cacheKey] = \true; } /** * Anonymizes an IP/IPv6. * * Removes the last byte for v4 and the last 8 bytes for v6 IPs */ public static function anonymize(string $ip) : string { $wrappedIPv6 = \false; if ('[' === \substr($ip, 0, 1) && ']' === \substr($ip, -1, 1)) { $wrappedIPv6 = \true; $ip = \substr($ip, 1, -1); } $packedAddress = \inet_pton($ip); if (4 === \strlen($packedAddress)) { $mask = '255.255.255.0'; } elseif ($ip === \inet_ntop($packedAddress & \inet_pton('::ffff:ffff:ffff'))) { $mask = '::ffff:ffff:ff00'; } elseif ($ip === \inet_ntop($packedAddress & \inet_pton('::ffff:ffff'))) { $mask = '::ffff:ff00'; } else { $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; } $ip = \inet_ntop($packedAddress & \inet_pton($mask)); if ($wrappedIPv6) { $ip = '[' . $ip . ']'; } return $ip; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * Represents an Accept-* header item. * * @author Jean-François Simon */ class AcceptHeaderItem { private $value; private $quality = 1.0; private $index = 0; private $attributes = []; public function __construct(string $value, array $attributes = []) { $this->value = $value; foreach ($attributes as $name => $value) { $this->setAttribute($name, $value); } } /** * Builds an AcceptHeaderInstance instance from a string. * * @return self */ public static function fromString(?string $itemValue) { $parts = HeaderUtils::split($itemValue ?? '', ';='); $part = \array_shift($parts); $attributes = HeaderUtils::combine($parts); return new self($part[0], $attributes); } /** * Returns header value's string representation. * * @return string */ public function __toString() { $string = $this->value . ($this->quality < 1 ? ';q=' . $this->quality : ''); if (\count($this->attributes) > 0) { $string .= '; ' . HeaderUtils::toString($this->attributes, ';'); } return $string; } /** * Set the item value. * * @return $this */ public function setValue(string $value) { $this->value = $value; return $this; } /** * Returns the item value. * * @return string */ public function getValue() { return $this->value; } /** * Set the item quality. * * @return $this */ public function setQuality(float $quality) { $this->quality = $quality; return $this; } /** * Returns the item quality. * * @return float */ public function getQuality() { return $this->quality; } /** * Set the item index. * * @return $this */ public function setIndex(int $index) { $this->index = $index; return $this; } /** * Returns the item index. * * @return int */ public function getIndex() { return $this->index; } /** * Tests if an attribute exists. * * @return bool */ public function hasAttribute(string $name) { return isset($this->attributes[$name]); } /** * Returns an attribute by its name. * * @param mixed $default * * @return mixed */ public function getAttribute(string $name, $default = null) { return $this->attributes[$name] ?? $default; } /** * Returns all attributes. * * @return array */ public function getAttributes() { return $this->attributes; } /** * Set an attribute. * * @return $this */ public function setAttribute(string $name, string $value) { if ('q' === $name) { $this->quality = (float) $value; } else { $this->attributes[$name] = $value; } return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\Routing\RequestContext; use _ContaoManager\Symfony\Component\Routing\RequestContextAwareInterface; /** * A helper service for manipulating URLs within and outside the request scope. * * @author Valentin Udaltsov */ final class UrlHelper { private $requestStack; private $requestContext; /** * @param RequestContextAwareInterface|RequestContext|null $requestContext */ public function __construct(RequestStack $requestStack, $requestContext = null) { if (null !== $requestContext && !$requestContext instanceof RequestContext && !$requestContext instanceof RequestContextAwareInterface) { throw new \TypeError(__METHOD__ . ': Argument #2 ($requestContext) must of type Symfony\\Component\\Routing\\RequestContextAwareInterface|Symfony\\Component\\Routing\\RequestContext|null, ' . \get_debug_type($requestContext) . ' given.'); } $this->requestStack = $requestStack; $this->requestContext = $requestContext; } public function getAbsoluteUrl(string $path) : string { if (\str_contains($path, '://') || '//' === \substr($path, 0, 2)) { return $path; } if (null === ($request = $this->requestStack->getMainRequest())) { return $this->getAbsoluteUrlFromContext($path); } if ('#' === $path[0]) { $path = $request->getRequestUri() . $path; } elseif ('?' === $path[0]) { $path = $request->getPathInfo() . $path; } if (!$path || '/' !== $path[0]) { $prefix = $request->getPathInfo(); $last = \strlen($prefix) - 1; if ($last !== ($pos = \strrpos($prefix, '/'))) { $prefix = \substr($prefix, 0, $pos) . '/'; } return $request->getUriForPath($prefix . $path); } return $request->getSchemeAndHttpHost() . $path; } public function getRelativePath(string $path) : string { if (\str_contains($path, '://') || '//' === \substr($path, 0, 2)) { return $path; } if (null === ($request = $this->requestStack->getMainRequest())) { return $path; } return $request->getRelativeUriForPath($path); } private function getAbsoluteUrlFromContext(string $path) : string { if (null === ($context = $this->requestContext)) { return $path; } if ($context instanceof RequestContextAwareInterface) { $context = $context->getContext(); } if ('' === ($host = $context->getHost())) { return $path; } $scheme = $context->getScheme(); $port = ''; if ('http' === $scheme && 80 !== $context->getHttpPort()) { $port = ':' . $context->getHttpPort(); } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) { $port = ':' . $context->getHttpsPort(); } if ('#' === $path[0]) { $queryString = $context->getQueryString(); $path = $context->getPathInfo() . ($queryString ? '?' . $queryString : '') . $path; } elseif ('?' === $path[0]) { $path = $context->getPathInfo() . $path; } if ('/' !== $path[0]) { $path = \rtrim($context->getBaseUrl(), '/') . '/' . $path; } return $scheme . '://' . $host . $port . $path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; /** * HeaderBag is a container for HTTP headers. * * @author Fabien Potencier * * @implements \IteratorAggregate> */ class HeaderBag implements \IteratorAggregate, \Countable { protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; /** * @var array> */ protected $headers = []; protected $cacheControl = []; public function __construct(array $headers = []) { foreach ($headers as $key => $values) { $this->set($key, $values); } } /** * Returns the headers as a string. * * @return string */ public function __toString() { if (!($headers = $this->all())) { return ''; } \ksort($headers); $max = \max(\array_map('strlen', \array_keys($headers))) + 1; $content = ''; foreach ($headers as $name => $values) { $name = \ucwords($name, '-'); foreach ($values as $value) { $content .= \sprintf("%-{$max}s %s\r\n", $name . ':', $value); } } return $content; } /** * Returns the headers. * * @param string|null $key The name of the headers to return or null to get them all * * @return array>|array */ public function all(?string $key = null) { if (null !== $key) { return $this->headers[\strtr($key, self::UPPER, self::LOWER)] ?? []; } return $this->headers; } /** * Returns the parameter keys. * * @return string[] */ public function keys() { return \array_keys($this->all()); } /** * Replaces the current HTTP headers by a new set. */ public function replace(array $headers = []) { $this->headers = []; $this->add($headers); } /** * Adds new headers the current HTTP headers set. */ public function add(array $headers) { foreach ($headers as $key => $values) { $this->set($key, $values); } } /** * Returns the first header by name or the default one. * * @return string|null */ public function get(string $key, ?string $default = null) { $headers = $this->all($key); if (!$headers) { return $default; } if (null === $headers[0]) { return null; } return (string) $headers[0]; } /** * Sets a header by name. * * @param string|string[]|null $values The value or an array of values * @param bool $replace Whether to replace the actual value or not (true by default) */ public function set(string $key, $values, bool $replace = \true) { $key = \strtr($key, self::UPPER, self::LOWER); if (\is_array($values)) { $values = \array_values($values); if (\true === $replace || !isset($this->headers[$key])) { $this->headers[$key] = $values; } else { $this->headers[$key] = \array_merge($this->headers[$key], $values); } } else { if (\true === $replace || !isset($this->headers[$key])) { $this->headers[$key] = [$values]; } else { $this->headers[$key][] = $values; } } if ('cache-control' === $key) { $this->cacheControl = $this->parseCacheControl(\implode(', ', $this->headers[$key])); } } /** * Returns true if the HTTP header is defined. * * @return bool */ public function has(string $key) { return \array_key_exists(\strtr($key, self::UPPER, self::LOWER), $this->all()); } /** * Returns true if the given HTTP header contains the given value. * * @return bool */ public function contains(string $key, string $value) { return \in_array($value, $this->all($key)); } /** * Removes a header. */ public function remove(string $key) { $key = \strtr($key, self::UPPER, self::LOWER); unset($this->headers[$key]); if ('cache-control' === $key) { $this->cacheControl = []; } } /** * Returns the HTTP header value converted to a date. * * @return \DateTimeInterface|null * * @throws \RuntimeException When the HTTP header is not parseable */ public function getDate(string $key, ?\DateTime $default = null) { if (null === ($value = $this->get($key))) { return $default; } if (\false === ($date = \DateTime::createFromFormat(\DATE_RFC2822, $value))) { throw new \RuntimeException(\sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value)); } return $date; } /** * Adds a custom Cache-Control directive. * * @param bool|string $value The Cache-Control directive value */ public function addCacheControlDirective(string $key, $value = \true) { $this->cacheControl[$key] = $value; $this->set('Cache-Control', $this->getCacheControlHeader()); } /** * Returns true if the Cache-Control directive is defined. * * @return bool */ public function hasCacheControlDirective(string $key) { return \array_key_exists($key, $this->cacheControl); } /** * Returns a Cache-Control directive value by name. * * @return bool|string|null */ public function getCacheControlDirective(string $key) { return $this->cacheControl[$key] ?? null; } /** * Removes a Cache-Control directive. */ public function removeCacheControlDirective(string $key) { unset($this->cacheControl[$key]); $this->set('Cache-Control', $this->getCacheControlHeader()); } /** * Returns an iterator for headers. * * @return \ArrayIterator> */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->headers); } /** * Returns the number of headers. * * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->headers); } protected function getCacheControlHeader() { \ksort($this->cacheControl); return HeaderUtils::toString($this->cacheControl, ','); } /** * Parses a Cache-Control HTTP header. * * @return array */ protected function parseCacheControl(string $header) { $parts = HeaderUtils::split($header, ',='); return HeaderUtils::combine($parts); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation; use _ContaoManager\Symfony\Component\HttpFoundation\File\UploadedFile; /** * FileBag is a container for uploaded files. * * @author Fabien Potencier * @author Bulat Shakirzyanov */ class FileBag extends ParameterBag { private const FILE_KEYS = ['error', 'name', 'size', 'tmp_name', 'type']; /** * @param array|UploadedFile[] $parameters An array of HTTP files */ public function __construct(array $parameters = []) { $this->replace($parameters); } /** * {@inheritdoc} */ public function replace(array $files = []) { $this->parameters = []; $this->add($files); } /** * {@inheritdoc} */ public function set(string $key, $value) { if (!\is_array($value) && !$value instanceof UploadedFile) { throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); } parent::set($key, $this->convertFileInformation($value)); } /** * {@inheritdoc} */ public function add(array $files = []) { foreach ($files as $key => $file) { $this->set($key, $file); } } /** * Converts uploaded files to UploadedFile instances. * * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information * * @return UploadedFile[]|UploadedFile|null */ protected function convertFileInformation($file) { if ($file instanceof UploadedFile) { return $file; } $file = $this->fixPhpFilesArray($file); $keys = \array_keys($file); \sort($keys); if (self::FILE_KEYS == $keys) { if (\UPLOAD_ERR_NO_FILE == $file['error']) { $file = null; } else { $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], \false); } } else { $file = \array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); if (\array_keys($keys) === $keys) { $file = \array_filter($file); } } return $file; } /** * Fixes a malformed PHP $_FILES array. * * PHP has a bug that the format of the $_FILES array differs, depending on * whether the uploaded file fields had normal field names or array-like * field names ("normal" vs. "parent[child]"). * * This method fixes the array to look like the "normal" $_FILES array. * * It's safe to pass an already converted array, in which case this method * just returns the original array unmodified. * * @return array */ protected function fixPhpFilesArray(array $data) { // Remove extra key added by PHP 8.1. unset($data['full_path']); $keys = \array_keys($data); \sort($keys); if (self::FILE_KEYS != $keys || !isset($data['name']) || !\is_array($data['name'])) { return $data; } $files = $data; foreach (self::FILE_KEYS as $k) { unset($files[$k]); } foreach ($data['name'] as $key => $name) { $files[$key] = $this->fixPhpFilesArray(['error' => $data['error'][$key], 'name' => $name, 'type' => $data['type'][$key], 'tmp_name' => $data['tmp_name'][$key], 'size' => $data['size'][$key]]); } return $files; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Exception; /** * The HTTP request contains headers with conflicting information. * * @author Magnus Nordlander */ class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Exception; /** * Interface for Request exceptions. * * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. */ interface RequestExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Exception; /** * Raised when a user sends a malformed request. */ class BadRequestException extends \UnexpectedValueException implements RequestExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Exception; /** * Raised when a user has performed an operation that should be considered * suspicious from a security perspective. */ class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Exception; /** * Thrown by Request::toArray() when the content cannot be JSON-decoded. * * @author Tobias Nyholm */ final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Exception; /** * Raised when a session does not exist. This happens in the following cases: * - the session is not enabled * - attempt to read a session outside a request context (ie. cli script). * * @author Jérémy Derussé */ class SessionNotFoundException extends \LogicException implements RequestExceptionInterface { public function __construct(string $message = 'There is currently no session available.', int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } } { "name": "symfony\/http-foundation", "type": "library", "description": "Defines an object-oriented layer for the HTTP specification", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-mbstring": "~1.1", "symfony\/polyfill-php80": "^1.16" }, "require-dev": { "predis\/predis": "~1.0", "symfony\/cache": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^5.4|^6.0", "symfony\/http-kernel": "^5.4.12|^6.0.12|^6.1.4", "symfony\/mime": "^4.4|^5.0|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0", "symfony\/rate-limiter": "^5.2|^6.0" }, "suggest": { "symfony\/mime": "To use the file extension guesser" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Attribute; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; /** * Attributes store. * * @author Drak */ interface AttributeBagInterface extends SessionBagInterface { /** * Checks if an attribute is defined. * * @return bool */ public function has(string $name); /** * Returns an attribute. * * @param mixed $default The default value if not found * * @return mixed */ public function get(string $name, $default = null); /** * Sets an attribute. * * @param mixed $value */ public function set(string $name, $value); /** * Returns attributes. * * @return array */ public function all(); public function replace(array $attributes); /** * Removes an attribute. * * @return mixed The removed value or null when it does not exist */ public function remove(string $name); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Attribute; \trigger_deprecation('symfony/http-foundation', '5.3', 'The "%s" class is deprecated.', NamespacedAttributeBag::class); /** * This class provides structured storage of session attributes using * a name spacing character in the key. * * @author Drak * * @deprecated since Symfony 5.3 */ class NamespacedAttributeBag extends AttributeBag { private $namespaceCharacter; /** * @param string $storageKey Session storage key * @param string $namespaceCharacter Namespace character to use in keys */ public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/') { $this->namespaceCharacter = $namespaceCharacter; parent::__construct($storageKey); } /** * {@inheritdoc} */ public function has(string $name) { // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is $attributes = $this->resolveAttributePath($name); $name = $this->resolveKey($name); if (null === $attributes) { return \false; } return \array_key_exists($name, $attributes); } /** * {@inheritdoc} */ public function get(string $name, $default = null) { // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is $attributes = $this->resolveAttributePath($name); $name = $this->resolveKey($name); if (null === $attributes) { return $default; } return \array_key_exists($name, $attributes) ? $attributes[$name] : $default; } /** * {@inheritdoc} */ public function set(string $name, $value) { $attributes =& $this->resolveAttributePath($name, \true); $name = $this->resolveKey($name); $attributes[$name] = $value; } /** * {@inheritdoc} */ public function remove(string $name) { $retval = null; $attributes =& $this->resolveAttributePath($name); $name = $this->resolveKey($name); if (null !== $attributes && \array_key_exists($name, $attributes)) { $retval = $attributes[$name]; unset($attributes[$name]); } return $retval; } /** * Resolves a path in attributes property and returns it as a reference. * * This method allows structured namespacing of session attributes. * * @param string $name Key name * @param bool $writeContext Write context, default false * * @return array|null */ protected function &resolveAttributePath(string $name, bool $writeContext = \false) { $array =& $this->attributes; $name = \str_starts_with($name, $this->namespaceCharacter) ? \substr($name, 1) : $name; // Check if there is anything to do, else return if (!$name) { return $array; } $parts = \explode($this->namespaceCharacter, $name); if (\count($parts) < 2) { if (!$writeContext) { return $array; } $array[$parts[0]] = []; return $array; } unset($parts[\count($parts) - 1]); foreach ($parts as $part) { if (null !== $array && !\array_key_exists($part, $array)) { if (!$writeContext) { $null = null; return $null; } $array[$part] = []; } $array =& $array[$part]; } return $array; } /** * Resolves the key from the name. * * This is the last part in a dot separated string. * * @return string */ protected function resolveKey(string $name) { if (\false !== ($pos = \strrpos($name, $this->namespaceCharacter))) { $name = \substr($name, $pos + 1); } return $name; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Attribute; /** * This class relates to session attribute storage. * * @implements \IteratorAggregate */ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable { private $name = 'attributes'; private $storageKey; protected $attributes = []; /** * @param string $storageKey The key used to store attributes in the session */ public function __construct(string $storageKey = '_sf2_attributes') { $this->storageKey = $storageKey; } /** * {@inheritdoc} */ public function getName() { return $this->name; } public function setName(string $name) { $this->name = $name; } /** * {@inheritdoc} */ public function initialize(array &$attributes) { $this->attributes =& $attributes; } /** * {@inheritdoc} */ public function getStorageKey() { return $this->storageKey; } /** * {@inheritdoc} */ public function has(string $name) { return \array_key_exists($name, $this->attributes); } /** * {@inheritdoc} */ public function get(string $name, $default = null) { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; } /** * {@inheritdoc} */ public function set(string $name, $value) { $this->attributes[$name] = $value; } /** * {@inheritdoc} */ public function all() { return $this->attributes; } /** * {@inheritdoc} */ public function replace(array $attributes) { $this->attributes = []; foreach ($attributes as $key => $value) { $this->set($key, $value); } } /** * {@inheritdoc} */ public function remove(string $name) { $retval = null; if (\array_key_exists($name, $this->attributes)) { $retval = $this->attributes[$name]; unset($this->attributes[$name]); } return $retval; } /** * {@inheritdoc} */ public function clear() { $return = $this->attributes; $this->attributes = []; return $return; } /** * Returns an iterator for attributes. * * @return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->attributes); } /** * Returns the number of attributes. * * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->attributes); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; /** * @author Kevin Bond */ interface SessionFactoryInterface { public function createSession() : SessionInterface; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; // Help opcache.preload discover always-needed symbols \class_exists(AttributeBag::class); \class_exists(FlashBag::class); \class_exists(SessionBagProxy::class); /** * @author Fabien Potencier * @author Drak * * @implements \IteratorAggregate */ class Session implements SessionInterface, \IteratorAggregate, \Countable { protected $storage; private $flashName; private $attributeName; private $data = []; private $usageIndex = 0; private $usageReporter; public function __construct(?SessionStorageInterface $storage = null, ?AttributeBagInterface $attributes = null, ?FlashBagInterface $flashes = null, ?callable $usageReporter = null) { $this->storage = $storage ?? new NativeSessionStorage(); $this->usageReporter = $usageReporter; $attributes = $attributes ?? new AttributeBag(); $this->attributeName = $attributes->getName(); $this->registerBag($attributes); $flashes = $flashes ?? new FlashBag(); $this->flashName = $flashes->getName(); $this->registerBag($flashes); } /** * {@inheritdoc} */ public function start() { return $this->storage->start(); } /** * {@inheritdoc} */ public function has(string $name) { return $this->getAttributeBag()->has($name); } /** * {@inheritdoc} */ public function get(string $name, $default = null) { return $this->getAttributeBag()->get($name, $default); } /** * {@inheritdoc} */ public function set(string $name, $value) { $this->getAttributeBag()->set($name, $value); } /** * {@inheritdoc} */ public function all() { return $this->getAttributeBag()->all(); } /** * {@inheritdoc} */ public function replace(array $attributes) { $this->getAttributeBag()->replace($attributes); } /** * {@inheritdoc} */ public function remove(string $name) { return $this->getAttributeBag()->remove($name); } /** * {@inheritdoc} */ public function clear() { $this->getAttributeBag()->clear(); } /** * {@inheritdoc} */ public function isStarted() { return $this->storage->isStarted(); } /** * Returns an iterator for attributes. * * @return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->getAttributeBag()->all()); } /** * Returns the number of attributes. * * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->getAttributeBag()->all()); } public function &getUsageIndex() : int { return $this->usageIndex; } /** * @internal */ public function isEmpty() : bool { if ($this->isStarted()) { ++$this->usageIndex; if ($this->usageReporter && 0 <= $this->usageIndex) { ($this->usageReporter)(); } } foreach ($this->data as &$data) { if (!empty($data)) { return \false; } } return \true; } /** * {@inheritdoc} */ public function invalidate(?int $lifetime = null) { $this->storage->clear(); return $this->migrate(\true, $lifetime); } /** * {@inheritdoc} */ public function migrate(bool $destroy = \false, ?int $lifetime = null) { return $this->storage->regenerate($destroy, $lifetime); } /** * {@inheritdoc} */ public function save() { $this->storage->save(); } /** * {@inheritdoc} */ public function getId() { return $this->storage->getId(); } /** * {@inheritdoc} */ public function setId(string $id) { if ($this->storage->getId() !== $id) { $this->storage->setId($id); } } /** * {@inheritdoc} */ public function getName() { return $this->storage->getName(); } /** * {@inheritdoc} */ public function setName(string $name) { $this->storage->setName($name); } /** * {@inheritdoc} */ public function getMetadataBag() { ++$this->usageIndex; if ($this->usageReporter && 0 <= $this->usageIndex) { ($this->usageReporter)(); } return $this->storage->getMetadataBag(); } /** * {@inheritdoc} */ public function registerBag(SessionBagInterface $bag) { $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); } /** * {@inheritdoc} */ public function getBag(string $name) { $bag = $this->storage->getBag($name); return \method_exists($bag, 'getBag') ? $bag->getBag() : $bag; } /** * Gets the flashbag interface. * * @return FlashBagInterface */ public function getFlashBag() { return $this->getBag($this->flashName); } /** * Gets the attributebag interface. * * Note that this method was added to help with IDE autocompletion. */ private function getAttributeBag() : AttributeBagInterface { return $this->getBag($this->attributeName); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; /** * Session utility functions. * * @author Nicolas Grekas * @author Rémon van de Kamp * * @internal */ final class SessionUtils { /** * Finds the session header amongst the headers that are to be sent, removes it, and returns * it so the caller can process it further. */ public static function popSessionCookie(string $sessionName, string $sessionId) : ?string { $sessionCookie = null; $sessionCookiePrefix = \sprintf(' %s=', \urlencode($sessionName)); $sessionCookieWithId = \sprintf('%s%s;', $sessionCookiePrefix, \urlencode($sessionId)); $otherCookies = []; foreach (\headers_list() as $h) { if (0 !== \stripos($h, 'Set-Cookie:')) { continue; } if (11 === \strpos($h, $sessionCookiePrefix, 11)) { $sessionCookie = $h; if (11 !== \strpos($h, $sessionCookieWithId, 11)) { $otherCookies[] = $h; } } else { $otherCookies[] = $h; } } if (null === $sessionCookie) { return null; } \header_remove('Set-Cookie'); foreach ($otherCookies as $h) { \header($h, \false); } return $sessionCookie; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; /** * Can be used in unit testing or in a situations where persisted sessions are not desired. * * @author Drak */ class NullSessionHandler extends AbstractSessionHandler { /** * @return bool */ #[\ReturnTypeWillChange] public function close() { return \true; } /** * @return bool */ #[\ReturnTypeWillChange] public function validateId($sessionId) { return \true; } /** * {@inheritdoc} */ protected function doRead(string $sessionId) { return ''; } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return \true; } /** * {@inheritdoc} */ protected function doWrite(string $sessionId, string $data) { return \true; } /** * {@inheritdoc} */ protected function doDestroy(string $sessionId) { return \true; } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionUtils; /** * This abstract session handler provides a generic implementation * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, * enabling strict and lazy session handling. * * @author Nicolas Grekas */ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { private $sessionName; private $prefetchId; private $prefetchData; private $newSessionId; private $igbinaryEmptyData; /** * @return bool */ #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { $this->sessionName = $sessionName; if (!\headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) { \header(\sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire'))); } return \true; } /** * @return string */ protected abstract function doRead(string $sessionId); /** * @return bool */ protected abstract function doWrite(string $sessionId, string $data); /** * @return bool */ protected abstract function doDestroy(string $sessionId); /** * @return bool */ #[\ReturnTypeWillChange] public function validateId($sessionId) { $this->prefetchData = $this->read($sessionId); $this->prefetchId = $sessionId; if (\PHP_VERSION_ID < 70317 || 70400 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 70405) { // work around https://bugs.php.net/79413 foreach (\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { if (!isset($frame['class']) && isset($frame['function']) && \in_array($frame['function'], ['session_regenerate_id', 'session_create_id'], \true)) { return '' === $this->prefetchData; } } } return '' !== $this->prefetchData; } /** * @return string */ #[\ReturnTypeWillChange] public function read($sessionId) { if (null !== $this->prefetchId) { $prefetchId = $this->prefetchId; $prefetchData = $this->prefetchData; $this->prefetchId = $this->prefetchData = null; if ($prefetchId === $sessionId || '' === $prefetchData) { $this->newSessionId = '' === $prefetchData ? $sessionId : null; return $prefetchData; } } $data = $this->doRead($sessionId); $this->newSessionId = '' === $data ? $sessionId : null; return $data; } /** * @return bool */ #[\ReturnTypeWillChange] public function write($sessionId, $data) { if (null === $this->igbinaryEmptyData) { // see https://github.com/igbinary/igbinary/issues/146 $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? \igbinary_serialize([]) : ''; } if ('' === $data || $this->igbinaryEmptyData === $data) { return $this->destroy($sessionId); } $this->newSessionId = null; return $this->doWrite($sessionId, $data); } /** * @return bool */ #[\ReturnTypeWillChange] public function destroy($sessionId) { if (!\headers_sent() && \filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { if (!$this->sessionName) { throw new \LogicException(\sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); } $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId); /* * We send an invalidation Set-Cookie header (zero lifetime) * when either the session was started or a cookie with * the session name was sent by the client (in which case * we know it's invalid as a valid session cookie would've * started the session). */ if (null === $cookie || isset($_COOKIE[$this->sessionName])) { if (\PHP_VERSION_ID < 70300) { \setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), \filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), \filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); } else { $params = \session_get_cookie_params(); unset($params['lifetime']); \setcookie($this->sessionName, '', $params); } } } return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; use _ContaoManager\Doctrine\DBAL\Configuration; use _ContaoManager\Doctrine\DBAL\DriverManager; use _ContaoManager\Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use _ContaoManager\Doctrine\DBAL\Tools\DsnParser; use _ContaoManager\Symfony\Component\Cache\Adapter\AbstractAdapter; use _ContaoManager\Symfony\Component\Cache\Traits\RedisClusterProxy; use _ContaoManager\Symfony\Component\Cache\Traits\RedisProxy; /** * @author Nicolas Grekas */ class SessionHandlerFactory { /** * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN */ public static function createHandler($connection) : AbstractSessionHandler { if (!\is_string($connection) && !\is_object($connection)) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, \get_debug_type($connection))); } if ($options = \is_string($connection) ? \parse_url($connection) : \false) { \parse_str($options['query'] ?? '', $options); } switch (\true) { case $connection instanceof \Redis: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \_ContaoManager\Predis\ClientInterface: case $connection instanceof RedisProxy: case $connection instanceof RedisClusterProxy: return new RedisSessionHandler($connection); case $connection instanceof \Memcached: return new MemcachedSessionHandler($connection); case $connection instanceof \PDO: return new PdoSessionHandler($connection); case !\is_string($connection): throw new \InvalidArgumentException(\sprintf('Unsupported Connection: "%s".', \get_debug_type($connection))); case \str_starts_with($connection, 'file://'): $savePath = \substr($connection, 7); return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath)); case \str_starts_with($connection, 'redis:'): case \str_starts_with($connection, 'rediss:'): case \str_starts_with($connection, 'memcached:'): if (!\class_exists(AbstractAdapter::class)) { throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".'); } $handlerClass = \str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => \true]); return new $handlerClass($connection, \array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1])); case \str_starts_with($connection, 'pdo_oci://'): if (!\class_exists(DriverManager::class)) { throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".'); } $connection[3] = '-'; $params = \class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection]; $config = new Configuration(); if (\class_exists(DefaultSchemaManagerFactory::class)) { $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); } $connection = DriverManager::getConnection($params, $config); // The condition should be removed once support for DBAL <3.3 is dropped $connection = \method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection(); // no break; case \str_starts_with($connection, 'mssql://'): case \str_starts_with($connection, 'mysql://'): case \str_starts_with($connection, 'mysql2://'): case \str_starts_with($connection, 'pgsql://'): case \str_starts_with($connection, 'postgres://'): case \str_starts_with($connection, 'postgresql://'): case \str_starts_with($connection, 'sqlsrv://'): case \str_starts_with($connection, 'sqlite://'): case \str_starts_with($connection, 'sqlite3://'): return new PdoSessionHandler($connection, $options ?: []); } throw new \InvalidArgumentException(\sprintf('Unsupported Connection: "%s".', $connection)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; /** * Session handler using a PDO connection to read and write data. * * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements * different locking strategies to handle concurrent access to the same session. * Locking is necessary to prevent loss of data due to race conditions and to keep * the session data consistent between read() and write(). With locking, requests * for the same session will wait until the other one finished writing. For this * reason it's best practice to close a session as early as possible to improve * concurrency. PHPs internal files session handler also implements locking. * * Attention: Since SQLite does not support row level locks but locks the whole database, * it means only one session can be accessed at a time. Even different sessions would wait * for another to finish. So saving session in SQLite should only be considered for * development or prototypes. * * Session data is a binary string that can contain non-printable characters like the null byte. * For this reason it must be saved in a binary column in the database like BLOB in MySQL. * Saving it in a character column could corrupt the data. You can use createTable() * to initialize a correctly defined table. * * @see https://php.net/sessionhandlerinterface * * @author Fabien Potencier * @author Michael Williams * @author Tobias Schultze */ class PdoSessionHandler extends AbstractSessionHandler { /** * No locking is done. This means sessions are prone to loss of data due to * race conditions of concurrent requests to the same session. The last session * write will win in this case. It might be useful when you implement your own * logic to deal with this like an optimistic approach. */ public const LOCK_NONE = 0; /** * Creates an application-level lock on a session. The disadvantage is that the * lock is not enforced by the database and thus other, unaware parts of the * application could still concurrently modify the session. The advantage is it * does not require a transaction. * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. */ public const LOCK_ADVISORY = 1; /** * Issues a real row lock. Since it uses a transaction between opening and * closing a session, you have to be careful when you use same database connection * that you also use for your application logic. This mode is the default because * it's the only reliable solution across DBMSs. */ public const LOCK_TRANSACTIONAL = 2; private const MAX_LIFETIME = 315576000; /** * @var \PDO|null PDO instance or null when not connected yet */ private $pdo; /** * DSN string or null for session.save_path or false when lazy connection disabled. * * @var string|false|null */ private $dsn = \false; /** * @var string|null */ private $driver; /** * @var string */ private $table = 'sessions'; /** * @var string */ private $idCol = 'sess_id'; /** * @var string */ private $dataCol = 'sess_data'; /** * @var string */ private $lifetimeCol = 'sess_lifetime'; /** * @var string */ private $timeCol = 'sess_time'; /** * Username when lazy-connect. * * @var string|null */ private $username = null; /** * Password when lazy-connect. * * @var string|null */ private $password = null; /** * Connection options when lazy-connect. * * @var array */ private $connectionOptions = []; /** * The strategy for locking, see constants. * * @var int */ private $lockMode = self::LOCK_TRANSACTIONAL; /** * It's an array to support multiple reads before closing which is manual, non-standard usage. * * @var \PDOStatement[] An array of statements to release advisory locks */ private $unlockStatements = []; /** * True when the current session exists but expired according to session.gc_maxlifetime. * * @var bool */ private $sessionExpired = \false; /** * Whether a transaction is active. * * @var bool */ private $inTransaction = \false; /** * Whether gc() has been called. * * @var bool */ private $gcCalled = \false; /** * You can either pass an existing database connection as PDO instance or * pass a DSN string that will be used to lazy-connect to the database * when the session is actually used. Furthermore it's possible to pass null * which will then use the session.save_path ini setting as PDO DSN parameter. * * List of available options: * * db_table: The name of the table [default: sessions] * * db_id_col: The column where to store the session id [default: sess_id] * * db_data_col: The column where to store the session data [default: sess_data] * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] * * db_time_col: The column where to store the timestamp [default: sess_time] * * db_username: The username when lazy-connect [default: ''] * * db_password: The password when lazy-connect [default: ''] * * db_connection_options: An array of driver-specific connection options [default: []] * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] * * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION */ public function __construct($pdoOrDsn = null, array $options = []) { if ($pdoOrDsn instanceof \PDO) { if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { throw new \InvalidArgumentException(\sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); } $this->pdo = $pdoOrDsn; $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); } elseif (\is_string($pdoOrDsn) && \str_contains($pdoOrDsn, '://')) { $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); } else { $this->dsn = $pdoOrDsn; } $this->table = $options['db_table'] ?? $this->table; $this->idCol = $options['db_id_col'] ?? $this->idCol; $this->dataCol = $options['db_data_col'] ?? $this->dataCol; $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; $this->timeCol = $options['db_time_col'] ?? $this->timeCol; $this->username = $options['db_username'] ?? $this->username; $this->password = $options['db_password'] ?? $this->password; $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; $this->lockMode = $options['lock_mode'] ?? $this->lockMode; } /** * Creates the table to store sessions which can be called once for setup. * * Session ID is saved in a column of maximum length 128 because that is enough even * for a 512 bit configured session.hash_function like Whirlpool. Session data is * saved in a BLOB. One could also use a shorter inlined varbinary column * if one was sure the data fits into it. * * @throws \PDOException When the table already exists * @throws \DomainException When an unsupported PDO driver is used */ public function createTable() { // connect if we are not yet $this->getConnection(); switch ($this->driver) { case 'mysql': // We use varbinary for the ID column because it prevents unwanted conversions: // - character set conversions between server and client // - trailing space removal // - case-insensitivity // - language processing like é == e $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARBINARY(128) NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER UNSIGNED NOT NULL, {$this->timeCol} INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; break; case 'sqlite': $sql = "CREATE TABLE {$this->table} ({$this->idCol} TEXT NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)"; break; case 'pgsql': $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR(128) NOT NULL PRIMARY KEY, {$this->dataCol} BYTEA NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)"; break; case 'oci': $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR2(128) NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)"; break; case 'sqlsrv': $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR(128) NOT NULL PRIMARY KEY, {$this->dataCol} VARBINARY(MAX) NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)"; break; default: throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); } try { $this->pdo->exec($sql); $this->pdo->exec("CREATE INDEX EXPIRY ON {$this->table} ({$this->lifetimeCol})"); } catch (\PDOException $e) { $this->rollback(); throw $e; } } /** * Returns true when the current session exists but expired according to session.gc_maxlifetime. * * Can be used to distinguish between a new session and one that expired due to inactivity. * * @return bool */ public function isSessionExpired() { return $this->sessionExpired; } /** * @return bool */ #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { $this->sessionExpired = \false; if (null === $this->pdo) { $this->connect($this->dsn ?: $savePath); } return parent::open($savePath, $sessionName); } /** * @return string */ #[\ReturnTypeWillChange] public function read($sessionId) { try { return parent::read($sessionId); } catch (\PDOException $e) { $this->rollback(); throw $e; } } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. // This way, pruning expired sessions does not block them from being started while the current session is used. $this->gcCalled = \true; return 0; } /** * {@inheritdoc} */ protected function doDestroy(string $sessionId) { // delete the record associated with this id $sql = "DELETE FROM {$this->table} WHERE {$this->idCol} = :id"; try { $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->execute(); } catch (\PDOException $e) { $this->rollback(); throw $e; } return \true; } /** * {@inheritdoc} */ protected function doWrite(string $sessionId, string $data) { $maxlifetime = (int) \ini_get('session.gc_maxlifetime'); try { // We use a single MERGE SQL query when supported by the database. $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); if (null !== $mergeStmt) { $mergeStmt->execute(); return \true; } $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime); $updateStmt->execute(); // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). // We can just catch such an error and re-execute the update. This is similar to a serializable // transaction with retry logic on serialization failures but without the overhead and without possible // false positives due to longer gap locking. if (!$updateStmt->rowCount()) { try { $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime); $insertStmt->execute(); } catch (\PDOException $e) { // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys if (\str_starts_with($e->getCode(), '23')) { $updateStmt->execute(); } else { throw $e; } } } } catch (\PDOException $e) { $this->rollback(); throw $e; } return \true; } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { $expiry = \time() + (int) \ini_get('session.gc_maxlifetime'); try { $updateStmt = $this->pdo->prepare("UPDATE {$this->table} SET {$this->lifetimeCol} = :expiry, {$this->timeCol} = :time WHERE {$this->idCol} = :id"); $updateStmt->bindValue(':id', $sessionId, \PDO::PARAM_STR); $updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT); $updateStmt->bindValue(':time', \time(), \PDO::PARAM_INT); $updateStmt->execute(); } catch (\PDOException $e) { $this->rollback(); throw $e; } return \true; } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { $this->commit(); while ($unlockStmt = \array_shift($this->unlockStatements)) { $unlockStmt->execute(); } if ($this->gcCalled) { $this->gcCalled = \false; // delete the session records that have expired $sql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} < :time AND {$this->lifetimeCol} > :min"; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':time', \time(), \PDO::PARAM_INT); $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); $stmt->execute(); // to be removed in 6.0 if ('mysql' === $this->driver) { $legacySql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} <= :min AND {$this->lifetimeCol} + {$this->timeCol} < :time"; } else { $legacySql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} <= :min AND {$this->lifetimeCol} < :time - {$this->timeCol}"; } $stmt = $this->pdo->prepare($legacySql); $stmt->bindValue(':time', \time(), \PDO::PARAM_INT); $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); $stmt->execute(); } if (\false !== $this->dsn) { $this->pdo = null; // only close lazy-connection $this->driver = null; } return \true; } /** * Lazy-connects to the database. */ private function connect(string $dsn) : void { $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); } /** * Builds a PDO DSN from a URL-like connection string. * * @todo implement missing support for oci DSN (which look totally different from other PDO ones) */ private function buildDsnFromUrl(string $dsnOrUrl) : string { // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = \preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); $params = \parse_url($url); if (\false === $params) { return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already. } $params = \array_map('rawurldecode', $params); // Override the default username and password. Values passed through options will still win over these in the constructor. if (isset($params['user'])) { $this->username = $params['user']; } if (isset($params['pass'])) { $this->password = $params['pass']; } if (!isset($params['scheme'])) { throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler.'); } $driverAliasMap = [ 'mssql' => 'sqlsrv', 'mysql2' => 'mysql', // Amazon RDS, for some weird reason 'postgres' => 'pgsql', 'postgresql' => 'pgsql', 'sqlite3' => 'sqlite', ]; $driver = $driverAliasMap[$params['scheme']] ?? $params['scheme']; // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. if (\str_starts_with($driver, 'pdo_') || \str_starts_with($driver, 'pdo-')) { $driver = \substr($driver, 4); } $dsn = null; switch ($driver) { case 'mysql': $dsn = 'mysql:'; if ('' !== ($params['query'] ?? '')) { $queryParams = []; \parse_str($params['query'], $queryParams); if ('' !== ($queryParams['charset'] ?? '')) { $dsn .= 'charset=' . $queryParams['charset'] . ';'; } if ('' !== ($queryParams['unix_socket'] ?? '')) { $dsn .= 'unix_socket=' . $queryParams['unix_socket'] . ';'; if (isset($params['path'])) { $dbName = \substr($params['path'], 1); // Remove the leading slash $dsn .= 'dbname=' . $dbName . ';'; } return $dsn; } } // If "unix_socket" is not in the query, we continue with the same process as pgsql // no break case 'pgsql': $dsn ?? ($dsn = 'pgsql:'); if (isset($params['host']) && '' !== $params['host']) { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port']) && '' !== $params['port']) { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['path'])) { $dbName = \substr($params['path'], 1); // Remove the leading slash $dsn .= 'dbname=' . $dbName . ';'; } return $dsn; case 'sqlite': return 'sqlite:' . \substr($params['path'], 1); case 'sqlsrv': $dsn = 'sqlsrv:server='; if (isset($params['host'])) { $dsn .= $params['host']; } if (isset($params['port']) && '' !== $params['port']) { $dsn .= ',' . $params['port']; } if (isset($params['path'])) { $dbName = \substr($params['path'], 1); // Remove the leading slash $dsn .= ';Database=' . $dbName; } return $dsn; default: throw new \InvalidArgumentException(\sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme'])); } } /** * Helper method to begin a transaction. * * Since SQLite does not support row level locks, we have to acquire a reserved lock * on the database immediately. Because of https://bugs.php.net/42766 we have to create * such a transaction manually which also means we cannot use PDO::commit or * PDO::rollback or PDO::inTransaction for SQLite. * * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . * So we change it to READ COMMITTED. */ private function beginTransaction() : void { if (!$this->inTransaction) { if ('sqlite' === $this->driver) { $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); } else { if ('mysql' === $this->driver) { $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); } $this->pdo->beginTransaction(); } $this->inTransaction = \true; } } /** * Helper method to commit a transaction. */ private function commit() : void { if ($this->inTransaction) { try { // commit read-write transaction which also releases the lock if ('sqlite' === $this->driver) { $this->pdo->exec('COMMIT'); } else { $this->pdo->commit(); } $this->inTransaction = \false; } catch (\PDOException $e) { $this->rollback(); throw $e; } } } /** * Helper method to rollback a transaction. */ private function rollback() : void { // We only need to rollback if we are in a transaction. Otherwise the resulting // error would hide the real problem why rollback was called. We might not be // in a transaction when not using the transactional locking behavior or when // two callbacks (e.g. destroy and write) are invoked that both fail. if ($this->inTransaction) { if ('sqlite' === $this->driver) { $this->pdo->exec('ROLLBACK'); } else { $this->pdo->rollBack(); } $this->inTransaction = \false; } } /** * Reads the session data in respect to the different locking strategies. * * We need to make sure we do not return session data that is already considered garbage according * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. * * @return string */ protected function doRead(string $sessionId) { if (self::LOCK_ADVISORY === $this->lockMode) { $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); } $selectSql = $this->getSelectSql(); $selectStmt = $this->pdo->prepare($selectSql); $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $insertStmt = null; while (\true) { $selectStmt->execute(); $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); if ($sessionRows) { $expiry = (int) $sessionRows[0][1]; if ($expiry <= self::MAX_LIFETIME) { $expiry += $sessionRows[0][2]; } if ($expiry < \time()) { $this->sessionExpired = \true; return ''; } return \is_resource($sessionRows[0][0]) ? \stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; } if (null !== $insertStmt) { $this->rollback(); throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); } if (!\filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { // In strict mode, session fixation is not possible: new sessions always start with a unique // random id, so that concurrency is not possible and this code path can be skipped. // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block // until other connections to the session are committed. try { $insertStmt = $this->getInsertStatement($sessionId, '', 0); $insertStmt->execute(); } catch (\PDOException $e) { // Catch duplicate key error because other connection created the session already. // It would only not be the case when the other connection destroyed the session. if (\str_starts_with($e->getCode(), '23')) { // Retrieve finished session data written by concurrent connection by restarting the loop. // We have to start a new transaction as a failed query will mark the current transaction as // aborted in PostgreSQL and disallow further queries within it. $this->rollback(); $this->beginTransaction(); continue; } throw $e; } } return ''; } } /** * Executes an application-level lock on the database. * * @return \PDOStatement The statement that needs to be executed later to release the lock * * @throws \DomainException When an unsupported PDO driver is used * * @todo implement missing advisory locks * - for oci using DBMS_LOCK.REQUEST * - for sqlsrv using sp_getapplock with LockOwner = Session */ private function doAdvisoryLock(string $sessionId) : \PDOStatement { switch ($this->driver) { case 'mysql': // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. $lockId = \substr($sessionId, 0, 64); // should we handle the return value? 0 on timeout, null on error // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); $stmt->execute(); $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); return $releaseStmt; case 'pgsql': // Obtaining an exclusive session level advisory lock requires an integer key. // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters. // So we cannot just use hexdec(). if (4 === \PHP_INT_SIZE) { $sessionInt1 = $this->convertStringToInt($sessionId); $sessionInt2 = $this->convertStringToInt(\substr($sessionId, 4, 4)); $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); $stmt->execute(); $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); } else { $sessionBigInt = $this->convertStringToInt($sessionId); $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); $stmt->execute(); $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); } return $releaseStmt; case 'sqlite': throw new \DomainException('SQLite does not support advisory locks.'); default: throw new \DomainException(\sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); } } /** * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. * * Keep in mind, PHP integers are signed. */ private function convertStringToInt(string $string) : int { if (4 === \PHP_INT_SIZE) { return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); } $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]); $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); return $int2 + ($int1 << 32); } /** * Return a locking or nonlocking SQL query to read session information. * * @throws \DomainException When an unsupported PDO driver is used */ private function getSelectSql() : string { if (self::LOCK_TRANSACTIONAL === $this->lockMode) { $this->beginTransaction(); // selecting the time column should be removed in 6.0 switch ($this->driver) { case 'mysql': case 'oci': case 'pgsql': return "SELECT {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol} FROM {$this->table} WHERE {$this->idCol} = :id FOR UPDATE"; case 'sqlsrv': return "SELECT {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol} FROM {$this->table} WITH (UPDLOCK, ROWLOCK) WHERE {$this->idCol} = :id"; case 'sqlite': // we already locked when starting transaction break; default: throw new \DomainException(\sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); } } return "SELECT {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol} FROM {$this->table} WHERE {$this->idCol} = :id"; } /** * Returns an insert statement supported by the database for writing session data. */ private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime) : \PDOStatement { switch ($this->driver) { case 'oci': $data = \fopen('php://memory', 'r+'); \fwrite($data, $sessionData); \rewind($data); $sql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING {$this->dataCol} into :data"; break; default: $data = $sessionData; $sql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :expiry, :time)"; break; } $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); $stmt->bindValue(':expiry', \time() + $maxlifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', \time(), \PDO::PARAM_INT); return $stmt; } /** * Returns an update statement supported by the database for writing session data. */ private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime) : \PDOStatement { switch ($this->driver) { case 'oci': $data = \fopen('php://memory', 'r+'); \fwrite($data, $sessionData); \rewind($data); $sql = "UPDATE {$this->table} SET {$this->dataCol} = EMPTY_BLOB(), {$this->lifetimeCol} = :expiry, {$this->timeCol} = :time WHERE {$this->idCol} = :id RETURNING {$this->dataCol} into :data"; break; default: $data = $sessionData; $sql = "UPDATE {$this->table} SET {$this->dataCol} = :data, {$this->lifetimeCol} = :expiry, {$this->timeCol} = :time WHERE {$this->idCol} = :id"; break; } $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); $stmt->bindValue(':expiry', \time() + $maxlifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', \time(), \PDO::PARAM_INT); return $stmt; } /** * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. */ private function getMergeStatement(string $sessionId, string $data, int $maxlifetime) : ?\PDOStatement { switch (\true) { case 'mysql' === $this->driver: $mergeSql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :expiry, :time) " . "ON DUPLICATE KEY UPDATE {$this->dataCol} = VALUES({$this->dataCol}), {$this->lifetimeCol} = VALUES({$this->lifetimeCol}), {$this->timeCol} = VALUES({$this->timeCol})"; break; case 'sqlsrv' === $this->driver && \version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): // MERGE is only available since SQL Server 2008 and must be terminated by semicolon // It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ $mergeSql = "MERGE INTO {$this->table} WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ({$this->idCol} = ?) " . "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " . "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?;"; break; case 'sqlite' === $this->driver: $mergeSql = "INSERT OR REPLACE INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :expiry, :time)"; break; case 'pgsql' === $this->driver && \version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): $mergeSql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :expiry, :time) " . "ON CONFLICT ({$this->idCol}) DO UPDATE SET ({$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) = (EXCLUDED.{$this->dataCol}, EXCLUDED.{$this->lifetimeCol}, EXCLUDED.{$this->timeCol})"; break; default: // MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html return null; } $mergeStmt = $this->pdo->prepare($mergeSql); if ('sqlsrv' === $this->driver) { $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); $mergeStmt->bindValue(4, \time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(5, \time(), \PDO::PARAM_INT); $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); $mergeStmt->bindValue(7, \time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(8, \time(), \PDO::PARAM_INT); } else { $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); $mergeStmt->bindValue(':expiry', \time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(':time', \time(), \PDO::PARAM_INT); } return $mergeStmt; } /** * Return a PDO instance. * * @return \PDO */ protected function getConnection() { if (null === $this->pdo) { $this->connect($this->dsn ?: \ini_get('session.save_path')); } return $this->pdo; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Ahmed TAILOULOUTE */ class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { private $handler; private $marshaller; public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller) { $this->handler = $handler; $this->marshaller = $marshaller; } /** * @return bool */ #[\ReturnTypeWillChange] public function open($savePath, $name) { return $this->handler->open($savePath, $name); } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { return $this->handler->close(); } /** * @return bool */ #[\ReturnTypeWillChange] public function destroy($sessionId) { return $this->handler->destroy($sessionId); } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { return $this->handler->gc($maxlifetime); } /** * @return string */ #[\ReturnTypeWillChange] public function read($sessionId) { return $this->marshaller->unmarshall($this->handler->read($sessionId)); } /** * @return bool */ #[\ReturnTypeWillChange] public function write($sessionId, $data) { $failed = []; $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); if (isset($failed['data'])) { return \false; } return $this->handler->write($sessionId, $marshalledData['data']); } /** * @return bool */ #[\ReturnTypeWillChange] public function validateId($sessionId) { return $this->handler->validateId($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return $this->handler->updateTimestamp($sessionId, $data); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; /** * Native session handler using PHP's built in file storage. * * @author Drak */ class NativeFileSessionHandler extends \SessionHandler { /** * @param string|null $savePath Path of directory to save session files * Default null will leave setting as defined by PHP. * '/path', 'N;/path', or 'N;octal-mode;/path * * @see https://php.net/session.configuration#ini.session.save-path for further details. * * @throws \InvalidArgumentException On invalid $savePath * @throws \RuntimeException When failing to create the save directory */ public function __construct(?string $savePath = null) { if (null === $savePath) { $savePath = \ini_get('session.save_path'); } $baseDir = $savePath; if ($count = \substr_count($savePath, ';')) { if ($count > 2) { throw new \InvalidArgumentException(\sprintf('Invalid argument $savePath \'%s\'.', $savePath)); } // characters after last ';' are the path $baseDir = \ltrim(\strrchr($savePath, ';'), ';'); } if ($baseDir && !\is_dir($baseDir) && !@\mkdir($baseDir, 0777, \true) && !\is_dir($baseDir)) { throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $baseDir)); } if ($savePath !== \ini_get('session.save_path')) { \ini_set('session.save_path', $savePath); } if ('files' !== \ini_get('session.save_handler')) { \ini_set('session.save_handler', 'files'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; /** * Migrating session handler for migrating from one handler to another. It reads * from the current handler and writes both the current and new ones. * * It ignores errors from the new handler. * * @author Ross Motley * @author Oliver Radwell */ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { /** * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface */ private $currentHandler; /** * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface */ private $writeOnlyHandler; public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) { if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) { $currentHandler = new StrictSessionHandler($currentHandler); } if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) { $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler); } $this->currentHandler = $currentHandler; $this->writeOnlyHandler = $writeOnlyHandler; } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { $result = $this->currentHandler->close(); $this->writeOnlyHandler->close(); return $result; } /** * @return bool */ #[\ReturnTypeWillChange] public function destroy($sessionId) { $result = $this->currentHandler->destroy($sessionId); $this->writeOnlyHandler->destroy($sessionId); return $result; } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { $result = $this->currentHandler->gc($maxlifetime); $this->writeOnlyHandler->gc($maxlifetime); return $result; } /** * @return bool */ #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { $result = $this->currentHandler->open($savePath, $sessionName); $this->writeOnlyHandler->open($savePath, $sessionName); return $result; } /** * @return string */ #[\ReturnTypeWillChange] public function read($sessionId) { // No reading from new handler until switch-over return $this->currentHandler->read($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function write($sessionId, $sessionData) { $result = $this->currentHandler->write($sessionId, $sessionData); $this->writeOnlyHandler->write($sessionId, $sessionData); return $result; } /** * @return bool */ #[\ReturnTypeWillChange] public function validateId($sessionId) { // No reading from new handler until switch-over return $this->currentHandler->validateId($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $sessionData) { $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; use _ContaoManager\Symfony\Component\Cache\Marshaller\MarshallerInterface; /** * @author Ahmed TAILOULOUTE */ class IdentityMarshaller implements MarshallerInterface { /** * {@inheritdoc} */ public function marshall(array $values, ?array &$failed) : array { foreach ($values as $key => $value) { if (!\is_string($value)) { throw new \LogicException(\sprintf('%s accepts only string as data.', __METHOD__)); } } return $values; } /** * {@inheritdoc} */ public function unmarshall(string $value) : string { return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; use MongoDB\BSON\Binary; use MongoDB\BSON\UTCDateTime; use _ContaoManager\MongoDB\Client; use _ContaoManager\MongoDB\Collection; /** * Session handler using the mongodb/mongodb package and MongoDB driver extension. * * @author Markus Bachmann * * @see https://packagist.org/packages/mongodb/mongodb * @see https://php.net/mongodb */ class MongoDbSessionHandler extends AbstractSessionHandler { private $mongo; /** * @var Collection */ private $collection; /** * @var array */ private $options; /** * Constructor. * * List of available options: * * database: The name of the database [required] * * collection: The name of the collection [required] * * id_field: The field name for storing the session id [default: _id] * * data_field: The field name for storing the session data [default: data] * * time_field: The field name for storing the timestamp [default: time] * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. * * It is strongly recommended to put an index on the `expiry_field` for * garbage-collection. Alternatively it's possible to automatically expire * the sessions in the database as described below: * * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions * automatically. Such an index can for example look like this: * * db..createIndex( * { "": 1 }, * { "expireAfterSeconds": 0 } * ) * * More details on: https://docs.mongodb.org/manual/tutorial/expire-data/ * * If you use such an index, you can drop `gc_probability` to 0 since * no garbage-collection is required. * * @throws \InvalidArgumentException When "database" or "collection" not provided */ public function __construct(Client $mongo, array $options) { if (!isset($options['database']) || !isset($options['collection'])) { throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.'); } $this->mongo = $mongo; $this->options = \array_merge(['id_field' => '_id', 'data_field' => 'data', 'time_field' => 'time', 'expiry_field' => 'expires_at'], $options); } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { return \true; } /** * {@inheritdoc} */ protected function doDestroy(string $sessionId) { $this->getCollection()->deleteOne([$this->options['id_field'] => $sessionId]); return \true; } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { return $this->getCollection()->deleteMany([$this->options['expiry_field'] => ['$lt' => new UTCDateTime()]])->getDeletedCount(); } /** * {@inheritdoc} */ protected function doWrite(string $sessionId, string $data) { $expiry = new UTCDateTime((\time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); $fields = [$this->options['time_field'] => new UTCDateTime(), $this->options['expiry_field'] => $expiry, $this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY)]; $this->getCollection()->updateOne([$this->options['id_field'] => $sessionId], ['$set' => $fields], ['upsert' => \true]); return \true; } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { $expiry = new UTCDateTime((\time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); $this->getCollection()->updateOne([$this->options['id_field'] => $sessionId], ['$set' => [$this->options['time_field'] => new UTCDateTime(), $this->options['expiry_field'] => $expiry]]); return \true; } /** * {@inheritdoc} */ protected function doRead(string $sessionId) { $dbData = $this->getCollection()->findOne([$this->options['id_field'] => $sessionId, $this->options['expiry_field'] => ['$gte' => new UTCDateTime()]]); if (null === $dbData) { return ''; } return $dbData[$this->options['data_field']]->getData(); } private function getCollection() : Collection { if (null === $this->collection) { $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); } return $this->collection; } /** * @return Client */ protected function getMongo() { return $this->mongo; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; use _ContaoManager\Predis\Response\ErrorInterface; use _ContaoManager\Symfony\Component\Cache\Traits\RedisClusterProxy; use _ContaoManager\Symfony\Component\Cache\Traits\RedisProxy; /** * Redis based session storage handler based on the Redis class * provided by the PHP redis extension. * * @author Dalibor Karlović */ class RedisSessionHandler extends AbstractSessionHandler { private $redis; /** * @var string Key prefix for shared environments */ private $prefix; /** * @var int Time to live in seconds */ private $ttl; /** * List of available options: * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server * * ttl: The time to live in seconds. * * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis * * @throws \InvalidArgumentException When unsupported client or options are passed */ public function __construct($redis, array $options = []) { if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \_ContaoManager\Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) { throw new \InvalidArgumentException(\sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\\ClientInterface, "%s" given.', __METHOD__, \get_debug_type($redis))); } if ($diff = \array_diff(\array_keys($options), ['prefix', 'ttl'])) { throw new \InvalidArgumentException(\sprintf('The following options are not supported "%s".', \implode(', ', $diff))); } $this->redis = $redis; $this->prefix = $options['prefix'] ?? 'sf_s'; $this->ttl = $options['ttl'] ?? null; } /** * {@inheritdoc} */ protected function doRead(string $sessionId) : string { return $this->redis->get($this->prefix . $sessionId) ?: ''; } /** * {@inheritdoc} */ protected function doWrite(string $sessionId, string $data) : bool { $result = $this->redis->setEx($this->prefix . $sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data); return $result && !$result instanceof ErrorInterface; } /** * {@inheritdoc} */ protected function doDestroy(string $sessionId) : bool { static $unlink = \true; if ($unlink) { try { $unlink = \false !== $this->redis->unlink($this->prefix . $sessionId); } catch (\Throwable $e) { $unlink = \false; } } if (!$unlink) { $this->redis->del($this->prefix . $sessionId); } return \true; } /** * {@inheritdoc} */ #[\ReturnTypeWillChange] public function close() : bool { return \true; } /** * {@inheritdoc} * * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { return 0; } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return (bool) $this->redis->expire($this->prefix . $sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; /** * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. * * @author Nicolas Grekas */ class StrictSessionHandler extends AbstractSessionHandler { private $handler; private $doDestroy; public function __construct(\SessionHandlerInterface $handler) { if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { throw new \LogicException(\sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', \get_debug_type($handler), self::class)); } $this->handler = $handler; } /** * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. * * @internal */ public function isWrapper() : bool { return $this->handler instanceof \SessionHandler; } /** * @return bool */ #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { parent::open($savePath, $sessionName); return $this->handler->open($savePath, $sessionName); } /** * {@inheritdoc} */ protected function doRead(string $sessionId) { return $this->handler->read($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return $this->write($sessionId, $data); } /** * {@inheritdoc} */ protected function doWrite(string $sessionId, string $data) { return $this->handler->write($sessionId, $data); } /** * @return bool */ #[\ReturnTypeWillChange] public function destroy($sessionId) { $this->doDestroy = \true; $destroyed = parent::destroy($sessionId); return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; } /** * {@inheritdoc} */ protected function doDestroy(string $sessionId) { $this->doDestroy = \false; return $this->handler->destroy($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { return $this->handler->close(); } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { return $this->handler->gc($maxlifetime); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler; /** * Memcached based session storage handler based on the Memcached class * provided by the PHP memcached extension. * * @see https://php.net/memcached * * @author Drak */ class MemcachedSessionHandler extends AbstractSessionHandler { private $memcached; /** * @var int Time to live in seconds */ private $ttl; /** * @var string Key prefix for shared environments */ private $prefix; /** * Constructor. * * List of available options: * * prefix: The prefix to use for the memcached keys in order to avoid collision * * ttl: The time to live in seconds. * * @throws \InvalidArgumentException When unsupported options are passed */ public function __construct(\Memcached $memcached, array $options = []) { $this->memcached = $memcached; if ($diff = \array_diff(\array_keys($options), ['prefix', 'expiretime', 'ttl'])) { throw new \InvalidArgumentException(\sprintf('The following options are not supported "%s".', \implode(', ', $diff))); } $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null; $this->prefix = $options['prefix'] ?? 'sf2s'; } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { return $this->memcached->quit(); } /** * {@inheritdoc} */ protected function doRead(string $sessionId) { return $this->memcached->get($this->prefix . $sessionId) ?: ''; } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { $this->memcached->touch($this->prefix . $sessionId, $this->getCompatibleTtl()); return \true; } /** * {@inheritdoc} */ protected function doWrite(string $sessionId, string $data) { return $this->memcached->set($this->prefix . $sessionId, $data, $this->getCompatibleTtl()); } private function getCompatibleTtl() : int { $ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')); // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time. // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct. if ($ttl > 60 * 60 * 24 * 30) { $ttl += \time(); } return $ttl; } /** * {@inheritdoc} */ protected function doDestroy(string $sessionId) { $result = $this->memcached->delete($this->prefix . $sessionId); return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { // not required here because memcached will auto expire the records anyhow. return 0; } /** * Return a Memcached instance. * * @return \Memcached */ protected function getMemcached() { return $this->memcached; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionUtils; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; // Help opcache.preload discover always-needed symbols \class_exists(MetadataBag::class); \class_exists(StrictSessionHandler::class); \class_exists(SessionHandlerProxy::class); /** * This provides a base class for session attribute storage. * * @author Drak */ class NativeSessionStorage implements SessionStorageInterface { /** * @var SessionBagInterface[] */ protected $bags = []; /** * @var bool */ protected $started = \false; /** * @var bool */ protected $closed = \false; /** * @var AbstractProxy|\SessionHandlerInterface */ protected $saveHandler; /** * @var MetadataBag */ protected $metadataBag; /** * @var string|null */ private $emulateSameSite; /** * Depending on how you want the storage driver to behave you probably * want to override this constructor entirely. * * List of options for $options array with their defaults. * * @see https://php.net/session.configuration for options * but we omit 'session.' from the beginning of the keys for convenience. * * ("auto_start", is not supported as it tells PHP to start a session before * PHP starts to execute user-land code. Setting during runtime has no effect). * * cache_limiter, "" (use "0" to prevent headers from being sent entirely). * cache_expire, "0" * cookie_domain, "" * cookie_httponly, "" * cookie_lifetime, "0" * cookie_path, "/" * cookie_secure, "" * cookie_samesite, null * gc_divisor, "100" * gc_maxlifetime, "1440" * gc_probability, "1" * lazy_write, "1" * name, "PHPSESSID" * referer_check, "" * serialize_handler, "php" * use_strict_mode, "1" * use_cookies, "1" * use_only_cookies, "1" * use_trans_sid, "0" * sid_length, "32" * sid_bits_per_character, "5" * trans_sid_hosts, $_SERVER['HTTP_HOST'] * trans_sid_tags, "a=href,area=href,frame=src,form=" * * @param AbstractProxy|\SessionHandlerInterface|null $handler */ public function __construct(array $options = [], $handler = null, ?MetadataBag $metaBag = null) { if (!\extension_loaded('session')) { throw new \LogicException('PHP extension "session" is required.'); } $options += ['cache_limiter' => '', 'cache_expire' => 0, 'use_cookies' => 1, 'lazy_write' => 1, 'use_strict_mode' => 1]; \session_register_shutdown(); $this->setMetadataBag($metaBag); $this->setOptions($options); $this->setSaveHandler($handler); } /** * Gets the save handler instance. * * @return AbstractProxy|\SessionHandlerInterface */ public function getSaveHandler() { return $this->saveHandler; } /** * {@inheritdoc} */ public function start() { if ($this->started) { return \true; } if (\PHP_SESSION_ACTIVE === \session_status()) { throw new \RuntimeException('Failed to start the session: already started by PHP.'); } if (\filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && \headers_sent($file, $line)) { throw new \RuntimeException(\sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); } $sessionId = $_COOKIE[\session_name()] ?? null; /* * Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`. * * ---------- Part 1 * * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6. * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character. * Allowed values are integers such as: * - 4 for range `a-f0-9` * - 5 for range `a-v0-9` * - 6 for range `a-zA-Z0-9,-` * * ---------- Part 2 * * The part `{22,250}` is related to the PHP ini directive `session.sid_length`. * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length. * Allowed values are integers between 22 and 256, but we use 250 for the max. * * Where does the 250 come from? * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255. * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250. * * ---------- Conclusion * * The parts 1 and 2 prevent the warning below: * `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.` * * The part 2 prevents the warning below: * `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).` */ if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !\preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) { // the session ID in the header is invalid, create a new one \session_id(\session_create_id()); } // ok to try and start the session if (!\session_start()) { throw new \RuntimeException('Failed to start the session.'); } if (null !== $this->emulateSameSite) { $originalCookie = SessionUtils::popSessionCookie(\session_name(), \session_id()); if (null !== $originalCookie) { \header(\sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), \false); } } $this->loadSession(); return \true; } /** * {@inheritdoc} */ public function getId() { return $this->saveHandler->getId(); } /** * {@inheritdoc} */ public function setId(string $id) { $this->saveHandler->setId($id); } /** * {@inheritdoc} */ public function getName() { return $this->saveHandler->getName(); } /** * {@inheritdoc} */ public function setName(string $name) { $this->saveHandler->setName($name); } /** * {@inheritdoc} */ public function regenerate(bool $destroy = \false, ?int $lifetime = null) { // Cannot regenerate the session ID for non-active sessions. if (\PHP_SESSION_ACTIVE !== \session_status()) { return \false; } if (\headers_sent()) { return \false; } if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) { $this->save(); \ini_set('session.cookie_lifetime', $lifetime); $this->start(); } if ($destroy) { $this->metadataBag->stampNew(); } $isRegenerated = \session_regenerate_id($destroy); if (null !== $this->emulateSameSite) { $originalCookie = SessionUtils::popSessionCookie(\session_name(), \session_id()); if (null !== $originalCookie) { \header(\sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), \false); } } return $isRegenerated; } /** * {@inheritdoc} */ public function save() { // Store a copy so we can restore the bags in case the session was not left empty $session = $_SESSION; foreach ($this->bags as $bag) { if (empty($_SESSION[$key = $bag->getStorageKey()])) { unset($_SESSION[$key]); } } if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === \array_keys($_SESSION)) { unset($_SESSION[$key]); } // Register error handler to add information about the current save handler $previousHandler = \set_error_handler(function ($type, $msg, $file, $line) use(&$previousHandler) { if (\E_WARNING === $type && \str_starts_with($msg, 'session_write_close():')) { $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; $msg = \sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler)); } return $previousHandler ? $previousHandler($type, $msg, $file, $line) : \false; }); try { \session_write_close(); } finally { \restore_error_handler(); // Restore only if not empty if ($_SESSION) { $_SESSION = $session; } } $this->closed = \true; $this->started = \false; } /** * {@inheritdoc} */ public function clear() { // clear out the bags foreach ($this->bags as $bag) { $bag->clear(); } // clear out the session $_SESSION = []; // reconnect the bags to the session $this->loadSession(); } /** * {@inheritdoc} */ public function registerBag(SessionBagInterface $bag) { if ($this->started) { throw new \LogicException('Cannot register a bag when the session is already started.'); } $this->bags[$bag->getName()] = $bag; } /** * {@inheritdoc} */ public function getBag(string $name) { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(\sprintf('The SessionBagInterface "%s" is not registered.', $name)); } if (!$this->started && $this->saveHandler->isActive()) { $this->loadSession(); } elseif (!$this->started) { $this->start(); } return $this->bags[$name]; } public function setMetadataBag(?MetadataBag $metaBag = null) { if (null === $metaBag) { $metaBag = new MetadataBag(); } $this->metadataBag = $metaBag; } /** * Gets the MetadataBag. * * @return MetadataBag */ public function getMetadataBag() { return $this->metadataBag; } /** * {@inheritdoc} */ public function isStarted() { return $this->started; } /** * Sets session.* ini variables. * * For convenience we omit 'session.' from the beginning of the keys. * Explicitly ignores other ini keys. * * @param array $options Session ini directives [key => value] * * @see https://php.net/session.configuration */ public function setOptions(array $options) { if (\headers_sent() || \PHP_SESSION_ACTIVE === \session_status()) { return; } $validOptions = \array_flip(['cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', 'gc_divisor', 'gc_maxlifetime', 'gc_probability', 'lazy_write', 'name', 'referer_check', 'serialize_handler', 'use_strict_mode', 'use_cookies', 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags']); foreach ($options as $key => $value) { if (isset($validOptions[$key])) { if (\str_starts_with($key, 'upload_progress.')) { \trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key); continue; } if ('url_rewriter.tags' === $key) { \trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key); } if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) { // PHP < 7.3 does not support same_site cookies. We will emulate it in // the start() method instead. $this->emulateSameSite = $value; continue; } if ('cookie_secure' === $key && 'auto' === $value) { continue; } \ini_set('url_rewriter.tags' !== $key ? 'session.' . $key : $key, $value); } } } /** * Registers session save handler as a PHP session handler. * * To use internal PHP session save handlers, override this method using ini_set with * session.save_handler and session.save_path e.g. * * ini_set('session.save_handler', 'files'); * ini_set('session.save_path', '/tmp'); * * or pass in a \SessionHandler instance which configures session.save_handler in the * constructor, for a template see NativeFileSessionHandler. * * @see https://php.net/session-set-save-handler * @see https://php.net/sessionhandlerinterface * @see https://php.net/sessionhandler * * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler * * @throws \InvalidArgumentException */ public function setSaveHandler($saveHandler = null) { if (!$saveHandler instanceof AbstractProxy && !$saveHandler instanceof \SessionHandlerInterface && null !== $saveHandler) { throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \\SessionHandlerInterface; or be null.'); } // Wrap $saveHandler in proxy and prevent double wrapping of proxy if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { $saveHandler = new SessionHandlerProxy($saveHandler); } elseif (!$saveHandler instanceof AbstractProxy) { $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); } $this->saveHandler = $saveHandler; if (\headers_sent() || \PHP_SESSION_ACTIVE === \session_status()) { return; } if ($this->saveHandler instanceof SessionHandlerProxy) { \session_set_save_handler($this->saveHandler, \false); } } /** * Load the session with attributes. * * After starting the session, PHP retrieves the session from whatever handlers * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). * PHP takes the return value from the read() handler, unserializes it * and populates $_SESSION with the result automatically. */ protected function loadSession(?array &$session = null) { if (null === $session) { $session =& $_SESSION; } $bags = \array_merge($this->bags, [$this->metadataBag]); foreach ($bags as $bag) { $key = $bag->getStorageKey(); $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : []; $bag->initialize($session[$key]); } $this->started = \true; $this->closed = \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; /** * StorageInterface. * * @author Fabien Potencier * @author Drak */ interface SessionStorageInterface { /** * Starts the session. * * @return bool * * @throws \RuntimeException if something goes wrong starting the session */ public function start(); /** * Checks if the session is started. * * @return bool */ public function isStarted(); /** * Returns the session ID. * * @return string */ public function getId(); /** * Sets the session ID. */ public function setId(string $id); /** * Returns the session name. * * @return string */ public function getName(); /** * Sets the session name. */ public function setName(string $name); /** * Regenerates id that represents this storage. * * This method must invoke session_regenerate_id($destroy) unless * this interface is used for a storage object designed for unit * or functional testing where a real PHP session would interfere * with testing. * * Note regenerate+destroy should not clear the session data in memory * only delete the session data from persistent storage. * * Care: When regenerating the session ID no locking is involved in PHP's * session design. See https://bugs.php.net/61470 for a discussion. * So you must make sure the regenerated session is saved BEFORE sending the * headers with the new ID. Symfony's HttpKernel offers a listener for this. * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. * Otherwise session data could get lost again for concurrent requests with the * new ID. One result could be that you get logged out after just logging in. * * @param bool $destroy Destroy session when regenerating? * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value * will leave the system settings unchanged, 0 sets the cookie * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * * @return bool * * @throws \RuntimeException If an error occurs while regenerating this storage */ public function regenerate(bool $destroy = \false, ?int $lifetime = null); /** * Force the session to be saved and closed. * * This method must invoke session_write_close() unless this interface is * used for a storage object design for unit or functional testing where * a real PHP session would interfere with testing, in which case * it should actually persist the session data if required. * * @throws \RuntimeException if the session is saved without being started, or if the session * is already closed */ public function save(); /** * Clear all session data in memory. */ public function clear(); /** * Gets a SessionBagInterface by name. * * @return SessionBagInterface * * @throws \InvalidArgumentException If the bag does not exist */ public function getBag(string $name); /** * Registers a SessionBagInterface for use. */ public function registerBag(SessionBagInterface $bag); /** * @return MetadataBag */ public function getMetadataBag(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Request; // Help opcache.preload discover always-needed symbols \class_exists(MockFileSessionStorage::class); /** * @author Jérémy Derussé */ class MockFileSessionStorageFactory implements SessionStorageFactoryInterface { private $savePath; private $name; private $metaBag; /** * @see MockFileSessionStorage constructor. */ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) { $this->savePath = $savePath; $this->name = $name; $this->metaBag = $metaBag; } public function createStorage(?Request $request) : SessionStorageInterface { return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Proxy; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; /** * @author Drak */ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { protected $handler; public function __construct(\SessionHandlerInterface $handler) { $this->handler = $handler; $this->wrapper = $handler instanceof \SessionHandler; $this->saveHandlerName = $this->wrapper || $handler instanceof StrictSessionHandler && $handler->isWrapper() ? \ini_get('session.save_handler') : 'user'; } /** * @return \SessionHandlerInterface */ public function getHandler() { return $this->handler; } // \SessionHandlerInterface /** * @return bool */ #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { return $this->handler->open($savePath, $sessionName); } /** * @return bool */ #[\ReturnTypeWillChange] public function close() { return $this->handler->close(); } /** * @return string|false */ #[\ReturnTypeWillChange] public function read($sessionId) { return $this->handler->read($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function write($sessionId, $data) { return $this->handler->write($sessionId, $data); } /** * @return bool */ #[\ReturnTypeWillChange] public function destroy($sessionId) { return $this->handler->destroy($sessionId); } /** * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { return $this->handler->gc($maxlifetime); } /** * @return bool */ #[\ReturnTypeWillChange] public function validateId($sessionId) { return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); } /** * @return bool */ #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Proxy; /** * @author Drak */ abstract class AbstractProxy { /** * Flag if handler wraps an internal PHP session handler (using \SessionHandler). * * @var bool */ protected $wrapper = \false; /** * @var string */ protected $saveHandlerName; /** * Gets the session.save_handler name. * * @return string|null */ public function getSaveHandlerName() { return $this->saveHandlerName; } /** * Is this proxy handler and instance of \SessionHandlerInterface. * * @return bool */ public function isSessionHandlerInterface() { return $this instanceof \SessionHandlerInterface; } /** * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. * * @return bool */ public function isWrapper() { return $this->wrapper; } /** * Has a session started? * * @return bool */ public function isActive() { return \PHP_SESSION_ACTIVE === \session_status(); } /** * Gets the session ID. * * @return string */ public function getId() { return \session_id(); } /** * Sets the session ID. * * @throws \LogicException */ public function setId(string $id) { if ($this->isActive()) { throw new \LogicException('Cannot change the ID of an active session.'); } \session_id($id); } /** * Gets the session name. * * @return string */ public function getName() { return \session_name(); } /** * Sets the session name. * * @throws \LogicException */ public function setName(string $name) { if ($this->isActive()) { throw new \LogicException('Cannot change the name of an active session.'); } \session_name($name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Request; // Help opcache.preload discover always-needed symbols \class_exists(NativeSessionStorage::class); /** * @author Jérémy Derussé */ class NativeSessionStorageFactory implements SessionStorageFactoryInterface { private $options; private $handler; private $metaBag; private $secure; /** * @see NativeSessionStorage constructor. */ public function __construct(array $options = [], $handler = null, ?MetadataBag $metaBag = null, bool $secure = \false) { $this->options = $options; $this->handler = $handler; $this->metaBag = $metaBag; $this->secure = $secure; } public function createStorage(?Request $request) : SessionStorageInterface { $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); if ($this->secure && $request && $request->isSecure()) { $storage->setOptions(['cookie_secure' => \true]); } return $storage; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; /** * Allows session to be started by PHP and managed by Symfony. * * @author Drak */ class PhpBridgeSessionStorage extends NativeSessionStorage { /** * @param AbstractProxy|\SessionHandlerInterface|null $handler */ public function __construct($handler = null, ?MetadataBag $metaBag = null) { if (!\extension_loaded('session')) { throw new \LogicException('PHP extension "session" is required.'); } $this->setMetadataBag($metaBag); $this->setSaveHandler($handler); } /** * {@inheritdoc} */ public function start() { if ($this->started) { return \true; } $this->loadSession(); return \true; } /** * {@inheritdoc} */ public function clear() { // clear out the bags and nothing else that may be set // since the purpose of this driver is to share a handler foreach ($this->bags as $bag) { $bag->clear(); } // reconnect the bags to the session $this->loadSession(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * @author Jérémy Derussé */ interface SessionStorageFactoryInterface { /** * Creates a new instance of SessionStorageInterface. */ public function createStorage(?Request $request) : SessionStorageInterface; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Request; /** * @author Jérémy Derussé * * @internal to be removed in Symfony 6 */ final class ServiceSessionFactory implements SessionStorageFactoryInterface { private $storage; public function __construct(SessionStorageInterface $storage) { $this->storage = $storage; } public function createStorage(?Request $request) : SessionStorageInterface { if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { $this->storage->setOptions(['cookie_secure' => \true]); } return $this->storage; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; /** * MockFileSessionStorage is used to mock sessions for * functional testing where you may need to persist session data * across separate PHP processes. * * No PHP session is actually started since a session can be initialized * and shutdown only once per PHP execution cycle and this class does * not pollute any session related globals, including session_*() functions * or session.* PHP ini directives. * * @author Drak */ class MockFileSessionStorage extends MockArraySessionStorage { private $savePath; /** * @param string|null $savePath Path of directory to save session files */ public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) { if (null === $savePath) { $savePath = \sys_get_temp_dir(); } if (!\is_dir($savePath) && !@\mkdir($savePath, 0777, \true) && !\is_dir($savePath)) { throw new \RuntimeException(\sprintf('Session Storage was not able to create directory "%s".', $savePath)); } $this->savePath = $savePath; parent::__construct($name, $metaBag); } /** * {@inheritdoc} */ public function start() { if ($this->started) { return \true; } if (!$this->id) { $this->id = $this->generateId(); } $this->read(); $this->started = \true; return \true; } /** * {@inheritdoc} */ public function regenerate(bool $destroy = \false, ?int $lifetime = null) { if (!$this->started) { $this->start(); } if ($destroy) { $this->destroy(); } return parent::regenerate($destroy, $lifetime); } /** * {@inheritdoc} */ public function save() { if (!$this->started) { throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); } $data = $this->data; foreach ($this->bags as $bag) { if (empty($data[$key = $bag->getStorageKey()])) { unset($data[$key]); } } if ([$key = $this->metadataBag->getStorageKey()] === \array_keys($data)) { unset($data[$key]); } try { if ($data) { $path = $this->getFilePath(); $tmp = $path . \bin2hex(\random_bytes(6)); \file_put_contents($tmp, \serialize($data)); \rename($tmp, $path); } else { $this->destroy(); } } finally { $this->data = $data; } // this is needed when the session object is re-used across multiple requests // in functional tests. $this->started = \false; } /** * Deletes a session from persistent storage. * Deliberately leaves session data in memory intact. */ private function destroy() : void { \set_error_handler(static function () { }); try { \unlink($this->getFilePath()); } finally { \restore_error_handler(); } } /** * Calculate path to file. */ private function getFilePath() : string { return $this->savePath . '/' . $this->id . '.mocksess'; } /** * Reads session from storage and loads session. */ private function read() : void { \set_error_handler(static function () { }); try { $data = \file_get_contents($this->getFilePath()); } finally { \restore_error_handler(); } $this->data = $data ? \unserialize($data) : []; $this->loadSession(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Request; // Help opcache.preload discover always-needed symbols \class_exists(PhpBridgeSessionStorage::class); /** * @author Jérémy Derussé */ class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface { private $handler; private $metaBag; private $secure; /** * @see PhpBridgeSessionStorage constructor. */ public function __construct($handler = null, ?MetadataBag $metaBag = null, bool $secure = \false) { $this->handler = $handler; $this->metaBag = $metaBag; $this->secure = $secure; } public function createStorage(?Request $request) : SessionStorageInterface { $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); if ($this->secure && $request && $request->isSecure()) { $storage->setOptions(['cookie_secure' => \true]); } return $storage; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; /** * Metadata container. * * Adds metadata to the session. * * @author Drak */ class MetadataBag implements SessionBagInterface { public const CREATED = 'c'; public const UPDATED = 'u'; public const LIFETIME = 'l'; /** * @var string */ private $name = '__metadata'; /** * @var string */ private $storageKey; /** * @var array */ protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0]; /** * Unix timestamp. * * @var int */ private $lastUsed; /** * @var int */ private $updateThreshold; /** * @param string $storageKey The key used to store bag in the session * @param int $updateThreshold The time to wait between two UPDATED updates */ public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0) { $this->storageKey = $storageKey; $this->updateThreshold = $updateThreshold; } /** * {@inheritdoc} */ public function initialize(array &$array) { $this->meta =& $array; if (isset($array[self::CREATED])) { $this->lastUsed = $this->meta[self::UPDATED]; $timeStamp = \time(); if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { $this->meta[self::UPDATED] = $timeStamp; } } else { $this->stampCreated(); } } /** * Gets the lifetime that the session cookie was set with. * * @return int */ public function getLifetime() { return $this->meta[self::LIFETIME]; } /** * Stamps a new session's metadata. * * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value * will leave the system settings unchanged, 0 sets the cookie * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. */ public function stampNew(?int $lifetime = null) { $this->stampCreated($lifetime); } /** * {@inheritdoc} */ public function getStorageKey() { return $this->storageKey; } /** * Gets the created timestamp metadata. * * @return int Unix timestamp */ public function getCreated() { return $this->meta[self::CREATED]; } /** * Gets the last used metadata. * * @return int Unix timestamp */ public function getLastUsed() { return $this->lastUsed; } /** * {@inheritdoc} */ public function clear() { // nothing to do return null; } /** * {@inheritdoc} */ public function getName() { return $this->name; } /** * Sets name. */ public function setName(string $name) { $this->name = $name; } private function stampCreated(?int $lifetime = null) : void { $timeStamp = \time(); $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; /** * MockArraySessionStorage mocks the session for unit tests. * * No PHP session is actually started since a session can be initialized * and shutdown only once per PHP execution cycle. * * When doing functional testing, you should use MockFileSessionStorage instead. * * @author Fabien Potencier * @author Bulat Shakirzyanov * @author Drak */ class MockArraySessionStorage implements SessionStorageInterface { /** * @var string */ protected $id = ''; /** * @var string */ protected $name; /** * @var bool */ protected $started = \false; /** * @var bool */ protected $closed = \false; /** * @var array */ protected $data = []; /** * @var MetadataBag */ protected $metadataBag; /** * @var array|SessionBagInterface[] */ protected $bags = []; public function __construct(string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null) { $this->name = $name; $this->setMetadataBag($metaBag); } public function setSessionData(array $array) { $this->data = $array; } /** * {@inheritdoc} */ public function start() { if ($this->started) { return \true; } if (empty($this->id)) { $this->id = $this->generateId(); } $this->loadSession(); return \true; } /** * {@inheritdoc} */ public function regenerate(bool $destroy = \false, ?int $lifetime = null) { if (!$this->started) { $this->start(); } $this->metadataBag->stampNew($lifetime); $this->id = $this->generateId(); return \true; } /** * {@inheritdoc} */ public function getId() { return $this->id; } /** * {@inheritdoc} */ public function setId(string $id) { if ($this->started) { throw new \LogicException('Cannot set session ID after the session has started.'); } $this->id = $id; } /** * {@inheritdoc} */ public function getName() { return $this->name; } /** * {@inheritdoc} */ public function setName(string $name) { $this->name = $name; } /** * {@inheritdoc} */ public function save() { if (!$this->started || $this->closed) { throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); } // nothing to do since we don't persist the session data $this->closed = \false; $this->started = \false; } /** * {@inheritdoc} */ public function clear() { // clear out the bags foreach ($this->bags as $bag) { $bag->clear(); } // clear out the session $this->data = []; // reconnect the bags to the session $this->loadSession(); } /** * {@inheritdoc} */ public function registerBag(SessionBagInterface $bag) { $this->bags[$bag->getName()] = $bag; } /** * {@inheritdoc} */ public function getBag(string $name) { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(\sprintf('The SessionBagInterface "%s" is not registered.', $name)); } if (!$this->started) { $this->start(); } return $this->bags[$name]; } /** * {@inheritdoc} */ public function isStarted() { return $this->started; } public function setMetadataBag(?MetadataBag $bag = null) { if (null === $bag) { $bag = new MetadataBag(); } $this->metadataBag = $bag; } /** * Gets the MetadataBag. * * @return MetadataBag */ public function getMetadataBag() { return $this->metadataBag; } /** * Generates a session ID. * * This doesn't need to be particularly cryptographically secure since this is just * a mock. * * @return string */ protected function generateId() { return \hash('sha256', \uniqid('ss_mock_', \true)); } protected function loadSession() { $bags = \array_merge($this->bags, [$this->metadataBag]); foreach ($bags as $bag) { $key = $bag->getStorageKey(); $this->data[$key] = $this->data[$key] ?? []; $bag->initialize($this->data[$key]); } $this->started = \true; $this->closed = \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; /** * Interface for the session. * * @author Drak */ interface SessionInterface { /** * Starts the session storage. * * @return bool * * @throws \RuntimeException if session fails to start */ public function start(); /** * Returns the session ID. * * @return string */ public function getId(); /** * Sets the session ID. */ public function setId(string $id); /** * Returns the session name. * * @return string */ public function getName(); /** * Sets the session name. */ public function setName(string $name); /** * Invalidates the current session. * * Clears all session attributes and flashes and regenerates the * session and deletes the old session from persistence. * * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value * will leave the system settings unchanged, 0 sets the cookie * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * * @return bool */ public function invalidate(?int $lifetime = null); /** * Migrates the current session to a new session id while maintaining all * session attributes. * * @param bool $destroy Whether to delete the old session or leave it to garbage collection * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value * will leave the system settings unchanged, 0 sets the cookie * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * * @return bool */ public function migrate(bool $destroy = \false, ?int $lifetime = null); /** * Force the session to be saved and closed. * * This method is generally not required for real sessions as * the session will be automatically saved at the end of * code execution. */ public function save(); /** * Checks if an attribute is defined. * * @return bool */ public function has(string $name); /** * Returns an attribute. * * @param mixed $default The default value if not found * * @return mixed */ public function get(string $name, $default = null); /** * Sets an attribute. * * @param mixed $value */ public function set(string $name, $value); /** * Returns attributes. * * @return array */ public function all(); /** * Sets attributes. */ public function replace(array $attributes); /** * Removes an attribute. * * @return mixed The removed value or null when it does not exist */ public function remove(string $name); /** * Clears all attributes. */ public function clear(); /** * Checks if the session was started. * * @return bool */ public function isStarted(); /** * Registers a SessionBagInterface with the session. */ public function registerBag(SessionBagInterface $bag); /** * Gets a bag instance by name. * * @return SessionBagInterface */ public function getBag(string $name); /** * Gets session meta. * * @return MetadataBag */ public function getMetadataBag(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash; /** * AutoExpireFlashBag flash message container. * * @author Drak */ class AutoExpireFlashBag implements FlashBagInterface { private $name = 'flashes'; private $flashes = ['display' => [], 'new' => []]; private $storageKey; /** * @param string $storageKey The key used to store flashes in the session */ public function __construct(string $storageKey = '_symfony_flashes') { $this->storageKey = $storageKey; } /** * {@inheritdoc} */ public function getName() { return $this->name; } public function setName(string $name) { $this->name = $name; } /** * {@inheritdoc} */ public function initialize(array &$flashes) { $this->flashes =& $flashes; // The logic: messages from the last request will be stored in new, so we move them to previous // This request we will show what is in 'display'. What is placed into 'new' this time round will // be moved to display next time round. $this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : []; $this->flashes['new'] = []; } /** * {@inheritdoc} */ public function add(string $type, $message) { $this->flashes['new'][$type][] = $message; } /** * {@inheritdoc} */ public function peek(string $type, array $default = []) { return $this->has($type) ? $this->flashes['display'][$type] : $default; } /** * {@inheritdoc} */ public function peekAll() { return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : []; } /** * {@inheritdoc} */ public function get(string $type, array $default = []) { $return = $default; if (!$this->has($type)) { return $return; } if (isset($this->flashes['display'][$type])) { $return = $this->flashes['display'][$type]; unset($this->flashes['display'][$type]); } return $return; } /** * {@inheritdoc} */ public function all() { $return = $this->flashes['display']; $this->flashes['display'] = []; return $return; } /** * {@inheritdoc} */ public function setAll(array $messages) { $this->flashes['new'] = $messages; } /** * {@inheritdoc} */ public function set(string $type, $messages) { $this->flashes['new'][$type] = (array) $messages; } /** * {@inheritdoc} */ public function has(string $type) { return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; } /** * {@inheritdoc} */ public function keys() { return \array_keys($this->flashes['display']); } /** * {@inheritdoc} */ public function getStorageKey() { return $this->storageKey; } /** * {@inheritdoc} */ public function clear() { return $this->all(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionBagInterface; /** * FlashBagInterface. * * @author Drak */ interface FlashBagInterface extends SessionBagInterface { /** * Adds a flash message for the given type. * * @param mixed $message */ public function add(string $type, $message); /** * Registers one or more messages for a given type. * * @param string|array $messages */ public function set(string $type, $messages); /** * Gets flash messages for a given type. * * @param string $type Message category type * @param array $default Default value if $type does not exist * * @return array */ public function peek(string $type, array $default = []); /** * Gets all flash messages. * * @return array */ public function peekAll(); /** * Gets and clears flash from the stack. * * @param array $default Default value if $type does not exist * * @return array */ public function get(string $type, array $default = []); /** * Gets and clears flashes from the stack. * * @return array */ public function all(); /** * Sets all flash messages. */ public function setAll(array $messages); /** * Has flash messages for a given type? * * @return bool */ public function has(string $type); /** * Returns a list of all defined types. * * @return array */ public function keys(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session\Flash; /** * FlashBag flash message container. * * @author Drak */ class FlashBag implements FlashBagInterface { private $name = 'flashes'; private $flashes = []; private $storageKey; /** * @param string $storageKey The key used to store flashes in the session */ public function __construct(string $storageKey = '_symfony_flashes') { $this->storageKey = $storageKey; } /** * {@inheritdoc} */ public function getName() { return $this->name; } public function setName(string $name) { $this->name = $name; } /** * {@inheritdoc} */ public function initialize(array &$flashes) { $this->flashes =& $flashes; } /** * {@inheritdoc} */ public function add(string $type, $message) { $this->flashes[$type][] = $message; } /** * {@inheritdoc} */ public function peek(string $type, array $default = []) { return $this->has($type) ? $this->flashes[$type] : $default; } /** * {@inheritdoc} */ public function peekAll() { return $this->flashes; } /** * {@inheritdoc} */ public function get(string $type, array $default = []) { if (!$this->has($type)) { return $default; } $return = $this->flashes[$type]; unset($this->flashes[$type]); return $return; } /** * {@inheritdoc} */ public function all() { $return = $this->peekAll(); $this->flashes = []; return $return; } /** * {@inheritdoc} */ public function set(string $type, $messages) { $this->flashes[$type] = (array) $messages; } /** * {@inheritdoc} */ public function setAll(array $messages) { $this->flashes = $messages; } /** * {@inheritdoc} */ public function has(string $type) { return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; } /** * {@inheritdoc} */ public function keys() { return \array_keys($this->flashes); } /** * {@inheritdoc} */ public function getStorageKey() { return $this->storageKey; } /** * {@inheritdoc} */ public function clear() { return $this->all(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; // Help opcache.preload discover always-needed symbols \class_exists(Session::class); /** * @author Jérémy Derussé */ class SessionFactory implements SessionFactoryInterface { private $requestStack; private $storageFactory; private $usageReporter; public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, ?callable $usageReporter = null) { $this->requestStack = $requestStack; $this->storageFactory = $storageFactory; $this->usageReporter = $usageReporter; } public function createSession() : SessionInterface { return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; /** * @author Nicolas Grekas * * @internal */ final class SessionBagProxy implements SessionBagInterface { private $bag; private $data; private $usageIndex; private $usageReporter; public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter) { $this->bag = $bag; $this->data =& $data; $this->usageIndex =& $usageIndex; $this->usageReporter = $usageReporter; } public function getBag() : SessionBagInterface { ++$this->usageIndex; if ($this->usageReporter && 0 <= $this->usageIndex) { ($this->usageReporter)(); } return $this->bag; } public function isEmpty() : bool { if (!isset($this->data[$this->bag->getStorageKey()])) { return \true; } ++$this->usageIndex; if ($this->usageReporter && 0 <= $this->usageIndex) { ($this->usageReporter)(); } return empty($this->data[$this->bag->getStorageKey()]); } /** * {@inheritdoc} */ public function getName() : string { return $this->bag->getName(); } /** * {@inheritdoc} */ public function initialize(array &$array) : void { ++$this->usageIndex; if ($this->usageReporter && 0 <= $this->usageIndex) { ($this->usageReporter)(); } $this->data[$this->bag->getStorageKey()] =& $array; $this->bag->initialize($array); } /** * {@inheritdoc} */ public function getStorageKey() : string { return $this->bag->getStorageKey(); } /** * {@inheritdoc} */ public function clear() { return $this->bag->clear(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\HttpFoundation\Session; /** * Session Bag store. * * @author Drak */ interface SessionBagInterface { /** * Gets this bag's name. * * @return string */ public function getName(); /** * Initializes the Bag. */ public function initialize(array &$array); /** * Gets the storage key for this bag. * * @return string */ public function getStorageKey(); /** * Clears out data from bag. * * @return mixed Whatever data was contained */ public function clear(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter; use _ContaoManager\Symfony\Component\VarExporter\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; use _ContaoManager\Symfony\Component\VarExporter\Internal\Hydrator; use _ContaoManager\Symfony\Component\VarExporter\Internal\Registry; /** * A utility class to create objects without calling their constructor. * * @author Nicolas Grekas */ final class Instantiator { /** * Creates an object and sets its properties without calling its constructor nor any other methods. * * For example: * * // creates an empty instance of Foo * Instantiator::instantiate(Foo::class); * * // creates a Foo instance and sets one of its properties * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); * * // creates a Foo instance and sets a private property defined on its parent Bar class * Instantiator::instantiate(Foo::class, [], [ * Bar::class => ['privateBarProperty' => $propertyValue], * ]); * * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be created * by using the special "\0" property name to define their internal value: * * // creates an SplObjectStorage where $info1 is attached to $obj1, etc. * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); * * // creates an ArrayObject populated with $inputArray * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]); * * @param string $class The class of the instance to create * @param array $properties The properties to set on the instance * @param array $privateProperties The private properties to set on the instance, * keyed by their declaring class * * @throws ExceptionInterface When the instance cannot be created */ public static function instantiate(string $class, array $properties = [], array $privateProperties = []) : object { $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); if (Registry::$cloneable[$class]) { $wrappedInstance = [clone Registry::$prototypes[$class]]; } elseif (Registry::$instantiableWithoutConstructor[$class]) { $wrappedInstance = [$reflector->newInstanceWithoutConstructor()]; } elseif (null === Registry::$prototypes[$class]) { throw new NotInstantiableTypeException($class); } elseif ($reflector->implementsInterface('Serializable') && (\PHP_VERSION_ID < 70400 || !\method_exists($class, '__unserialize'))) { $wrappedInstance = [\unserialize('C:' . \strlen($class) . ':"' . $class . '":0:{}')]; } else { $wrappedInstance = [\unserialize('O:' . \strlen($class) . ':"' . $class . '":0:{}')]; } if ($properties) { $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties; } foreach ($privateProperties as $class => $properties) { if (!$properties) { continue; } foreach ($properties as $name => $value) { // because they're also used for "unserialization", hydrators // deal with array of instances, so we need to wrap values $properties[$name] = [$value]; } (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance); } return $wrappedInstance[0]; } } Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.1.0 ----- * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values 4.2.0 ----- * added the component * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Internal; /** * @author Nicolas Grekas * * @internal */ class Values { public $values; public function __construct(array $values) { $this->values = $values; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Internal; use _ContaoManager\Symfony\Component\VarExporter\Exception\ClassNotFoundException; /** * @author Nicolas Grekas * * @internal */ class Hydrator { public static $hydrators = []; public $registry; public $values; public $properties; public $value; public $wakeups; public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups) { $this->registry = $registry; $this->values = $values; $this->properties = $properties; $this->value = $value; $this->wakeups = $wakeups; } public static function hydrate($objects, $values, $properties, $value, $wakeups) { foreach ($properties as $class => $vars) { (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects); } foreach ($wakeups as $k => $v) { if (\is_array($v)) { $objects[-$k]->__unserialize($v); } else { $objects[$v]->__wakeup(); } } return $value; } public static function getHydrator($class) { switch ($class) { case 'stdClass': return self::$hydrators[$class] = static function ($properties, $objects) { foreach ($properties as $name => $values) { foreach ($values as $i => $v) { $objects[$i]->{$name} = $v; } } }; case 'ErrorException': return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class extends \ErrorException { }); case 'TypeError': return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class extends \Error { }); case 'SplObjectStorage': return self::$hydrators[$class] = static function ($properties, $objects) { foreach ($properties as $name => $values) { if ("\x00" === $name) { foreach ($values as $i => $v) { for ($j = 0; $j < \count($v); ++$j) { $objects[$i]->attach($v[$j], $v[++$j]); } } continue; } foreach ($values as $i => $v) { $objects[$i]->{$name} = $v; } } }; } if (!\class_exists($class) && !\interface_exists($class, \false) && !\trait_exists($class, \false)) { throw new ClassNotFoundException($class); } $classReflector = new \ReflectionClass($class); switch ($class) { case 'ArrayIterator': case 'ArrayObject': $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']); return self::$hydrators[$class] = static function ($properties, $objects) use($constructor) { foreach ($properties as $name => $values) { if ("\x00" !== $name) { foreach ($values as $i => $v) { $objects[$i]->{$name} = $v; } } } foreach ($properties["\x00"] ?? [] as $i => $v) { $constructor($objects[$i], $v); } }; } if (!$classReflector->isInternal()) { return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); } if ($classReflector->name !== $class) { return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name); } $propertySetters = []; foreach ($classReflector->getProperties() as $propertyReflector) { if (!$propertyReflector->isStatic()) { $propertyReflector->setAccessible(\true); $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']); } } if (!$propertySetters) { return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'); } return self::$hydrators[$class] = static function ($properties, $objects) use($propertySetters) { foreach ($properties as $name => $values) { if ($setValue = $propertySetters[$name] ?? null) { foreach ($values as $i => $v) { $setValue($objects[$i], $v); } continue; } foreach ($values as $i => $v) { $objects[$i]->{$name} = $v; } } }; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Internal; use _ContaoManager\Symfony\Component\VarExporter\Exception\ClassNotFoundException; use _ContaoManager\Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; /** * @author Nicolas Grekas * * @internal */ class Registry { public static $reflectors = []; public static $prototypes = []; public static $factories = []; public static $cloneable = []; public static $instantiableWithoutConstructor = []; public $classes = []; public function __construct(array $classes) { $this->classes = $classes; } public static function unserialize($objects, $serializables) { $unserializeCallback = \ini_set('unserialize_callback_func', __CLASS__ . '::getClassReflector'); try { foreach ($serializables as $k => $v) { $objects[$k] = \unserialize($v); } } finally { \ini_set('unserialize_callback_func', $unserializeCallback); } return $objects; } public static function p($class) { self::getClassReflector($class, \true, \true); return self::$prototypes[$class]; } public static function f($class) { $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, \true, \false); return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']); } public static function getClassReflector($class, $instantiableWithoutConstructor = \false, $cloneable = null) { if (!($isClass = \class_exists($class)) && !\interface_exists($class, \false) && !\trait_exists($class, \false)) { throw new ClassNotFoundException($class); } $reflector = new \ReflectionClass($class); if ($instantiableWithoutConstructor) { $proto = $reflector->newInstanceWithoutConstructor(); } elseif (!$isClass || $reflector->isAbstract()) { throw new NotInstantiableTypeException($class); } elseif ($reflector->name !== $class) { $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, \false, $cloneable); self::$cloneable[$class] = self::$cloneable[$name]; self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; self::$prototypes[$class] = self::$prototypes[$name]; return self::$reflectors[$class] = $reflector; } else { try { $proto = $reflector->newInstanceWithoutConstructor(); $instantiableWithoutConstructor = \true; } catch (\ReflectionException $e) { $proto = $reflector->implementsInterface('Serializable') && !\method_exists($class, '__unserialize') ? 'C:' : 'O:'; if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { $proto = null; } else { try { $proto = @\unserialize($proto . \strlen($class) . ':"' . $class . '":0:{}'); } catch (\Exception $e) { if (__FILE__ !== $e->getFile()) { throw $e; } throw new NotInstantiableTypeException($class, $e); } if (\false === $proto) { throw new NotInstantiableTypeException($class); } } } if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !\method_exists($class, '__sleep') && (\PHP_VERSION_ID < 70400 || !\method_exists($class, '__serialize'))) { try { \serialize($proto); } catch (\Exception $e) { throw new NotInstantiableTypeException($class, $e); } } } if (null === $cloneable) { if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !\method_exists($proto, '__wakeup') && (\PHP_VERSION_ID < 70400 || !\method_exists($class, '__unserialize')))) { throw new NotInstantiableTypeException($class); } $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); } self::$cloneable[$class] = $cloneable; self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; self::$prototypes[$class] = $proto; if ($proto instanceof \Throwable) { static $setTrace; if (null === $setTrace) { $setTrace = [new \ReflectionProperty(\Error::class, 'trace'), new \ReflectionProperty(\Exception::class, 'trace')]; $setTrace[0]->setAccessible(\true); $setTrace[1]->setAccessible(\true); $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']); $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']); } $setTrace[$proto instanceof \Exception]($proto, []); } return self::$reflectors[$class] = $reflector; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Internal; /** * @author Nicolas Grekas * * @internal */ class Reference { public $id; public $value; public $count = 0; public function __construct(int $id, $value = null) { $this->id = $id; $this->value = $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Internal; use _ContaoManager\Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; /** * @author Nicolas Grekas * * @internal */ class Exporter { /** * Prepares an array of values for VarExporter. * * For performance this method is public and has no type-hints. * * @param array &$values * @param \SplObjectStorage $objectsPool * @param array &$refsPool * @param int &$objectsCount * @param bool &$valuesAreStatic * * @throws NotInstantiableTypeException When a value cannot be serialized */ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) : array { $refs = $values; foreach ($values as $k => $value) { if (\is_resource($value)) { throw new NotInstantiableTypeException(\get_resource_type($value) . ' resource'); } $refs[$k] = $objectsPool; if ($isRef = !($valueIsStatic = $values[$k] !== $objectsPool)) { $values[$k] =& $value; // Break hard references to make $values completely unset($value); // independent from the original structure $refs[$k] = $value = $values[$k]; if ($value instanceof Reference && 0 > $value->id) { $valuesAreStatic = \false; ++$value->count; continue; } $refsPool[] = [&$refs[$k], $value, &$value]; $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); } if (\is_array($value)) { if ($value) { $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); } goto handle_value; } elseif (!\is_object($value) || $value instanceof \UnitEnum) { goto handle_value; } $valueIsStatic = \false; if (isset($objectsPool[$value])) { ++$objectsCount; $value = new Reference($objectsPool[$value][0]); goto handle_value; } $class = \get_class($value); $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); $properties = []; if ($reflector->hasMethod('__serialize')) { if (!$reflector->getMethod('__serialize')->isPublic()) { throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); } if (!\is_array($serializeProperties = $value->__serialize())) { throw new \TypeError($class . '::__serialize() must return an array'); } if ($reflector->hasMethod('__unserialize')) { $properties = $serializeProperties; } else { foreach ($serializeProperties as $n => $v) { $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; $properties[$c][$n] = $v; } } goto prepare_value; } $sleep = null; $proto = Registry::$prototypes[$class]; if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { // ArrayIterator and ArrayObject need special care because their "flags" // option changes the behavior of the (array) casting operator. [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); // populates Registry::$prototypes[$class] with a new instance Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { // By implementing Serializable, SplObjectStorage breaks // internal references; let's deal with it on our own. foreach (clone $value as $v) { $properties[] = $v; $properties[] = $value[$v]; } $properties = ['SplObjectStorage' => ["\x00" => $properties]]; $arrayValue = (array) $value; } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod) { ++$objectsCount; $objectsPool[$value] = [$id = \count($objectsPool), \serialize($value), [], 0]; $value = new Reference($id); goto handle_value; } else { if (\method_exists($class, '__sleep')) { if (!\is_array($sleep = $value->__sleep())) { \trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE); $value = null; goto handle_value; } $sleep = \array_flip($sleep); } $arrayValue = (array) $value; } $proto = (array) $proto; foreach ($arrayValue as $name => $v) { $i = 0; $n = (string) $name; if ('' === $n || "\x00" !== $n[0]) { $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; } elseif ('*' === $n[1]) { $n = \substr($n, 3); $c = $reflector->getProperty($n)->class; if ('Error' === $c) { $c = 'TypeError'; } elseif ('Exception' === $c) { $c = 'ErrorException'; } } else { $i = \strpos($n, "\x00", 2); $c = \substr($n, 1, $i - 1); $n = \substr($n, 1 + $i); } if (null !== $sleep) { if (!isset($sleep[$name]) && (!isset($sleep[$n]) || $i && $c !== $class)) { unset($arrayValue[$name]); continue; } unset($sleep[$name], $sleep[$n]); } if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { $properties[$c][$n] = $v; } } if ($sleep) { foreach ($sleep as $n => $v) { \trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); } } if (\method_exists($class, '__unserialize')) { $properties = $arrayValue; } prepare_value: $objectsPool[$value] = [$id = \count($objectsPool)]; $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); ++$objectsCount; $objectsPool[$value] = [$id, $class, $properties, \method_exists($class, '__unserialize') ? -$objectsCount : (\method_exists($class, '__wakeup') ? $objectsCount : 0)]; $value = new Reference($id); handle_value: if ($isRef) { unset($value); // Break the hard reference created above } elseif (!$valueIsStatic) { $values[$k] = $value; } $valuesAreStatic = $valueIsStatic && $valuesAreStatic; } return $values; } public static function export($value, string $indent = '') { switch (\true) { case \is_int($value) || \is_float($value): return \var_export($value, \true); case [] === $value: return '[]'; case \false === $value: return 'false'; case \true === $value: return 'true'; case null === $value: return 'null'; case '' === $value: return "''"; case $value instanceof \UnitEnum: return '\\' . \ltrim(\var_export($value, \true), '\\'); } if ($value instanceof Reference) { if (0 <= $value->id) { return '$o[' . $value->id . ']'; } if (!$value->count) { return self::export($value->value, $indent); } $value = -$value->id; return '&$r[' . $value . ']'; } $subIndent = $indent . ' '; if (\is_string($value)) { $code = \sprintf("'%s'", \addcslashes($value, "'\\")); $code = \preg_replace_callback("/((?:[\\0\\r\\n]|‪|‫|‭|‮|⁦|⁧|⁨|‬|⁩)++)(.)/", function ($m) use($subIndent) { $m[1] = \sprintf('\'."%s".\'', \str_replace(["\x00", "\r", "\n", "‪", "‫", "‭", "‮", "⁦", "⁧", "⁨", "‬", "⁩", '\\n\\'], ['\\0', '\\r', '\\n', '\\u{202A}', '\\u{202B}', '\\u{202D}', '\\u{202E}', '\\u{2066}', '\\u{2067}', '\\u{2068}', '\\u{202C}', '\\u{2069}', '\\n"' . "\n" . $subIndent . '."\\'], $m[1])); if ("'" === $m[2]) { return \substr($m[1], 0, -2); } if ('n".\'' === \substr($m[1], -4)) { return \substr_replace($m[1], "\n" . $subIndent . ".'" . $m[2], -2); } return $m[1] . $m[2]; }, $code, -1, $count); if ($count && \str_starts_with($code, "''.")) { $code = \substr($code, 3); } return $code; } if (\is_array($value)) { $j = -1; $code = ''; foreach ($value as $k => $v) { $code .= $subIndent; if (!\is_int($k) || 1 !== $k - $j) { $code .= self::export($k, $subIndent) . ' => '; } if (\is_int($k) && $k > $j) { $j = $k; } $code .= self::export($v, $subIndent) . ",\n"; } return "[\n" . $code . $indent . ']'; } if ($value instanceof Values) { $code = $subIndent . "\$r = [],\n"; foreach ($value->values as $k => $v) { $code .= $subIndent . '$r[' . $k . '] = ' . self::export($v, $subIndent) . ",\n"; } return "[\n" . $code . $indent . ']'; } if ($value instanceof Registry) { return self::exportRegistry($value, $indent, $subIndent); } if ($value instanceof Hydrator) { return self::exportHydrator($value, $indent, $subIndent); } throw new \UnexpectedValueException(\sprintf('Cannot export value of type "%s".', \get_debug_type($value))); } private static function exportRegistry(Registry $value, string $indent, string $subIndent) : string { $code = ''; $serializables = []; $seen = []; $prototypesAccess = 0; $factoriesAccess = 0; $r = '\\' . Registry::class; $j = -1; foreach ($value->classes as $k => $class) { if (':' === ($class[1] ?? null)) { $serializables[$k] = $class; continue; } if (!Registry::$instantiableWithoutConstructor[$class]) { if (\is_subclass_of($class, 'Serializable') && !\method_exists($class, '__unserialize')) { $serializables[$k] = 'C:' . \strlen($class) . ':"' . $class . '":0:{}'; } else { $serializables[$k] = 'O:' . \strlen($class) . ':"' . $class . '":0:{}'; } if (\is_subclass_of($class, 'Throwable')) { $eol = \is_subclass_of($class, 'Error') ? "\x00Error\x00" : "\x00Exception\x00"; $serializables[$k] = \substr_replace($serializables[$k], '1:{s:' . (5 + \strlen($eol)) . ':"' . $eol . 'trace";a:0:{}}', -4); } continue; } $code .= $subIndent . (1 !== $k - $j ? $k . ' => ' : ''); $j = $k; $eol = ",\n"; $c = '[' . self::export($class) . ']'; if ($seen[$class] ?? \false) { if (Registry::$cloneable[$class]) { ++$prototypesAccess; $code .= 'clone $p' . $c; } else { ++$factoriesAccess; $code .= '$f' . $c . '()'; } } else { $seen[$class] = \true; if (Registry::$cloneable[$class]) { $code .= 'clone (' . ($prototypesAccess++ ? '$p' : '($p = &' . $r . '::$prototypes)') . $c . ' ?? ' . $r . '::p'; } else { $code .= '(' . ($factoriesAccess++ ? '$f' : '($f = &' . $r . '::$factories)') . $c . ' ?? ' . $r . '::f'; $eol = '()' . $eol; } $code .= '(' . \substr($c, 1, -1) . '))'; } $code .= $eol; } if (1 === $prototypesAccess) { $code = \str_replace('($p = &' . $r . '::$prototypes)', $r . '::$prototypes', $code); } if (1 === $factoriesAccess) { $code = \str_replace('($f = &' . $r . '::$factories)', $r . '::$factories', $code); } if ('' !== $code) { $code = "\n" . $code . $indent; } if ($serializables) { $code = $r . '::unserialize([' . $code . '], ' . self::export($serializables, $indent) . ')'; } else { $code = '[' . $code . ']'; } return '$o = ' . $code; } private static function exportHydrator(Hydrator $value, string $indent, string $subIndent) : string { $code = ''; foreach ($value->properties as $class => $properties) { $code .= $subIndent . ' ' . self::export($class) . ' => ' . self::export($properties, $subIndent . ' ') . ",\n"; } $code = [self::export($value->registry, $subIndent), self::export($value->values, $subIndent), '' !== $code ? "[\n" . $code . $subIndent . ']' : '[]', self::export($value->value, $subIndent), self::export($value->wakeups, $subIndent)]; return '\\' . \get_class($value) . "::hydrate(\n" . $subIndent . \implode(",\n" . $subIndent, $code) . "\n" . $indent . ')'; } /** * @param \ArrayIterator|\ArrayObject $value * @param \ArrayIterator|\ArrayObject $proto */ private static function getArrayObjectProperties($value, $proto) : array { $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); $properties = [$arrayValue = (array) $value, $reflector->getMethod('getFlags')->invoke($value), $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator']; $reflector = $reflector->getMethod('setFlags'); $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); if ($properties[1] & \ArrayObject::STD_PROP_LIST) { $reflector->invoke($value, 0); $properties[0] = (array) $value; } else { $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); $arrayValue = (array) $value; } $reflector->invoke($value, $properties[1]); if ([[], 0, 'ArrayIterator'] === $properties) { $properties = []; } else { if ('ArrayIterator' === $properties[2]) { unset($properties[2]); } $properties = [$reflector->class => ["\x00" => $properties]]; } return [$arrayValue, $properties]; } } VarExporter Component ===================== The VarExporter component allows exporting any serializable PHP data structure to plain PHP code. While doing so, it preserves all the semantics associated with the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, `__serialize`, `__unserialize`). It also provides an instantiator that allows creating and populating objects without calling their constructor nor any other methods. The reason to use this component *vs* `serialize()` or [igbinary](https://github.com/igbinary/igbinary) is performance: thanks to OPcache, the resulting code is significantly faster and more memory efficient than using `unserialize()` or `igbinary_unserialize()`. Unlike `var_export()`, this works on any serializable PHP value. It also provides a few improvements over `var_export()`/`serialize()`: * the output is PSR-2 compatible; * the output can be re-indented without messing up with `\r` or `\n` in the data * missing classes throw a `ClassNotFoundException` instead of being unserialized to `PHP_Incomplete_Class` objects; * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` instances are preserved; * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes throw an exception when being serialized (their unserialized version is broken anyway, see https://bugs.php.net/76737). Resources --------- * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter; use _ContaoManager\Symfony\Component\VarExporter\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\VarExporter\Internal\Exporter; use _ContaoManager\Symfony\Component\VarExporter\Internal\Hydrator; use _ContaoManager\Symfony\Component\VarExporter\Internal\Registry; use _ContaoManager\Symfony\Component\VarExporter\Internal\Values; /** * Exports serializable PHP values to PHP code. * * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) * while preserving all the semantics associated with serialize() (unlike var_export()). * * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). * * @author Nicolas Grekas */ final class VarExporter { /** * Exports a serializable PHP value to PHP code. * * @param mixed $value The value to export * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise * @param array &$foundClasses Classes found in the value are added to this list as both keys and values * * @throws ExceptionInterface When the provided value cannot be serialized */ public static function export($value, ?bool &$isStaticValue = null, array &$foundClasses = []) : string { $isStaticValue = \true; if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) { return Exporter::export($value); } $objectsPool = new \SplObjectStorage(); $refsPool = []; $objectsCount = 0; try { $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; } finally { $references = []; foreach ($refsPool as $i => $v) { if ($v[0]->count) { $references[1 + $i] = $v[2]; } $v[0] = $v[1]; } } if ($isStaticValue) { return Exporter::export($value); } $classes = []; $values = []; $states = []; foreach ($objectsPool as $i => $v) { [, $class, $values[], $wakeup] = $objectsPool[$v]; $foundClasses[$class] = $classes[] = $class; if (0 < $wakeup) { $states[$wakeup] = $i; } elseif (0 > $wakeup) { $states[-$wakeup] = [$i, \array_pop($values)]; $values[] = []; } } \ksort($states); $wakeups = [null]; foreach ($states as $v) { if (\is_array($v)) { $wakeups[-$v[0]] = $v[1]; } else { $wakeups[] = $v; } } if (null === $wakeups[0]) { unset($wakeups[0]); } $properties = []; foreach ($values as $i => $vars) { foreach ($vars as $class => $values) { foreach ($values as $name => $v) { $properties[$class][$name][$i] = $v; } } } if ($classes || $references) { $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); } else { $isStaticValue = \true; } return Exporter::export($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Exception; interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Exception; class ClassNotFoundException extends \Exception implements ExceptionInterface { public function __construct(string $class, ?\Throwable $previous = null) { parent::__construct(\sprintf('Class "%s" not found.', $class), 0, $previous); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\VarExporter\Exception; class NotInstantiableTypeException extends \Exception implements ExceptionInterface { public function __construct(string $type, ?\Throwable $previous = null) { parent::__construct(\sprintf('Type "%s" is not instantiable.', $type), 0, $previous); } } { "name": "symfony\/var-exporter", "type": "library", "description": "Allows exporting any serializable PHP data structure to plain PHP code", "keywords": [ "export", "serialize", "instantiate", "hydrate", "construct", "clone" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/polyfill-php80": "^1.16" }, "require-dev": { "symfony\/var-dumper": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\VarExporter\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer; interface ErrorEnhancerInterface { /** * Returns an \Throwable instance if the class is able to improve the error, null otherwise. */ public function enhance(\Throwable $error) : ?\Throwable; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer; use Composer\Autoload\ClassLoader; use _ContaoManager\Symfony\Component\ErrorHandler\DebugClassLoader; use _ContaoManager\Symfony\Component\ErrorHandler\Error\ClassNotFoundError; use _ContaoManager\Symfony\Component\ErrorHandler\Error\FatalError; /** * @author Fabien Potencier */ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface { public function enhance(\Throwable $error) : ?\Throwable { // Some specific versions of PHP produce a fatal error when extending a not found class. $message = !$error instanceof FatalError ? $error->getMessage() : $error->getError()['message']; if (!\preg_match('/^(Class|Interface|Trait) [\'"]([^\'"]+)[\'"] not found$/', $message, $matches)) { return null; } $typeName = \strtolower($matches[1]); $fullyQualifiedClassName = $matches[2]; if (\false !== ($namespaceSeparatorIndex = \strrpos($fullyQualifiedClassName, '\\'))) { $className = \substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); $namespacePrefix = \substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); $message = \sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); $tail = ' for another namespace?'; } else { $className = $fullyQualifiedClassName; $message = \sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); $tail = '?'; } if ($candidates = $this->getClassCandidates($className)) { $tail = \array_pop($candidates) . '"?'; if ($candidates) { $tail = ' for e.g. "' . \implode('", "', $candidates) . '" or "' . $tail; } else { $tail = ' for "' . $tail; } } $message .= "\nDid you forget a \"use\" statement" . $tail; return new ClassNotFoundError($message, $error); } /** * Tries to guess the full namespace for a given class name. * * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer * autoloader (that should cover all common cases). * * @param string $class A class name (without its namespace) * * Returns an array of possible fully qualified class names */ private function getClassCandidates(string $class) : array { if (!\is_array($functions = \spl_autoload_functions())) { return []; } // find Symfony and Composer autoloaders $classes = []; foreach ($functions as $function) { if (!\is_array($function)) { continue; } // get class loaders wrapped by DebugClassLoader if ($function[0] instanceof DebugClassLoader) { $function = $function[0]->getClassLoader(); if (!\is_array($function)) { continue; } } if ($function[0] instanceof ClassLoader) { foreach ($function[0]->getPrefixes() as $prefix => $paths) { foreach ($paths as $path) { $classes[] = $this->findClassInPath($path, $class, $prefix); } } foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { foreach ($paths as $path) { $classes[] = $this->findClassInPath($path, $class, $prefix); } } } } return \array_unique(\array_merge([], ...$classes)); } private function findClassInPath(string $path, string $class, string $prefix) : array { $path = (\realpath($path . '/' . \strtr($prefix, '\\_', '//')) ?: \realpath($path . '/' . \dirname(\strtr($prefix, '\\_', '//')))) ?: \realpath($path); if (!$path || !\is_dir($path)) { return []; } $classes = []; $filename = $class . '.php'; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { if ($filename == $file->getFileName() && ($class = $this->convertFileToClass($path, $file->getPathName(), $prefix))) { $classes[] = $class; } } return $classes; } private function convertFileToClass(string $path, string $file, string $prefix) : ?string { $candidates = [ // namespaced class $namespacedClass = \str_replace([$path . \DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file), // namespaced class (with target dir) $prefix . $namespacedClass, // namespaced class (with target dir and separator) $prefix . '\\' . $namespacedClass, // PEAR class \str_replace('\\', '_', $namespacedClass), // PEAR class (with target dir) \str_replace('\\', '_', $prefix . $namespacedClass), // PEAR class (with target dir and separator) \str_replace('\\', '_', $prefix . '\\' . $namespacedClass), ]; if ($prefix) { $candidates = \array_filter($candidates, function ($candidate) use($prefix) { return 0 === \strpos($candidate, $prefix); }); } // We cannot use the autoloader here as most of them use require; but if the class // is not found, the new autoloader call will require the file again leading to a // "cannot redeclare class" error. foreach ($candidates as $candidate) { if ($this->classExists($candidate)) { return $candidate; } } try { require_once $file; } catch (\Throwable $e) { return null; } foreach ($candidates as $candidate) { if ($this->classExists($candidate)) { return $candidate; } } return null; } private function classExists(string $class) : bool { return \class_exists($class, \false) || \interface_exists($class, \false) || \trait_exists($class, \false); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer; use _ContaoManager\Symfony\Component\ErrorHandler\Error\FatalError; use _ContaoManager\Symfony\Component\ErrorHandler\Error\UndefinedFunctionError; /** * @author Fabien Potencier */ class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface { /** * {@inheritdoc} */ public function enhance(\Throwable $error) : ?\Throwable { if ($error instanceof FatalError) { return null; } $message = $error->getMessage(); $messageLen = \strlen($message); $notFoundSuffix = '()'; $notFoundSuffixLen = \strlen($notFoundSuffix); if ($notFoundSuffixLen > $messageLen) { return null; } if (0 !== \substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) { return null; } $prefix = 'Call to undefined function '; $prefixLen = \strlen($prefix); if (0 !== \strpos($message, $prefix)) { return null; } $fullyQualifiedFunctionName = \substr($message, $prefixLen, -$notFoundSuffixLen); if (\false !== ($namespaceSeparatorIndex = \strrpos($fullyQualifiedFunctionName, '\\'))) { $functionName = \substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); $namespacePrefix = \substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); $message = \sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); } else { $functionName = $fullyQualifiedFunctionName; $message = \sprintf('Attempted to call function "%s" from the global namespace.', $functionName); } $candidates = []; foreach (\get_defined_functions() as $type => $definedFunctionNames) { foreach ($definedFunctionNames as $definedFunctionName) { if (\false !== ($namespaceSeparatorIndex = \strrpos($definedFunctionName, '\\'))) { $definedFunctionNameBasename = \substr($definedFunctionName, $namespaceSeparatorIndex + 1); } else { $definedFunctionNameBasename = $definedFunctionName; } if ($definedFunctionNameBasename === $functionName) { $candidates[] = '\\' . $definedFunctionName; } } } if ($candidates) { \sort($candidates); $last = \array_pop($candidates) . '"?'; if ($candidates) { $candidates = 'e.g. "' . \implode('", "', $candidates) . '" or "' . $last; } else { $candidates = '"' . $last; } $message .= "\nDid you mean to call " . $candidates; } return new UndefinedFunctionError($message, $error); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer; use _ContaoManager\Symfony\Component\ErrorHandler\Error\FatalError; use _ContaoManager\Symfony\Component\ErrorHandler\Error\UndefinedMethodError; /** * @author Grégoire Pineau */ class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface { /** * {@inheritdoc} */ public function enhance(\Throwable $error) : ?\Throwable { if ($error instanceof FatalError) { return null; } $message = $error->getMessage(); \preg_match('/^Call to undefined method (.*)::(.*)\\(\\)$/', $message, $matches); if (!$matches) { return null; } $className = $matches[1]; $methodName = $matches[2]; $message = \sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); if ('' === $methodName || !\class_exists($className) || null === ($methods = \get_class_methods($className))) { // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) return new UndefinedMethodError($message, $error); } $candidates = []; foreach ($methods as $definedMethodName) { $lev = \levenshtein($methodName, $definedMethodName); if ($lev <= \strlen($methodName) / 3 || \false !== \strpos($definedMethodName, $methodName)) { $candidates[] = $definedMethodName; } } if ($candidates) { \sort($candidates); $last = \array_pop($candidates) . '"?'; if ($candidates) { $candidates = 'e.g. "' . \implode('", "', $candidates) . '" or "' . $last; } else { $candidates = '"' . $last; } $message .= "\nDid you mean to call " . $candidates; } return new UndefinedMethodError($message, $error); } } Copyright (c) 2019-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Make `DebugClassLoader` trigger deprecation notices on missing return types * Add `SYMFONY_PATCH_TYPE_DECLARATIONS='force=2'` mode to `DebugClassLoader` to turn annotations into native return types 5.2.0 ----- * added the ability to set `HtmlErrorRenderer::$template` to a custom template to render when not in debug mode. 5.1.0 ----- * The `HtmlErrorRenderer` and `SerializerErrorRenderer` add `X-Debug-Exception` and `X-Debug-Exception-File` headers in debug mode. 4.4.0 ----- * added the component * added `ErrorHandler::call()` method utility to turn any PHP error into `\ErrorException` #!/usr/bin/env php * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ('cli' !== \PHP_SAPI) { throw new \Exception('This script must be run from the command line.'); } if (\in_array('-h', $argv) || \in_array('--help', $argv)) { echo \implode(\PHP_EOL, [' Patches type declarations based on "@return" PHPDoc and triggers deprecations for', ' incompatible method declarations.', '', ' This assists you to make your package compatible with Symfony 6, but it can be used', ' for any class/package.', '', ' Available configuration via environment variables:', ' SYMFONY_PATCH_TYPE_DECLARATIONS', ' An url-encoded string to change the behavior of the script. Available parameters:', ' - "force": any value enables deprecation notices - can be any of:', ' - "phpdoc" to patch only docblock annotations', ' - "2" to add all possible return types', ' - "1" to add return types but only to tests/final/internal/private methods', ' - "php": the target version of PHP - e.g. "7.1" doesn\'t generate "object" types', ' - "deprecations": "1" to trigger a deprecation notice when a child class misses a', ' return type while the parent declares an "@return" annotation', '', ' SYMFONY_PATCH_TYPE_EXCLUDE', ' A regex matched against the full path to the class - any match will be excluded', '', ' Example: "SYMFONY_PATCH_TYPE_DECLARATIONS=php=7.4 ./patch-type-declarations"']); exit; } if (\false === \getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) { \putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=force=2'); echo 'No SYMFONY_PATCH_TYPE_DECLARATIONS env var set, patching type declarations in all methods (run the command with "-h" for more information).' . \PHP_EOL; } if (\is_file($autoload = __DIR__ . '/../../../../autoload.php')) { // noop } elseif (\is_file($autoload = __DIR__ . '/../../../../../../../autoload.php')) { // noop } else { echo \PHP_EOL . ' /!\\ Cannot find the Composer autoloader, did you forget to run "composer install"?' . \PHP_EOL; exit(1); } if (\is_file($phpunitAutoload = \dirname($autoload) . '/bin/.phpunit/phpunit/vendor/autoload.php')) { require $phpunitAutoload; } $loader = (require $autoload); Symfony\Component\ErrorHandler\DebugClassLoader::enable(); $deprecations = []; \set_error_handler(function ($type, $msg, $file, $line, $context = []) use(&$deprecations) { if (\E_USER_DEPRECATED !== $type) { return; } [, , , , , $class] = \explode('"', $msg); $deprecations[$class][] = $msg; }); $exclude = \getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null; foreach ($loader->getClassMap() as $class => $file) { if (\false !== \strpos($file = \realpath($file), \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR)) { continue; } if ($exclude && \preg_match($exclude, $file)) { continue; } \class_exists($class); } Symfony\Component\ErrorHandler\DebugClassLoader::checkClasses(); foreach ($deprecations as $class => $classDeprecations) { echo $class . ' (' . \count($classDeprecations) . ')' . \PHP_EOL; echo \implode(\PHP_EOL, $classDeprecations) . \PHP_EOL . \PHP_EOL; } if ($deprecations && \false !== \strpos(\getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?? '', 'force')) { echo 'These deprecations might be fixed by the patch script, run this again to check for type deprecations.' . \PHP_EOL; } #!/usr/bin/env php * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if ('cli' !== \PHP_SAPI) { throw new \Exception('This script must be run from the command line.'); } // Run from the root of the php-src repository, this script generates // a table with all the methods that have a tentative return type. // // Usage: find -name *.stub.php | sort | /path/to/extract-tentative-return-types.php > /path/to/TentativeTypes.php echo << * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\\Component\\ErrorHandler\\Internal; /** * This class has been generated by extract-tentative-return-types.php. * * @internal */ class TentativeTypes { public const RETURN_TYPES = [ EOPHP; while (\false !== ($file = \fgets(\STDIN))) { $code = \file_get_contents(\substr($file, 0, -1)); if (!\str_contains($code, '@tentative-return-type')) { continue; } $code = \preg_split('{^\\s*(?:(?:abstract )?class|interface|trait) ([^\\s]++)}m', $code, -1, \PREG_SPLIT_DELIM_CAPTURE); if (1 === \count($code)) { continue; } for ($i = 1; null !== ($class = $code[$i] ?? null); $i += 2) { $methods = $code[1 + $i]; if (!\str_contains($methods, '@tentative-return-type')) { continue; } echo " '{$class}' => [\n"; \preg_replace_callback('{@tentative-return-type.*?[\\s]function ([^(]++)[^)]++\\)\\s*+:\\s*+([^\\n;\\{]++)}s', function ($m) { $m[2] = \str_replace(' ', '', $m[2]); echo " '{$m[1]}' => '{$m[2]}',\n"; return ''; }, $methods); echo " ],\n"; } } echo <<

    formatFileFromText(\nl2br($exceptionMessage)); ?>

    include('assets/images/symfony-ghost.svg.php'); ?>
    toArray(); $exceptionWithUserCode = []; $exceptionAsArrayCount = \count($exceptionAsArray); $last = $exceptionAsArrayCount - 1; foreach ($exceptionAsArray as $i => $e) { foreach ($e['trace'] as $trace) { if ($trace['file'] && \false === \mb_strpos($trace['file'], '/vendor/') && \false === \mb_strpos($trace['file'], '/var/cache/') && $i < $last) { $exceptionWithUserCode[] = $i; } } } ?>

    1) { ?> Exceptions Exception

    $e) { echo $this->include('views/traces.html.php', ['exception' => $e, 'index' => $i + 1, 'expand' => \in_array($i, $exceptionWithUserCode, \true) || [] === $exceptionWithUserCode && 0 === $i]); } ?>

    Logs countErrors()) { ?>countErrors(); ?>

    getLogs()) { ?> include('views/logs.html.php', ['logs' => $logger->getLogs()]); ?>

    No log messages

    1) { ?> Stack Traces Stack Trace

    $e) { echo $this->include('views/traces_text.html.php', ['exception' => $e, 'index' => $i + 1, 'numExceptions' => $exceptionAsArrayCount]); } ?>

    Output content

    ">
    include('assets/images/icon-minus-square-o.svg'); ?> include('assets/images/icon-plus-square-o.svg'); ?>

    1) { ?>

    escape($exception['message']); ?>

    $trace) { $isVendorTrace = $trace['file'] && (\false !== \mb_strpos($trace['file'], '/vendor/') || \false !== \mb_strpos($trace['file'], '/var/cache/')); $displayCodeSnippet = $isFirstUserCode && !$isVendorTrace; if ($displayCodeSnippet) { $isFirstUserCode = \false; } ?>
    include('views/trace.html.php', ['prefix' => $index, 'i' => $i, 'trace' => $trace, 'style' => $isVendorTrace ? 'compact' : ($displayCodeSnippet ? 'expanded' : '')]); ?>
    1) { ?> [/] include('assets/images/icon-minus-square-o.svg'); ?> include('assets/images/icon-plus-square-o.svg'); ?>
    escape($exception['class']) . ":\n";
        if ($exception['message']) {
            echo $this->escape($exception['message']) . "\n";
        }
        foreach ($exception['trace'] as $trace) {
            echo "\n  ";
            if ($trace['function']) {
                echo $this->escape('at ' . $trace['class'] . $trace['type'] . $trace['function']) . '(' . (isset($trace['args']) ? $this->formatArgsAsText($trace['args']) : '') . ')';
            }
            if ($trace['file'] && $trace['line']) {
                echo ($trace['function'] ? "\n     (" : 'at ') . \strtr(\strip_tags($this->formatFile($trace['file'], $trace['line'])), [' at line ' . $trace['line'] => '']) . ':' . $trace['line'] . ($trace['function'] ? ')' : '');
            }
        }
        ?>
                    
    Level Channel Message = 400) { $status = 'error'; } elseif ($log['priority'] >= 300) { $status = 'warning'; } else { $severity = 0; if (($exception = $log['context']['exception'] ?? null) instanceof \ErrorException || $exception instanceof \_ContaoManager\Symfony\Component\ErrorHandler\Exception\SilencedErrorContext) { $severity = $exception->getSeverity(); } $status = \E_DEPRECATED === $severity || \E_USER_DEPRECATED === $severity ? 'warning' : 'normal'; } ?> data-filter-channel="escape($log['channel']); ?>"> escape($log['priorityName']); ?> escape($log['channel']); ?> formatLogMessage($log['message'], $log['context']); ?>
    escape(\json_encode($log['context'], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
            ?>
    " data-toggle-selector="#trace-html--" data-toggle-initial=""> include('assets/images/icon-minus-square.svg'); ?> include('assets/images/icon-plus-square.svg'); ?> abbrClass($trace['class']); ?>(formatArgs($trace['args']); ?>) getFileLink($trace['file'], $lineNumber); $filePath = \strtr(\strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line ' . $lineNumber => '']); $filePathParts = \explode(\DIRECTORY_SEPARATOR, $filePath); ?> in (line )
    fileExcerpt($trace['file'], $trace['line'], 5), ['#DD0000' => 'var(--highlight-string)', '#007700' => 'var(--highlight-keyword)', '#0000BB' => 'var(--highlight-default)', '#FF8000' => 'var(--highlight-comment)']); ?>
    An Error Occurred: <?php echo $statusText; ?>

    Oops! An Error Occurred

    The server returned a " ".

    Something is broken. Please let us know what you were doing when this error occurred. We will fix it as soon as possible. Sorry for any inconvenience caused.

    --> <?php echo $_message; ?>

    include('assets/images/symfony-logo.svg'); ?> Symfony Exception

    include('views/exception.html.php', $context); ?> *:first-child { margin-top: 0; } .tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; } .tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; } .sf-tabs .tab:not(:first-child) { display: none; } [data-filters] { position: relative; } [data-filtered] { cursor: pointer; } [data-filtered]:after { content: '\00a0\25BE'; } [data-filtered]:hover .filter-list li { display: inline-flex; } [class*="filter-hidden-"] { display: none; } .filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; } .filter-list :after { content: ''; } .filter-list li { background: var(--tab-disabled-background); border-bottom: var(--border); color: var(--tab-disabled-color); display: none; list-style: none; margin: 0; padding: 5px 10px; text-align: left; font-weight: normal; } .filter-list li.active { background: var(--tab-background); color: var(--tab-color); } .filter-list li.last-active { background: var(--tab-active-background); color: var(--tab-active-color); } .filter-list-level li { cursor: s-resize; } .filter-list-level li.active { cursor: n-resize; } .filter-list-level li.last-active { cursor: default; } .filter-list-level li.last-active:before { content: '\2714\00a0'; } .filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } .filter-list-choice li.active:before { color: unset; } .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } .container::after { content: ""; display: table; clear: both; } header { background-color: #222; color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; } header .container { display: flex; justify-content: space-between; } .logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; } .logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; } .help-link { margin-left: 15px; } .help-link a { color: inherit; } .help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; } .help-link a:hover { color: #EEE; text-decoration: none; } .help-link a:hover svg { opacity: .9; } .exception-summary { background: var(--background-error); border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; } .exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; } .exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; } .exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; } .exception-http small { font-size: 13px; opacity: .7; } .exception-hierarchy { flex: 1; } .exception-hierarchy .icon { margin: 0 3px; opacity: .7; } .exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; } .exception-without-message .exception-message-wrapper { display: none; } .exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; } .exception-message { flex-grow: 1; } .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } .exception-message.long { font-size: 18px; } .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } .exception-message a:hover { border-bottom-color: #ffffff; } .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } .trace + .trace { margin-top: 30px; } .trace-head { background-color: var(--base-2); padding: 10px; position: relative; } .trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } .trace-head .trace-namespace { color: #999; display: block; font-size: 13px; } .trace-head .icon { position: absolute; right: 0; top: 0; } .trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; } .trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; } .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } .trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; } .trace-line + .trace-line { border-top: var(--border); } .trace-line:hover { background: var(--base-1); } .trace-line a { color: var(--base-6); } .trace-line .icon { opacity: .4; position: absolute; left: 10px; } .trace-line .icon svg { fill: var(--base-5); height: 16px; width: 16px; } .trace-line .icon.icon-copy { left: auto; top: auto; padding-left: 5px; display: none } .trace-line:hover .icon.icon-copy:not(.hidden) { display: inline-block } .trace-line-header { padding-left: 36px; padding-right: 10px; } .trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; } .trace-class { color: var(--color-error); } .trace-type { padding: 0 2px; } .trace-method { color: var(--color-error); font-weight: bold; } .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } .trace-code { background: var(--base-0); font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; } .trace-code ol { margin: 0; float: left; } .trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; } .trace-code li + li { margin-top: 5px; } .trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; } .trace-code li code { color: var(--base-6); white-space: nowrap; } .trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; } @media (min-width: 575px) { .hidden-xs-down { display: initial; } .help-link { margin-left: 30px; } } .sf-reset .traces { padding-bottom: 14px; } .sf-reset .traces li { font-size: 12px; color: #868686; padding: 5px 4px; list-style-type: decimal; margin-left: 20px; } .sf-reset #logs .traces li.error { font-style: normal; color: #AA3333; background: #f9ecec; } .sf-reset #logs .traces li.warning { font-style: normal; background: #ffcc00; } /* fix for Opera not liking empty
  • */ .sf-reset .traces li:after { content: "\00A0"; } .sf-reset .trace { border: 1px solid #D3D3D3; padding: 10px; overflow: auto; margin: 10px 0 20px; } .sf-reset .block-exception { -moz-border-radius: 16px; -webkit-border-radius: 16px; border-radius: 16px; margin-bottom: 20px; background-color: #f6f6f6; border: 1px solid #dfdfdf; padding: 30px 28px; word-wrap: break-word; overflow: hidden; } .sf-reset .block-exception div { color: #313131; font-size: 10px; } .sf-reset .block-exception-detected .illustration-exception, .sf-reset .block-exception-detected .text-exception { float: left; } .sf-reset .block-exception-detected .illustration-exception { width: 152px; } .sf-reset .block-exception-detected .text-exception { width: 670px; padding: 30px 44px 24px 46px; position: relative; } .sf-reset .text-exception .open-quote, .sf-reset .text-exception .close-quote { font-family: Arial, Helvetica, sans-serif; position: absolute; color: #C9C9C9; font-size: 8em; } .sf-reset .open-quote { top: 0; left: 0; } .sf-reset .close-quote { bottom: -0.5em; right: 50px; } .sf-reset .block-exception p { font-family: Arial, Helvetica, sans-serif; } .sf-reset .block-exception p a, .sf-reset .block-exception p a:hover { color: #565656; } .sf-reset .logs h2 { float: left; width: 654px; } .sf-reset .error-count, .sf-reset .support { float: right; width: 170px; text-align: right; } .sf-reset .error-count span { display: inline-block; background-color: #aacd4e; -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; padding: 4px; color: white; margin-right: 2px; font-size: 11px; font-weight: bold; } .sf-reset .support a { display: inline-block; -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; padding: 4px; color: #000000; margin-right: 2px; font-size: 11px; font-weight: bold; } .sf-reset .toggle { vertical-align: middle; } .sf-reset .linked ul, .sf-reset .linked li { display: inline; } .sf-reset #output-content { color: #000; font-size: 12px; } .sf-reset #traces-text pre { white-space: pre; font-size: 12px; font-family: monospace; } body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; } .container { margin: 30px; max-width: 600px; } h1 { color: #dc3545; font-size: 24px; } h2 { font-size: 18px; } addElementToGhost(); ?> data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAADVUlEQVRIx82XX0jTURTHLYPyqZdefQx66CEo80+aYpoIkqzUikz6Z5klQoWUWYRIJYEUGpQ+lIr9U5dOTLdCtkmWZis3rbnC5fw/neYW002307mX/cZvP3/7o1PwwOdh95x7vnf39zvnd29AgBer2xO6DclAXiMqZAqxIiNIN/IYSUS2BPhjmGATchUxI+ADWiRhpWK7HKuHFVBFdmU5YvnI4grFGCaReF/EBH4KsZlGgj2JBTuCYBWRIYF8YoEOJ6wBt/gEs7mBbyOjQXruPLSdOgPCiEiPSUUHDoL8Ug5IUo9B/d5wrt+G7OAKNrODPuVdB6vRCIzN6SdBlpW9RIgk/1FeAXabzRlrUPVCS/JhbmwudztnGeeH9AyXBIwtmM3wLinZJZHifjHw2V+NBoRh+9ixQrbgbnaSIcl7cGea6hoXQbNe7za241oeO5Z0p42M4BV2EqP2D50wo+6HzvwC6C4sApNOR8cmOrtcnhtj2kYRyC9eBvXzKrBZrXSs72kFd1t3MoKVbMekQkEnSNKOO8fac3LpmK6l1TlGtsxmsdKFsecPYgwxst0cwROMYDXboSotg0WLBRqjY51jLYcENElXwW2XJKPydvoI2GN9T8rBtrAArYIUruBJXkFheCQYlCpQP6uk5dAQFQNaUROMSGVQFxLmkoQsxDJrhLbTZ+nvVsERME9MgPJRKV/58AsyomTSzE813WLFvWK++qI0xSfQl8k8Pg46sYRuv5t6dS+4RqxDwaa4BGjYH+NTQvKScIp9+YL/hoZh3jDtLRHtt2C3g6bmhX+CpsFBWg7ilDSPgj0lD2ncr5ev/BP8VvyAJhqVyZeUhPOrEhEFxgEtjft846Z/guQTNT89Q5P9flMLoth4F7808wKtWWKzAwNQHxrh/1vaid2F+XpYTSbQf1XA2McOmOpROnvpvMEA4tSjq1cW0sws2gCYxswY6TKkvzYnJq1NHZLnRU4BX+4U0uburvusu8Kv8iHY7qefkM4IFngJHEOUXmLEPgiGsI8YnlZILit3vSSLRTQe/MPIZva5pshNIEmyFQlCvruJKXPkCEfmePzkphXHdzZNQdoRI9KPlBAxlj/I8U97ERPS5bjGbWDFbEdqHVe5caTBeZZx2H/IMvzeN15yoQAAAABJRU5ErkJggg== /* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. */ /* .tab'); var tabNavigation = document.createElement('ul'); tabNavigation.className = 'tab-navigation'; var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ for (var j = 0; j < tabs.length; j++) { var tabId = 'tab-' + i + '-' + j; var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; var tabNavigationItem = document.createElement('li'); tabNavigationItem.setAttribute('data-tab-id', tabId); if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } tabNavigationItem.innerHTML = tabTitle; tabNavigation.appendChild(tabNavigationItem); var tabContent = tabs[j].querySelector('.tab-content'); tabContent.parentElement.setAttribute('id', tabId); } tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); } /* display the active tab and add the 'click' event listeners */ for (i = 0; i < tabGroups.length; i++) { tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li'); for (j = 0; j < tabNavigation.length; j++) { tabId = tabNavigation[j].getAttribute('data-tab-id'); document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; if (hasClass(tabNavigation[j], 'active')) { document.getElementById(tabId).className = 'block'; } else { document.getElementById(tabId).className = 'hidden'; } tabNavigation[j].addEventListener('click', function(e) { var activeTab = e.target || e.srcElement; /* needed because when the tab contains HTML contents, user can click */ /* on any of those elements instead of their parent '
  • ' element */ while (activeTab.tagName.toLowerCase() !== 'li') { activeTab = activeTab.parentNode; } /* get the full list of tabs through the parent of the active tab element */ var tabNavigation = activeTab.parentNode.children; for (var k = 0; k < tabNavigation.length; k++) { var tabId = tabNavigation[k].getAttribute('data-tab-id'); document.getElementById(tabId).className = 'hidden'; removeClass(tabNavigation[k], 'active'); } addClass(activeTab, 'active'); var activeTabId = activeTab.getAttribute('data-tab-id'); document.getElementById(activeTabId).className = 'block'; }); } tabGroups[i].setAttribute('data-processed', 'true'); } })(); (function createToggles() { var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); for (var i = 0; i < toggles.length; i++) { var elementSelector = toggles[i].getAttribute('data-toggle-selector'); var element = document.querySelector(elementSelector); addClass(element, 'sf-toggle-content'); if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') { addClass(toggles[i], 'sf-toggle-on'); addClass(element, 'sf-toggle-visible'); } else { addClass(toggles[i], 'sf-toggle-off'); addClass(element, 'sf-toggle-hidden'); } addEventListener(toggles[i], 'click', function(e) { e.preventDefault(); if ('' !== window.getSelection().toString()) { /* Don't do anything on text selection */ return; } var toggle = e.target || e.srcElement; /* needed because when the toggle contains HTML contents, user can click */ /* on any of those elements instead of their parent '.sf-toggle' element */ while (!hasClass(toggle, 'sf-toggle')) { toggle = toggle.parentNode; } var element = document.querySelector(toggle.getAttribute('data-toggle-selector')); toggleClass(toggle, 'sf-toggle-on'); toggleClass(toggle, 'sf-toggle-off'); toggleClass(element, 'sf-toggle-hidden'); toggleClass(element, 'sf-toggle-visible'); /* the toggle doesn't change its contents when clicking on it */ if (!toggle.hasAttribute('data-toggle-alt-content')) { return; } if (!toggle.hasAttribute('data-toggle-original-content')) { toggle.setAttribute('data-toggle-original-content', toggle.innerHTML); } var currentContent = toggle.innerHTML; var originalContent = toggle.getAttribute('data-toggle-original-content'); var altContent = toggle.getAttribute('data-toggle-alt-content'); toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; }); /* Prevents from disallowing clicks on links inside toggles */ var toggleLinks = toggles[i].querySelectorAll('a'); for (var j = 0; j < toggleLinks.length; j++) { addEventListener(toggleLinks[j], 'click', function(e) { e.stopPropagation(); }); } /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */ var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]'); for (var k = 0; k < copyToClipboardElements.length; k++) { addEventListener(copyToClipboardElements[k], 'click', function(e) { e.stopPropagation(); }); } toggles[i].setAttribute('data-processed', 'true'); } })(); (function createFilters() { document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) { var filters = filter.closest('[data-filters]'), type = 'choice', name = filter.dataset.filter, ucName = name.charAt(0).toUpperCase()+name.slice(1), list = document.createElement('ul'), values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'), labels = {}, defaults = null, indexed = {}, processed = {}; if (typeof values === 'string') { type = 'level'; labels = values.split(','); values = values.toLowerCase().split(','); defaults = values.length - 1; } addClass(list, 'filter-list'); addClass(list, 'filter-list-'+type); values.forEach(function (value, i) { if (value instanceof HTMLElement) { value = value.dataset['filter'+ucName]; } if (value in processed) { return; } var option = document.createElement('li'), label = i in labels ? labels[i] : value, active = false, matches; if ('' === label) { option.innerHTML = '(none)'; } else { option.innerText = label; } option.dataset.filter = value; option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows'); indexed[value] = i; list.appendChild(option); addEventListener(option, 'click', function () { if ('choice' === type) { filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { if (option.dataset.filter === row.dataset['filter'+ucName]) { toggleClass(row, 'filter-hidden-'+name); } }); toggleClass(option, 'active'); } else if ('level' === type) { if (i === this.parentNode.querySelectorAll('.active').length - 1) { return; } this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) { if (j <= i) { addClass(currentOption, 'active'); if (i === j) { addClass(currentOption, 'last-active'); } else { removeClass(currentOption, 'last-active'); } } else { removeClass(currentOption, 'active'); removeClass(currentOption, 'last-active'); } }); filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { if (i < indexed[row.dataset['filter'+ucName]]) { addClass(row, 'filter-hidden-'+name); } else { removeClass(row, 'filter-hidden-'+name); } }); } }); if ('choice' === type) { active = null === defaults || 0 <= defaults.indexOf(value); } else if ('level' === type) { active = i <= defaults; if (active && i === defaults) { addClass(option, 'last-active'); } } if (active) { addClass(option, 'active'); } else { filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) { toggleClass(row, 'filter-hidden-'+name); }); } processed[value] = true; }); if (1 < list.childNodes.length) { filter.appendChild(list); filter.dataset.filtered = ''; } }); })(); })(); /*]]>*/ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Internal; /** * This class has been generated by extract-tentative-return-types.php. * * @internal */ class TentativeTypes { public const RETURN_TYPES = ['CURLFile' => ['getFilename' => 'string', 'getMimeType' => 'string', 'getPostFilename' => 'string', 'setMimeType' => 'void', 'setPostFilename' => 'void'], 'DateTimeInterface' => ['format' => 'string', 'getTimezone' => 'DateTimeZone|false', 'getOffset' => 'int', 'getTimestamp' => 'int', 'diff' => 'DateInterval', '__wakeup' => 'void'], 'DateTime' => ['__wakeup' => 'void', '__set_state' => 'DateTime', 'createFromImmutable' => 'static', 'createFromFormat' => 'DateTime|false', 'getLastErrors' => 'array|false', 'format' => 'string', 'modify' => 'DateTime|false', 'add' => 'DateTime', 'sub' => 'DateTime', 'getTimezone' => 'DateTimeZone|false', 'setTimezone' => 'DateTime', 'getOffset' => 'int', 'setTime' => 'DateTime', 'setDate' => 'DateTime', 'setISODate' => 'DateTime', 'setTimestamp' => 'DateTime', 'getTimestamp' => 'int', 'diff' => 'DateInterval'], 'DateTimeImmutable' => ['__wakeup' => 'void', '__set_state' => 'DateTimeImmutable', 'createFromFormat' => 'DateTimeImmutable|false', 'getLastErrors' => 'array|false', 'format' => 'string', 'getTimezone' => 'DateTimeZone|false', 'getOffset' => 'int', 'getTimestamp' => 'int', 'diff' => 'DateInterval', 'modify' => 'DateTimeImmutable|false', 'add' => 'DateTimeImmutable', 'sub' => 'DateTimeImmutable', 'setTimezone' => 'DateTimeImmutable', 'setTime' => 'DateTimeImmutable', 'setDate' => 'DateTimeImmutable', 'setISODate' => 'DateTimeImmutable', 'setTimestamp' => 'DateTimeImmutable', 'createFromMutable' => 'static'], 'DateTimeZone' => ['getName' => 'string', 'getOffset' => 'int', 'getTransitions' => 'array|false', 'getLocation' => 'array|false', 'listAbbreviations' => 'array', 'listIdentifiers' => 'array', '__wakeup' => 'void', '__set_state' => 'DateTimeZone'], 'DateInterval' => ['createFromDateString' => 'DateInterval|false', 'format' => 'string', '__wakeup' => 'void', '__set_state' => 'DateInterval'], 'DatePeriod' => ['getStartDate' => 'DateTimeInterface', 'getEndDate' => '?DateTimeInterface', 'getDateInterval' => 'DateInterval', 'getRecurrences' => '?int', '__wakeup' => 'void', '__set_state' => 'DatePeriod'], 'DOMNode' => ['C14N' => 'string|false', 'C14NFile' => 'int|false', 'getLineNo' => 'int', 'getNodePath' => '?string', 'hasAttributes' => 'bool', 'hasChildNodes' => 'bool', 'isDefaultNamespace' => 'bool', 'isSameNode' => 'bool', 'isSupported' => 'bool', 'lookupNamespaceURI' => '?string', 'lookupPrefix' => '?string', 'normalize' => 'void'], 'DOMImplementation' => ['getFeature' => 'never', 'hasFeature' => 'bool'], 'DOMDocumentFragment' => ['appendXML' => 'bool'], 'DOMNodeList' => ['count' => 'int'], 'DOMCharacterData' => ['appendData' => 'bool', 'insertData' => 'bool', 'deleteData' => 'bool', 'replaceData' => 'bool'], 'DOMAttr' => ['isId' => 'bool'], 'DOMElement' => ['getAttribute' => 'string', 'getAttributeNS' => 'string', 'getElementsByTagName' => 'DOMNodeList', 'getElementsByTagNameNS' => 'DOMNodeList', 'hasAttribute' => 'bool', 'hasAttributeNS' => 'bool', 'removeAttribute' => 'bool', 'removeAttributeNS' => 'void', 'setAttributeNS' => 'void', 'setIdAttribute' => 'void', 'setIdAttributeNS' => 'void', 'setIdAttributeNode' => 'void'], 'DOMDocument' => ['createComment' => 'DOMComment', 'createDocumentFragment' => 'DOMDocumentFragment', 'createTextNode' => 'DOMText', 'getElementById' => '?DOMElement', 'getElementsByTagName' => 'DOMNodeList', 'getElementsByTagNameNS' => 'DOMNodeList', 'normalizeDocument' => 'void', 'registerNodeClass' => 'bool', 'save' => 'int|false', 'saveHTML' => 'string|false', 'saveHTMLFile' => 'int|false', 'saveXML' => 'string|false', 'schemaValidate' => 'bool', 'schemaValidateSource' => 'bool', 'relaxNGValidate' => 'bool', 'relaxNGValidateSource' => 'bool', 'validate' => 'bool', 'xinclude' => 'int|false'], 'DOMText' => ['isWhitespaceInElementContent' => 'bool', 'isElementContentWhitespace' => 'bool'], 'DOMNamedNodeMap' => ['getNamedItem' => '?DOMNode', 'getNamedItemNS' => '?DOMNode', 'item' => '?DOMNode', 'count' => 'int'], 'DOMXPath' => ['evaluate' => 'mixed', 'query' => 'mixed', 'registerNamespace' => 'bool', 'registerPhpFunctions' => 'void'], 'finfo' => ['file' => 'string|false', 'buffer' => 'string|false'], 'IntlPartsIterator' => ['getBreakIterator' => 'IntlBreakIterator', 'getRuleStatus' => 'int'], 'IntlBreakIterator' => ['createCharacterInstance' => '?IntlBreakIterator', 'createCodePointInstance' => 'IntlCodePointBreakIterator', 'createLineInstance' => '?IntlBreakIterator', 'createSentenceInstance' => '?IntlBreakIterator', 'createTitleInstance' => '?IntlBreakIterator', 'createWordInstance' => '?IntlBreakIterator', 'current' => 'int', 'first' => 'int', 'following' => 'int', 'getErrorCode' => 'int', 'getErrorMessage' => 'string', 'getLocale' => 'string|false', 'getPartsIterator' => 'IntlPartsIterator', 'getText' => '?string', 'isBoundary' => 'bool', 'last' => 'int', 'next' => 'int', 'preceding' => 'int', 'previous' => 'int', 'setText' => '?bool'], 'IntlRuleBasedBreakIterator' => ['getBinaryRules' => 'string|false', 'getRules' => 'string|false', 'getRuleStatus' => 'int', 'getRuleStatusVec' => 'array|false'], 'IntlCodePointBreakIterator' => ['getLastCodePoint' => 'int'], 'IntlCalendar' => ['createInstance' => '?IntlCalendar', 'equals' => 'bool', 'fieldDifference' => 'int|false', 'add' => 'bool', 'after' => 'bool', 'before' => 'bool', 'fromDateTime' => '?IntlCalendar', 'get' => 'int|false', 'getActualMaximum' => 'int|false', 'getActualMinimum' => 'int|false', 'getAvailableLocales' => 'array', 'getDayOfWeekType' => 'int|false', 'getErrorCode' => 'int|false', 'getErrorMessage' => 'string|false', 'getFirstDayOfWeek' => 'int|false', 'getGreatestMinimum' => 'int|false', 'getKeywordValuesForLocale' => 'IntlIterator|false', 'getLeastMaximum' => 'int|false', 'getLocale' => 'string|false', 'getMaximum' => 'int|false', 'getMinimalDaysInFirstWeek' => 'int|false', 'getMinimum' => 'int|false', 'getNow' => 'float', 'getRepeatedWallTimeOption' => 'int', 'getSkippedWallTimeOption' => 'int', 'getTime' => 'float|false', 'getTimeZone' => 'IntlTimeZone|false', 'getType' => 'string', 'getWeekendTransition' => 'int|false', 'inDaylightTime' => 'bool', 'isEquivalentTo' => 'bool', 'isLenient' => 'bool', 'isWeekend' => 'bool', 'roll' => 'bool', 'isSet' => 'bool', 'setTime' => 'bool', 'setTimeZone' => 'bool', 'toDateTime' => 'DateTime|false'], 'IntlGregorianCalendar' => ['setGregorianChange' => 'bool', 'getGregorianChange' => 'float', 'isLeapYear' => 'bool'], 'Collator' => ['create' => '?Collator', 'compare' => 'int|false', 'sort' => 'bool', 'sortWithSortKeys' => 'bool', 'asort' => 'bool', 'getAttribute' => 'int|false', 'setAttribute' => 'bool', 'getStrength' => 'int', 'getLocale' => 'string|false', 'getErrorCode' => 'int|false', 'getErrorMessage' => 'string|false', 'getSortKey' => 'string|false'], 'IntlIterator' => ['current' => 'mixed', 'key' => 'mixed', 'next' => 'void', 'rewind' => 'void', 'valid' => 'bool'], 'UConverter' => ['convert' => 'string|false', 'fromUCallback' => 'string|int|array|null', 'getAliases' => 'array|false|null', 'getAvailable' => 'array', 'getDestinationEncoding' => 'string|false|null', 'getDestinationType' => 'int|false|null', 'getErrorCode' => 'int', 'getErrorMessage' => '?string', 'getSourceEncoding' => 'string|false|null', 'getSourceType' => 'int|false|null', 'getStandards' => '?array', 'getSubstChars' => 'string|false|null', 'reasonText' => 'string', 'setDestinationEncoding' => 'bool', 'setSourceEncoding' => 'bool', 'setSubstChars' => 'bool', 'toUCallback' => 'string|int|array|null', 'transcode' => 'string|false'], 'IntlDateFormatter' => ['create' => '?IntlDateFormatter', 'getDateType' => 'int|false', 'getTimeType' => 'int|false', 'getCalendar' => 'int|false', 'setCalendar' => 'bool', 'getTimeZoneId' => 'string|false', 'getCalendarObject' => 'IntlCalendar|false|null', 'getTimeZone' => 'IntlTimeZone|false', 'setTimeZone' => '?bool', 'setPattern' => 'bool', 'getPattern' => 'string|false', 'getLocale' => 'string|false', 'setLenient' => 'void', 'isLenient' => 'bool', 'format' => 'string|false', 'formatObject' => 'string|false', 'parse' => 'int|float|false', 'localtime' => 'array|false', 'getErrorCode' => 'int', 'getErrorMessage' => 'string'], 'NumberFormatter' => ['create' => '?NumberFormatter', 'format' => 'string|false', 'parse' => 'int|float|false', 'formatCurrency' => 'string|false', 'parseCurrency' => 'float|false', 'setAttribute' => 'bool', 'getAttribute' => 'int|float|false', 'setTextAttribute' => 'bool', 'getTextAttribute' => 'string|false', 'setSymbol' => 'bool', 'getSymbol' => 'string|false', 'setPattern' => 'bool', 'getPattern' => 'string|false', 'getLocale' => 'string|false', 'getErrorCode' => 'int', 'getErrorMessage' => 'string'], 'Locale' => ['getDefault' => 'string', 'getPrimaryLanguage' => '?string', 'getScript' => '?string', 'getRegion' => '?string', 'getKeywords' => 'array|false|null', 'getDisplayScript' => 'string|false', 'getDisplayRegion' => 'string|false', 'getDisplayName' => 'string|false', 'getDisplayLanguage' => 'string|false', 'getDisplayVariant' => 'string|false', 'composeLocale' => 'string|false', 'parseLocale' => '?array', 'getAllVariants' => '?array', 'filterMatches' => '?bool', 'lookup' => '?string', 'canonicalize' => '?string', 'acceptFromHttp' => 'string|false'], 'MessageFormatter' => ['create' => '?MessageFormatter', 'format' => 'string|false', 'formatMessage' => 'string|false', 'parse' => 'array|false', 'parseMessage' => 'array|false', 'setPattern' => 'bool', 'getPattern' => 'string|false', 'getLocale' => 'string', 'getErrorCode' => 'int', 'getErrorMessage' => 'string'], 'Normalizer' => ['normalize' => 'string|false', 'isNormalized' => 'bool', 'getRawDecomposition' => '?string'], 'ResourceBundle' => ['create' => '?ResourceBundle', 'get' => 'mixed', 'count' => 'int', 'getLocales' => 'array|false', 'getErrorCode' => 'int', 'getErrorMessage' => 'string'], 'Spoofchecker' => ['isSuspicious' => 'bool', 'areConfusable' => 'bool', 'setAllowedLocales' => 'void', 'setChecks' => 'void', 'setRestrictionLevel' => 'void'], 'IntlTimeZone' => ['countEquivalentIDs' => 'int|false', 'createDefault' => 'IntlTimeZone', 'createEnumeration' => 'IntlIterator|false', 'createTimeZone' => '?IntlTimeZone', 'createTimeZoneIDEnumeration' => 'IntlIterator|false', 'fromDateTimeZone' => '?IntlTimeZone', 'getCanonicalID' => 'string|false', 'getDisplayName' => 'string|false', 'getDSTSavings' => 'int', 'getEquivalentID' => 'string|false', 'getErrorCode' => 'int|false', 'getErrorMessage' => 'string|false', 'getGMT' => 'IntlTimeZone', 'getID' => 'string|false', 'getOffset' => 'bool', 'getRawOffset' => 'int', 'getRegion' => 'string|false', 'getTZDataVersion' => 'string|false', 'getUnknown' => 'IntlTimeZone', 'getWindowsID' => 'string|false', 'getIDForWindowsID' => 'string|false', 'hasSameRules' => 'bool', 'toDateTimeZone' => 'DateTimeZone|false', 'useDaylightTime' => 'bool'], 'Transliterator' => ['create' => '?Transliterator', 'createFromRules' => '?Transliterator', 'createInverse' => '?Transliterator', 'listIDs' => 'array|false', 'transliterate' => 'string|false', 'getErrorCode' => 'int|false', 'getErrorMessage' => 'string|false'], 'IntlChar' => ['hasBinaryProperty' => '?bool', 'charAge' => '?array', 'charDigitValue' => '?int', 'charDirection' => '?int', 'charFromName' => '?int', 'charMirror' => 'int|string|null', 'charName' => '?string', 'charType' => '?int', 'chr' => '?string', 'digit' => 'int|false|null', 'enumCharNames' => '?bool', 'enumCharTypes' => 'void', 'foldCase' => 'int|string|null', 'forDigit' => 'int', 'getBidiPairedBracket' => 'int|string|null', 'getBlockCode' => '?int', 'getCombiningClass' => '?int', 'getFC_NFKC_Closure' => 'string|false|null', 'getIntPropertyMaxValue' => 'int', 'getIntPropertyMinValue' => 'int', 'getIntPropertyValue' => '?int', 'getNumericValue' => '?float', 'getPropertyEnum' => 'int', 'getPropertyName' => 'string|false', 'getPropertyValueEnum' => 'int', 'getPropertyValueName' => 'string|false', 'getUnicodeVersion' => 'array', 'isalnum' => '?bool', 'isalpha' => '?bool', 'isbase' => '?bool', 'isblank' => '?bool', 'iscntrl' => '?bool', 'isdefined' => '?bool', 'isdigit' => '?bool', 'isgraph' => '?bool', 'isIDIgnorable' => '?bool', 'isIDPart' => '?bool', 'isIDStart' => '?bool', 'isISOControl' => '?bool', 'isJavaIDPart' => '?bool', 'isJavaIDStart' => '?bool', 'isJavaSpaceChar' => '?bool', 'islower' => '?bool', 'isMirrored' => '?bool', 'isprint' => '?bool', 'ispunct' => '?bool', 'isspace' => '?bool', 'istitle' => '?bool', 'isUAlphabetic' => '?bool', 'isULowercase' => '?bool', 'isupper' => '?bool', 'isUUppercase' => '?bool', 'isUWhiteSpace' => '?bool', 'isWhitespace' => '?bool', 'isxdigit' => '?bool', 'ord' => '?int', 'tolower' => 'int|string|null', 'totitle' => 'int|string|null', 'toupper' => 'int|string|null'], 'JsonSerializable' => ['jsonSerialize' => 'mixed'], 'mysqli' => ['autocommit' => 'bool', 'begin_transaction' => 'bool', 'change_user' => 'bool', 'character_set_name' => 'string', 'commit' => 'bool', 'connect' => 'bool', 'dump_debug_info' => 'bool', 'get_charset' => '?object', 'get_client_info' => 'string', 'get_connection_stats' => 'array', 'get_server_info' => 'string', 'get_warnings' => 'mysqli_warning|false', 'kill' => 'bool', 'multi_query' => 'bool', 'more_results' => 'bool', 'next_result' => 'bool', 'ping' => 'bool', 'poll' => 'int|false', 'prepare' => 'mysqli_stmt|false', 'query' => 'mysqli_result|bool', 'real_connect' => 'bool', 'real_escape_string' => 'string', 'reap_async_query' => 'mysqli_result|bool', 'escape_string' => 'string', 'real_query' => 'bool', 'release_savepoint' => 'bool', 'rollback' => 'bool', 'savepoint' => 'bool', 'select_db' => 'bool', 'set_charset' => 'bool', 'options' => 'bool', 'set_opt' => 'bool', 'stat' => 'string|false', 'stmt_init' => 'mysqli_stmt|false', 'store_result' => 'mysqli_result|false', 'thread_safe' => 'bool', 'use_result' => 'mysqli_result|false', 'refresh' => 'bool'], 'mysqli_result' => ['close' => 'void', 'free' => 'void', 'data_seek' => 'bool', 'fetch_field' => 'object|false', 'fetch_fields' => 'array', 'fetch_field_direct' => 'object|false', 'fetch_all' => 'array', 'fetch_array' => 'array|null|false', 'fetch_assoc' => 'array|null|false', 'fetch_object' => 'object|null|false', 'fetch_row' => 'array|null|false', 'field_seek' => 'bool', 'free_result' => 'void'], 'mysqli_stmt' => ['attr_get' => 'int', 'attr_set' => 'bool', 'bind_param' => 'bool', 'bind_result' => 'bool', 'data_seek' => 'void', 'execute' => 'bool', 'fetch' => '?bool', 'get_warnings' => 'mysqli_warning|false', 'result_metadata' => 'mysqli_result|false', 'more_results' => 'bool', 'next_result' => 'bool', 'num_rows' => 'int|string', 'send_long_data' => 'bool', 'free_result' => 'void', 'reset' => 'bool', 'prepare' => 'bool', 'store_result' => 'bool', 'get_result' => 'mysqli_result|false'], 'OCILob' => ['save' => 'bool', 'import' => 'bool', 'saveFile' => 'bool', 'load' => 'string|false', 'read' => 'string|false', 'eof' => 'bool', 'tell' => 'int|false', 'rewind' => 'bool', 'seek' => 'bool', 'size' => 'int|false', 'write' => 'int|false', 'append' => 'bool', 'truncate' => 'bool', 'erase' => 'int|false', 'flush' => 'bool', 'setBuffering' => 'bool', 'getBuffering' => 'bool', 'writeToFile' => 'bool', 'export' => 'bool', 'writeTemporary' => 'bool', 'close' => 'bool', 'free' => 'bool'], 'OCICollection' => ['free' => 'bool', 'append' => 'bool', 'getElem' => 'string|float|null|false', 'assign' => 'bool', 'assignElem' => 'bool', 'size' => 'int|false', 'max' => 'int|false', 'trim' => 'bool'], 'PDO' => ['beginTransaction' => 'bool', 'commit' => 'bool', 'errorCode' => '?string', 'errorInfo' => 'array', 'exec' => 'int|false', 'getAttribute' => 'mixed', 'getAvailableDrivers' => 'array', 'inTransaction' => 'bool', 'lastInsertId' => 'string|false', 'prepare' => 'PDOStatement|false', 'query' => 'PDOStatement|false', 'quote' => 'string|false', 'rollBack' => 'bool', 'setAttribute' => 'bool'], 'PDOStatement' => ['bindColumn' => 'bool', 'bindParam' => 'bool', 'bindValue' => 'bool', 'closeCursor' => 'bool', 'columnCount' => 'int', 'debugDumpParams' => '?bool', 'errorCode' => '?string', 'errorInfo' => 'array', 'execute' => 'bool', 'fetch' => 'mixed', 'fetchAll' => 'array', 'fetchColumn' => 'mixed', 'fetchObject' => 'object|false', 'getAttribute' => 'mixed', 'getColumnMeta' => 'array|false', 'nextRowset' => 'bool', 'rowCount' => 'int', 'setAttribute' => 'bool'], 'PDO_PGSql_Ext' => ['pgsqlCopyFromArray' => 'bool', 'pgsqlCopyFromFile' => 'bool', 'pgsqlCopyToArray' => 'array|false', 'pgsqlCopyToFile' => 'bool', 'pgsqlLOBCreate' => 'string|false', 'pgsqlLOBUnlink' => 'bool', 'pgsqlGetNotify' => 'array|false', 'pgsqlGetPid' => 'int'], 'PDO_SQLite_Ext' => ['sqliteCreateFunction' => 'bool', 'sqliteCreateAggregate' => 'bool', 'sqliteCreateCollation' => 'bool'], 'Phar' => ['addEmptyDir' => 'void', 'addFile' => 'void', 'addFromString' => 'void', 'buildFromDirectory' => 'array', 'buildFromIterator' => 'array', 'compressFiles' => 'void', 'compress' => '?Phar', 'decompress' => '?Phar', 'convertToExecutable' => '?Phar', 'convertToData' => '?PharData', 'count' => 'int', 'extractTo' => 'bool', 'getAlias' => '?string', 'getPath' => 'string', 'getMetadata' => 'mixed', 'getModified' => 'bool', 'getSignature' => 'array|false', 'getStub' => 'string', 'getVersion' => 'string', 'hasMetadata' => 'bool', 'isBuffering' => 'bool', 'isCompressed' => 'int|false', 'isFileFormat' => 'bool', 'isWritable' => 'bool', 'offsetExists' => 'bool', 'offsetGet' => 'SplFileInfo', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'setAlias' => 'bool', 'setDefaultStub' => 'bool', 'setMetadata' => 'void', 'setSignatureAlgorithm' => 'void', 'startBuffering' => 'void', 'stopBuffering' => 'void'], 'PharData' => ['addEmptyDir' => 'void', 'addFile' => 'void', 'addFromString' => 'void', 'buildFromDirectory' => 'array', 'buildFromIterator' => 'array', 'compressFiles' => 'void', 'compress' => '?PharData', 'decompress' => '?PharData', 'convertToExecutable' => '?Phar', 'convertToData' => '?PharData', 'count' => 'int', 'extractTo' => 'bool', 'getAlias' => '?string', 'getPath' => 'string', 'getMetadata' => 'mixed', 'getModified' => 'bool', 'getSignature' => 'array|false', 'getStub' => 'string', 'getVersion' => 'string', 'hasMetadata' => 'bool', 'isBuffering' => 'bool', 'isCompressed' => 'int|false', 'isFileFormat' => 'bool', 'isWritable' => 'bool', 'offsetExists' => 'bool', 'offsetGet' => 'SplFileInfo', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'setAlias' => 'bool', 'setDefaultStub' => 'bool', 'setMetadata' => 'void', 'setSignatureAlgorithm' => 'void', 'startBuffering' => 'void', 'stopBuffering' => 'void'], 'PharFileInfo' => ['chmod' => 'void', 'getCompressedSize' => 'int', 'getCRC32' => 'int', 'getContent' => 'string', 'getMetadata' => 'mixed', 'getPharFlags' => 'int', 'hasMetadata' => 'bool', 'isCompressed' => 'bool', 'isCRCChecked' => 'bool', 'setMetadata' => 'void'], 'Reflection' => ['getModifierNames' => 'array'], 'ReflectionFunctionAbstract' => ['inNamespace' => 'bool', 'isClosure' => 'bool', 'isDeprecated' => 'bool', 'isInternal' => 'bool', 'isUserDefined' => 'bool', 'isGenerator' => 'bool', 'isVariadic' => 'bool', 'isStatic' => 'bool', 'getClosureThis' => '?object', 'getClosureCalledClass' => '?ReflectionClass', 'getClosureScopeClass' => '?ReflectionClass', 'getDocComment' => 'string|false', 'getEndLine' => 'int|false', 'getExtension' => '?ReflectionExtension', 'getExtensionName' => 'string|false', 'getFileName' => 'string|false', 'getName' => 'string', 'getNamespaceName' => 'string', 'getNumberOfParameters' => 'int', 'getNumberOfRequiredParameters' => 'int', 'getParameters' => 'array', 'getShortName' => 'string', 'getStartLine' => 'int|false', 'getStaticVariables' => 'array', 'returnsReference' => 'bool', 'hasReturnType' => 'bool', 'getReturnType' => '?ReflectionType'], 'ReflectionFunction' => ['isDisabled' => 'bool', 'invoke' => 'mixed', 'invokeArgs' => 'mixed', 'getClosure' => 'Closure', 'getExecutingLine' => 'int', 'getExecutingFile' => 'string', 'getTrace' => 'array', 'getFunction' => 'ReflectionFunctionAbstract', 'getThis' => '?object', 'getExecutingGenerator' => 'Generator'], 'ReflectionMethod' => ['isPublic' => 'bool', 'isPrivate' => 'bool', 'isProtected' => 'bool', 'isAbstract' => 'bool', 'isFinal' => 'bool', 'isConstructor' => 'bool', 'isDestructor' => 'bool', 'getClosure' => 'Closure', 'getModifiers' => 'int', 'invoke' => 'mixed', 'invokeArgs' => 'mixed', 'getDeclaringClass' => 'ReflectionClass', 'getPrototype' => 'ReflectionMethod', 'setAccessible' => 'void'], 'ReflectionClass' => ['getName' => 'string', 'isInternal' => 'bool', 'isUserDefined' => 'bool', 'isAnonymous' => 'bool', 'isInstantiable' => 'bool', 'isCloneable' => 'bool', 'getFileName' => 'string|false', 'getStartLine' => 'int|false', 'getEndLine' => 'int|false', 'getDocComment' => 'string|false', 'getConstructor' => '?ReflectionMethod', 'hasMethod' => 'bool', 'getMethod' => 'ReflectionMethod', 'getMethods' => 'array', 'hasProperty' => 'bool', 'getProperty' => 'ReflectionProperty', 'getProperties' => 'array', 'hasConstant' => 'bool', 'getConstants' => 'array', 'getReflectionConstants' => 'array', 'getConstant' => 'mixed', 'getReflectionConstant' => 'ReflectionClassConstant|false', 'getInterfaces' => 'array', 'getInterfaceNames' => 'array', 'isInterface' => 'bool', 'getTraits' => 'array', 'getTraitNames' => 'array', 'getTraitAliases' => 'array', 'isTrait' => 'bool', 'isAbstract' => 'bool', 'isFinal' => 'bool', 'getModifiers' => 'int', 'isInstance' => 'bool', 'newInstance' => 'object', 'newInstanceWithoutConstructor' => 'object', 'newInstanceArgs' => '?object', 'getParentClass' => 'ReflectionClass|false', 'isSubclassOf' => 'bool', 'getStaticProperties' => '?array', 'getStaticPropertyValue' => 'mixed', 'setStaticPropertyValue' => 'void', 'getDefaultProperties' => 'array', 'isIterable' => 'bool', 'isIterateable' => 'bool', 'implementsInterface' => 'bool', 'getExtension' => '?ReflectionExtension', 'getExtensionName' => 'string|false', 'inNamespace' => 'bool', 'getNamespaceName' => 'string', 'getShortName' => 'string'], 'ReflectionProperty' => ['getName' => 'string', 'getValue' => 'mixed', 'setValue' => 'void', 'isInitialized' => 'bool', 'isPublic' => 'bool', 'isPrivate' => 'bool', 'isProtected' => 'bool', 'isStatic' => 'bool', 'isDefault' => 'bool', 'getModifiers' => 'int', 'getDeclaringClass' => 'ReflectionClass', 'getDocComment' => 'string|false', 'setAccessible' => 'void', 'getType' => '?ReflectionType', 'hasType' => 'bool', 'getDefaultValue' => 'mixed'], 'ReflectionClassConstant' => ['getName' => 'string', 'getValue' => 'mixed', 'isPublic' => 'bool', 'isPrivate' => 'bool', 'isProtected' => 'bool', 'getModifiers' => 'int', 'getDeclaringClass' => 'ReflectionClass', 'getDocComment' => 'string|false'], 'ReflectionParameter' => ['getName' => 'string', 'isPassedByReference' => 'bool', 'canBePassedByValue' => 'bool', 'getDeclaringFunction' => 'ReflectionFunctionAbstract', 'getDeclaringClass' => '?ReflectionClass', 'getClass' => '?ReflectionClass', 'hasType' => 'bool', 'getType' => '?ReflectionType', 'isArray' => 'bool', 'isCallable' => 'bool', 'allowsNull' => 'bool', 'getPosition' => 'int', 'isOptional' => 'bool', 'isDefaultValueAvailable' => 'bool', 'getDefaultValue' => 'mixed', 'isDefaultValueConstant' => 'bool', 'getDefaultValueConstantName' => '?string', 'isVariadic' => 'bool'], 'ReflectionType' => ['allowsNull' => 'bool'], 'ReflectionNamedType' => ['getName' => 'string', 'isBuiltin' => 'bool'], 'ReflectionExtension' => ['getName' => 'string', 'getVersion' => '?string', 'getFunctions' => 'array', 'getConstants' => 'array', 'getINIEntries' => 'array', 'getClasses' => 'array', 'getClassNames' => 'array', 'getDependencies' => 'array', 'info' => 'void', 'isPersistent' => 'bool', 'isTemporary' => 'bool'], 'ReflectionZendExtension' => ['getName' => 'string', 'getVersion' => 'string', 'getAuthor' => 'string', 'getURL' => 'string', 'getCopyright' => 'string'], 'SessionHandlerInterface' => ['open' => 'bool', 'close' => 'bool', 'read' => 'string|false', 'write' => 'bool', 'destroy' => 'bool', 'gc' => 'int|false'], 'SessionIdInterface' => ['create_sid' => 'string'], 'SessionUpdateTimestampHandlerInterface' => ['validateId' => 'bool', 'updateTimestamp' => 'bool'], 'SessionHandler' => ['open' => 'bool', 'close' => 'bool', 'read' => 'string|false', 'write' => 'bool', 'destroy' => 'bool', 'gc' => 'int|false', 'create_sid' => 'string'], 'SimpleXMLElement' => ['xpath' => 'array|null|false', 'registerXPathNamespace' => 'bool', 'asXML' => 'string|bool', 'saveXML' => 'string|bool', 'getNamespaces' => 'array', 'getDocNamespaces' => 'array|false', 'children' => '?SimpleXMLElement', 'attributes' => '?SimpleXMLElement', 'addChild' => '?SimpleXMLElement', 'addAttribute' => 'void', 'getName' => 'string', 'count' => 'int', 'rewind' => 'void', 'valid' => 'bool', 'current' => 'SimpleXMLElement', 'key' => 'string', 'next' => 'void', 'hasChildren' => 'bool', 'getChildren' => '?SimpleXMLElement'], 'SNMP' => ['close' => 'bool', 'setSecurity' => 'bool', 'get' => 'mixed', 'getnext' => 'mixed', 'walk' => 'array|false', 'set' => 'bool', 'getErrno' => 'int', 'getError' => 'string'], 'SoapServer' => ['fault' => 'void', 'addSoapHeader' => 'void', 'setPersistence' => 'void', 'setClass' => 'void', 'setObject' => 'void', 'getFunctions' => 'array', 'addFunction' => 'void', 'handle' => 'void'], 'SoapClient' => ['__call' => 'mixed', '__soapCall' => 'mixed', '__getFunctions' => '?array', '__getTypes' => '?array', '__getLastRequest' => '?string', '__getLastResponse' => '?string', '__getLastRequestHeaders' => '?string', '__getLastResponseHeaders' => '?string', '__doRequest' => '?string', '__setCookie' => 'void', '__getCookies' => 'array', '__setSoapHeaders' => 'bool', '__setLocation' => '?string'], 'ArrayObject' => ['offsetExists' => 'bool', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'append' => 'void', 'getArrayCopy' => 'array', 'count' => 'int', 'getFlags' => 'int', 'setFlags' => 'void', 'asort' => 'bool', 'ksort' => 'bool', 'uasort' => 'bool', 'uksort' => 'bool', 'natsort' => 'bool', 'natcasesort' => 'bool', 'unserialize' => 'void', 'serialize' => 'string', '__serialize' => 'array', '__unserialize' => 'void', 'getIterator' => 'Iterator', 'exchangeArray' => 'array', 'setIteratorClass' => 'void', 'getIteratorClass' => 'string', '__debugInfo' => 'array'], 'ArrayIterator' => ['offsetExists' => 'bool', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'append' => 'void', 'getArrayCopy' => 'array', 'count' => 'int', 'getFlags' => 'int', 'setFlags' => 'void', 'asort' => 'bool', 'ksort' => 'bool', 'uasort' => 'bool', 'uksort' => 'bool', 'natsort' => 'bool', 'natcasesort' => 'bool', 'unserialize' => 'void', 'serialize' => 'string', '__serialize' => 'array', '__unserialize' => 'void', 'rewind' => 'void', 'current' => 'mixed', 'key' => 'string|int|null', 'next' => 'void', 'valid' => 'bool', 'seek' => 'void', '__debugInfo' => 'array'], 'RecursiveArrayIterator' => ['hasChildren' => 'bool', 'getChildren' => '?RecursiveArrayIterator'], 'SplFileInfo' => ['getPath' => 'string', 'getFilename' => 'string', 'getExtension' => 'string', 'getBasename' => 'string', 'getPathname' => 'string', 'getPerms' => 'int|false', 'getInode' => 'int|false', 'getSize' => 'int|false', 'getOwner' => 'int|false', 'getGroup' => 'int|false', 'getATime' => 'int|false', 'getMTime' => 'int|false', 'getCTime' => 'int|false', 'getType' => 'string|false', 'isWritable' => 'bool', 'isReadable' => 'bool', 'isExecutable' => 'bool', 'isFile' => 'bool', 'isDir' => 'bool', 'isLink' => 'bool', 'getLinkTarget' => 'string|false', 'getRealPath' => 'string|false', 'getFileInfo' => 'SplFileInfo', 'getPathInfo' => '?SplFileInfo', 'openFile' => 'SplFileObject', 'setFileClass' => 'void', 'setInfoClass' => 'void', '__debugInfo' => 'array', '_bad_state_ex' => 'void'], 'DirectoryIterator' => ['getFilename' => 'string', 'getExtension' => 'string', 'getBasename' => 'string', 'isDot' => 'bool', 'rewind' => 'void', 'valid' => 'bool', 'key' => 'mixed', 'current' => 'mixed', 'next' => 'void', 'seek' => 'void'], 'FilesystemIterator' => ['rewind' => 'void', 'key' => 'string', 'current' => 'string|SplFileInfo|FilesystemIterator', 'getFlags' => 'int', 'setFlags' => 'void'], 'RecursiveDirectoryIterator' => ['hasChildren' => 'bool', 'getChildren' => 'RecursiveDirectoryIterator', 'getSubPath' => 'string', 'getSubPathname' => 'string'], 'GlobIterator' => ['count' => 'int'], 'SplFileObject' => ['rewind' => 'void', 'eof' => 'bool', 'valid' => 'bool', 'fgets' => 'string', 'fread' => 'string|false', 'fgetcsv' => 'array|false', 'fputcsv' => 'int|false', 'setCsvControl' => 'void', 'getCsvControl' => 'array', 'flock' => 'bool', 'fflush' => 'bool', 'ftell' => 'int|false', 'fseek' => 'int', 'fgetc' => 'string|false', 'fpassthru' => 'int', 'fscanf' => 'array|int|null', 'fwrite' => 'int|false', 'fstat' => 'array', 'ftruncate' => 'bool', 'current' => 'string|array|false', 'key' => 'int', 'next' => 'void', 'setFlags' => 'void', 'getFlags' => 'int', 'setMaxLineLen' => 'void', 'getMaxLineLen' => 'int', 'hasChildren' => 'false', 'getChildren' => 'null', 'seek' => 'void', 'getCurrentLine' => 'string'], 'SplDoublyLinkedList' => ['add' => 'void', 'pop' => 'mixed', 'shift' => 'mixed', 'push' => 'void', 'unshift' => 'void', 'top' => 'mixed', 'bottom' => 'mixed', '__debugInfo' => 'array', 'count' => 'int', 'isEmpty' => 'bool', 'setIteratorMode' => 'int', 'getIteratorMode' => 'int', 'offsetExists' => 'bool', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'rewind' => 'void', 'current' => 'mixed', 'key' => 'int', 'prev' => 'void', 'next' => 'void', 'valid' => 'bool', 'unserialize' => 'void', 'serialize' => 'string', '__serialize' => 'array', '__unserialize' => 'void'], 'SplQueue' => ['enqueue' => 'void', 'dequeue' => 'mixed'], 'SplFixedArray' => ['__wakeup' => 'void', 'count' => 'int', 'toArray' => 'array', 'fromArray' => 'SplFixedArray', 'getSize' => 'int', 'offsetExists' => 'bool', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void'], 'SplPriorityQueue' => ['compare' => 'int', 'setExtractFlags' => 'int', 'top' => 'mixed', 'extract' => 'mixed', 'count' => 'int', 'isEmpty' => 'bool', 'rewind' => 'void', 'current' => 'mixed', 'key' => 'int', 'next' => 'void', 'valid' => 'bool', 'isCorrupted' => 'bool', 'getExtractFlags' => 'int', '__debugInfo' => 'array'], 'SplHeap' => ['extract' => 'mixed', 'insert' => 'bool', 'top' => 'mixed', 'count' => 'int', 'isEmpty' => 'bool', 'rewind' => 'void', 'current' => 'mixed', 'key' => 'int', 'next' => 'void', 'valid' => 'bool', 'recoverFromCorruption' => 'bool', 'compare' => 'int', 'isCorrupted' => 'bool', '__debugInfo' => 'array'], 'SplMinHeap' => ['compare' => 'int'], 'SplMaxHeap' => ['compare' => 'int'], 'EmptyIterator' => ['current' => 'never', 'next' => 'void', 'key' => 'never', 'valid' => 'false', 'rewind' => 'void'], 'CallbackFilterIterator' => ['accept' => 'bool'], 'RecursiveCallbackFilterIterator' => ['hasChildren' => 'bool', 'getChildren' => 'RecursiveCallbackFilterIterator'], 'RecursiveIterator' => ['hasChildren' => 'bool', 'getChildren' => '?RecursiveIterator'], 'RecursiveIteratorIterator' => ['rewind' => 'void', 'valid' => 'bool', 'key' => 'mixed', 'current' => 'mixed', 'next' => 'void', 'getDepth' => 'int', 'getSubIterator' => '?RecursiveIterator', 'getInnerIterator' => 'RecursiveIterator', 'beginIteration' => 'void', 'endIteration' => 'void', 'callHasChildren' => 'bool', 'callGetChildren' => '?RecursiveIterator', 'beginChildren' => 'void', 'endChildren' => 'void', 'nextElement' => 'void', 'setMaxDepth' => 'void', 'getMaxDepth' => 'int|false'], 'OuterIterator' => ['getInnerIterator' => '?Iterator'], 'IteratorIterator' => ['getInnerIterator' => '?Iterator', 'rewind' => 'void', 'valid' => 'bool', 'key' => 'mixed', 'current' => 'mixed', 'next' => 'void'], 'FilterIterator' => ['accept' => 'bool', 'rewind' => 'void', 'next' => 'void'], 'RecursiveFilterIterator' => ['hasChildren' => 'bool', 'getChildren' => '?RecursiveFilterIterator'], 'ParentIterator' => ['accept' => 'bool'], 'SeekableIterator' => ['seek' => 'void'], 'LimitIterator' => ['rewind' => 'void', 'valid' => 'bool', 'next' => 'void', 'seek' => 'int', 'getPosition' => 'int'], 'CachingIterator' => ['rewind' => 'void', 'valid' => 'bool', 'next' => 'void', 'hasNext' => 'bool', 'getFlags' => 'int', 'setFlags' => 'void', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'offsetExists' => 'bool', 'getCache' => 'array', 'count' => 'int'], 'RecursiveCachingIterator' => ['hasChildren' => 'bool', 'getChildren' => '?RecursiveCachingIterator'], 'NoRewindIterator' => ['rewind' => 'void', 'valid' => 'bool', 'key' => 'mixed', 'current' => 'mixed', 'next' => 'void'], 'AppendIterator' => ['append' => 'void', 'rewind' => 'void', 'valid' => 'bool', 'current' => 'mixed', 'next' => 'void', 'getIteratorIndex' => '?int', 'getArrayIterator' => 'ArrayIterator'], 'InfiniteIterator' => ['next' => 'void'], 'RegexIterator' => ['accept' => 'bool', 'getMode' => 'int', 'setMode' => 'void', 'getFlags' => 'int', 'setFlags' => 'void', 'getRegex' => 'string', 'getPregFlags' => 'int', 'setPregFlags' => 'void'], 'RecursiveRegexIterator' => ['accept' => 'bool', 'hasChildren' => 'bool', 'getChildren' => 'RecursiveRegexIterator'], 'RecursiveTreeIterator' => ['key' => 'mixed', 'current' => 'mixed', 'getPrefix' => 'string', 'setPostfix' => 'void', 'setPrefixPart' => 'void', 'getEntry' => 'string', 'getPostfix' => 'string'], 'SplObserver' => ['update' => 'void'], 'SplSubject' => ['attach' => 'void', 'detach' => 'void', 'notify' => 'void'], 'SplObjectStorage' => ['attach' => 'void', 'detach' => 'void', 'contains' => 'bool', 'addAll' => 'int', 'removeAll' => 'int', 'removeAllExcept' => 'int', 'getInfo' => 'mixed', 'setInfo' => 'void', 'count' => 'int', 'rewind' => 'void', 'valid' => 'bool', 'key' => 'int', 'current' => 'object', 'next' => 'void', 'unserialize' => 'void', 'serialize' => 'string', 'offsetExists' => 'bool', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void', 'getHash' => 'string', '__serialize' => 'array', '__unserialize' => 'void', '__debugInfo' => 'array'], 'MultipleIterator' => ['getFlags' => 'int', 'setFlags' => 'void', 'attachIterator' => 'void', 'detachIterator' => 'void', 'containsIterator' => 'bool', 'countIterators' => 'int', 'rewind' => 'void', 'valid' => 'bool', 'key' => 'array', 'current' => 'array', 'next' => 'void', '__debugInfo' => 'array'], 'SQLite3' => ['open' => 'void', 'version' => 'array', 'lastInsertRowID' => 'int', 'lastErrorCode' => 'int', 'lastExtendedErrorCode' => 'int', 'lastErrorMsg' => 'string', 'changes' => 'int', 'busyTimeout' => 'bool', 'loadExtension' => 'bool', 'backup' => 'bool', 'escapeString' => 'string', 'prepare' => 'SQLite3Stmt|false', 'exec' => 'bool', 'query' => 'SQLite3Result|false', 'querySingle' => 'mixed', 'createFunction' => 'bool', 'createAggregate' => 'bool', 'createCollation' => 'bool', 'enableExceptions' => 'bool', 'enableExtendedResultCodes' => 'bool', 'setAuthorizer' => 'bool'], 'SQLite3Stmt' => ['bindParam' => 'bool', 'bindValue' => 'bool', 'clear' => 'bool', 'close' => 'bool', 'execute' => 'SQLite3Result|false', 'getSQL' => 'string|false', 'paramCount' => 'int', 'readOnly' => 'bool', 'reset' => 'bool'], 'SQLite3Result' => ['numColumns' => 'int', 'columnName' => 'string|false', 'columnType' => 'int|false', 'fetchArray' => 'array|false', 'reset' => 'bool'], 'Directory' => ['close' => 'void', 'rewind' => 'void', 'read' => 'string|false'], 'php_user_filter' => ['filter' => 'int', 'onCreate' => 'bool', 'onClose' => 'void'], 'tidy' => ['getOpt' => 'string|int|bool', 'cleanRepair' => 'bool', 'parseFile' => 'bool', 'parseString' => 'bool', 'repairString' => 'string|false', 'repairFile' => 'string|false', 'diagnose' => 'bool', 'getRelease' => 'string', 'getConfig' => 'array', 'getStatus' => 'int', 'getHtmlVer' => 'int', 'getOptDoc' => 'string|false', 'isXhtml' => 'bool', 'isXml' => 'bool', 'root' => '?tidyNode', 'head' => '?tidyNode', 'html' => '?tidyNode', 'body' => '?tidyNode'], 'XMLReader' => ['getAttribute' => '?string', 'getAttributeNo' => '?string', 'getAttributeNs' => '?string', 'getParserProperty' => 'bool', 'isValid' => 'bool', 'lookupNamespace' => '?string', 'moveToAttribute' => 'bool', 'moveToAttributeNo' => 'bool', 'moveToAttributeNs' => 'bool', 'moveToElement' => 'bool', 'moveToFirstAttribute' => 'bool', 'moveToNextAttribute' => 'bool', 'read' => 'bool', 'next' => 'bool', 'readInnerXml' => 'string', 'readOuterXml' => 'string', 'readString' => 'string', 'setSchema' => 'bool', 'setParserProperty' => 'bool', 'setRelaxNGSchema' => 'bool', 'setRelaxNGSchemaSource' => 'bool', 'expand' => 'DOMNode|false'], 'XMLWriter' => ['openUri' => 'bool', 'openMemory' => 'bool', 'setIndent' => 'bool', 'setIndentString' => 'bool', 'startComment' => 'bool', 'endComment' => 'bool', 'startAttribute' => 'bool', 'endAttribute' => 'bool', 'writeAttribute' => 'bool', 'startAttributeNs' => 'bool', 'writeAttributeNs' => 'bool', 'startElement' => 'bool', 'endElement' => 'bool', 'fullEndElement' => 'bool', 'startElementNs' => 'bool', 'writeElement' => 'bool', 'writeElementNs' => 'bool', 'startPi' => 'bool', 'endPi' => 'bool', 'writePi' => 'bool', 'startCdata' => 'bool', 'endCdata' => 'bool', 'writeCdata' => 'bool', 'text' => 'bool', 'writeRaw' => 'bool', 'startDocument' => 'bool', 'endDocument' => 'bool', 'writeComment' => 'bool', 'startDtd' => 'bool', 'endDtd' => 'bool', 'writeDtd' => 'bool', 'startDtdElement' => 'bool', 'endDtdElement' => 'bool', 'writeDtdElement' => 'bool', 'startDtdAttlist' => 'bool', 'endDtdAttlist' => 'bool', 'writeDtdAttlist' => 'bool', 'startDtdEntity' => 'bool', 'endDtdEntity' => 'bool', 'writeDtdEntity' => 'bool', 'outputMemory' => 'string', 'flush' => 'string|int'], 'XSLTProcessor' => ['importStylesheet' => 'bool', 'transformToDoc' => 'DOMDocument|false', 'transformToUri' => 'int', 'transformToXml' => 'string|null|false', 'setParameter' => 'bool', 'getParameter' => 'string|false', 'removeParameter' => 'bool', 'hasExsltSupport' => 'bool', 'registerPHPFunctions' => 'void', 'setSecurityPrefs' => 'int', 'getSecurityPrefs' => 'int'], 'ZipArchive' => ['open' => 'bool|int', 'setPassword' => 'bool', 'close' => 'bool', 'count' => 'int', 'getStatusString' => 'string', 'addEmptyDir' => 'bool', 'addFromString' => 'bool', 'addFile' => 'bool', 'replaceFile' => 'bool', 'addGlob' => 'array|false', 'addPattern' => 'array|false', 'renameIndex' => 'bool', 'renameName' => 'bool', 'setArchiveComment' => 'bool', 'getArchiveComment' => 'string|false', 'setCommentIndex' => 'bool', 'setCommentName' => 'bool', 'setMtimeIndex' => 'bool', 'setMtimeName' => 'bool', 'getCommentIndex' => 'string|false', 'getCommentName' => 'string|false', 'deleteIndex' => 'bool', 'deleteName' => 'bool', 'statName' => 'array|false', 'statIndex' => 'array|false', 'locateName' => 'int|false', 'getNameIndex' => 'string|false', 'unchangeArchive' => 'bool', 'unchangeAll' => 'bool', 'unchangeIndex' => 'bool', 'unchangeName' => 'bool', 'extractTo' => 'bool', 'getFromName' => 'string|false', 'getFromIndex' => 'string|false', 'setExternalAttributesName' => 'bool', 'setExternalAttributesIndex' => 'bool', 'getExternalAttributesName' => 'bool', 'getExternalAttributesIndex' => 'bool', 'setCompressionName' => 'bool', 'setCompressionIndex' => 'bool', 'setEncryptionName' => 'bool', 'setEncryptionIndex' => 'bool', 'registerProgressCallback' => 'bool', 'registerCancelCallback' => 'bool'], 'Exception' => ['__wakeup' => 'void'], 'Error' => ['__wakeup' => 'void'], 'IteratorAggregate' => ['getIterator' => 'Traversable'], 'Iterator' => ['current' => 'mixed', 'next' => 'void', 'key' => 'mixed', 'valid' => 'bool', 'rewind' => 'void'], 'ArrayAccess' => ['offsetExists' => 'bool', 'offsetGet' => 'mixed', 'offsetSet' => 'void', 'offsetUnset' => 'void'], 'Countable' => ['count' => 'int']]; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler; /** * Registers all the debug tools. * * @author Fabien Potencier */ class Debug { public static function enable() : ErrorHandler { \error_reporting(-1); if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true)) { \ini_set('display_errors', 0); } elseif (!\filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log')) { // CLI - display errors only if they're not already logged to STDERR \ini_set('display_errors', 1); } @\ini_set('zend.assertions', 1); \ini_set('assert.active', 1); \ini_set('assert.exception', 1); DebugClassLoader::enable(); return ErrorHandler::register(new ErrorHandler(new BufferingLogger(), \true)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\FlattenException; use _ContaoManager\Symfony\Component\HttpFoundation\Request; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\Serializer\Exception\NotEncodableValueException; use _ContaoManager\Symfony\Component\Serializer\SerializerInterface; /** * Formats an exception using Serializer for rendering. * * @author Nicolas Grekas */ class SerializerErrorRenderer implements ErrorRendererInterface { private $serializer; private $format; private $fallbackErrorRenderer; private $debug; /** * @param string|callable(FlattenException) $format The format as a string or a callable that should return it * formats not supported by Request::getMimeTypes() should be given as mime types * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it */ public function __construct(SerializerInterface $serializer, $format, ?ErrorRendererInterface $fallbackErrorRenderer = null, $debug = \false) { if (!\is_string($format) && !\is_callable($format)) { throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($format))); } if (!\is_bool($debug) && !\is_callable($debug)) { throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); } $this->serializer = $serializer; $this->format = $format; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); $this->debug = $debug; } /** * {@inheritdoc} */ public function render(\Throwable $exception) : FlattenException { $headers = ['Vary' => 'Accept']; $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); if ($debug) { $headers['X-Debug-Exception'] = \rawurlencode($exception->getMessage()); $headers['X-Debug-Exception-File'] = \rawurlencode($exception->getFile()) . ':' . $exception->getLine(); } $flattenException = FlattenException::createFromThrowable($exception, null, $headers); try { $format = \is_string($this->format) ? $this->format : ($this->format)($flattenException); $headers['Content-Type'] = Request::getMimeTypes($format)[0] ?? $format; $flattenException->setAsString($this->serializer->serialize($flattenException, $format, ['exception' => $exception, 'debug' => $debug])); } catch (NotEncodableValueException $e) { $flattenException = $this->fallbackErrorRenderer->render($exception); } return $flattenException->setHeaders($flattenException->getHeaders() + $headers); } public static function getPreferredFormat(RequestStack $requestStack) : \Closure { return static function () use($requestStack) { if (!($request = $requestStack->getCurrentRequest())) { throw new NotEncodableValueException(); } return $request->getPreferredFormat(); }; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\FlattenException; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; // Help opcache.preload discover always-needed symbols \class_exists(CliDumper::class); /** * @author Nicolas Grekas */ class CliErrorRenderer implements ErrorRendererInterface { /** * {@inheritdoc} */ public function render(\Throwable $exception) : FlattenException { $cloner = new VarCloner(); $dumper = new class extends CliDumper { protected function supportsColors() : bool { $outputStream = $this->outputStream; $this->outputStream = \fopen('php://stdout', 'w'); try { return parent::supportsColors(); } finally { $this->outputStream = $outputStream; } } }; return FlattenException::createFromThrowable($exception)->setAsString($dumper->dump($cloner->cloneVar($exception), \true)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\FlattenException; /** * Formats an exception to be used as response content. * * @author Yonel Ceruto */ interface ErrorRendererInterface { /** * Renders a Throwable as a FlattenException. */ public function render(\Throwable $exception) : FlattenException; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\FlattenException; use _ContaoManager\Symfony\Component\HttpFoundation\RequestStack; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use _ContaoManager\Symfony\Component\HttpKernel\Log\DebugLoggerInterface; /** * @author Yonel Ceruto */ class HtmlErrorRenderer implements ErrorRendererInterface { private const GHOST_ADDONS = ['02-14' => self::GHOST_HEART, '02-29' => self::GHOST_PLUS, '10-18' => self::GHOST_GIFT]; private const GHOST_GIFT = 'M124.00534057617188,5.3606138080358505 C124.40059661865234,4.644828304648399 125.1237564086914,3.712414965033531 123.88127899169922,3.487462028861046 C123.53517150878906,3.3097832053899765 123.18894958496094,2.9953975528478622 122.8432846069336,3.345616325736046 C122.07421112060547,3.649444565176964 121.40750122070312,4.074306473135948 122.2164306640625,4.869479164481163 C122.57514953613281,5.3830065578222275 122.90142822265625,6.503447040915489 123.3077621459961,6.626829609274864 C123.55027770996094,6.210384353995323 123.7774658203125,5.785196766257286 124.00534057617188,5.3606138080358505 zM122.30630493164062,7.336987480521202 C121.60028076171875,6.076864704489708 121.03211975097656,4.72498320043087 120.16796875,3.562500938773155 C119.11695098876953,2.44033907353878 117.04605865478516,2.940566048026085 116.57544708251953,4.387995228171349 C115.95028686523438,5.819030746817589 117.2991714477539,7.527640804648399 118.826171875,7.348545059561729 C119.98493194580078,7.367936596274376 121.15027618408203,7.420116886496544 122.30630493164062,7.336987480521202 zM128.1732177734375,7.379541382193565 C129.67486572265625,7.17823551595211 130.53842163085938,5.287807449698448 129.68344116210938,4.032590612769127 C128.92578125,2.693056806921959 126.74605560302734,2.6463639587163925 125.98509216308594,4.007616028189659 C125.32617950439453,5.108129009604454 124.75428009033203,6.258124336600304 124.14962768554688,7.388818249106407 C125.48638916015625,7.465229496359825 126.8357162475586,7.447416767477989 128.1732177734375,7.379541382193565 zM130.6601104736328,8.991325363516808 C131.17202758789062,8.540884003043175 133.1543731689453,8.009847149252892 131.65304565429688,7.582054600119591 C131.2811279296875,7.476506695151329 130.84751892089844,6.99234913289547 130.5132598876953,7.124847874045372 C129.78744506835938,8.02728746831417 128.67140197753906,8.55669592320919 127.50616455078125,8.501235947012901 C127.27806091308594,8.576229080557823 126.11459350585938,8.38720129430294 126.428955078125,8.601900085806847 C127.25099182128906,9.070617660880089 128.0523223876953,9.579657539725304 128.902587890625,9.995706543326378 C129.49813842773438,9.678531631827354 130.0761260986328,9.329126343131065 130.6601104736328,8.991325363516808 zM118.96446990966797,9.246344551444054 C119.4022445678711,8.991325363516808 119.84001922607422,8.736305221915245 120.27779388427734,8.481284126639366 C118.93965911865234,8.414779648184776 117.40827941894531,8.607666000723839 116.39698791503906,7.531384453177452 C116.11186981201172,7.212117180228233 115.83845520019531,6.846597656607628 115.44329071044922,7.248530372977257 C114.96995544433594,7.574637398123741 113.5140609741211,7.908811077475548 114.63501739501953,8.306883797049522 C115.61112976074219,8.883499130606651 116.58037567138672,9.474181160330772 117.58061218261719,10.008124336600304 C118.05723571777344,9.784612640738487 118.50651550292969,9.5052699893713 118.96446990966797,9.246344551444054 zM125.38018035888672,12.091858848929405 C125.9474868774414,11.636047348380089 127.32159423828125,11.201767906546593 127.36749267578125,10.712632164359093 C126.08487701416016,9.974547371268272 124.83960723876953,9.152772888541222 123.49772644042969,8.528907760977745 C123.03594207763672,8.353693947196007 122.66152954101562,8.623294815421104 122.28982543945312,8.857431396842003 C121.19065856933594,9.51122473180294 120.06505584716797,10.12446115911007 119.00167083740234,10.835315689444542 C120.39238739013672,11.69529627263546 121.79983520507812,12.529837593436241 123.22095489501953,13.338589653372765 C123.94580841064453,12.932025894522667 124.66128540039062,12.508862480521202 125.38018035888672,12.091858848929405 zM131.07164001464844,13.514615997672081 C131.66018676757812,13.143282875418663 132.2487335205078,12.771927818655968 132.8372802734375,12.400571808218956 C132.8324737548828,11.156818374991417 132.8523406982422,9.912529930472374 132.81829833984375,8.669195160269737 C131.63046264648438,9.332009300589561 130.45948791503906,10.027913078665733 129.30828857421875,10.752535805106163 C129.182373046875,12.035354599356651 129.24623107910156,13.33940313756466 129.27359008789062,14.628684982657433 C129.88104248046875,14.27079389989376 130.4737548828125,13.888019546866417 131.07164001464844,13.514640793204308 zM117.26847839355469,12.731024727225304 C117.32825469970703,11.67083452641964 117.45709991455078,10.46224020421505 116.17853546142578,10.148179039359093 C115.37110900878906,9.77159021794796 114.25194549560547,8.806716904044151 113.62991333007812,8.81639002263546 C113.61052703857422,10.0110072940588 113.62078857421875,11.20585821568966 113.61869049072266,12.400571808218956 C114.81139373779297,13.144886955618858 115.98292541503906,13.925040230154991 117.20137023925781,14.626662239432335 C117.31951141357422,14.010867103934288 117.24227905273438,13.35805033147335 117.26847839355469,12.731024727225304 zM125.80937957763672,16.836034759879112 C126.51483917236328,16.390663132071495 127.22030639648438,15.945291504263878 127.92576599121094,15.49991987645626 C127.92250061035156,14.215868934988976 127.97560119628906,12.929980263113976 127.91757202148438,11.647302612662315 C127.14225769042969,11.869626984000206 126.25550079345703,12.556857094168663 125.43866729736328,12.983742699027061 C124.82704162597656,13.342005714774132 124.21542358398438,13.700271591544151 123.60379028320312,14.05853746831417 C123.61585235595703,15.429577812552452 123.57081604003906,16.803131088614464 123.64839172363281,18.172149643301964 C124.37957000732422,17.744937881827354 125.09130859375,17.284801468253136 125.80937957763672,16.836034759879112 zM122.8521499633789,16.115344032645226 C122.8521499633789,15.429741844534874 122.8521499633789,14.744139656424522 122.8521499633789,14.05853746831417 C121.43595123291016,13.230924591422081 120.02428436279297,12.395455345511436 118.60256958007812,11.577354416251183 C118.52394104003906,12.888403877615929 118.56887817382812,14.204405769705772 118.55702209472656,15.517732605338097 C119.97289276123047,16.4041957706213 121.37410736083984,17.314891800284386 122.80789947509766,18.172149643301964 C122.86368560791016,17.488990768790245 122.84332275390625,16.800363525748253 122.8521499633789,16.115344032645226 zM131.10684204101562,18.871450409293175 C131.68399047851562,18.48711584508419 132.2611541748047,18.10278509557247 132.8383026123047,17.718475326895714 C132.81423950195312,16.499977096915245 132.89776611328125,15.264989838004112 132.77627563476562,14.05993078649044 C131.5760040283203,14.744719490408897 130.41763305664062,15.524359688162804 129.23875427246094,16.255397781729698 C129.26707458496094,17.516149505972862 129.18060302734375,18.791316971182823 129.3108367919922,20.041303619742393 C129.91973876953125,19.667551025748253 130.51010131835938,19.264152511954308 131.10684204101562,18.871450409293175 zM117.2557373046875,18.188333496451378 C117.25104522705078,17.549470886588097 117.24633026123047,16.91058538854122 117.24163055419922,16.271720871329308 C116.04924774169922,15.525708183646202 114.87187957763672,14.75476549565792 113.66158294677734,14.038097366690636 C113.5858383178711,15.262084946036339 113.62901306152344,16.49083898961544 113.61761474609375,17.717010483145714 C114.82051086425781,18.513254150748253 116.00987243652344,19.330610260367393 117.22888946533203,20.101993545889854 C117.27559661865234,19.466014847159386 117.25241088867188,18.825733169913292 117.2557373046875,18.188333496451378 zM125.8398666381836,22.38675306737423 C126.54049682617188,21.921453461050987 127.24110412597656,21.456151947379112 127.94172668457031,20.99083136022091 C127.94009399414062,19.693386062979698 127.96646118164062,18.395381912589073 127.93160247802734,17.098379120230675 C126.50540924072266,17.97775076329708 125.08877563476562,18.873308166861534 123.68258666992188,19.78428266942501 C123.52366638183594,21.03710363805294 123.626708984375,22.32878302037716 123.62647247314453,23.595300659537315 C124.06291198730469,23.86113165318966 125.1788101196289,22.68297766149044 125.8398666381836,22.38675306737423 zM122.8521499633789,21.83134649693966 C122.76741790771484,20.936696991324425 123.21651458740234,19.67745779454708 122.0794677734375,19.330633148550987 C120.93280029296875,18.604360565543175 119.7907485961914,17.870157226920128 118.62899780273438,17.16818617284298 C118.45966339111328,18.396427139639854 118.63676452636719,19.675991043448448 118.50668334960938,20.919256195425987 C119.89984130859375,21.92635916173458 121.32942199707031,22.88914106786251 122.78502655029297,23.803510650992393 C122.90177917480469,23.1627406924963 122.82917022705078,22.48402212560177 122.8521499633789,21.83134649693966 zM117.9798355102539,21.59483526647091 C116.28416442871094,20.46288488805294 114.58848571777344,19.330957397818565 112.892822265625,18.199007019400597 C112.89473724365234,14.705654129385948 112.84647369384766,11.211485847830772 112.90847778320312,7.718807205557823 C113.7575912475586,7.194885239005089 114.66117858886719,6.765397056937218 115.5350341796875,6.284702762961388 C114.97061157226562,4.668964847922325 115.78496551513672,2.7054970115423203 117.42159271240234,2.1007001250982285 C118.79354095458984,1.537783369421959 120.44731903076172,2.0457767099142075 121.32200622558594,3.23083733022213 C121.95732116699219,2.9050118774175644 122.59264373779297,2.5791852325201035 123.22796630859375,2.253336176276207 C123.86669921875,2.5821153968572617 124.50543975830078,2.9108948558568954 125.1441650390625,3.23967407643795 C126.05941009521484,2.154020771384239 127.62747192382812,1.5344576686620712 128.986328125,2.1429056972265244 C130.61741638183594,2.716217741370201 131.50650024414062,4.675290569663048 130.9215545654297,6.2884936183691025 C131.8018341064453,6.78548763692379 132.7589111328125,7.1738648265600204 133.5660400390625,7.780336365103722 C133.60182189941406,11.252970680594444 133.56637573242188,14.726140961050987 133.5631103515625,18.199007019400597 C130.18914794921875,20.431867584586143 126.86984252929688,22.74994657933712 123.44108581542969,24.897907242178917 C122.44406127929688,24.897628769278526 121.5834732055664,23.815067276358604 120.65831756591797,23.37616156041622 C119.76387023925781,22.784828171133995 118.87168884277344,22.19007681310177 117.9798355102539,21.59483526647091 z'; private const GHOST_HEART = 'M125.91386369681868,8.305165958366445 C128.95033202169043,-0.40540639102854037 140.8469835342744,8.305165958366445 125.91386369681868,19.504526138305664 C110.98208663272044,8.305165958366445 122.87795231771452,-0.40540639102854037 125.91386369681868,8.305165958366445 z'; private const GHOST_PLUS = 'M111.36824226379395,8.969108581542969 L118.69175148010254,8.969108581542969 L118.69175148010254,1.6455793380737305 L126.20429420471191,1.6455793380737305 L126.20429420471191,8.969108581542969 L133.52781105041504,8.969108581542969 L133.52781105041504,16.481630325317383 L126.20429420471191,16.481630325317383 L126.20429420471191,23.805158615112305 L118.69175148010254,23.805158615112305 L118.69175148010254,16.481630325317383 L111.36824226379395,16.481630325317383 z'; private $debug; private $charset; private $fileLinkFormat; private $projectDir; private $outputBuffer; private $logger; private static $template = 'views/error.html.php'; /** * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it * @param string|FileLinkFormatter|null $fileLinkFormat * @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it */ public function __construct($debug = \false, ?string $charset = null, $fileLinkFormat = null, ?string $projectDir = null, $outputBuffer = '', ?LoggerInterface $logger = null) { if (!\is_bool($debug) && !\is_callable($debug)) { throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); } if (!\is_string($outputBuffer) && !\is_callable($outputBuffer)) { throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($outputBuffer))); } $this->debug = $debug; $this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8'); $this->fileLinkFormat = $fileLinkFormat ?: (\ini_get('xdebug.file_link_format') ?: \get_cfg_var('xdebug.file_link_format')); $this->projectDir = $projectDir; $this->outputBuffer = $outputBuffer; $this->logger = $logger; } /** * {@inheritdoc} */ public function render(\Throwable $exception) : FlattenException { $headers = ['Content-Type' => 'text/html; charset=' . $this->charset]; if (\is_bool($this->debug) ? $this->debug : ($this->debug)($exception)) { $headers['X-Debug-Exception'] = \rawurlencode($exception->getMessage()); $headers['X-Debug-Exception-File'] = \rawurlencode($exception->getFile()) . ':' . $exception->getLine(); } $exception = FlattenException::createFromThrowable($exception, null, $headers); return $exception->setAsString($this->renderException($exception)); } /** * Gets the HTML content associated with the given exception. */ public function getBody(FlattenException $exception) : string { return $this->renderException($exception, 'views/exception.html.php'); } /** * Gets the stylesheet associated with the given exception. */ public function getStylesheet() : string { if (!$this->debug) { return $this->include('assets/css/error.css'); } return $this->include('assets/css/exception.css'); } public static function isDebug(RequestStack $requestStack, bool $debug) : \Closure { return static function () use($requestStack, $debug) : bool { if (!($request = $requestStack->getCurrentRequest())) { return $debug; } return $debug && $request->attributes->getBoolean('showException', \true); }; } public static function getAndCleanOutputBuffer(RequestStack $requestStack) : \Closure { return static function () use($requestStack) : string { if (!($request = $requestStack->getCurrentRequest())) { return ''; } $startObLevel = $request->headers->get('X-Php-Ob-Level', -1); if (\ob_get_level() <= $startObLevel) { return ''; } Response::closeOutputBuffers($startObLevel + 1, \true); return \ob_get_clean(); }; } private function renderException(FlattenException $exception, string $debugTemplate = 'views/exception_full.html.php') : string { $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); $statusText = $this->escape($exception->getStatusText()); $statusCode = $this->escape($exception->getStatusCode()); if (!$debug) { return $this->include(self::$template, ['statusText' => $statusText, 'statusCode' => $statusCode]); } $exceptionMessage = $this->escape($exception->getMessage()); return $this->include($debugTemplate, ['exception' => $exception, 'exceptionMessage' => $exceptionMessage, 'statusText' => $statusText, 'statusCode' => $statusCode, 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, 'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)()]); } /** * Formats an array as a string. */ private function formatArgs(array $args) : string { $result = []; foreach ($args as $key => $item) { if ('object' === $item[0]) { $formattedValue = \sprintf('object(%s)', $this->abbrClass($item[1])); } elseif ('array' === $item[0]) { $formattedValue = \sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); } elseif ('null' === $item[0]) { $formattedValue = 'null'; } elseif ('boolean' === $item[0]) { $formattedValue = '' . \strtolower(\var_export($item[1], \true)) . ''; } elseif ('resource' === $item[0]) { $formattedValue = 'resource'; } elseif (\preg_match('/[^\\x07-\\x0D\\x1B\\x20-\\xFF]/', $item[1])) { $formattedValue = 'binary string'; } else { $formattedValue = \str_replace("\n", '', $this->escape(\var_export($item[1], \true))); } $result[] = \is_int($key) ? $formattedValue : \sprintf("'%s' => %s", $this->escape($key), $formattedValue); } return \implode(', ', $result); } private function formatArgsAsText(array $args) { return \strip_tags($this->formatArgs($args)); } private function escape(string $string) : string { return \htmlspecialchars($string, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); } private function abbrClass(string $class) : string { $parts = \explode('\\', $class); $short = \array_pop($parts); return \sprintf('%s', $class, $short); } private function getFileRelative(string $file) : ?string { $file = \str_replace('\\', '/', $file); if (null !== $this->projectDir && 0 === \strpos($file, $this->projectDir)) { return \ltrim(\substr($file, \strlen($this->projectDir)), '/'); } return null; } /** * Returns the link for a given file/line pair. * * @return string|false */ private function getFileLink(string $file, int $line) { if ($fmt = $this->fileLinkFormat) { return \is_string($fmt) ? \strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); } return \false; } /** * Formats a file path. * * @param string $file An absolute file path * @param int $line The line number * @param string $text Use this text for the link rather than the file path */ private function formatFile(string $file, int $line, ?string $text = null) : string { $file = \trim($file); if (null === $text) { $text = $file; if (null !== ($rel = $this->getFileRelative($text))) { $rel = \explode('/', $rel, 2); $text = \sprintf('%s%s', $this->projectDir, $rel[0], '/' . ($rel[1] ?? '')); } } if (0 < $line) { $text .= ' at line ' . $line; } if (\false !== ($link = $this->getFileLink($file, $line))) { return \sprintf('%s', $this->escape($link), $text); } return $text; } /** * Returns an excerpt of a code file around the given line number. * * @param string $file A file path * @param int $line The selected line number * @param int $srcContext The number of displayed lines around or -1 for the whole file */ private function fileExcerpt(string $file, int $line, int $srcContext = 3) : string { return ''; if (\is_file($file) && \is_readable($file)) { // highlight_file could throw warnings // see https://bugs.php.net/25725 $code = @\highlight_file($file, \true); if (\PHP_VERSION_ID >= 80300) { // remove main pre/code tags $code = \preg_replace('#^\\s*(.*)\\s*#s', '\\1', $code); // split multiline code tags $code = \preg_replace_callback('#]++)>((?:[^<]*+\\n)++[^<]*+)#', function ($m) { return "" . \str_replace("\n", "\n", $m[2]) . ''; }, $code); // Convert spaces to html entities to preserve indentation when rendered $code = \str_replace(' ', ' ', $code); $content = \explode("\n", $code); } else { // remove main code/span tags $code = \preg_replace('#^\\s*(.*)\\s*#s', '\\1', $code); // split multiline spans $code = \preg_replace_callback('#]++)>((?:[^<]*+
    )++[^<]*+)
    #', function ($m) { return "" . \str_replace('
    ', "

    ", $m[2]) . ''; }, $code); $content = \explode('
    ', $code); } $lines = []; if (0 > $srcContext) { $srcContext = \count($content); } for ($i = \max($line - $srcContext, 1), $max = \min($line + $srcContext, \count($content)); $i <= $max; ++$i) { $lines[] = '' . $this->fixCodeMarkup($content[$i - 1]) . '
  • '; } return '
      ' . \implode("\n", $lines) . '
    '; } return ''; } private function fixCodeMarkup(string $line) { // ending tag from previous line $opening = \strpos($line, ''); if (\false !== $closing && (\false === $opening || $closing < $opening)) { $line = \substr_replace($line, '', $closing, 7); } // missing tag at the end of line $opening = \strrpos($line, ''); if (\false !== $opening && (\false === $closing || $closing < $opening)) { $line .= ''; } return \trim($line); } private function formatFileFromText(string $text) { return \preg_replace_callback('/in ("|")?(.+?)\\1(?: +(?:on|at))? +line (\\d+)/s', function ($match) { return 'in ' . $this->formatFile($match[2], $match[3]); }, $text); } private function formatLogMessage(string $message, array $context) { if ($context && \false !== \strpos($message, '{')) { $replacements = []; foreach ($context as $key => $val) { if (\is_scalar($val)) { $replacements['{' . $key . '}'] = $val; } } if ($replacements) { $message = \strtr($message, $replacements); } } return $this->escape($message); } private function addElementToGhost() : string { if (!isset(self::GHOST_ADDONS[\date('m-d')])) { return ''; } return ''; } private function include(string $name, array $context = []) : string { \extract($context, \EXTR_SKIP); \ob_start(); include \is_file(\dirname(__DIR__) . '/Resources/' . $name) ? \dirname(__DIR__) . '/Resources/' . $name : $name; return \trim(\ob_get_clean()); } /** * Allows overriding the default non-debug template. * * @param string $template path to the custom template file to render */ public static function setTemplate(string $template) : void { self::$template = $template; } } ErrorHandler Component ====================== The ErrorHandler component provides tools to manage errors and ease debugging PHP code. Getting Started --------------- ``` $ composer require symfony/error-handler ``` ```php use Symfony\Component\ErrorHandler\Debug; use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\ErrorHandler\DebugClassLoader; Debug::enable(); // or enable only one feature //ErrorHandler::register(); //DebugClassLoader::enable(); // If you want a custom generic template when debug is not enabled // HtmlErrorRenderer::setTemplate('/path/to/custom/error.html.php'); $data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) { // if any code executed inside this anonymous function fails, a PHP exception // will be thrown, even if the code uses the '@' PHP silence operator $data = json_decode(file_get_contents($filename), true); $data['read_at'] = date($datetimeFormat); file_put_contents($filename, json_encode($data)); return $data; }); ``` Resources --------- * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler; use Composer\InstalledVersions; use _ContaoManager\Doctrine\Common\Persistence\Proxy as LegacyProxy; use _ContaoManager\Doctrine\Persistence\Proxy; use _ContaoManager\Mockery\MockInterface; use _ContaoManager\Phake\IMock; use _ContaoManager\PHPUnit\Framework\MockObject\Matcher\StatelessInvocation; use _ContaoManager\PHPUnit\Framework\MockObject\MockObject; use _ContaoManager\Prophecy\Prophecy\ProphecySubjectInterface; use _ContaoManager\ProxyManager\Proxy\ProxyInterface; use _ContaoManager\Symfony\Component\ErrorHandler\Internal\TentativeTypes; use _ContaoManager\Symfony\Component\HttpClient\HttplugClient; /** * Autoloader checking if the class is really defined in the file found. * * The ClassLoader will wrap all registered autoloaders * and will throw an exception if a file is found but does * not declare the class. * * It can also patch classes to turn docblocks into actual return types. * This behavior is controlled by the SYMFONY_PATCH_TYPE_DECLARATIONS env var, * which is a url-encoded array with the follow parameters: * - "force": any value enables deprecation notices - can be any of: * - "phpdoc" to patch only docblock annotations * - "2" to add all possible return types * - "1" to add return types but only to tests/final/internal/private methods * - "php": the target version of PHP - e.g. "7.1" doesn't generate "object" types * - "deprecations": "1" to trigger a deprecation notice when a child class misses a * return type while the parent declares an "@return" annotation * * Note that patching doesn't care about any coding style so you'd better to run * php-cs-fixer after, with rules "phpdoc_trim_consecutive_blank_line_separation" * and "no_superfluous_phpdoc_tags" enabled typically. * * @author Fabien Potencier * @author Christophe Coevoet * @author Nicolas Grekas * @author Guilhem Niot */ class DebugClassLoader { private const SPECIAL_RETURN_TYPES = ['void' => 'void', 'null' => 'null', 'resource' => 'resource', 'boolean' => 'bool', 'true' => 'true', 'false' => 'false', 'integer' => 'int', 'array' => 'array', 'bool' => 'bool', 'callable' => 'callable', 'float' => 'float', 'int' => 'int', 'iterable' => 'iterable', 'object' => 'object', 'string' => 'string', 'self' => 'self', 'parent' => 'parent', 'mixed' => 'mixed', 'static' => 'static', '$this' => 'static', 'list' => 'array', 'class-string' => 'string', 'never' => 'never']; private const BUILTIN_RETURN_TYPES = ['void' => \true, 'array' => \true, 'false' => \true, 'bool' => \true, 'callable' => \true, 'float' => \true, 'int' => \true, 'iterable' => \true, 'object' => \true, 'string' => \true, 'self' => \true, 'parent' => \true, 'mixed' => \true, 'static' => \true, 'null' => \true, 'true' => \true, 'never' => \true]; private const MAGIC_METHODS = ['__isset' => 'bool', '__sleep' => 'array', '__toString' => 'string', '__debugInfo' => 'array', '__serialize' => 'array']; private $classLoader; private $isFinder; private $loaded = []; private $patchTypes; private static $caseCheck; private static $checkedClasses = []; private static $final = []; private static $finalMethods = []; private static $deprecated = []; private static $internal = []; private static $internalMethods = []; private static $annotatedParameters = []; private static $darwinCache = ['/' => ['/', []]]; private static $method = []; private static $returnTypes = []; private static $methodTraits = []; private static $fileOffsets = []; public function __construct(callable $classLoader) { $this->classLoader = $classLoader; $this->isFinder = \is_array($classLoader) && \method_exists($classLoader[0], 'findFile'); \parse_str(\getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes); $this->patchTypes += ['force' => null, 'php' => \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION, 'deprecations' => \PHP_VERSION_ID >= 70400]; if ('phpdoc' === $this->patchTypes['force']) { $this->patchTypes['force'] = 'docblock'; } if (!isset(self::$caseCheck)) { $file = \is_file(__FILE__) ? __FILE__ : \rtrim(\realpath('.'), \DIRECTORY_SEPARATOR); $i = \strrpos($file, \DIRECTORY_SEPARATOR); $dir = \substr($file, 0, 1 + $i); $file = \substr($file, 1 + $i); $test = \strtoupper($file) === $file ? \strtolower($file) : \strtoupper($file); $test = \realpath($dir . $test); if (\false === $test || \false === $i) { // filesystem is case sensitive self::$caseCheck = 0; } elseif (\substr($test, -\strlen($file)) === $file) { // filesystem is case insensitive and realpath() normalizes the case of characters self::$caseCheck = 1; } elseif ('Darwin' === \PHP_OS_FAMILY) { // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters self::$caseCheck = 2; } else { // filesystem case checks failed, fallback to disabling them self::$caseCheck = 0; } } } public function getClassLoader() : callable { return $this->classLoader; } /** * Wraps all autoloaders. */ public static function enable() : void { // Ensures we don't hit https://bugs.php.net/42098 \class_exists(\_ContaoManager\Symfony\Component\ErrorHandler\ErrorHandler::class); \class_exists(\_ContaoManager\Psr\Log\LogLevel::class); if (!\is_array($functions = \spl_autoload_functions())) { return; } foreach ($functions as $function) { \spl_autoload_unregister($function); } foreach ($functions as $function) { if (!\is_array($function) || !$function[0] instanceof self) { $function = [new static($function), 'loadClass']; } \spl_autoload_register($function); } } /** * Disables the wrapping. */ public static function disable() : void { if (!\is_array($functions = \spl_autoload_functions())) { return; } foreach ($functions as $function) { \spl_autoload_unregister($function); } foreach ($functions as $function) { if (\is_array($function) && $function[0] instanceof self) { $function = $function[0]->getClassLoader(); } \spl_autoload_register($function); } } public static function checkClasses() : bool { if (!\is_array($functions = \spl_autoload_functions())) { return \false; } $loader = null; foreach ($functions as $function) { if (\is_array($function) && $function[0] instanceof self) { $loader = $function[0]; break; } } if (null === $loader) { return \false; } static $offsets = ['get_declared_interfaces' => 0, 'get_declared_traits' => 0, 'get_declared_classes' => 0]; foreach ($offsets as $getSymbols => $i) { $symbols = $getSymbols(); for (; $i < \count($symbols); ++$i) { if (!\is_subclass_of($symbols[$i], MockObject::class) && !\is_subclass_of($symbols[$i], ProphecySubjectInterface::class) && !\is_subclass_of($symbols[$i], Proxy::class) && !\is_subclass_of($symbols[$i], ProxyInterface::class) && !\is_subclass_of($symbols[$i], LegacyProxy::class) && !\is_subclass_of($symbols[$i], MockInterface::class) && !\is_subclass_of($symbols[$i], IMock::class)) { $loader->checkClass($symbols[$i]); } } $offsets[$getSymbols] = $i; } return \true; } public function findFile(string $class) : ?string { return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null; } /** * Loads the given class or interface. * * @throws \RuntimeException */ public function loadClass(string $class) : void { $e = \error_reporting(\error_reporting() | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR); try { if ($this->isFinder && !isset($this->loaded[$class])) { $this->loaded[$class] = \true; if (!($file = $this->classLoader[0]->findFile($class) ?: '')) { // no-op } elseif (\function_exists('opcache_is_script_cached') && @\opcache_is_script_cached($file)) { include $file; return; } elseif (\false === (include $file)) { return; } } else { ($this->classLoader)($class); $file = ''; } } finally { \error_reporting($e); } $this->checkClass($class, $file); } private function checkClass(string $class, ?string $file = null) : void { $exists = null === $file || \class_exists($class, \false) || \interface_exists($class, \false) || \trait_exists($class, \false); if (null !== $file && $class && '\\' === $class[0]) { $class = \substr($class, 1); } if ($exists) { if (isset(self::$checkedClasses[$class])) { return; } self::$checkedClasses[$class] = \true; $refl = new \ReflectionClass($class); if (null === $file && $refl->isInternal()) { return; } $name = $refl->getName(); if ($name !== $class && 0 === \strcasecmp($name, $class)) { throw new \RuntimeException(\sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); } $deprecations = $this->checkAnnotations($refl, $name); foreach ($deprecations as $message) { @\trigger_error($message, \E_USER_DEPRECATED); } } if (!$file) { return; } if (!$exists) { if (\false !== \strpos($class, '/')) { throw new \RuntimeException(\sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\\" in PHP, not "/".', $class)); } throw new \RuntimeException(\sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); } if (self::$caseCheck && ($message = $this->checkCase($refl, $file, $class))) { throw new \RuntimeException(\sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2])); } } public function checkAnnotations(\ReflectionClass $refl, string $class) : array { if ('Symfony\\Bridge\\PhpUnit\\Legacy\\SymfonyTestsListenerForV7' === $class || 'Symfony\\Bridge\\PhpUnit\\Legacy\\SymfonyTestsListenerForV6' === $class) { return []; } $deprecations = []; $className = \false !== \strpos($class, "@anonymous\x00") ? ((\get_parent_class($class) ?: \key(\class_implements($class))) ?: 'class') . '@anonymous' : $class; // Don't trigger deprecations for classes in the same vendor if ($class !== $className) { $vendor = \preg_match('/^namespace ([^;\\\\\\s]++)[;\\\\]/m', @\file_get_contents($refl->getFileName()), $vendor) ? $vendor[1] . '\\' : ''; $vendorLen = \strlen($vendor); } elseif (2 > ($vendorLen = 1 + (\strpos($class, '\\') ?: \strpos($class, '_')))) { $vendorLen = 0; $vendor = ''; } else { $vendor = \str_replace('_', '\\', \substr($class, 0, $vendorLen)); } $parent = \get_parent_class($class) ?: null; self::$returnTypes[$class] = []; $classIsTemplate = \false; // Detect annotations on the class if ($doc = $this->parsePhpDoc($refl)) { $classIsTemplate = isset($doc['template']); foreach (['final', 'deprecated', 'internal'] as $annotation) { if (null !== ($description = $doc[$annotation][0] ?? null)) { self::${$annotation}[$class] = '' !== $description ? ' ' . $description . (\preg_match('/[.!]$/', $description) ? '' : '.') : '.'; } } if ($refl->isInterface() && isset($doc['method'])) { foreach ($doc['method'] as $name => [$static, $returnType, $signature, $description]) { self::$method[$class][] = [$class, $static, $returnType, $name . $signature, $description]; if ('' !== $returnType) { $this->setReturnType($returnType, $refl->name, $name, $refl->getFileName(), $parent); } } } } $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent); if ($parent) { $parentAndOwnInterfaces[$parent] = $parent; if (!isset(self::$checkedClasses[$parent])) { $this->checkClass($parent); } if (isset(self::$final[$parent])) { $deprecations[] = \sprintf('The "%s" class is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $className); } } // Detect if the parent is annotated foreach ($parentAndOwnInterfaces + \class_uses($class, \false) as $use) { if (!isset(self::$checkedClasses[$use])) { $this->checkClass($use); } if (isset(self::$deprecated[$use]) && \strncmp($vendor, \str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class]) && !(HttplugClient::class === $class && \in_array($use, [\_ContaoManager\Http\Client\HttpClient::class, \_ContaoManager\Http\Message\RequestFactory::class, \_ContaoManager\Http\Message\StreamFactory::class, \_ContaoManager\Http\Message\UriFactory::class], \true))) { $type = \class_exists($class, \false) ? 'class' : (\interface_exists($class, \false) ? 'interface' : 'trait'); $verb = \class_exists($use, \false) || \interface_exists($class, \false) ? 'extends' : (\interface_exists($use, \false) ? 'implements' : 'uses'); $deprecations[] = \sprintf('The "%s" %s %s "%s" that is deprecated%s', $className, $type, $verb, $use, self::$deprecated[$use]); } if (isset(self::$internal[$use]) && \strncmp($vendor, \str_replace('_', '\\', $use), $vendorLen)) { $deprecations[] = \sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, \class_exists($use, \false) ? 'class' : (\interface_exists($use, \false) ? 'interface' : 'trait'), self::$internal[$use], $className); } if (isset(self::$method[$use])) { if ($refl->isAbstract()) { if (isset(self::$method[$class])) { self::$method[$class] = \array_merge(self::$method[$class], self::$method[$use]); } else { self::$method[$class] = self::$method[$use]; } } elseif (!$refl->isInterface()) { if (!\strncmp($vendor, \str_replace('_', '\\', $use), $vendorLen) && 0 === \strpos($className, 'Symfony\\') && (!\class_exists(InstalledVersions::class) || 'symfony/symfony' !== InstalledVersions::getRootPackage()['name'])) { // skip "same vendor" @method deprecations for Symfony\* classes unless symfony/symfony is being tested continue; } $hasCall = $refl->hasMethod('__call'); $hasStaticCall = $refl->hasMethod('__callStatic'); foreach (self::$method[$use] as [$interface, $static, $returnType, $name, $description]) { if ($static ? $hasStaticCall : $hasCall) { continue; } $realName = \substr($name, 0, \strpos($name, '(')); if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || $static && !$methodRefl->isStatic() || !$static && $methodRefl->isStatic()) { $deprecations[] = \sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '') . $interface, $name, $returnType ? ': ' . $returnType : '', null === $description ? '.' : ': ' . $description); } } } } } if (\trait_exists($class)) { $file = $refl->getFileName(); foreach ($refl->getMethods() as $method) { if ($method->getFileName() === $file) { self::$methodTraits[$file][$method->getStartLine()] = $class; } } return $deprecations; } // Inherit @final, @internal, @param and @return annotations for methods self::$finalMethods[$class] = []; self::$internalMethods[$class] = []; self::$annotatedParameters[$class] = []; foreach ($parentAndOwnInterfaces as $use) { foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes'] as $property) { if (isset(self::${$property}[$use])) { self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; } } if (null !== (TentativeTypes::RETURN_TYPES[$use] ?? null)) { foreach (TentativeTypes::RETURN_TYPES[$use] as $method => $returnType) { $returnType = \explode('|', $returnType); foreach ($returnType as $i => $t) { if ('?' !== $t && !isset(self::BUILTIN_RETURN_TYPES[$t])) { $returnType[$i] = '\\' . $t; } } $returnType = \implode('|', $returnType); self::$returnTypes[$class] += [$method => [$returnType, 0 === \strpos($returnType, '?') ? \substr($returnType, 1) . '|null' : $returnType, $use, '']]; } } } foreach ($refl->getMethods() as $method) { if ($method->class !== $class) { continue; } if (null === ($ns = self::$methodTraits[$method->getFileName()][$method->getStartLine()] ?? null)) { $ns = $vendor; $len = $vendorLen; } elseif (2 > ($len = 1 + (\strpos($ns, '\\') ?: \strpos($ns, '_')))) { $len = 0; $ns = ''; } else { $ns = \str_replace('_', '\\', \substr($ns, 0, $len)); } if ($parent && isset(self::$finalMethods[$parent][$method->name])) { [$declaringClass, $message] = self::$finalMethods[$parent][$method->name]; $deprecations[] = \sprintf('The "%s::%s()" method is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className); } if (isset(self::$internalMethods[$class][$method->name])) { [$declaringClass, $message] = self::$internalMethods[$class][$method->name]; if (\strncmp($ns, $declaringClass, $len)) { $deprecations[] = \sprintf('The "%s::%s()" method is considered internal%s It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className); } } // To read method annotations $doc = $this->parsePhpDoc($method); if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) { unset($doc['return']); } if (isset(self::$annotatedParameters[$class][$method->name])) { $definedParameters = []; foreach ($method->getParameters() as $parameter) { $definedParameters[$parameter->name] = \true; } foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) { if (!isset($definedParameters[$parameterName]) && !isset($doc['param'][$parameterName])) { $deprecations[] = \sprintf($deprecation, $className); } } } $forcePatchTypes = $this->patchTypes['force']; if ($canAddReturnType = null !== $forcePatchTypes && \false === \strpos($method->getFileName(), \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR)) { if ('void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) { $this->patchTypes['force'] = $forcePatchTypes ?: 'docblock'; } $canAddReturnType = 2 === (int) $forcePatchTypes || \false !== \stripos($method->getFileName(), \DIRECTORY_SEPARATOR . 'Tests' . \DIRECTORY_SEPARATOR) || $refl->isFinal() || $method->isFinal() || $method->isPrivate() || '.' === (self::$internal[$class] ?? null) && !$refl->isAbstract() || '.' === (self::$final[$class] ?? null) || '' === ($doc['final'][0] ?? null) || '' === ($doc['internal'][0] ?? null); } if (null !== ($returnType = self::$returnTypes[$class][$method->name] ?? null) && 'docblock' === $this->patchTypes['force'] && !$method->hasReturnType() && isset(TentativeTypes::RETURN_TYPES[$returnType[2]][$method->name])) { $this->patchReturnTypeWillChange($method); } if (null !== ($returnType ?? ($returnType = self::MAGIC_METHODS[$method->name] ?? null)) && !$method->hasReturnType() && !isset($doc['return'])) { [$normalizedType, $returnType, $declaringClass, $declaringFile] = \is_string($returnType) ? [$returnType, $returnType, '', ''] : $returnType; if ($canAddReturnType && 'docblock' !== $this->patchTypes['force']) { $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); } if (!isset($doc['deprecated']) && \strncmp($ns, $declaringClass, $len)) { if ('docblock' === $this->patchTypes['force']) { $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); } elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) { $deprecations[] = \sprintf('Method "%s::%s()" might add "%s" as a native return type declaration in the future. Do the same in %s "%s" now to avoid errors or add an explicit @return annotation to suppress this message.', $declaringClass, $method->name, $normalizedType, \interface_exists($declaringClass) ? 'implementation' : 'child class', $className); } } } if (!$doc) { $this->patchTypes['force'] = $forcePatchTypes; continue; } if (isset($doc['return']) || 'void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) { $this->setReturnType($doc['return'] ?? self::MAGIC_METHODS[$method->name], $method->class, $method->name, $method->getFileName(), $parent, $method->getReturnType()); if (isset(self::$returnTypes[$class][$method->name][0]) && $canAddReturnType) { $this->fixReturnStatements($method, self::$returnTypes[$class][$method->name][0]); } if ($method->isPrivate()) { unset(self::$returnTypes[$class][$method->name]); } } $this->patchTypes['force'] = $forcePatchTypes; if ($method->isPrivate()) { continue; } $finalOrInternal = \false; foreach (['final', 'internal'] as $annotation) { if (null !== ($description = $doc[$annotation][0] ?? null)) { self::${$annotation . 'Methods'}[$class][$method->name] = [$class, '' !== $description ? ' ' . $description . (\preg_match('/[[:punct:]]$/', $description) ? '' : '.') : '.']; $finalOrInternal = \true; } } if ($finalOrInternal || $method->isConstructor() || !isset($doc['param']) || StatelessInvocation::class === $class) { continue; } if (!isset(self::$annotatedParameters[$class][$method->name])) { $definedParameters = []; foreach ($method->getParameters() as $parameter) { $definedParameters[$parameter->name] = \true; } } foreach ($doc['param'] as $parameterName => $parameterType) { if (!isset($definedParameters[$parameterName])) { self::$annotatedParameters[$class][$method->name][$parameterName] = \sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its %s "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType . ' ' : '', $parameterName, \interface_exists($className) ? 'interface' : 'parent class', $className); } } } return $deprecations; } public function checkCase(\ReflectionClass $refl, string $file, string $class) : ?array { $real = \explode('\\', $class . \strrchr($file, '.')); $tail = \explode(\DIRECTORY_SEPARATOR, \str_replace('/', \DIRECTORY_SEPARATOR, $file)); $i = \count($tail) - 1; $j = \count($real) - 1; while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { --$i; --$j; } \array_splice($tail, 0, $i + 1); if (!$tail) { return null; } $tail = \DIRECTORY_SEPARATOR . \implode(\DIRECTORY_SEPARATOR, $tail); $tailLen = \strlen($tail); $real = $refl->getFileName(); if (2 === self::$caseCheck) { $real = $this->darwinRealpath($real); } if (0 === \substr_compare($real, $tail, -$tailLen, $tailLen, \true) && 0 !== \substr_compare($real, $tail, -$tailLen, $tailLen, \false)) { return [\substr($tail, -$tailLen + 1), \substr($real, -$tailLen + 1), \substr($real, 0, -$tailLen + 1)]; } return null; } /** * `realpath` on MacOSX doesn't normalize the case of characters. */ private function darwinRealpath(string $real) : string { $i = 1 + \strrpos($real, '/'); $file = \substr($real, $i); $real = \substr($real, 0, $i); if (isset(self::$darwinCache[$real])) { $kDir = $real; } else { $kDir = \strtolower($real); if (isset(self::$darwinCache[$kDir])) { $real = self::$darwinCache[$kDir][0]; } else { $dir = \getcwd(); if (!@\chdir($real)) { return $real . $file; } $real = \getcwd() . '/'; \chdir($dir); $dir = $real; $k = $kDir; $i = \strlen($dir) - 1; while (!isset(self::$darwinCache[$k])) { self::$darwinCache[$k] = [$dir, []]; self::$darwinCache[$dir] =& self::$darwinCache[$k]; while ('/' !== $dir[--$i]) { } $k = \substr($k, 0, ++$i); $dir = \substr($dir, 0, $i--); } } } $dirFiles = self::$darwinCache[$kDir][1]; if (!isset($dirFiles[$file]) && ') : eval()\'d code' === \substr($file, -17)) { // Get the file name from "file_name.php(123) : eval()'d code" $file = \substr($file, 0, \strrpos($file, '(', -17)); } if (isset($dirFiles[$file])) { return $real . $dirFiles[$file]; } $kFile = \strtolower($file); if (!isset($dirFiles[$kFile])) { foreach (\scandir($real, 2) as $f) { if ('.' !== $f[0]) { $dirFiles[$f] = $f; if ($f === $file) { $kFile = $k = $file; } elseif ($f !== ($k = \strtolower($f))) { $dirFiles[$k] = $f; } } } self::$darwinCache[$kDir][1] = $dirFiles; } return $real . $dirFiles[$kFile]; } /** * `class_implements` includes interfaces from the parents so we have to manually exclude them. * * @return string[] */ private function getOwnInterfaces(string $class, ?string $parent) : array { $ownInterfaces = \class_implements($class, \false); if ($parent) { foreach (\class_implements($parent, \false) as $interface) { unset($ownInterfaces[$interface]); } } foreach ($ownInterfaces as $interface) { foreach (\class_implements($interface) as $interface) { unset($ownInterfaces[$interface]); } } return $ownInterfaces; } private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, ?\ReflectionType $returnType = null) : void { if ('__construct' === $method) { return; } if ('null' === $types) { self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename]; return; } if ($nullable = 0 === \strpos($types, 'null|')) { $types = \substr($types, 5); } elseif ($nullable = '|null' === \substr($types, -5)) { $types = \substr($types, 0, -5); } $arrayType = ['array' => 'array']; $typesMap = []; $glue = \false !== \strpos($types, '&') ? '&' : '|'; foreach (\explode($glue, $types) as $t) { $t = self::SPECIAL_RETURN_TYPES[\strtolower($t)] ?? $t; $typesMap[$this->normalizeType($t, $class, $parent, $returnType)][$t] = $t; } if (isset($typesMap['array'])) { if (isset($typesMap['Traversable']) || isset($typesMap['\\Traversable'])) { $typesMap['iterable'] = $arrayType !== $typesMap['array'] ? $typesMap['array'] : ['iterable']; unset($typesMap['array'], $typesMap['Traversable'], $typesMap['\\Traversable']); } elseif ($arrayType !== $typesMap['array'] && isset(self::$returnTypes[$class][$method]) && !$returnType) { return; } } if (isset($typesMap['array']) && isset($typesMap['iterable'])) { if ($arrayType !== $typesMap['array']) { $typesMap['iterable'] = $typesMap['array']; } unset($typesMap['array']); } $iterable = $object = \true; foreach ($typesMap as $n => $t) { if ('null' !== $n) { $iterable = $iterable && (\in_array($n, ['array', 'iterable']) || \false !== \strpos($n, 'Iterator')); $object = $object && (\in_array($n, ['callable', 'object', '$this', 'static']) || !isset(self::SPECIAL_RETURN_TYPES[$n])); } } $phpTypes = []; $docTypes = []; foreach ($typesMap as $n => $t) { if ('null' === $n) { $nullable = \true; continue; } $docTypes[] = $t; if ('mixed' === $n || 'void' === $n) { $nullable = \false; $phpTypes = ['' => $n]; continue; } if ('resource' === $n) { // there is no native type for "resource" return; } if (!isset($phpTypes[''])) { $phpTypes[] = $n; } } $docTypes = \array_merge([], ...$docTypes); if (!$phpTypes) { return; } if (1 < \count($phpTypes)) { if ($iterable && '8.0' > $this->patchTypes['php']) { $phpTypes = $docTypes = ['iterable']; } elseif ($object && 'object' === $this->patchTypes['force']) { $phpTypes = $docTypes = ['object']; } elseif ('8.0' > $this->patchTypes['php']) { // ignore multi-types return declarations return; } } $phpType = \sprintf($nullable ? 1 < \count($phpTypes) ? '%s|null' : '?%s' : '%s', \implode($glue, $phpTypes)); $docType = \sprintf($nullable ? '%s|null' : '%s', \implode($glue, $docTypes)); self::$returnTypes[$class][$method] = [$phpType, $docType, $class, $filename]; } private function normalizeType(string $type, string $class, ?string $parent, ?\ReflectionType $returnType) : string { if (isset(self::SPECIAL_RETURN_TYPES[$lcType = \strtolower($type)])) { if ('parent' === ($lcType = self::SPECIAL_RETURN_TYPES[$lcType])) { $lcType = null !== $parent ? '\\' . $parent : 'parent'; } elseif ('self' === $lcType) { $lcType = '\\' . $class; } return $lcType; } // We could resolve "use" statements to return the FQDN // but this would be too expensive for a runtime checker if ('[]' !== \substr($type, -2)) { return $type; } if ($returnType instanceof \ReflectionNamedType) { $type = $returnType->getName(); if ('mixed' !== $type) { return isset(self::SPECIAL_RETURN_TYPES[$type]) ? $type : '\\' . $type; } } return 'array'; } /** * Utility method to add #[ReturnTypeWillChange] where php triggers deprecations. */ private function patchReturnTypeWillChange(\ReflectionMethod $method) { if (\PHP_VERSION_ID >= 80000 && \count($method->getAttributes(\ReturnTypeWillChange::class))) { return; } if (!\is_file($file = $method->getFileName())) { return; } $fileOffset = self::$fileOffsets[$file] ?? 0; $code = \file($file); $startLine = $method->getStartLine() + $fileOffset - 2; if (\false !== \stripos($code[$startLine], 'ReturnTypeWillChange')) { return; } $code[$startLine] .= " #[\\ReturnTypeWillChange]\n"; self::$fileOffsets[$file] = 1 + $fileOffset; \file_put_contents($file, $code); } /** * Utility method to add @return annotations to the Symfony code-base where it triggers self-deprecations. */ private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType) { static $patchedMethods = []; static $useStatements = []; if (!\is_file($file = $method->getFileName()) || isset($patchedMethods[$file][$startLine = $method->getStartLine()])) { return; } $patchedMethods[$file][$startLine] = \true; $fileOffset = self::$fileOffsets[$file] ?? 0; $startLine += $fileOffset - 2; if ($nullable = '|null' === \substr($returnType, -5)) { $returnType = \substr($returnType, 0, -5); } $glue = \false !== \strpos($returnType, '&') ? '&' : '|'; $returnType = \explode($glue, $returnType); $code = \file($file); foreach ($returnType as $i => $type) { if (\preg_match('/((?:\\[\\])+)$/', $type, $m)) { $type = \substr($type, 0, -\strlen($m[1])); $format = '%s' . $m[1]; } else { $format = null; } if (isset(self::SPECIAL_RETURN_TYPES[$type]) || '\\' === $type[0] && !($p = \strrpos($type, '\\', 1))) { continue; } [$namespace, $useOffset, $useMap] = $useStatements[$file] ?? ($useStatements[$file] = self::getUseStatements($file)); if ('\\' !== $type[0]) { [$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ?? ($useStatements[$declaringFile] = self::getUseStatements($declaringFile)); $p = \strpos($type, '\\', 1); $alias = $p ? \substr($type, 0, $p) : $type; if (isset($declaringUseMap[$alias])) { $type = '\\' . $declaringUseMap[$alias] . ($p ? \substr($type, $p) : ''); } else { $type = '\\' . $declaringNamespace . $type; } $p = \strrpos($type, '\\', 1); } $alias = \substr($type, 1 + $p); $type = \substr($type, 1); if (!isset($useMap[$alias]) && (\class_exists($c = $namespace . $alias) || \interface_exists($c) || \trait_exists($c))) { $useMap[$alias] = $c; } if (!isset($useMap[$alias])) { $useStatements[$file][2][$alias] = $type; $code[$useOffset] = "use {$type};\n" . $code[$useOffset]; ++$fileOffset; } elseif ($useMap[$alias] !== $type) { $alias .= 'FIXME'; $useStatements[$file][2][$alias] = $type; $code[$useOffset] = "use {$type} as {$alias};\n" . $code[$useOffset]; ++$fileOffset; } $returnType[$i] = null !== $format ? \sprintf($format, $alias) : $alias; } if ('docblock' === $this->patchTypes['force'] || 'object' === $normalizedType && '7.1' === $this->patchTypes['php']) { $returnType = \implode($glue, $returnType) . ($nullable ? '|null' : ''); if (\false !== \strpos($code[$startLine], '#[')) { --$startLine; } if ($method->getDocComment()) { $code[$startLine] = " * @return {$returnType}\n" . $code[$startLine]; } else { $code[$startLine] .= <<fixReturnStatements($method, $normalizedType); } private static function getUseStatements(string $file) : array { $namespace = ''; $useMap = []; $useOffset = 0; if (!\is_file($file)) { return [$namespace, $useOffset, $useMap]; } $file = \file($file); for ($i = 0; $i < \count($file); ++$i) { if (\preg_match('/^(class|interface|trait|abstract) /', $file[$i])) { break; } if (0 === \strpos($file[$i], 'namespace ')) { $namespace = \substr($file[$i], \strlen('namespace '), -2) . '\\'; $useOffset = $i + 2; } if (0 === \strpos($file[$i], 'use ')) { $useOffset = $i; for (; 0 === \strpos($file[$i], 'use '); ++$i) { $u = \explode(' as ', \substr($file[$i], 4, -2), 2); if (1 === \count($u)) { $p = \strrpos($u[0], '\\'); $useMap[\substr($u[0], \false !== $p ? 1 + $p : 0)] = $u[0]; } else { $useMap[$u[1]] = $u[0]; } } break; } } return [$namespace, $useOffset, $useMap]; } private function fixReturnStatements(\ReflectionMethod $method, string $returnType) { if ('docblock' !== $this->patchTypes['force']) { if ('7.1' === $this->patchTypes['php'] && 'object' === \ltrim($returnType, '?')) { return; } if ('7.4' > $this->patchTypes['php'] && $method->hasReturnType()) { return; } if ('8.0' > $this->patchTypes['php'] && (\false !== \strpos($returnType, '|') || \in_array($returnType, ['mixed', 'static'], \true))) { return; } if ('8.1' > $this->patchTypes['php'] && \false !== \strpos($returnType, '&')) { return; } } if (!\is_file($file = $method->getFileName())) { return; } $fixedCode = $code = \file($file); $i = (self::$fileOffsets[$file] ?? 0) + $method->getStartLine(); if ('?' !== $returnType && 'docblock' !== $this->patchTypes['force']) { $fixedCode[$i - 1] = \preg_replace('/\\)(?::[^;\\n]++)?(;?\\n)/', "): {$returnType}\\1", $code[$i - 1]); } $end = $method->isGenerator() ? $i : $method->getEndLine(); $inClosure = \false; $braces = 0; for (; $i < $end; ++$i) { if (!$inClosure) { $inClosure = \str_contains($code[$i], 'function ('); } if ($inClosure) { $braces += \substr_count($code[$i], '{') - \substr_count($code[$i], '}'); $inClosure = $braces > 0; continue; } if ('void' === $returnType) { $fixedCode[$i] = \str_replace(' return null;', ' return;', $code[$i]); } elseif ('mixed' === $returnType || '?' === $returnType[0]) { $fixedCode[$i] = \str_replace(' return;', ' return null;', $code[$i]); } else { $fixedCode[$i] = \str_replace(' return;', " return {$returnType}!?;", $code[$i]); } } if ($fixedCode !== $code) { \file_put_contents($file, $fixedCode); } } /** * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector */ private function parsePhpDoc(\Reflector $reflector) : array { if (!($doc = $reflector->getDocComment())) { return []; } $tagName = ''; $tagContent = ''; $tags = []; foreach (\explode("\n", \substr($doc, 3, -2)) as $line) { $line = \ltrim($line); $line = \ltrim($line, '*'); if ('' === ($line = \trim($line))) { if ('' !== $tagName) { $tags[$tagName][] = $tagContent; } $tagName = $tagContent = ''; continue; } if ('@' === $line[0]) { if ('' !== $tagName) { $tags[$tagName][] = $tagContent; $tagContent = ''; } if (\preg_match('{^@([-a-zA-Z0-9_:]++)(\\s|$)}', $line, $m)) { $tagName = $m[1]; $tagContent = \str_replace("\t", ' ', \ltrim(\substr($line, 2 + \strlen($tagName)))); } else { $tagName = ''; } } elseif ('' !== $tagName) { $tagContent .= ' ' . \str_replace("\t", ' ', $line); } } if ('' !== $tagName) { $tags[$tagName][] = $tagContent; } foreach ($tags['method'] ?? [] as $i => $method) { unset($tags['method'][$i]); $parts = \preg_split('{(\\s++|\\((?:[^()]*+|(?R))*\\)(?: *: *[^ ]++)?|<(?:[^<>]*+|(?R))*>|\\{(?:[^{}]*+|(?R))*\\})}', $method, -1, \PREG_SPLIT_DELIM_CAPTURE); $returnType = ''; $static = 'static' === $parts[0]; for ($i = $static ? 2 : 0; null !== ($p = $parts[$i] ?? null); $i += 2) { if (\in_array($p, ['', '|', '&', 'callable'], \true) || \in_array(\substr($returnType, -1), ['|', '&'], \true)) { $returnType .= \trim($parts[$i - 1] ?? '') . $p; continue; } $signature = '(' === ($parts[$i + 1][0] ?? '(') ? $parts[$i + 1] ?? '()' : null; if (null === $signature && '' === $returnType) { $returnType = $p; continue; } if ($static && 2 === $i) { $static = \false; $returnType = 'static'; } if (\in_array($description = \trim(\implode('', \array_slice($parts, 2 + $i))), ['', '.'], \true)) { $description = null; } elseif (!\preg_match('/[.!]$/', $description)) { $description .= '.'; } $tags['method'][$p] = [$static, $returnType, $signature ?? '()', $description]; break; } } foreach ($tags['param'] ?? [] as $i => $param) { unset($tags['param'][$i]); if (\strlen($param) !== \strcspn($param, '<{(')) { $param = \preg_replace('{\\(([^()]*+|(?R))*\\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\\{([^{}]*+|(?R))*\\}}', '', $param); } if (\false === ($i = \strpos($param, '$'))) { continue; } $type = 0 === $i ? '' : \rtrim(\substr($param, 0, $i), ' &'); $param = \substr($param, 1 + $i, (\strpos($param, ' ', $i) ?: 1 + $i + \strlen($param)) - $i - 1); $tags['param'][$param] = $type; } foreach (['var', 'return'] as $k) { if (null === ($v = $tags[$k][0] ?? null)) { continue; } if (\strlen($v) !== \strcspn($v, '<{(')) { $v = \preg_replace('{\\(([^()]*+|(?R))*\\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\\{([^{}]*+|(?R))*\\}}', '', $v); } $tags[$k] = \substr($v, 0, \strpos($v, ' ') ?: \strlen($v)) ?: null; } return $tags; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler; use _ContaoManager\Psr\Log\AbstractLogger; /** * A buffering logger that stacks logs for later. * * @author Nicolas Grekas */ class BufferingLogger extends AbstractLogger { private $logs = []; public function log($level, $message, array $context = []) : void { $this->logs[] = [$level, $message, $context]; } public function cleanLogs() : array { $logs = $this->logs; $this->logs = []; return $logs; } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { foreach ($this->logs as [$level, $message, $context]) { if (\false !== \strpos($message, '{')) { foreach ($context as $key => $val) { if (null === $val || \is_scalar($val) || \is_object($val) && \is_callable([$val, '__toString'])) { $message = \str_replace("{{$key}}", $val, $message); } elseif ($val instanceof \DateTimeInterface) { $message = \str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message); } elseif (\is_object($val)) { $message = \str_replace("{{$key}}", '[object ' . \get_class($val) . ']', $message); } else { $message = \str_replace("{{$key}}", '[' . \gettype($val) . ']', $message); } } } \error_log(\sprintf('%s [%s] %s', \date(\DateTime::RFC3339), $level, $message)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Psr\Log\LogLevel; use _ContaoManager\Symfony\Component\ErrorHandler\Error\FatalError; use _ContaoManager\Symfony\Component\ErrorHandler\Error\OutOfMemoryError; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; /** * A generic ErrorHandler for the PHP engine. * * Provides five bit fields that control how errors are handled: * - thrownErrors: errors thrown as \ErrorException * - loggedErrors: logged errors, when not @-silenced * - scopedErrors: errors thrown or logged with their local context * - tracedErrors: errors logged with their stack trace * - screamedErrors: never @-silenced errors * * Each error level can be logged by a dedicated PSR-3 logger object. * Screaming only applies to logging. * Throwing takes precedence over logging. * Uncaught exceptions are logged as E_ERROR. * E_DEPRECATED and E_USER_DEPRECATED levels never throw. * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. * As errors have a performance cost, repeated errors are all logged, so that the developer * can see them and weight them as more important to fix than others of the same level. * * @author Nicolas Grekas * @author Grégoire Pineau * * @final */ class ErrorHandler { private $levels = [\E_DEPRECATED => 'Deprecated', \E_USER_DEPRECATED => 'User Deprecated', \E_NOTICE => 'Notice', \E_USER_NOTICE => 'User Notice', \E_STRICT => 'Runtime Notice', \E_WARNING => 'Warning', \E_USER_WARNING => 'User Warning', \E_COMPILE_WARNING => 'Compile Warning', \E_CORE_WARNING => 'Core Warning', \E_USER_ERROR => 'User Error', \E_RECOVERABLE_ERROR => 'Catchable Fatal Error', \E_COMPILE_ERROR => 'Compile Error', \E_PARSE => 'Parse Error', \E_ERROR => 'Error', \E_CORE_ERROR => 'Core Error']; private $loggers = [\E_DEPRECATED => [null, LogLevel::INFO], \E_USER_DEPRECATED => [null, LogLevel::INFO], \E_NOTICE => [null, LogLevel::WARNING], \E_USER_NOTICE => [null, LogLevel::WARNING], \E_STRICT => [null, LogLevel::WARNING], \E_WARNING => [null, LogLevel::WARNING], \E_USER_WARNING => [null, LogLevel::WARNING], \E_COMPILE_WARNING => [null, LogLevel::WARNING], \E_CORE_WARNING => [null, LogLevel::WARNING], \E_USER_ERROR => [null, LogLevel::CRITICAL], \E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL], \E_COMPILE_ERROR => [null, LogLevel::CRITICAL], \E_PARSE => [null, LogLevel::CRITICAL], \E_ERROR => [null, LogLevel::CRITICAL], \E_CORE_ERROR => [null, LogLevel::CRITICAL]]; private $thrownErrors = 0x1fff; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED private $scopedErrors = 0x1fff; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED private $tracedErrors = 0x77fb; // E_ALL - E_STRICT - E_PARSE private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE private $loggedErrors = 0; private $configureException; private $debug; private $isRecursive = 0; private $isRoot = \false; private $exceptionHandler; private $bootstrappingLogger; private static $reservedMemory; private static $toStringException; private static $silencedErrorCache = []; private static $silencedErrorCount = 0; private static $exitCode = 0; /** * Registers the error handler. */ public static function register(?self $handler = null, bool $replace = \true) : self { if (null === self::$reservedMemory) { self::$reservedMemory = \str_repeat('x', 32768); \register_shutdown_function(__CLASS__ . '::handleFatalError'); } if ($handlerIsNew = null === $handler) { $handler = new static(); } if (null === ($prev = \set_error_handler([$handler, 'handleError']))) { \restore_error_handler(); // Specifying the error types earlier would expose us to https://bugs.php.net/63206 \set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors); $handler->isRoot = \true; } if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { $handler = $prev[0]; $replace = \false; } if (!$replace && $prev) { \restore_error_handler(); $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; } else { $handlerIsRegistered = \true; } if (\is_array($prev = \set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) { \restore_exception_handler(); if (!$handlerIsRegistered) { $handler = $prev[0]; } elseif ($handler !== $prev[0] && $replace) { \set_exception_handler([$handler, 'handleException']); $p = $prev[0]->setExceptionHandler(null); $handler->setExceptionHandler($p); $prev[0]->setExceptionHandler($p); } } else { $handler->setExceptionHandler($prev ?? [$handler, 'renderException']); } $handler->throwAt(\E_ALL & $handler->thrownErrors, \true); return $handler; } /** * Calls a function and turns any PHP error into \ErrorException. * * @return mixed What $function(...$arguments) returns * * @throws \ErrorException When $function(...$arguments) triggers a PHP error */ public static function call(callable $function, ...$arguments) { \set_error_handler(static function (int $type, string $message, string $file, int $line) { if (__FILE__ === $file) { $trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); $file = $trace[2]['file'] ?? $file; $line = $trace[2]['line'] ?? $line; } throw new \ErrorException($message, 0, $type, $file, $line); }); try { return $function(...$arguments); } finally { \restore_error_handler(); } } public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = \false) { if ($bootstrappingLogger) { $this->bootstrappingLogger = $bootstrappingLogger; $this->setDefaultLogger($bootstrappingLogger); } $traceReflector = new \ReflectionProperty(\Exception::class, 'trace'); $traceReflector->setAccessible(\true); $this->configureException = \Closure::bind(static function ($e, $trace, $file = null, $line = null) use($traceReflector) { $traceReflector->setValue($e, $trace); $e->file = $file ?? $e->file; $e->line = $line ?? $e->line; }, null, new class extends \Exception { }); $this->debug = $debug; } /** * Sets a logger to non assigned errors levels. * * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants * @param bool $replace Whether to replace or not any existing logger */ public function setDefaultLogger(LoggerInterface $logger, $levels = \E_ALL, bool $replace = \false) : void { $loggers = []; if (\is_array($levels)) { foreach ($levels as $type => $logLevel) { if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { $loggers[$type] = [$logger, $logLevel]; } } } else { if (null === $levels) { $levels = \E_ALL; } foreach ($this->loggers as $type => $log) { if ($type & $levels && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { $log[0] = $logger; $loggers[$type] = $log; } } } $this->setLoggers($loggers); } /** * Sets a logger for each error level. * * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map * * @return array The previous map * * @throws \InvalidArgumentException */ public function setLoggers(array $loggers) : array { $prevLogged = $this->loggedErrors; $prev = $this->loggers; $flush = []; foreach ($loggers as $type => $log) { if (!isset($prev[$type])) { throw new \InvalidArgumentException('Unknown error type: ' . $type); } if (!\is_array($log)) { $log = [$log]; } elseif (!\array_key_exists(0, $log)) { throw new \InvalidArgumentException('No logger provided.'); } if (null === $log[0]) { $this->loggedErrors &= ~$type; } elseif ($log[0] instanceof LoggerInterface) { $this->loggedErrors |= $type; } else { throw new \InvalidArgumentException('Invalid logger provided.'); } $this->loggers[$type] = $log + $prev[$type]; if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { $flush[$type] = $type; } } $this->reRegister($prevLogged | $this->thrownErrors); if ($flush) { foreach ($this->bootstrappingLogger->cleanLogs() as $log) { $type = ThrowableUtils::getSeverity($log[2]['exception']); if (!isset($flush[$type])) { $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); } elseif ($this->loggers[$type][0]) { $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); } } } return $prev; } /** * Sets a user exception handler. * * @param callable(\Throwable $e)|null $handler * * @return callable|null The previous exception handler */ public function setExceptionHandler(?callable $handler) : ?callable { $prev = $this->exceptionHandler; $this->exceptionHandler = $handler; return $prev; } /** * Sets the PHP error levels that throw an exception when a PHP error occurs. * * @param int $levels A bit field of E_* constants for thrown errors * @param bool $replace Replace or amend the previous value * * @return int The previous value */ public function throwAt(int $levels, bool $replace = \false) : int { $prev = $this->thrownErrors; $this->thrownErrors = ($levels | \E_RECOVERABLE_ERROR | \E_USER_ERROR) & ~\E_USER_DEPRECATED & ~\E_DEPRECATED; if (!$replace) { $this->thrownErrors |= $prev; } $this->reRegister($prev | $this->loggedErrors); return $prev; } /** * Sets the PHP error levels for which local variables are preserved. * * @param int $levels A bit field of E_* constants for scoped errors * @param bool $replace Replace or amend the previous value * * @return int The previous value */ public function scopeAt(int $levels, bool $replace = \false) : int { $prev = $this->scopedErrors; $this->scopedErrors = $levels; if (!$replace) { $this->scopedErrors |= $prev; } return $prev; } /** * Sets the PHP error levels for which the stack trace is preserved. * * @param int $levels A bit field of E_* constants for traced errors * @param bool $replace Replace or amend the previous value * * @return int The previous value */ public function traceAt(int $levels, bool $replace = \false) : int { $prev = $this->tracedErrors; $this->tracedErrors = $levels; if (!$replace) { $this->tracedErrors |= $prev; } return $prev; } /** * Sets the error levels where the @-operator is ignored. * * @param int $levels A bit field of E_* constants for screamed errors * @param bool $replace Replace or amend the previous value * * @return int The previous value */ public function screamAt(int $levels, bool $replace = \false) : int { $prev = $this->screamedErrors; $this->screamedErrors = $levels; if (!$replace) { $this->screamedErrors |= $prev; } return $prev; } /** * Re-registers as a PHP error handler if levels changed. */ private function reRegister(int $prev) : void { if ($prev !== ($this->thrownErrors | $this->loggedErrors)) { $handler = \set_error_handler('is_int'); $handler = \is_array($handler) ? $handler[0] : null; \restore_error_handler(); if ($handler === $this) { \restore_error_handler(); if ($this->isRoot) { \set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors); } else { \set_error_handler([$this, 'handleError']); } } } } /** * Handles errors by filtering then logging them according to the configured bit fields. * * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself * * @throws \ErrorException When $this->thrownErrors requests so * * @internal */ public function handleError(int $type, string $message, string $file, int $line) : bool { if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && \false !== \strpos($message, '" targeting switch is equivalent to "break')) { $type = \E_DEPRECATED; } // Level is the current error reporting level to manage silent error. $level = \error_reporting(); $silenced = 0 === ($level & $type); // Strong errors are not authorized to be silenced. $level |= \E_RECOVERABLE_ERROR | \E_USER_ERROR | \E_DEPRECATED | \E_USER_DEPRECATED; $log = $this->loggedErrors & $type; $throw = $this->thrownErrors & $type & $level; $type &= $level | $this->screamedErrors; // Never throw on warnings triggered by assert() if (\E_WARNING === $type && 'a' === $message[0] && 0 === \strncmp($message, 'assert(): ', 10)) { $throw = 0; } if (!$type || !$log && !$throw) { return \false; } $logMessage = $this->levels[$type] . ': ' . $message; if (null !== self::$toStringException) { $errorAsException = self::$toStringException; self::$toStringException = null; } elseif (!$throw && !($type & $level)) { if (!isset(self::$silencedErrorCache[$id = $file . ':' . $line])) { $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, \false) : []; $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace); } elseif (isset(self::$silencedErrorCache[$id][$message])) { $lightTrace = null; $errorAsException = self::$silencedErrorCache[$id][$message]; ++$errorAsException->count; } else { $lightTrace = []; $errorAsException = null; } if (100 < ++self::$silencedErrorCount) { self::$silencedErrorCache = $lightTrace = []; self::$silencedErrorCount = 1; } if ($errorAsException) { self::$silencedErrorCache[$id][$message] = $errorAsException; } if (null === $lightTrace) { return \true; } } else { if (\false !== \strpos($message, '@anonymous')) { $backtrace = \debug_backtrace(\false, 5); for ($i = 1; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['function'], $backtrace[$i]['args'][0]) && ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function'])) { if ($backtrace[$i]['args'][0] !== $message) { $message = $this->parseAnonymousClass($backtrace[$i]['args'][0]); $logMessage = $this->levels[$type] . ': ' . $message; } break; } } } $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); if ($throw || $this->tracedErrors & $type) { $backtrace = $errorAsException->getTrace(); $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); ($this->configureException)($errorAsException, $lightTrace, $file, $line); } else { ($this->configureException)($errorAsException, []); $backtrace = []; } } if ($throw) { if (\PHP_VERSION_ID < 70400 && \E_USER_ERROR & $type) { for ($i = 1; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) && '__toString' === $backtrace[$i]['function'] && '->' === $backtrace[$i]['type'] && !isset($backtrace[$i - 1]['class']) && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])) { // Here, we know trigger_error() has been called from __toString(). // PHP triggers a fatal error when throwing from __toString(). // A small convention allows working around the limitation: // given a caught $e exception in __toString(), quitting the method with // `return trigger_error($e, E_USER_ERROR);` allows this error handler // to make $e get through the __toString() barrier. $context = 4 < \func_num_args() ? \func_get_arg(4) ?: [] : []; foreach ($context as $e) { if ($e instanceof \Throwable && $e->__toString() === $message) { self::$toStringException = $e; return \true; } } // Display the original error message instead of the default one. $this->handleException($errorAsException); // Stop the process by giving back the error to the native handler. return \false; } } } throw $errorAsException; } if ($this->isRecursive) { $log = 0; } else { if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { $currentErrorHandler = \set_error_handler('is_int'); \restore_error_handler(); } try { $this->isRecursive = \true; $level = $type & $level ? $this->loggers[$type][1] : LogLevel::DEBUG; $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); } finally { $this->isRecursive = \false; if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { \set_error_handler($currentErrorHandler); } } } return !$silenced && $type && $log; } /** * Handles an exception by logging then forwarding it to another handler. * * @internal */ public function handleException(\Throwable $exception) { $handlerException = null; if (!$exception instanceof FatalError) { self::$exitCode = 255; $type = ThrowableUtils::getSeverity($exception); } else { $type = $exception->getError()['type']; } if ($this->loggedErrors & $type) { if (\false !== \strpos($message = $exception->getMessage(), "@anonymous\x00")) { $message = $this->parseAnonymousClass($message); } if ($exception instanceof FatalError) { $message = 'Fatal ' . $message; } elseif ($exception instanceof \Error) { $message = 'Uncaught Error: ' . $message; } elseif ($exception instanceof \ErrorException) { $message = 'Uncaught ' . $message; } else { $message = 'Uncaught Exception: ' . $message; } try { $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]); } catch (\Throwable $handlerException) { } } if (!$exception instanceof OutOfMemoryError) { foreach ($this->getErrorEnhancers() as $errorEnhancer) { if ($e = $errorEnhancer->enhance($exception)) { $exception = $e; break; } } } $exceptionHandler = $this->exceptionHandler; $this->exceptionHandler = [$this, 'renderException']; if (null === $exceptionHandler || $exceptionHandler === $this->exceptionHandler) { $this->exceptionHandler = null; } try { if (null !== $exceptionHandler) { return $exceptionHandler($exception); } $handlerException = $handlerException ?: $exception; } catch (\Throwable $handlerException) { } if ($exception === $handlerException && null === $this->exceptionHandler) { self::$reservedMemory = null; // Disable the fatal error handler throw $exception; // Give back $exception to the native handler } $loggedErrors = $this->loggedErrors; if ($exception === $handlerException) { $this->loggedErrors &= ~$type; } try { $this->handleException($handlerException); } finally { $this->loggedErrors = $loggedErrors; } } /** * Shutdown registered function for handling PHP fatal errors. * * @param array|null $error An array as returned by error_get_last() * * @internal */ public static function handleFatalError(?array $error = null) : void { if (null === self::$reservedMemory) { return; } $handler = self::$reservedMemory = null; $handlers = []; $previousHandler = null; $sameHandlerLimit = 10; while (!\is_array($handler) || !$handler[0] instanceof self) { $handler = \set_exception_handler('is_int'); \restore_exception_handler(); if (!$handler) { break; } \restore_exception_handler(); if ($handler !== $previousHandler) { \array_unshift($handlers, $handler); $previousHandler = $handler; } elseif (0 === --$sameHandlerLimit) { $handler = null; break; } } foreach ($handlers as $h) { \set_exception_handler($h); } if (!$handler) { return; } if ($handler !== $h) { $handler[0]->setExceptionHandler($h); } $handler = $handler[0]; $handlers = []; if ($exit = null === $error) { $error = \error_get_last(); } if ($error && ($error['type'] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR)) { // Let's not throw anymore but keep logging $handler->throwAt(0, \true); $trace = $error['backtrace'] ?? null; if (0 === \strpos($error['message'], 'Allowed memory') || 0 === \strpos($error['message'], 'Out of memory')) { $fatalError = new OutOfMemoryError($handler->levels[$error['type']] . ': ' . $error['message'], 0, $error, 2, \false, $trace); } else { $fatalError = new FatalError($handler->levels[$error['type']] . ': ' . $error['message'], 0, $error, 2, \true, $trace); } } else { $fatalError = null; } try { if (null !== $fatalError) { self::$exitCode = 255; $handler->handleException($fatalError); } } catch (FatalError $e) { // Ignore this re-throw } if ($exit && self::$exitCode) { $exitCode = self::$exitCode; \register_shutdown_function('register_shutdown_function', function () use($exitCode) { exit($exitCode); }); } } /** * Renders the given exception. * * As this method is mainly called during boot where nothing is yet available, * the output is always either HTML or CLI depending where PHP runs. */ private function renderException(\Throwable $exception) : void { $renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], \true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug); $exception = $renderer->render($exception); if (!\headers_sent()) { \http_response_code($exception->getStatusCode()); foreach ($exception->getHeaders() as $name => $value) { \header($name . ': ' . $value, \false); } } echo $exception->getAsString(); } /** * Override this method if you want to define more error enhancers. * * @return ErrorEnhancerInterface[] */ protected function getErrorEnhancers() : iterable { return [new UndefinedFunctionErrorEnhancer(), new UndefinedMethodErrorEnhancer(), new ClassNotFoundErrorEnhancer()]; } /** * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader. */ private function cleanTrace(array $backtrace, int $type, string &$file, int &$line, bool $throw) : array { $lightTrace = $backtrace; for ($i = 0; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { $lightTrace = \array_slice($lightTrace, 1 + $i); break; } } if (\E_USER_DEPRECATED === $type) { for ($i = 0; isset($lightTrace[$i]); ++$i) { if (!isset($lightTrace[$i]['file'], $lightTrace[$i]['line'], $lightTrace[$i]['function'])) { continue; } if (!isset($lightTrace[$i]['class']) && 'trigger_deprecation' === $lightTrace[$i]['function']) { $file = $lightTrace[$i]['file']; $line = $lightTrace[$i]['line']; $lightTrace = \array_slice($lightTrace, 1 + $i); break; } } } if (\class_exists(DebugClassLoader::class, \false)) { for ($i = \count($lightTrace) - 2; 0 < $i; --$i) { if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) { \array_splice($lightTrace, --$i, 2); } } } if (!($throw || $this->scopedErrors & $type)) { for ($i = 0; isset($lightTrace[$i]); ++$i) { unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); } } return $lightTrace; } /** * Parse the error message by removing the anonymous class notation * and using the parent class instead if possible. */ private function parseAnonymousClass(string $message) : string { return \preg_replace_callback('/[a-zA-Z_\\x7f-\\xff][\\\\a-zA-Z0-9_\\x7f-\\xff]*+@anonymous\\x00.*?\\.php(?:0x?|:[0-9]++\\$)[0-9a-fA-F]++/', static function ($m) { return \class_exists($m[0], \false) ? ((\get_parent_class($m[0]) ?: \key(\class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0]; }, $message); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler; use _ContaoManager\Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; /** * @internal */ class ThrowableUtils { /** * @param SilencedErrorContext|\Throwable */ public static function getSeverity($throwable) : int { if ($throwable instanceof \ErrorException || $throwable instanceof SilencedErrorContext) { return $throwable->getSeverity(); } if ($throwable instanceof \ParseError) { return \E_PARSE; } if ($throwable instanceof \TypeError) { return \E_RECOVERABLE_ERROR; } return \E_ERROR; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Error; class OutOfMemoryError extends FatalError { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Error; class UndefinedMethodError extends \Error { /** * {@inheritdoc} */ public function __construct(string $message, \Throwable $previous) { parent::__construct($message, $previous->getCode(), $previous->getPrevious()); foreach (['file' => $previous->getFile(), 'line' => $previous->getLine(), 'trace' => $previous->getTrace()] as $property => $value) { $refl = new \ReflectionProperty(\Error::class, $property); $refl->setAccessible(\true); $refl->setValue($this, $value); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Error; class FatalError extends \Error { private $error; /** * {@inheritdoc} * * @param array $error An array as returned by error_get_last() */ public function __construct(string $message, int $code, array $error, ?int $traceOffset = null, bool $traceArgs = \true, ?array $trace = null) { parent::__construct($message, $code); $this->error = $error; if (null !== $trace) { if (!$traceArgs) { foreach ($trace as &$frame) { unset($frame['args'], $frame['this'], $frame); } } } elseif (null !== $traceOffset) { if (\function_exists('xdebug_get_function_stack') && ($trace = @\xdebug_get_function_stack())) { if (0 < $traceOffset) { \array_splice($trace, -$traceOffset); } foreach ($trace as &$frame) { if (!isset($frame['type'])) { // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 if (isset($frame['class'])) { $frame['type'] = '::'; } } elseif ('dynamic' === $frame['type']) { $frame['type'] = '->'; } elseif ('static' === $frame['type']) { $frame['type'] = '::'; } // XDebug also has a different name for the parameters array if (!$traceArgs) { unset($frame['params'], $frame['args']); } elseif (isset($frame['params']) && !isset($frame['args'])) { $frame['args'] = $frame['params']; unset($frame['params']); } } unset($frame); $trace = \array_reverse($trace); } else { $trace = []; } } foreach (['file' => $error['file'], 'line' => $error['line'], 'trace' => $trace] as $property => $value) { if (null !== $value) { $refl = new \ReflectionProperty(\Error::class, $property); $refl->setAccessible(\true); $refl->setValue($this, $value); } } } /** * {@inheritdoc} */ public function getError() : array { return $this->error; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Error; class UndefinedFunctionError extends \Error { /** * {@inheritdoc} */ public function __construct(string $message, \Throwable $previous) { parent::__construct($message, $previous->getCode(), $previous->getPrevious()); foreach (['file' => $previous->getFile(), 'line' => $previous->getLine(), 'trace' => $previous->getTrace()] as $property => $value) { $refl = new \ReflectionProperty(\Error::class, $property); $refl->setAccessible(\true); $refl->setValue($this, $value); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Error; class ClassNotFoundError extends \Error { /** * {@inheritdoc} */ public function __construct(string $message, \Throwable $previous) { parent::__construct($message, $previous->getCode(), $previous->getPrevious()); foreach (['file' => $previous->getFile(), 'line' => $previous->getLine(), 'trace' => $previous->getTrace()] as $property => $value) { $refl = new \ReflectionProperty(\Error::class, $property); $refl->setAccessible(\true); $refl->setValue($this, $value); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Exception; use _ContaoManager\Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; use _ContaoManager\Symfony\Component\HttpFoundation\Response; use _ContaoManager\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; /** * FlattenException wraps a PHP Error or Exception to be able to serialize it. * * Basically, this class removes all objects from the trace. * * @author Fabien Potencier */ class FlattenException { /** @var string */ private $message; /** @var int|string */ private $code; /** @var self|null */ private $previous; /** @var array */ private $trace; /** @var string */ private $traceAsString; /** @var string */ private $class; /** @var int */ private $statusCode; /** @var string */ private $statusText; /** @var array */ private $headers; /** @var string */ private $file; /** @var int */ private $line; /** @var string|null */ private $asString; /** * @return static */ public static function create(\Exception $exception, ?int $statusCode = null, array $headers = []) : self { return static::createFromThrowable($exception, $statusCode, $headers); } /** * @return static */ public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []) : self { $e = new static(); $e->setMessage($exception->getMessage()); $e->setCode($exception->getCode()); if ($exception instanceof HttpExceptionInterface) { $statusCode = $exception->getStatusCode(); $headers = \array_merge($headers, $exception->getHeaders()); } elseif ($exception instanceof RequestExceptionInterface) { $statusCode = 400; } if (null === $statusCode) { $statusCode = 500; } if (\class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { $statusText = Response::$statusTexts[$statusCode]; } else { $statusText = 'Whoops, looks like something went wrong.'; } $e->setStatusText($statusText); $e->setStatusCode($statusCode); $e->setHeaders($headers); $e->setTraceFromThrowable($exception); $e->setClass(\get_class($exception)); $e->setFile($exception->getFile()); $e->setLine($exception->getLine()); $previous = $exception->getPrevious(); if ($previous instanceof \Throwable) { $e->setPrevious(static::createFromThrowable($previous)); } return $e; } public function toArray() : array { $exceptions = []; foreach (\array_merge([$this], $this->getAllPrevious()) as $exception) { $exceptions[] = ['message' => $exception->getMessage(), 'class' => $exception->getClass(), 'trace' => $exception->getTrace()]; } return $exceptions; } public function getStatusCode() : int { return $this->statusCode; } /** * @return $this */ public function setStatusCode(int $code) : self { $this->statusCode = $code; return $this; } public function getHeaders() : array { return $this->headers; } /** * @return $this */ public function setHeaders(array $headers) : self { $this->headers = $headers; return $this; } public function getClass() : string { return $this->class; } /** * @return $this */ public function setClass(string $class) : self { $this->class = \false !== \strpos($class, "@anonymous\x00") ? ((\get_parent_class($class) ?: \key(\class_implements($class))) ?: 'class') . '@anonymous' : $class; return $this; } public function getFile() : string { return $this->file; } /** * @return $this */ public function setFile(string $file) : self { $this->file = $file; return $this; } public function getLine() : int { return $this->line; } /** * @return $this */ public function setLine(int $line) : self { $this->line = $line; return $this; } public function getStatusText() : string { return $this->statusText; } /** * @return $this */ public function setStatusText(string $statusText) : self { $this->statusText = $statusText; return $this; } public function getMessage() : string { return $this->message; } /** * @return $this */ public function setMessage(string $message) : self { if (\false !== \strpos($message, "@anonymous\x00")) { $message = \preg_replace_callback('/[a-zA-Z_\\x7f-\\xff][\\\\a-zA-Z0-9_\\x7f-\\xff]*+@anonymous\\x00.*?\\.php(?:0x?|:[0-9]++\\$)[0-9a-fA-F]++/', function ($m) { return \class_exists($m[0], \false) ? ((\get_parent_class($m[0]) ?: \key(\class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0]; }, $message); } $this->message = $message; return $this; } /** * @return int|string int most of the time (might be a string with PDOException) */ public function getCode() { return $this->code; } /** * @param int|string $code * * @return $this */ public function setCode($code) : self { $this->code = $code; return $this; } public function getPrevious() : ?self { return $this->previous; } /** * @return $this */ public function setPrevious(?self $previous) : self { $this->previous = $previous; return $this; } /** * @return self[] */ public function getAllPrevious() : array { $exceptions = []; $e = $this; while ($e = $e->getPrevious()) { $exceptions[] = $e; } return $exceptions; } public function getTrace() : array { return $this->trace; } /** * @return $this */ public function setTraceFromThrowable(\Throwable $throwable) : self { $this->traceAsString = $throwable->getTraceAsString(); return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine()); } /** * @return $this */ public function setTrace(array $trace, ?string $file, ?int $line) : self { $this->trace = []; $this->trace[] = ['namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => $file, 'line' => $line, 'args' => []]; foreach ($trace as $entry) { $class = ''; $namespace = ''; if (isset($entry['class'])) { $parts = \explode('\\', $entry['class']); $class = \array_pop($parts); $namespace = \implode('\\', $parts); } $this->trace[] = ['namespace' => $namespace, 'short_class' => $class, 'class' => $entry['class'] ?? '', 'type' => $entry['type'] ?? '', 'function' => $entry['function'] ?? null, 'file' => $entry['file'] ?? null, 'line' => $entry['line'] ?? null, 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : []]; } return $this; } private function flattenArgs(array $args, int $level = 0, int &$count = 0) : array { $result = []; foreach ($args as $key => $value) { if (++$count > 10000.0) { return ['array', '*SKIPPED over 10000 entries*']; } if ($value instanceof \__PHP_Incomplete_Class) { $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)]; } elseif (\is_object($value)) { $result[$key] = ['object', \get_class($value)]; } elseif (\is_array($value)) { if ($level > 10) { $result[$key] = ['array', '*DEEP NESTED ARRAY*']; } else { $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)]; } } elseif (null === $value) { $result[$key] = ['null', null]; } elseif (\is_bool($value)) { $result[$key] = ['boolean', $value]; } elseif (\is_int($value)) { $result[$key] = ['integer', $value]; } elseif (\is_float($value)) { $result[$key] = ['float', $value]; } elseif (\is_resource($value)) { $result[$key] = ['resource', \get_resource_type($value)]; } else { $result[$key] = ['string', (string) $value]; } } return $result; } private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) : string { $array = new \ArrayObject($value); return $array['__PHP_Incomplete_Class_Name']; } public function getTraceAsString() : string { return $this->traceAsString; } /** * @return $this */ public function setAsString(?string $asString) : self { $this->asString = $asString; return $this; } public function getAsString() : string { if (null !== $this->asString) { return $this->asString; } $message = ''; $next = \false; foreach (\array_reverse(\array_merge([$this], $this->getAllPrevious())) as $exception) { if ($next) { $message .= 'Next '; } else { $next = \true; } $message .= $exception->getClass(); if ('' != $exception->getMessage()) { $message .= ': ' . $exception->getMessage(); } $message .= ' in ' . $exception->getFile() . ':' . $exception->getLine() . "\nStack trace:\n" . $exception->getTraceAsString() . "\n\n"; } return \rtrim($message); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\ErrorHandler\Exception; /** * Data Object that represents a Silenced Error. * * @author Grégoire Pineau */ class SilencedErrorContext implements \JsonSerializable { public $count = 1; private $severity; private $file; private $line; private $trace; public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1) { $this->severity = $severity; $this->file = $file; $this->line = $line; $this->trace = $trace; $this->count = $count; } public function getSeverity() : int { return $this->severity; } public function getFile() : string { return $this->file; } public function getLine() : int { return $this->line; } public function getTrace() : array { return $this->trace; } public function jsonSerialize() : array { return ['severity' => $this->severity, 'file' => $this->file, 'line' => $this->line, 'trace' => $this->trace, 'count' => $this->count]; } } { "name": "symfony\/error-handler", "type": "library", "description": "Provides tools to manage errors and ease debugging PHP code", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "psr\/log": "^1|^2|^3", "symfony\/var-dumper": "^4.4|^5.0|^6.0" }, "require-dev": { "symfony\/http-kernel": "^4.4|^5.0|^6.0", "symfony\/serializer": "^4.4|^5.0|^6.0", "symfony\/deprecation-contracts": "^2.1|^3" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\ErrorHandler\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "bin": [ "Resources\/bin\/patch-type-declarations" ], "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Cache; use _ContaoManager\Psr\Cache\CacheItemInterface; /** * Computes and returns the cached value of an item. * * @author Nicolas Grekas */ interface CallbackInterface { /** * @param CacheItemInterface|ItemInterface $item The item to compute the value for * @param bool &$save Should be set to false when the value should not be saved in the pool * * @return mixed The computed value for the passed item */ public function __invoke(CacheItemInterface $item, bool &$save); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Cache; use _ContaoManager\Psr\Cache\CacheException; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Cache\InvalidArgumentException; /** * Augments PSR-6's CacheItemInterface with support for tags and metadata. * * @author Nicolas Grekas */ interface ItemInterface extends CacheItemInterface { /** * References the Unix timestamp stating when the item will expire. */ public const METADATA_EXPIRY = 'expiry'; /** * References the time the item took to be created, in milliseconds. */ public const METADATA_CTIME = 'ctime'; /** * References the list of tags that were assigned to the item, as string[]. */ public const METADATA_TAGS = 'tags'; /** * Reserved characters that cannot be used in a key or tag. */ public const RESERVED_CHARACTERS = '{}()/\\@:'; /** * Adds a tag to a cache item. * * Tags are strings that follow the same validation rules as keys. * * @param string|string[] $tags A tag or array of tags * * @return $this * * @throws InvalidArgumentException When $tag is not valid * @throws CacheException When the item comes from a pool that is not tag-aware */ public function tag($tags) : self; /** * Returns a list of metadata info that were saved alongside with the cached value. * * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. */ public function getMetadata() : array; } Copyright (c) 2018-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= The changelog is maintained for all Symfony contracts at the following URL: https://github.com/symfony/contracts/blob/main/CHANGELOG.md * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Cache; use _ContaoManager\Psr\Cache\CacheItemInterface; use _ContaoManager\Psr\Cache\InvalidArgumentException; /** * Covers most simple to advanced caching needs. * * @author Nicolas Grekas */ interface CacheInterface { /** * Fetches a value from the pool or computes it if not found. * * On cache misses, a callback is called that should return the missing value. * This callback is given a PSR-6 CacheItemInterface instance corresponding to the * requested key, that could be used e.g. for expiration control. It could also * be an ItemInterface instance when its additional features are needed. * * @param string $key The key of the item to retrieve from the cache * @param callable|CallbackInterface $callback Should return the computed value for the given key/item * @param float|null $beta A float that, as it grows, controls the likeliness of triggering * early expiration. 0 disables it, INF forces immediate expiration. * The default (or providing null) is implementation dependent but should * typically be 1.0, which should provide optimal stampede protection. * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} * * @return mixed * * @throws InvalidArgumentException When $key is not valid or when $beta is negative */ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); /** * Removes an item from the pool. * * @param string $key The key to delete * * @throws InvalidArgumentException When $key is not valid * * @return bool True if the item was successfully removed, false if there was any error */ public function delete(string $key) : bool; } Symfony Cache Contracts ======================= A set of abstractions extracted out of the Symfony components. Can be used to build on semantics that the Symfony components proved useful - and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Cache; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Psr\Cache\InvalidArgumentException; use _ContaoManager\Psr\Log\LoggerInterface; // Help opcache.preload discover always-needed symbols \class_exists(InvalidArgumentException::class); /** * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. * * @author Nicolas Grekas */ trait CacheTrait { /** * {@inheritdoc} * * @return mixed */ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) { return $this->doGet($this, $key, $callback, $beta, $metadata); } /** * {@inheritdoc} */ public function delete(string $key) : bool { return $this->deleteItem($key); } private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null) { if (0 > ($beta = $beta ?? 1.0)) { throw new class(\sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException { }; } $item = $pool->getItem($key); $recompute = !$item->isHit() || \INF === $beta; $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; if (!$recompute && $metadata) { $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? \false; $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? \false; if ($recompute = $ctime && $expiry && $expiry <= ($now = \microtime(\true)) - $ctime / 1000 * $beta * \log(\random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { // force applying defaultLifetime to expiry $item->expiresAt(null); $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', ['key' => $key, 'delta' => \sprintf('%.1f', $expiry - $now)]); } } if ($recompute) { $save = \true; $item->set($callback($item, $save)); if ($save) { $pool->save($item); } } return $item->get(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Contracts\Cache; use _ContaoManager\Psr\Cache\InvalidArgumentException; /** * Allows invalidating cached items using tags. * * @author Nicolas Grekas */ interface TagAwareCacheInterface extends CacheInterface { /** * Invalidates cached items using tags. * * When implemented on a PSR-6 pool, invalidation should not apply * to deferred items. Instead, they should be committed as usual. * This allows replacing old tagged values by new ones without * race conditions. * * @param string[] $tags An array of tags to invalidate * * @return bool True on success * * @throws InvalidArgumentException When $tags is not valid */ public function invalidateTags(array $tags); } { "name": "symfony\/cache-contracts", "type": "library", "description": "Generic abstractions related to caching", "keywords": [ "abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "psr\/cache": "^1.0|^2.0|^3.0" }, "suggest": { "symfony\/cache-implementation": "" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Contracts\\Cache\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { "dev-main": "2.5-dev" }, "thanks": { "name": "symfony\/contracts", "url": "https:\/\/github.com\/symfony\/contracts" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * @author Nicolas Grekas */ class EnvVarProcessor implements EnvVarProcessorInterface { private $container; private $loaders; private $loadedVars = []; /** * @param EnvVarLoaderInterface[] $loaders */ public function __construct(ContainerInterface $container, ?\Traversable $loaders = null) { $this->container = $container; $this->loaders = $loaders ?? new \ArrayIterator(); } /** * {@inheritdoc} */ public static function getProvidedTypes() { return ['base64' => 'string', 'bool' => 'bool', 'not' => 'bool', 'const' => 'bool|int|float|string|array', 'csv' => 'array', 'file' => 'string', 'float' => 'float', 'int' => 'int', 'json' => 'array', 'key' => 'bool|int|float|string|array', 'url' => 'array', 'query_string' => 'array', 'resolve' => 'string', 'default' => 'bool|int|float|string|array', 'string' => 'string', 'trim' => 'string', 'require' => 'bool|int|float|string|array']; } /** * {@inheritdoc} */ public function getEnv(string $prefix, string $name, \Closure $getEnv) { $i = \strpos($name, ':'); if ('key' === $prefix) { if (\false === $i) { throw new RuntimeException(\sprintf('Invalid env "key:%s": a key specifier should be provided.', $name)); } $next = \substr($name, $i + 1); $key = \substr($name, 0, $i); $array = $getEnv($next); if (!\is_array($array)) { throw new RuntimeException(\sprintf('Resolved value of "%s" did not result in an array value.', $next)); } if (!isset($array[$key]) && !\array_key_exists($key, $array)) { throw new EnvNotFoundException(\sprintf('Key "%s" not found in %s (resolved from "%s").', $key, \json_encode($array), $next)); } return $array[$key]; } if ('default' === $prefix) { if (\false === $i) { throw new RuntimeException(\sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name)); } $next = \substr($name, $i + 1); $default = \substr($name, 0, $i); if ('' !== $default && !$this->container->hasParameter($default)) { throw new RuntimeException(\sprintf('Invalid env fallback in "default:%s": parameter "%s" not found.', $name, $default)); } try { $env = $getEnv($next); if ('' !== $env && null !== $env) { return $env; } } catch (EnvNotFoundException $e) { // no-op } return '' === $default ? null : $this->container->getParameter($default); } if ('file' === $prefix || 'require' === $prefix) { if (!\is_scalar($file = $getEnv($name))) { throw new RuntimeException(\sprintf('Invalid file name: env var "%s" is non-scalar.', $name)); } if (!\is_file($file)) { throw new EnvNotFoundException(\sprintf('File "%s" not found (resolved from "%s").', $file, $name)); } if ('file' === $prefix) { return \file_get_contents($file); } else { return require $file; } } $returnNull = \false; if ('' === $prefix) { $returnNull = \true; $prefix = 'string'; } if (\false !== $i || 'string' !== $prefix) { $env = $getEnv($name); } elseif (isset($_ENV[$name])) { $env = $_ENV[$name]; } elseif (isset($_SERVER[$name]) && !\str_starts_with($name, 'HTTP_')) { $env = $_SERVER[$name]; } elseif (\false === ($env = \getenv($name)) || null === $env) { // null is a possible value because of thread safety issues foreach ($this->loadedVars as $vars) { if (\false !== ($env = $vars[$name] ?? \false)) { break; } } if (\false === $env || null === $env) { $loaders = $this->loaders; $this->loaders = new \ArrayIterator(); try { $i = 0; $ended = \true; $count = $loaders instanceof \Countable ? $loaders->count() : 0; foreach ($loaders as $loader) { if (\count($this->loadedVars) > $i++) { continue; } $this->loadedVars[] = $vars = $loader->loadEnvVars(); if (\false !== ($env = $vars[$name] ?? \false)) { $ended = \false; break; } } if ($ended || $count === $i) { $loaders = $this->loaders; } } catch (ParameterCircularReferenceException $e) { // skip loaders that need an env var that is not defined } finally { $this->loaders = $loaders; } } if (\false === $env || null === $env) { if (!$this->container->hasParameter("env({$name})")) { throw new EnvNotFoundException(\sprintf('Environment variable not found: "%s".', $name)); } $env = $this->container->getParameter("env({$name})"); } } if (null === $env) { if ($returnNull) { return null; } if (!isset($this->getProvidedTypes()[$prefix])) { throw new RuntimeException(\sprintf('Unsupported env var prefix "%s".', $prefix)); } if (!\in_array($prefix, ['string', 'bool', 'not', 'int', 'float'], \true)) { return null; } } if (null !== $env && !\is_scalar($env)) { throw new RuntimeException(\sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix)); } if ('string' === $prefix) { return (string) $env; } if (\in_array($prefix, ['bool', 'not'], \true)) { $env = (bool) ((\filter_var($env, \FILTER_VALIDATE_BOOLEAN) ?: \filter_var($env, \FILTER_VALIDATE_INT)) ?: \filter_var($env, \FILTER_VALIDATE_FLOAT)); return 'not' === $prefix ? !$env : $env; } if ('int' === $prefix) { if (null !== $env && \false === ($env = \filter_var($env, \FILTER_VALIDATE_INT) ?: \filter_var($env, \FILTER_VALIDATE_FLOAT))) { throw new RuntimeException(\sprintf('Non-numeric env var "%s" cannot be cast to int.', $name)); } return (int) $env; } if ('float' === $prefix) { if (null !== $env && \false === ($env = \filter_var($env, \FILTER_VALIDATE_FLOAT))) { throw new RuntimeException(\sprintf('Non-numeric env var "%s" cannot be cast to float.', $name)); } return (float) $env; } if ('const' === $prefix) { if (!\defined($env)) { throw new RuntimeException(\sprintf('Env var "%s" maps to undefined constant "%s".', $name, $env)); } return \constant($env); } if ('base64' === $prefix) { return \base64_decode(\strtr($env, '-_', '+/')); } if ('json' === $prefix) { $env = \json_decode($env, \true); if (\JSON_ERROR_NONE !== \json_last_error()) { throw new RuntimeException(\sprintf('Invalid JSON in env var "%s": ', $name) . \json_last_error_msg()); } if (null !== $env && !\is_array($env)) { throw new RuntimeException(\sprintf('Invalid JSON env var "%s": array or null expected, "%s" given.', $name, \get_debug_type($env))); } return $env; } if ('url' === $prefix) { $params = \parse_url($env); if (\false === $params) { throw new RuntimeException(\sprintf('Invalid URL in env var "%s".', $name)); } if (!isset($params['scheme'], $params['host'])) { throw new RuntimeException(\sprintf('Invalid URL env var "%s": schema and host expected, "%s" given.', $name, $env)); } $params += ['port' => null, 'user' => null, 'pass' => null, 'path' => null, 'query' => null, 'fragment' => null]; $params['user'] = null !== $params['user'] ? \rawurldecode($params['user']) : null; $params['pass'] = null !== $params['pass'] ? \rawurldecode($params['pass']) : null; // remove the '/' separator $params['path'] = '/' === ($params['path'] ?? '/') ? '' : \substr($params['path'], 1); return $params; } if ('query_string' === $prefix) { $queryString = \parse_url($env, \PHP_URL_QUERY) ?: $env; \parse_str($queryString, $result); return $result; } if ('resolve' === $prefix) { return \preg_replace_callback('/%%|%([^%\\s]+)%/', function ($match) use($name, $getEnv) { if (!isset($match[1])) { return '%'; } if (\str_starts_with($match[1], 'env(') && \str_ends_with($match[1], ')') && 'env()' !== $match[1]) { $value = $getEnv(\substr($match[1], 4, -1)); } else { $value = $this->container->getParameter($match[1]); } if (!\is_scalar($value)) { throw new RuntimeException(\sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, \get_debug_type($value))); } return $value; }, $env); } if ('csv' === $prefix) { return \str_getcsv($env, ',', '"', \PHP_VERSION_ID >= 70400 ? '' : '\\'); } if ('trim' === $prefix) { return \trim($env); } throw new RuntimeException(\sprintf('Unsupported env var prefix "%s" for env name "%s".', $prefix, $name)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * ContainerAware trait. * * @author Fabien Potencier */ trait ContainerAwareTrait { /** * @var ContainerInterface|null */ protected $container; public function setContainer(?ContainerInterface $container = null) { $this->container = $container; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunction; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; /** * Define some ExpressionLanguage functions. * * To get a service, use service('request'). * To get a parameter, use parameter('kernel.debug'). * * @author Fabien Potencier */ class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface { private $serviceCompiler; public function __construct(?callable $serviceCompiler = null) { $this->serviceCompiler = $serviceCompiler; } public function getFunctions() { return [new ExpressionFunction('service', $this->serviceCompiler ?: function ($arg) { return \sprintf('$this->get(%s)', $arg); }, function (array $variables, $value) { return $variables['container']->get($value); }), new ExpressionFunction('parameter', function ($arg) { return \sprintf('$this->getParameter(%s)', $arg); }, function (array $variables, $value) { return $variables['container']->getParameter($value); })]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; // Help opcache.preload discover always-needed symbols \class_exists(RewindableGenerator::class); \class_exists(ArgumentServiceLocator::class); /** * Container is a dependency injection container. * * It gives access to object instances (services). * Services and parameters are simple key/pair stores. * The container can have four possible behaviors when a service * does not exist (or is not initialized for the last case): * * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at compilation time (the default) * * NULL_ON_INVALID_REFERENCE: Returns null * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference * (for instance, ignore a setter if the service does not exist) * * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references * * RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at runtime * * @author Fabien Potencier * @author Johannes M. Schmitt */ class Container implements ContainerInterface, ResetInterface { protected $parameterBag; protected $services = []; protected $privates = []; protected $fileMap = []; protected $methodMap = []; protected $factories = []; protected $aliases = []; protected $loading = []; protected $resolving = []; protected $syntheticIds = []; private $envCache = []; private $compiled = \false; private $getEnv; public function __construct(?ParameterBagInterface $parameterBag = null) { $this->parameterBag = $parameterBag ?? new EnvPlaceholderParameterBag(); } /** * Compiles the container. * * This method does two things: * * * Parameter values are resolved; * * The parameter bag is frozen. */ public function compile() { $this->parameterBag->resolve(); $this->parameterBag = new FrozenParameterBag($this->parameterBag->all()); $this->compiled = \true; } /** * Returns true if the container is compiled. * * @return bool */ public function isCompiled() { return $this->compiled; } /** * Gets the service container parameter bag. * * @return ParameterBagInterface */ public function getParameterBag() { return $this->parameterBag; } /** * Gets a parameter. * * @return array|bool|string|int|float|\UnitEnum|null * * @throws InvalidArgumentException if the parameter is not defined */ public function getParameter(string $name) { return $this->parameterBag->get($name); } /** * @return bool */ public function hasParameter(string $name) { return $this->parameterBag->has($name); } /** * Sets a parameter. * * @param string $name The parameter name * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value */ public function setParameter(string $name, $value) { $this->parameterBag->set($name, $value); } /** * Sets a service. * * Setting a synthetic service to null resets it: has() returns false and get() * behaves in the same way as if the service was never created. */ public function set(string $id, ?object $service) { // Runs the internal initializer; used by the dumped container to include always-needed files if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) { $initialize = $this->privates['service_container']; unset($this->privates['service_container']); $initialize(); } if ('service_container' === $id) { throw new InvalidArgumentException('You cannot set service "service_container".'); } if (!(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) { if (isset($this->syntheticIds[$id]) || !isset($this->getRemovedIds()[$id])) { // no-op } elseif (null === $service) { throw new InvalidArgumentException(\sprintf('The "%s" service is private, you cannot unset it.', $id)); } else { throw new InvalidArgumentException(\sprintf('The "%s" service is private, you cannot replace it.', $id)); } } elseif (isset($this->services[$id])) { throw new InvalidArgumentException(\sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); } if (isset($this->aliases[$id])) { unset($this->aliases[$id]); } if (null === $service) { unset($this->services[$id]); return; } $this->services[$id] = $service; } /** * Returns true if the given service is defined. * * @param string $id The service identifier * * @return bool */ public function has(string $id) { if (isset($this->aliases[$id])) { $id = $this->aliases[$id]; } if (isset($this->services[$id])) { return \true; } if ('service_container' === $id) { return \true; } return isset($this->fileMap[$id]) || isset($this->methodMap[$id]); } /** * Gets a service. * * @return object|null * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined * @throws \Exception if an exception has been thrown when the service has been resolved * * @see Reference */ public function get(string $id, int $invalidBehavior = 1) { return $this->services[$id] ?? $this->services[$id = $this->aliases[$id] ?? $id] ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? [$this, 'make'])($id, $invalidBehavior)); } /** * Creates a service. * * As a separate method to allow "get()" to use the really fast `??` operator. */ private function make(string $id, int $invalidBehavior) { if (isset($this->loading[$id])) { throw new ServiceCircularReferenceException($id, \array_merge(\array_keys($this->loading), [$id])); } $this->loading[$id] = \true; try { if (isset($this->fileMap[$id])) { return 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); } elseif (isset($this->methodMap[$id])) { return 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); } } catch (\Exception $e) { unset($this->services[$id]); throw $e; } finally { unset($this->loading[$id]); } if (1 === $invalidBehavior) { if (!$id) { throw new ServiceNotFoundException($id); } if (isset($this->syntheticIds[$id])) { throw new ServiceNotFoundException($id, null, null, [], \sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id)); } if (isset($this->getRemovedIds()[$id])) { throw new ServiceNotFoundException($id, null, null, [], \sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id)); } $alternatives = []; foreach ($this->getServiceIds() as $knownId) { if ('' === $knownId || '.' === $knownId[0]) { continue; } $lev = \levenshtein($id, $knownId); if ($lev <= \strlen($id) / 3 || \str_contains($knownId, $id)) { $alternatives[] = $knownId; } } throw new ServiceNotFoundException($id, null, null, $alternatives); } return null; } /** * Returns true if the given service has actually been initialized. * * @return bool */ public function initialized(string $id) { if (isset($this->aliases[$id])) { $id = $this->aliases[$id]; } if ('service_container' === $id) { return \false; } return isset($this->services[$id]); } /** * {@inheritdoc} */ public function reset() { $services = $this->services + $this->privates; $this->services = $this->factories = $this->privates = []; foreach ($services as $service) { try { if ($service instanceof ResetInterface) { $service->reset(); } } catch (\Throwable $e) { continue; } } } /** * Gets all service ids. * * @return string[] */ public function getServiceIds() { return \array_map('strval', \array_unique(\array_merge(['service_container'], \array_keys($this->fileMap), \array_keys($this->methodMap), \array_keys($this->aliases), \array_keys($this->services)))); } /** * Gets service ids that existed at compile time. * * @return array */ public function getRemovedIds() { return []; } /** * Camelizes a string. * * @return string */ public static function camelize(string $id) { return \strtr(\ucwords(\strtr($id, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']); } /** * A string to underscore. * * @return string */ public static function underscore(string $id) { return \strtolower(\preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], \str_replace('_', '.', $id))); } /** * Creates a service by requiring its factory file. */ protected function load(string $file) { return require $file; } /** * Fetches a variable from the environment. * * @return mixed * * @throws EnvNotFoundException When the environment variable is not found and has no default value */ protected function getEnv(string $name) { if (isset($this->resolving[$envName = "env({$name})"])) { throw new ParameterCircularReferenceException(\array_keys($this->resolving)); } if (isset($this->envCache[$name]) || \array_key_exists($name, $this->envCache)) { return $this->envCache[$name]; } if (!$this->has($id = 'container.env_var_processors_locator')) { $this->set($id, new ServiceLocator([])); } if (!$this->getEnv) { $this->getEnv = \Closure::fromCallable([$this, 'getEnv']); } $processors = $this->get($id); if (\false !== ($i = \strpos($name, ':'))) { $prefix = \substr($name, 0, $i); $localName = \substr($name, 1 + $i); } else { $prefix = 'string'; $localName = $name; } $processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this); if (\false === $i) { $prefix = ''; } $this->resolving[$envName] = \true; try { return $this->envCache[$name] = $processor->getEnv($prefix, $localName, $this->getEnv); } finally { unset($this->resolving[$envName]); } } /** * @param string|false $registry * @param string|bool $load * * @return mixed * * @internal */ protected final function getService($registry, string $id, ?string $method, $load) { if ('service_container' === $id) { return $this; } if (\is_string($load)) { throw new RuntimeException($load); } if (null === $method) { return \false !== $registry ? $this->{$registry}[$id] ?? null : null; } if (\false !== $registry) { return $this->{$registry}[$id] ?? ($this->{$registry}[$id] = $load ? $this->load($method) : $this->{$method}()); } if (!$load) { return $this->{$method}(); } return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method); } private function __clone() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; /** * Turns public and "container.reversible" services back to their ids. * * @author Nicolas Grekas */ final class ReverseContainer { private $serviceContainer; private $reversibleLocator; private $tagName; private $getServiceId; public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible') { $this->serviceContainer = $serviceContainer; $this->reversibleLocator = $reversibleLocator; $this->tagName = $tagName; $this->getServiceId = \Closure::bind(function (object $service) : ?string { return (\array_search($service, $this->services, \true) ?: \array_search($service, $this->privates, \true)) ?: null; }, $serviceContainer, Container::class); } /** * Returns the id of the passed object when it exists as a service. * * To be reversible, services need to be either public or be tagged with "container.reversible". */ public function getId(object $service) : ?string { if ($this->serviceContainer === $service) { return 'service_container'; } if (null === ($id = ($this->getServiceId)($service))) { return null; } if ($this->serviceContainer->has($id) || $this->reversibleLocator->has($id)) { return $id; } return null; } /** * @throws ServiceNotFoundException When the service is not reversible */ public function getService(string $id) : object { if ($this->reversibleLocator->has($id)) { return $this->reversibleLocator->get($id); } if (isset($this->serviceContainer->getRemovedIds()[$id])) { throw new ServiceNotFoundException($id, null, null, [], \sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName)); } return $this->serviceContainer->get($id); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * Represents a variable. * * $var = new Variable('a'); * * will be dumped as * * $a * * by the PHP dumper. * * @author Johannes M. Schmitt */ class Variable { private $name; public function __construct(string $name) { $this->name = $name; } /** * @return string */ public function __toString() { return $this->name; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * ExtensionInterface is the interface implemented by container extension classes. * * @author Fabien Potencier */ interface ExtensionInterface { /** * Loads a specific configuration. * * @throws \InvalidArgumentException When provided tag is not defined in this extension */ public function load(array $configs, ContainerBuilder $container); /** * Returns the namespace to be used for this extension (XML namespace). * * @return string */ public function getNamespace(); /** * Returns the base path for the XSD files. * * @return string|false */ public function getXsdValidationBasePath(); /** * Returns the recommended alias to use in XML. * * This alias is also the mandatory prefix to use when using YAML. * * @return string */ public function getAlias(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Processor; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; /** * Provides useful features shared by many extensions. * * @author Fabien Potencier */ abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface { private $processedConfigs = []; /** * {@inheritdoc} */ public function getXsdValidationBasePath() { return \false; } /** * {@inheritdoc} */ public function getNamespace() { return 'http://example.org/schema/dic/' . $this->getAlias(); } /** * Returns the recommended alias to use in XML. * * This alias is also the mandatory prefix to use when using YAML. * * This convention is to remove the "Extension" postfix from the class * name and then lowercase and underscore the result. So: * * AcmeHelloExtension * * becomes * * acme_hello * * This can be overridden in a sub-class to specify the alias manually. * * @return string * * @throws BadMethodCallException When the extension name does not follow conventions */ public function getAlias() { $className = static::class; if (!\str_ends_with($className, 'Extension')) { throw new BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.'); } $classBaseName = \substr(\strrchr($className, '\\'), 1, -9); return Container::underscore($classBaseName); } /** * {@inheritdoc} */ public function getConfiguration(array $config, ContainerBuilder $container) { $class = static::class; if (\str_contains($class, "\x00")) { return null; // ignore anonymous classes } $class = \substr_replace($class, '\\Configuration', \strrpos($class, '\\')); $class = $container->getReflectionClass($class); if (!$class) { return null; } if (!$class->implementsInterface(ConfigurationInterface::class)) { throw new LogicException(\sprintf('The extension configuration class "%s" must implement "%s".', $class->getName(), ConfigurationInterface::class)); } if (!($constructor = $class->getConstructor()) || !$constructor->getNumberOfRequiredParameters()) { return $class->newInstance(); } return null; } protected final function processConfiguration(ConfigurationInterface $configuration, array $configs) : array { $processor = new Processor(); return $this->processedConfigs[] = $processor->processConfiguration($configuration, $configs); } /** * @internal */ public final function getProcessedConfigs() : array { try { return $this->processedConfigs; } finally { $this->processedConfigs = []; } } /** * @return bool * * @throws InvalidArgumentException When the config is not enableable */ protected function isConfigEnabled(ContainerBuilder $container, array $config) { if (!\array_key_exists('enabled', $config)) { throw new InvalidArgumentException("The config array has no 'enabled' key."); } return (bool) $container->getParameterBag()->resolveValue($config['enabled']); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; interface PrependExtensionInterface { /** * Allow an extension to prepend the extension configurations. */ public function prepend(ContainerBuilder $container); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Extension; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * ConfigurationExtensionInterface is the interface implemented by container extension classes. * * @author Kevin Bond */ interface ConfigurationExtensionInterface { /** * Returns extension configuration. * * @return ConfigurationInterface|null */ public function getConfiguration(array $config, ContainerBuilder $container); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; /** * An attribute to tell how a base type should be autoconfigured. * * @author Nicolas Grekas */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class Autoconfigure { public function __construct(public ?array $tags = null, public ?array $calls = null, public ?array $bind = null, public bool|string|null $lazy = null, public ?bool $public = null, public ?bool $shared = null, public ?bool $autowire = null, public ?array $properties = null, public array|string|null $configurator = null) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; /** * An attribute to tell under which index and priority a service class should be found in tagged iterators/locators. * * @author Nicolas Grekas */ #[\Attribute(\Attribute::TARGET_CLASS)] class AsTaggedItem { public function __construct(public ?string $index = null, public ?int $priority = null) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * An attribute to tell how a dependency is used and hint named autowiring aliases. * * @author Nicolas Grekas */ #[\Attribute(\Attribute::TARGET_PARAMETER)] final class Target { /** * @var string */ public $name; public function __construct(string $name) { $this->name = \lcfirst(\str_replace(' ', '', \ucwords(\preg_replace('/[^a-zA-Z0-9\\x7f-\\xff]++/', ' ', $name)))); } public static function parseName(\ReflectionParameter $parameter) : string { if (80000 > \PHP_VERSION_ID || !($target = $parameter->getAttributes(self::class)[0] ?? null)) { return $parameter->name; } $name = $target->newInstance()->name; if (!\preg_match('/^[a-zA-Z_\\x7f-\\xff]/', $name)) { if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { $function = $function->class . '::' . $function->name; } else { $function = $function->name; } throw new InvalidArgumentException(\sprintf('Invalid #[Target] name "%s" on parameter "$%s" of "%s()": the first character must be a letter.', $name, $parameter->name, $function)); } return $name; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; /** * An attribute to tell under which environment this class should be registered as a service. * * @author Nicolas Grekas */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION | \Attribute::IS_REPEATABLE)] class When { public function __construct(public string $env) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_PARAMETER)] class TaggedIterator { public function __construct(public string $tag, public ?string $indexAttribute = null, public ?string $defaultIndexMethod = null, public ?string $defaultPriorityMethod = null) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; /** * An attribute to tell how a base type should be tagged. * * @author Nicolas Grekas */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class AutoconfigureTag extends Autoconfigure { public function __construct(?string $name = null, array $attributes = []) { parent::__construct(tags: [[$name ?? 0 => $attributes]]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Attribute; #[\Attribute(\Attribute::TARGET_PARAMETER)] class TaggedLocator { public function __construct(public string $tag, public ?string $indexAttribute = null, public ?string $defaultIndexMethod = null, public ?string $defaultPriorityMethod = null) { } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Add `$defaultIndexMethod` and `$defaultPriorityMethod` to `TaggedIterator` and `TaggedLocator` attributes * Add `service_closure()` to the PHP-DSL * Add support for autoconfigurable attributes on methods, properties and parameters * Make auto-aliases private by default * Add support for autowiring union and intersection types 5.3 --- * Add `ServicesConfigurator::remove()` in the PHP-DSL * Add `%env(not:...)%` processor to negate boolean values * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators * Add autoconfigurable attributes * Add support for autowiring tagged iterators and locators via attributes on PHP 8 * Add support for per-env configuration in XML and Yaml loaders * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration * Add support an integer return value for default_index_method * Add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match * Add `env()` and `EnvConfigurator` in the PHP-DSL * Add support for `ConfigBuilder` in the `PhpFileLoader` * Add `ContainerConfigurator::env()` to get the current environment * Add `#[Target]` to tell how a dependency is used and hint named autowiring aliases 5.2.0 ----- * added `param()` and `abstract_arg()` in the PHP-DSL * deprecated `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead * added support for the `#[Required]` attribute 5.1.0 ----- * deprecated `inline()` in favor of `inline_service()` and `ref()` in favor of `service()` when using the PHP-DSL * allow decorators to reference their decorated service using the special `.inner` id * added support to autowire public typed properties in php 7.4 * added support for defining method calls, a configurator, and property setters in `InlineServiceConfigurator` * added possibility to define abstract service arguments * allowed mixing "parent" and instanceof-conditionals/defaults/bindings * updated the signature of method `Definition::setDeprecated()` to `Definition::setDeprecation(string $package, string $version, string $message)` * updated the signature of method `Alias::setDeprecated()` to `Alias::setDeprecation(string $package, string $version, string $message)` * updated the signature of method `DeprecateTrait::deprecate()` to `DeprecateTrait::deprecation(string $package, string $version, string $message)` * deprecated the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service, configure them explicitly instead * added class `Symfony\Component\DependencyInjection\Dumper\Preloader` to help with preloading on PHP 7.4+ * added tags `container.preload`/`.no_preload` to declare extra classes to preload/services to not preload * allowed loading and dumping tags with an attribute named "name" * deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead * deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead * added support of PHP8 static return type for withers * added `AliasDeprecatedPublicServicesPass` to deprecate public services to private 5.0.0 ----- * removed support for auto-discovered extension configuration class which does not implement `ConfigurationInterface` * removed support for non-string default env() parameters * moved `ServiceSubscriberInterface` to the `Symfony\Contracts\Service` namespace * removed `RepeatedPass` and `RepeatablePassInterface` * removed support for short factory/configurator syntax from `YamlFileLoader` * removed `ResettableContainerInterface`, use `ResetInterface` instead * added argument `$returnsClone` to `Definition::addMethodCall()` * removed `tagged`, use `tagged_iterator` instead 4.4.0 ----- * added `CheckTypeDeclarationsPass` to check injected parameters type during compilation * added support for opcache.preload by generating a preloading script in the cache folder * added support for dumping the container in one file instead of many files * deprecated support for short factories and short configurators in Yaml * added `tagged_iterator` alias for `tagged` which might be deprecated in a future version * deprecated passing an instance of `Symfony\Component\DependencyInjection\Parameter` as class name to `Symfony\Component\DependencyInjection\Definition` * added support for binding iterable and tagged services * made singly-implemented interfaces detection be scoped by file * added ability to define a static priority method for tagged service * added support for improved syntax to define method calls in Yaml * made the `%env(base64:...)%` processor able to decode base64url * added ability to choose behavior of decorations on non existent decorated services 4.3.0 ----- * added `%env(trim:...)%` processor to trim a string value * added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%` * added `%env(url:...)%` processor to convert an URL or DNS into an array of components * added `%env(query_string:...)%` processor to convert a query string into an array of key values * added support for deprecating aliases * made `ContainerParametersResource` final and not implement `Serializable` anymore * added `ReverseContainer`: a container that turns services back to their ids * added ability to define an index for a tagged collection * added ability to define an index for services in an injected service locator argument * made `ServiceLocator` implement `ServiceProviderInterface` * deprecated support for non-string default env() parameters * added `%env(require:...)%` processor to `require()` a PHP file and use the value returned from it 4.2.0 ----- * added `ContainerBuilder::registerAliasForArgument()` to support autowiring by type+name * added support for binding by type+name * added `ServiceSubscriberTrait` to ease implementing `ServiceSubscriberInterface` using methods' return types * added `ServiceLocatorArgument` and `!service_locator` config tag for creating optimized service-locators * added support for autoconfiguring bindings * added `%env(key:...)%` processor to fetch a specific key from an array * deprecated `ServiceSubscriberInterface`, use the same interface from the `Symfony\Contracts\Service` namespace instead * deprecated `ResettableContainerInterface`, use `Symfony\Contracts\Service\ResetInterface` instead 4.1.0 ----- * added support for variadics in named arguments * added PSR-11 `ContainerBagInterface` and its `ContainerBag` implementation to access parameters as-a-service * added support for service's decorators autowiring * deprecated the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods * environment variables are validated when used in extension configuration * deprecated support for auto-discovered extension configuration class which does not implement `ConfigurationInterface` 4.0.0 ----- * Relying on service auto-registration while autowiring is not supported anymore. Explicitly inject your dependencies or create services whose ids are their fully-qualified class name. Before: ```php namespace App\Controller; use App\Mailer; class DefaultController { public function __construct(Mailer $mailer) { // ... } // ... } ``` ```yml services: App\Controller\DefaultController: autowire: true ``` After: ```php // same PHP code ``` ```yml services: App\Controller\DefaultController: autowire: true # or # App\Controller\DefaultController: # arguments: { $mailer: "@App\Mailer" } App\Mailer: autowire: true ``` * removed autowiring services based on the types they implement * added a third `$methodName` argument to the `getProxyFactoryCode()` method of the `DumperInterface` * removed support for autowiring types * removed `Container::isFrozen` * removed support for dumping an ucompiled container in `PhpDumper` * removed support for generating a dumped `Container` without populating the method map * removed support for case insensitive service identifiers * removed the `DefinitionDecorator` class, replaced by `ChildDefinition` * removed the `AutowireServiceResource` class and related `AutowirePass::createResourceForClass()` method * removed `LoggingFormatter`, `Compiler::getLoggingFormatter()` and `addLogMessage()` class and methods, use the `ContainerBuilder::log()` method instead * removed `FactoryReturnTypePass` * removed `ContainerBuilder::addClassResource()`, use the `addObjectResource()` or the `getReflectionClass()` method instead. * removed support for top-level anonymous services * removed silent behavior for unused attributes and elements * removed support for setting and accessing private services in `Container` * removed support for setting pre-defined services in `Container` * removed support for case insensitivity of parameter names * removed `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead 3.4.0 ----- * moved the `ExtensionCompilerPass` to before-optimization passes with priority -1000 * deprecated "public-by-default" definitions and aliases, the new default will be "private" in 4.0 * added `EnvVarProcessorInterface` and corresponding "container.env_var_processor" tag for processing env vars * added support for ignore-on-uninitialized references * deprecated service auto-registration while autowiring * deprecated the ability to check for the initialization of a private service with the `Container::initialized()` method * deprecated support for top-level anonymous services in XML * deprecated case insensitivity of parameter names * deprecated the `ResolveDefinitionTemplatesPass` class in favor of `ResolveChildDefinitionsPass` * added `TaggedIteratorArgument` with YAML (`!tagged foo`) and XML (``) support * deprecated `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead 3.3.0 ----- * deprecated autowiring services based on the types they implement; rename (or alias) your services to their FQCN id to make them autowirable * added "ServiceSubscriberInterface" - to allow for per-class explicit service-locator definitions * added "container.service_locator" tag for defining service-locator services * added anonymous services support in YAML configuration files using the `!service` tag. * added "TypedReference" and "ServiceClosureArgument" for creating service-locator services * added `ServiceLocator` - a PSR-11 container holding a set of services to be lazily loaded * added "instanceof" section for local interface-defined configs * added prototype services for PSR4-based discovery and registration * added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info * deprecated `ContainerBuilder::getClassResource()`, use `ContainerBuilder::getReflectionClass()` or `ContainerBuilder::addObjectResource()` instead * added `ContainerBuilder::fileExists()` for checking and tracking file or directory existence * deprecated autowiring-types, use aliases instead * added support for omitting the factory class name in a service definition if the definition class is set * deprecated case insensitivity of service identifiers * added "iterator" argument type for lazy iteration over a set of values and services * added file-wide configurable defaults for service attributes "public", "tags", "autowire" and "autoconfigure" * made the "class" attribute optional, using the "id" as fallback * using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and will not be supported anymore in 4.0 * deprecated the `DefinitionDecorator` class in favor of `ChildDefinition` * allow config files to be loaded using a glob pattern * [BC BREAK] the `NullDumper` class is now final 3.2.0 ----- * allowed to prioritize compiler passes by introducing a third argument to `PassConfig::addPass()`, to `Compiler::addPass` and to `ContainerBuilder::addCompilerPass()` * added support for PHP constants in YAML configuration files * deprecated the ability to set or unset a private service with the `Container::set()` method * deprecated the ability to check for the existence of a private service with the `Container::has()` method * deprecated the ability to request a private service with the `Container::get()` method * deprecated support for generating a dumped `Container` without populating the method map 3.0.0 ----- * removed all deprecated codes from 2.x versions 2.8.0 ----- * deprecated the abstract ContainerAware class in favor of ContainerAwareTrait * deprecated IntrospectableContainerInterface, to be merged with ContainerInterface in 3.0 * allowed specifying a directory to recursively load all configuration files it contains * deprecated the concept of scopes * added `Definition::setShared()` and `Definition::isShared()` * added ResettableContainerInterface to be able to reset the container to release memory on shutdown * added a way to define the priority of service decoration * added support for service autowiring 2.7.0 ----- * deprecated synchronized services 2.6.0 ----- * added new factory syntax and deprecated the old one 2.5.0 ----- * added DecoratorServicePass and a way to override a service definition (Definition::setDecoratedService()) * deprecated SimpleXMLElement class. 2.4.0 ----- * added support for expressions in service definitions * added ContainerAwareTrait to add default container aware behavior to a class 2.2.0 ----- * added Extension::isConfigEnabled() to ease working with enableable configurations * added an Extension base class with sensible defaults to be used in conjunction with the Config component. * added PrependExtensionInterface (to be able to allow extensions to prepend application configuration settings for any Bundle) 2.1.0 ----- * added IntrospectableContainerInterface (to be able to check if a service has been initialized or not) * added ConfigurationExtensionInterface * added Definition::clearTag() * component exceptions that inherit base SPL classes are now used exclusively (this includes dumped containers) * [BC BREAK] fixed unescaping of class arguments, method ParameterBag::unescapeValue() was made public * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Config; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; use _ContaoManager\Symfony\Component\Config\ResourceCheckerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; /** * @author Maxime Steinhausser */ class ContainerParametersResourceChecker implements ResourceCheckerInterface { /** @var ContainerInterface */ private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } /** * {@inheritdoc} */ public function supports(ResourceInterface $metadata) { return $metadata instanceof ContainerParametersResource; } /** * {@inheritdoc} */ public function isFresh(ResourceInterface $resource, int $timestamp) { foreach ($resource->getParameters() as $key => $value) { if (!$this->container->hasParameter($key) || $this->container->getParameter($key) !== $value) { return \false; } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Config; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; /** * Tracks container parameters. * * @author Maxime Steinhausser * * @final */ class ContainerParametersResource implements ResourceInterface { private $parameters; /** * @param array $parameters The container parameters to track */ public function __construct(array $parameters) { $this->parameters = $parameters; } public function __toString() : string { return 'container_parameters_' . \md5(\serialize($this->parameters)); } public function getParameters() : array { return $this->parameters; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Titouan Galopin * @author Nicolas Grekas */ trait ReferenceSetArgumentTrait { private $values; /** * @param Reference[] $values */ public function __construct(array $values) { $this->setValues($values); } /** * @return Reference[] */ public function getValues() { return $this->values; } /** * @param Reference[] $values The service references to put in the set */ public function setValues(array $values) { foreach ($values as $k => $v) { if (null !== $v && !$v instanceof Reference) { throw new InvalidArgumentException(\sprintf('A "%s" must hold only Reference instances, "%s" given.', __CLASS__, \get_debug_type($v))); } } $this->values = $values; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; /** * Represents an abstract service argument, which have to be set by a compiler pass or a DI extension. */ final class AbstractArgument { private $text; private $context; public function __construct(string $text = '') { $this->text = \trim($text, '. '); } public function setContext(string $context) : void { $this->context = $context . ' is abstract' . ('' === $this->text ? '' : ': '); } public function getText() : string { return $this->text; } public function getTextWithContext() : string { return $this->context . $this->text . '.'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; /** * @internal */ class RewindableGenerator implements \IteratorAggregate, \Countable { private $generator; private $count; /** * @param int|callable $count */ public function __construct(callable $generator, $count) { $this->generator = $generator; $this->count = $count; } public function getIterator() : \Traversable { $g = $this->generator; return $g(); } public function count() : int { if (\is_callable($count = $this->count)) { $this->count = $count(); } return $this->count; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Represents a closure acting as a service locator. * * @author Nicolas Grekas */ class ServiceLocatorArgument implements ArgumentInterface { use ReferenceSetArgumentTrait; private $taggedIteratorArgument; /** * @param Reference[]|TaggedIteratorArgument $values */ public function __construct($values = []) { if ($values instanceof TaggedIteratorArgument) { $this->taggedIteratorArgument = $values; $this->values = []; } else { $this->setValues($values); } } public function getTaggedIteratorArgument() : ?TaggedIteratorArgument { return $this->taggedIteratorArgument; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; /** * @author Guilhem Niot */ final class BoundArgument implements ArgumentInterface { public const SERVICE_BINDING = 0; public const DEFAULTS_BINDING = 1; public const INSTANCEOF_BINDING = 2; private static $sequence = 0; private $value; private $identifier; private $used; private $type; private $file; public function __construct($value, bool $trackUsage = \true, int $type = 0, ?string $file = null) { $this->value = $value; if ($trackUsage) { $this->identifier = ++self::$sequence; } else { $this->used = \true; } $this->type = $type; $this->file = $file; } /** * {@inheritdoc} */ public function getValues() : array { return [$this->value, $this->identifier, $this->used, $this->type, $this->file]; } /** * {@inheritdoc} */ public function setValues(array $values) { if (5 === \count($values)) { [$this->value, $this->identifier, $this->used, $this->type, $this->file] = $values; } else { [$this->value, $this->identifier, $this->used] = $values; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; /** * Represents a collection of values to lazily iterate over. * * @author Titouan Galopin */ class IteratorArgument implements ArgumentInterface { use ReferenceSetArgumentTrait; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; /** * Represents a collection of services found by tag name to lazily iterate over. * * @author Roland Franssen */ class TaggedIteratorArgument extends IteratorArgument { private $tag; private $indexAttribute; private $defaultIndexMethod; private $defaultPriorityMethod; private $needsIndexes = \false; /** * @param string $tag The name of the tag identifying the target services * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute * @param bool $needsIndexes Whether indexes are required and should be generated when computing the map * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute */ public function __construct(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, bool $needsIndexes = \false, ?string $defaultPriorityMethod = null) { parent::__construct([]); if (null === $indexAttribute && $needsIndexes) { $indexAttribute = \preg_match('/[^.]++$/', $tag, $m) ? $m[0] : $tag; } $this->tag = $tag; $this->indexAttribute = $indexAttribute; $this->defaultIndexMethod = $defaultIndexMethod ?: ($indexAttribute ? 'getDefault' . \str_replace(' ', '', \ucwords(\preg_replace('/[^a-zA-Z0-9\\x7f-\\xff]++/', ' ', $indexAttribute))) . 'Name' : null); $this->needsIndexes = $needsIndexes; $this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault' . \str_replace(' ', '', \ucwords(\preg_replace('/[^a-zA-Z0-9\\x7f-\\xff]++/', ' ', $indexAttribute))) . 'Priority' : null); } public function getTag() { return $this->tag; } public function getIndexAttribute() : ?string { return $this->indexAttribute; } public function getDefaultIndexMethod() : ?string { return $this->defaultIndexMethod; } public function needsIndexes() : bool { return $this->needsIndexes; } public function getDefaultPriorityMethod() : ?string { return $this->defaultPriorityMethod; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; /** * Represents a complex argument containing nested values. * * @author Titouan Galopin */ interface ArgumentInterface { /** * @return array */ public function getValues(); public function setValues(array $values); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; /** * @author Nicolas Grekas * * @internal */ class ServiceLocator extends BaseServiceLocator { private $factory; private $serviceMap; private $serviceTypes; public function __construct(\Closure $factory, array $serviceMap, ?array $serviceTypes = null) { $this->factory = $factory; $this->serviceMap = $serviceMap; $this->serviceTypes = $serviceTypes; parent::__construct($serviceMap); } /** * {@inheritdoc} * * @return mixed */ public function get(string $id) { return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id); } /** * {@inheritdoc} */ public function getProvidedServices() : array { return $this->serviceTypes ?? ($this->serviceTypes = \array_map(function () { return '?'; }, $this->serviceMap)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Argument; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Represents a service wrapped in a memoizing closure. * * @author Nicolas Grekas */ class ServiceClosureArgument implements ArgumentInterface { private $values; public function __construct(Reference $reference) { $this->values = [$reference]; } /** * {@inheritdoc} */ public function getValues() { return $this->values; } /** * {@inheritdoc} */ public function setValues(array $values) { if ([0] !== \array_keys($values) || !($values[0] instanceof Reference || null === $values[0])) { throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one Reference.'); } $this->values = $values; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use Composer\InstalledVersions; use _ContaoManager\Psr\Container\ContainerInterface as PsrContainerInterface; use _ContaoManager\Symfony\Component\Config\Resource\ClassExistenceResource; use _ContaoManager\Symfony\Component\Config\Resource\ComposerResource; use _ContaoManager\Symfony\Component\Config\Resource\DirectoryResource; use _ContaoManager\Symfony\Component\Config\Resource\FileExistenceResource; use _ContaoManager\Symfony\Component\Config\Resource\FileResource; use _ContaoManager\Symfony\Component\Config\Resource\GlobResource; use _ContaoManager\Symfony\Component\Config\Resource\ReflectionClassResource; use _ContaoManager\Symfony\Component\Config\Resource\ResourceInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\Target; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PassConfig; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; /** * ContainerBuilder is a DI container that provides an API to easily describe services. * * @author Fabien Potencier */ class ContainerBuilder extends Container implements TaggedContainerInterface { /** * @var array */ private $extensions = []; /** * @var array */ private $extensionsByNs = []; /** * @var array */ private $definitions = []; /** * @var array */ private $aliasDefinitions = []; /** * @var array */ private $resources = []; /** * @var array>> */ private $extensionConfigs = []; /** * @var Compiler */ private $compiler; /** * @var bool */ private $trackResources; /** * @var InstantiatorInterface|null */ private $proxyInstantiator; /** * @var ExpressionLanguage|null */ private $expressionLanguage; /** * @var ExpressionFunctionProviderInterface[] */ private $expressionLanguageProviders = []; /** * @var string[] with tag names used by findTaggedServiceIds */ private $usedTags = []; /** * @var string[][] a map of env var names to their placeholders */ private $envPlaceholders = []; /** * @var int[] a map of env vars to their resolution counter */ private $envCounters = []; /** * @var string[] the list of vendor directories */ private $vendors; /** * @var array */ private $autoconfiguredInstanceof = []; /** * @var array */ private $autoconfiguredAttributes = []; /** * @var array */ private $removedIds = []; /** * @var array */ private $removedBindingIds = []; private const INTERNAL_TYPES = ['int' => \true, 'float' => \true, 'string' => \true, 'bool' => \true, 'resource' => \true, 'object' => \true, 'array' => \true, 'null' => \true, 'callable' => \true, 'iterable' => \true, 'mixed' => \true]; public function __construct(?ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); $this->trackResources = \interface_exists(ResourceInterface::class); $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(\true)->setPublic(\true)); $this->setAlias(PsrContainerInterface::class, new Alias('service_container', \false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage = 'The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it.'); $this->setAlias(ContainerInterface::class, new Alias('service_container', \false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage); } /** * @var array */ private $classReflectors; /** * Sets the track resources flag. * * If you are not using the loaders and therefore don't want * to depend on the Config component, set this flag to false. */ public function setResourceTracking(bool $track) { $this->trackResources = $track; } /** * Checks if resources are tracked. * * @return bool */ public function isTrackingResources() { return $this->trackResources; } /** * Sets the instantiator to be used when fetching proxies. */ public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator) { $this->proxyInstantiator = $proxyInstantiator; } public function registerExtension(ExtensionInterface $extension) { $this->extensions[$extension->getAlias()] = $extension; if (\false !== $extension->getNamespace()) { $this->extensionsByNs[$extension->getNamespace()] = $extension; } } /** * Returns an extension by alias or namespace. * * @return ExtensionInterface * * @throws LogicException if the extension is not registered */ public function getExtension(string $name) { if (isset($this->extensions[$name])) { return $this->extensions[$name]; } if (isset($this->extensionsByNs[$name])) { return $this->extensionsByNs[$name]; } throw new LogicException(\sprintf('Container extension "%s" is not registered.', $name)); } /** * Returns all registered extensions. * * @return array */ public function getExtensions() { return $this->extensions; } /** * Checks if we have an extension. * * @return bool */ public function hasExtension(string $name) { return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]); } /** * Returns an array of resources loaded to build this configuration. * * @return ResourceInterface[] */ public function getResources() { return \array_values($this->resources); } /** * @return $this */ public function addResource(ResourceInterface $resource) { if (!$this->trackResources) { return $this; } if ($resource instanceof GlobResource && $this->inVendors($resource->getPrefix())) { return $this; } $this->resources[(string) $resource] = $resource; return $this; } /** * Sets the resources for this configuration. * * @param array $resources * * @return $this */ public function setResources(array $resources) { if (!$this->trackResources) { return $this; } $this->resources = $resources; return $this; } /** * Adds the object class hierarchy as resources. * * @param object|string $object An object instance or class name * * @return $this */ public function addObjectResource($object) { if ($this->trackResources) { if (\is_object($object)) { $object = \get_class($object); } if (!isset($this->classReflectors[$object])) { $this->classReflectors[$object] = new \ReflectionClass($object); } $class = $this->classReflectors[$object]; foreach ($class->getInterfaceNames() as $name) { if (null === ($interface =& $this->classReflectors[$name])) { $interface = new \ReflectionClass($name); } $file = $interface->getFileName(); if (\false !== $file && \file_exists($file)) { $this->fileExists($file); } } do { $file = $class->getFileName(); if (\false !== $file && \file_exists($file)) { $this->fileExists($file); } foreach ($class->getTraitNames() as $name) { $this->addObjectResource($name); } } while ($class = $class->getParentClass()); } return $this; } /** * Retrieves the requested reflection class and registers it for resource tracking. * * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true * * @final */ public function getReflectionClass(?string $class, bool $throw = \true) : ?\ReflectionClass { if (!($class = $this->getParameterBag()->resolveValue($class))) { return null; } if (isset(self::INTERNAL_TYPES[$class])) { return null; } $resource = $classReflector = null; try { if (isset($this->classReflectors[$class])) { $classReflector = $this->classReflectors[$class]; } elseif (\class_exists(ClassExistenceResource::class)) { $resource = new ClassExistenceResource($class, \false); $classReflector = $resource->isFresh(0) ? \false : new \ReflectionClass($class); } else { $classReflector = \class_exists($class) ? new \ReflectionClass($class) : \false; } } catch (\ReflectionException $e) { if ($throw) { throw $e; } } if ($this->trackResources) { if (!$classReflector) { $this->addResource($resource ?? new ClassExistenceResource($class, \false)); } elseif (!$classReflector->isInternal()) { $path = $classReflector->getFileName(); if (!$this->inVendors($path)) { $this->addResource(new ReflectionClassResource($classReflector, $this->vendors)); } } $this->classReflectors[$class] = $classReflector; } return $classReflector ?: null; } /** * Checks whether the requested file or directory exists and registers the result for resource tracking. * * @param string $path The file or directory path for which to check the existence * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed, * it will be used as pattern for tracking contents of the requested directory * * @final */ public function fileExists(string $path, $trackContents = \true) : bool { $exists = \file_exists($path); if (!$this->trackResources || $this->inVendors($path)) { return $exists; } if (!$exists) { $this->addResource(new FileExistenceResource($path)); return $exists; } if (\is_dir($path)) { if ($trackContents) { $this->addResource(new DirectoryResource($path, \is_string($trackContents) ? $trackContents : null)); } else { $this->addResource(new GlobResource($path, '/*', \false)); } } elseif ($trackContents) { $this->addResource(new FileResource($path)); } return $exists; } /** * Loads the configuration for an extension. * * @param string $extension The extension alias or namespace * @param array|null $values An array of values that customizes the extension * * @return $this * * @throws BadMethodCallException When this ContainerBuilder is compiled * @throws \LogicException if the extension is not registered */ public function loadFromExtension(string $extension, ?array $values = null) { if ($this->isCompiled()) { throw new BadMethodCallException('Cannot load from an extension on a compiled container.'); } $namespace = $this->getExtension($extension)->getAlias(); $this->extensionConfigs[$namespace][] = $values ?? []; return $this; } /** * Adds a compiler pass. * * @param string $type The type of compiler pass * @param int $priority Used to sort the passes * * @return $this */ public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { $this->getCompiler()->addPass($pass, $type, $priority); $this->addObjectResource($pass); return $this; } /** * Returns the compiler pass config which can then be modified. * * @return PassConfig */ public function getCompilerPassConfig() { return $this->getCompiler()->getPassConfig(); } /** * Returns the compiler. * * @return Compiler */ public function getCompiler() { if (null === $this->compiler) { $this->compiler = new Compiler(); } return $this->compiler; } /** * Sets a service. * * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function set(string $id, ?object $service) { if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { // setting a synthetic service on a compiled container is alright throw new BadMethodCallException(\sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id)); } unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->removedIds[$id]); parent::set($id, $service); } /** * Removes a service definition. */ public function removeDefinition(string $id) { if (isset($this->definitions[$id])) { unset($this->definitions[$id]); $this->removedIds[$id] = \true; } } /** * Returns true if the given service is defined. * * @param string $id The service identifier * * @return bool */ public function has(string $id) { return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); } /** * @return object|null * * @throws InvalidArgumentException when no definitions are available * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined * @throws \Exception * * @see Reference */ public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { if ($this->isCompiled() && isset($this->removedIds[$id])) { return ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior ? parent::get($id) : null; } return $this->doGet($id, $invalidBehavior); } private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, ?array &$inlineServices = null, bool $isConstructorArgument = \false) { if (isset($inlineServices[$id])) { return $inlineServices[$id]; } if (null === $inlineServices) { $isConstructorArgument = \true; $inlineServices = []; } try { if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { return $this->privates[$id] ?? parent::get($id, $invalidBehavior); } if (null !== ($service = $this->privates[$id] ?? parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE))) { return $service; } } catch (ServiceCircularReferenceException $e) { if ($isConstructorArgument) { throw $e; } } if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { $alias = $this->aliasDefinitions[$id]; if ($alias->isDeprecated()) { $deprecation = $alias->getDeprecation($id); \trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } return $this->doGet((string) $alias, $invalidBehavior, $inlineServices, $isConstructorArgument); } try { $definition = $this->getDefinition($id); } catch (ServiceNotFoundException $e) { if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $invalidBehavior) { return null; } throw $e; } if ($definition->hasErrors() && ($e = $definition->getErrors())) { throw new RuntimeException(\reset($e)); } if ($isConstructorArgument) { $this->loading[$id] = \true; } try { return $this->createService($definition, $inlineServices, $isConstructorArgument, $id); } finally { if ($isConstructorArgument) { unset($this->loading[$id]); } } } /** * Merges a ContainerBuilder with the current ContainerBuilder configuration. * * Service definitions overrides the current defined ones. * * But for parameters, they are overridden by the current ones. It allows * the parameters passed to the container constructor to have precedence * over the loaded ones. * * $container = new ContainerBuilder(new ParameterBag(['foo' => 'bar'])); * $loader = new LoaderXXX($container); * $loader->load('resource_name'); * $container->register('foo', 'stdClass'); * * In the above example, even if the loaded resource defines a foo * parameter, the value will still be 'bar' as defined in the ContainerBuilder * constructor. * * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function merge(self $container) { if ($this->isCompiled()) { throw new BadMethodCallException('Cannot merge on a compiled container.'); } $this->addDefinitions($container->getDefinitions()); $this->addAliases($container->getAliases()); $this->getParameterBag()->add($container->getParameterBag()->all()); if ($this->trackResources) { foreach ($container->getResources() as $resource) { $this->addResource($resource); } } foreach ($this->extensions as $name => $extension) { if (!isset($this->extensionConfigs[$name])) { $this->extensionConfigs[$name] = []; } $this->extensionConfigs[$name] = \array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name)); } if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) { $envPlaceholders = $container->getParameterBag()->getEnvPlaceholders(); $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag()); } else { $envPlaceholders = []; } foreach ($container->envCounters as $env => $count) { if (!$count && !isset($envPlaceholders[$env])) { continue; } if (!isset($this->envCounters[$env])) { $this->envCounters[$env] = $count; } else { $this->envCounters[$env] += $count; } } foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) { if (isset($this->autoconfiguredInstanceof[$interface])) { throw new InvalidArgumentException(\sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface)); } $this->autoconfiguredInstanceof[$interface] = $childDefinition; } foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) { if (isset($this->autoconfiguredAttributes[$attribute])) { throw new InvalidArgumentException(\sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute)); } $this->autoconfiguredAttributes[$attribute] = $configurator; } } /** * Returns the configuration array for the given extension. * * @return array> */ public function getExtensionConfig(string $name) { if (!isset($this->extensionConfigs[$name])) { $this->extensionConfigs[$name] = []; } return $this->extensionConfigs[$name]; } /** * Prepends a config array to the configs of the given extension. * * @param array $config */ public function prependExtensionConfig(string $name, array $config) { if (!isset($this->extensionConfigs[$name])) { $this->extensionConfigs[$name] = []; } \array_unshift($this->extensionConfigs[$name], $config); } /** * Compiles the container. * * This method passes the container to compiler * passes whose job is to manipulate and optimize * the container. * * The main compiler passes roughly do four things: * * * The extension configurations are merged; * * Parameter values are resolved; * * The parameter bag is frozen; * * Extension loading is disabled. * * @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current * env vars or be replaced by uniquely identifiable placeholders. * Set to "true" when you want to use the current ContainerBuilder * directly, keep to "false" when the container is dumped instead. */ public function compile(bool $resolveEnvPlaceholders = \false) { $compiler = $this->getCompiler(); if ($this->trackResources) { foreach ($compiler->getPassConfig()->getPasses() as $pass) { $this->addObjectResource($pass); } } $bag = $this->getParameterBag(); if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) { $compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000); } $compiler->compile($this); foreach ($this->definitions as $id => $definition) { if ($this->trackResources && $definition->isLazy()) { $this->getReflectionClass($definition->getClass()); } } $this->extensionConfigs = []; if ($bag instanceof EnvPlaceholderParameterBag) { if ($resolveEnvPlaceholders) { $this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), \true)); } $this->envPlaceholders = $bag->getEnvPlaceholders(); } parent::compile(); foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) { if (!$definition->isPublic() || $definition->isPrivate()) { $this->removedIds[$id] = \true; } } } /** * {@inheritdoc} */ public function getServiceIds() { return \array_map('strval', \array_unique(\array_merge(\array_keys($this->getDefinitions()), \array_keys($this->aliasDefinitions), parent::getServiceIds()))); } /** * Gets removed service or alias ids. * * @return array */ public function getRemovedIds() { return $this->removedIds; } /** * Adds the service aliases. * * @param array $aliases */ public function addAliases(array $aliases) { foreach ($aliases as $alias => $id) { $this->setAlias($alias, $id); } } /** * Sets the service aliases. * * @param array $aliases */ public function setAliases(array $aliases) { $this->aliasDefinitions = []; $this->addAliases($aliases); } /** * Sets an alias for an existing service. * * @param string $alias The alias to create * @param string|Alias $id The service to alias * * @return Alias * * @throws InvalidArgumentException if the id is not a string or an Alias * @throws InvalidArgumentException if the alias is for itself */ public function setAlias(string $alias, $id) { if ('' === $alias || '\\' === $alias[-1] || \strlen($alias) !== \strcspn($alias, "\x00\r\n'")) { throw new InvalidArgumentException(\sprintf('Invalid alias id: "%s".', $alias)); } if (\is_string($id)) { $id = new Alias($id); } elseif (!$id instanceof Alias) { throw new InvalidArgumentException('$id must be a string, or an Alias object.'); } if ($alias === (string) $id) { throw new InvalidArgumentException(\sprintf('An alias cannot reference itself, got a circular reference on "%s".', $alias)); } unset($this->definitions[$alias], $this->removedIds[$alias]); return $this->aliasDefinitions[$alias] = $id; } public function removeAlias(string $alias) { if (isset($this->aliasDefinitions[$alias])) { unset($this->aliasDefinitions[$alias]); $this->removedIds[$alias] = \true; } } /** * @return bool */ public function hasAlias(string $id) { return isset($this->aliasDefinitions[$id]); } /** * @return array */ public function getAliases() { return $this->aliasDefinitions; } /** * @return Alias * * @throws InvalidArgumentException if the alias does not exist */ public function getAlias(string $id) { if (!isset($this->aliasDefinitions[$id])) { throw new InvalidArgumentException(\sprintf('The service alias "%s" does not exist.', $id)); } return $this->aliasDefinitions[$id]; } /** * Registers a service definition. * * This methods allows for simple registration of service definition * with a fluid interface. * * @return Definition */ public function register(string $id, ?string $class = null) { return $this->setDefinition($id, new Definition($class)); } /** * Registers an autowired service definition. * * This method implements a shortcut for using setDefinition() with * an autowired definition. * * @return Definition */ public function autowire(string $id, ?string $class = null) { return $this->setDefinition($id, (new Definition($class))->setAutowired(\true)); } /** * Adds the service definitions. * * @param array $definitions */ public function addDefinitions(array $definitions) { foreach ($definitions as $id => $definition) { $this->setDefinition($id, $definition); } } /** * Sets the service definitions. * * @param array $definitions */ public function setDefinitions(array $definitions) { $this->definitions = []; $this->addDefinitions($definitions); } /** * Gets all service definitions. * * @return array */ public function getDefinitions() { return $this->definitions; } /** * Sets a service definition. * * @return Definition * * @throws BadMethodCallException When this ContainerBuilder is compiled */ public function setDefinition(string $id, Definition $definition) { if ($this->isCompiled()) { throw new BadMethodCallException('Adding definition to a compiled container is not allowed.'); } if ('' === $id || '\\' === $id[-1] || \strlen($id) !== \strcspn($id, "\x00\r\n'")) { throw new InvalidArgumentException(\sprintf('Invalid service id: "%s".', $id)); } unset($this->aliasDefinitions[$id], $this->removedIds[$id]); return $this->definitions[$id] = $definition; } /** * Returns true if a service definition exists under the given identifier. * * @return bool */ public function hasDefinition(string $id) { return isset($this->definitions[$id]); } /** * Gets a service definition. * * @return Definition * * @throws ServiceNotFoundException if the service definition does not exist */ public function getDefinition(string $id) { if (!isset($this->definitions[$id])) { throw new ServiceNotFoundException($id); } return $this->definitions[$id]; } /** * Gets a service definition by id or alias. * * The method "unaliases" recursively to return a Definition instance. * * @return Definition * * @throws ServiceNotFoundException if the service definition does not exist */ public function findDefinition(string $id) { $seen = []; while (isset($this->aliasDefinitions[$id])) { $id = (string) $this->aliasDefinitions[$id]; if (isset($seen[$id])) { $seen = \array_values($seen); $seen = \array_slice($seen, \array_search($id, $seen)); $seen[] = $id; throw new ServiceCircularReferenceException($id, $seen); } $seen[$id] = $id; } return $this->getDefinition($id); } /** * Creates a service for a service definition. * * @return mixed * * @throws RuntimeException When the factory definition is incomplete * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = \false, ?string $id = null, bool $tryProxy = \true) { if (null === $id && isset($inlineServices[$h = \spl_object_hash($definition)])) { return $inlineServices[$h]; } if ($definition instanceof ChildDefinition) { throw new RuntimeException(\sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id)); } if ($definition->isSynthetic()) { throw new RuntimeException(\sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); } if ($definition->isDeprecated()) { $deprecation = $definition->getDeprecation($id); \trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } if ($tryProxy && $definition->isLazy() && !($tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator)) { $proxy = $proxy->instantiateProxy($this, $definition, $id, function () use($definition, &$inlineServices, $id) { return $this->createService($definition, $inlineServices, \true, $id, \false); }); $this->shareService($definition, $proxy, $id, $inlineServices); return $proxy; } $parameterBag = $this->getParameterBag(); if (null !== $definition->getFile()) { require_once $parameterBag->resolveValue($definition->getFile()); } $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices, $isConstructorArgument); if (null !== ($factory = $definition->getFactory())) { if (\is_array($factory)) { $factory = [$this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]]; } elseif (!\is_string($factory)) { throw new RuntimeException(\sprintf('Cannot create service "%s" because of invalid factory.', $id)); } } if (null !== $id && $definition->isShared() && (isset($this->services[$id]) || isset($this->privates[$id])) && ($tryProxy || !$definition->isLazy())) { return $this->services[$id] ?? $this->privates[$id]; } if (!\array_is_list($arguments)) { $arguments = \array_combine(\array_map(function ($k) { return \preg_replace('/^.*\\$/', '', $k); }, \array_keys($arguments)), $arguments); } if (null !== $factory) { $service = $factory(...$arguments); if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) { $r = new \ReflectionClass($factory[0]); if (0 < \strpos($r->getDocComment(), "\n * @deprecated ")) { \trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name); } } } else { $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); if (!$definition->isDeprecated() && 0 < \strpos($r->getDocComment(), "\n * @deprecated ")) { \trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name); } } $lastWitherIndex = null; foreach ($definition->getMethodCalls() as $k => $call) { if ($call[2] ?? \false) { $lastWitherIndex = $k; } } if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) { // share only if proxying failed, or if not a proxy, and if no withers are found $this->shareService($definition, $service, $id, $inlineServices); } $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices); foreach ($properties as $name => $value) { $service->{$name} = $value; } foreach ($definition->getMethodCalls() as $k => $call) { $service = $this->callMethod($service, $call, $inlineServices); if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) { // share only if proxying failed, or if not a proxy, and this is the last wither $this->shareService($definition, $service, $id, $inlineServices); } } if ($callable = $definition->getConfigurator()) { if (\is_array($callable)) { $callable[0] = $parameterBag->resolveValue($callable[0]); if ($callable[0] instanceof Reference) { $callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices); } elseif ($callable[0] instanceof Definition) { $callable[0] = $this->createService($callable[0], $inlineServices); } } if (!\is_callable($callable)) { throw new InvalidArgumentException(\sprintf('The configure callable for class "%s" is not a callable.', \get_debug_type($service))); } $callable($service); } return $service; } /** * Replaces service references by the real service instance and evaluates expressions. * * @param mixed $value * * @return mixed The same value with all service references replaced by * the real service instances and all expressions evaluated */ public function resolveServices($value) { return $this->doResolveServices($value); } private function doResolveServices($value, array &$inlineServices = [], bool $isConstructorArgument = \false) { if (\is_array($value)) { foreach ($value as $k => $v) { $value[$k] = $this->doResolveServices($v, $inlineServices, $isConstructorArgument); } } elseif ($value instanceof ServiceClosureArgument) { $reference = $value->getValues()[0]; $value = function () use($reference) { return $this->resolveServices($reference); }; } elseif ($value instanceof IteratorArgument) { $value = new RewindableGenerator(function () use($value, &$inlineServices) { foreach ($value->getValues() as $k => $v) { foreach (self::getServiceConditionals($v) as $s) { if (!$this->has($s)) { continue 2; } } foreach (self::getInitializedConditionals($v) as $s) { if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { continue 2; } } (yield $k => $this->doResolveServices($v, $inlineServices)); } }, function () use($value) : int { $count = 0; foreach ($value->getValues() as $v) { foreach (self::getServiceConditionals($v) as $s) { if (!$this->has($s)) { continue 2; } } foreach (self::getInitializedConditionals($v) as $s) { if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { continue 2; } } ++$count; } return $count; }); } elseif ($value instanceof ServiceLocatorArgument) { $refs = $types = []; foreach ($value->getValues() as $k => $v) { if ($v) { $refs[$k] = [$v]; $types[$k] = $v instanceof TypedReference ? $v->getType() : '?'; } } $value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs, $types); } elseif ($value instanceof Reference) { $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument); } elseif ($value instanceof Definition) { $value = $this->createService($value, $inlineServices, $isConstructorArgument); } elseif ($value instanceof Parameter) { $value = $this->getParameter((string) $value); } elseif ($value instanceof Expression) { $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]); } elseif ($value instanceof AbstractArgument) { throw new RuntimeException($value->getTextWithContext()); } return $value; } /** * Returns service ids for a given tag. * * Example: * * $container->register('foo')->addTag('my.tag', ['hello' => 'world']); * * $serviceIds = $container->findTaggedServiceIds('my.tag'); * foreach ($serviceIds as $serviceId => $tags) { * foreach ($tags as $tag) { * echo $tag['hello']; * } * } * * @return array An array of tags with the tagged service as key, holding a list of attribute arrays */ public function findTaggedServiceIds(string $name, bool $throwOnAbstract = \false) { $this->usedTags[] = $name; $tags = []; foreach ($this->getDefinitions() as $id => $definition) { if ($definition->hasTag($name)) { if ($throwOnAbstract && $definition->isAbstract()) { throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name)); } $tags[$id] = $definition->getTag($name); } } return $tags; } /** * Returns all tags the defined services use. * * @return string[] */ public function findTags() { $tags = []; foreach ($this->getDefinitions() as $id => $definition) { $tags[] = \array_keys($definition->getTags()); } return \array_unique(\array_merge([], ...$tags)); } /** * Returns all tags not queried by findTaggedServiceIds. * * @return string[] */ public function findUnusedTags() { return \array_values(\array_diff($this->findTags(), $this->usedTags)); } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) { $this->expressionLanguageProviders[] = $provider; } /** * @return ExpressionFunctionProviderInterface[] */ public function getExpressionLanguageProviders() { return $this->expressionLanguageProviders; } /** * Returns a ChildDefinition that will be used for autoconfiguring the interface/class. * * @return ChildDefinition */ public function registerForAutoconfiguration(string $interface) { if (!isset($this->autoconfiguredInstanceof[$interface])) { $this->autoconfiguredInstanceof[$interface] = new ChildDefinition(''); } return $this->autoconfiguredInstanceof[$interface]; } /** * Registers an attribute that will be used for autoconfiguring annotated classes. * * The third argument passed to the callable is the reflector of the * class/method/property/parameter that the attribute targets. Using one or many of * \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter as a type-hint * for this argument allows filtering which attributes should be passed to the callable. * * @template T * * @param class-string $attributeClass * @param callable(ChildDefinition, T, \Reflector): void $configurator */ public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator) : void { $this->autoconfiguredAttributes[$attributeClass] = $configurator; } /** * Registers an autowiring alias that only binds to a specific argument name. * * The argument name is derived from $name if provided (from $id otherwise) * using camel case: "foo.bar" or "foo_bar" creates an alias bound to * "$fooBar"-named arguments with $type as type-hint. Such arguments will * receive the service $id when autowiring is used. */ public function registerAliasForArgument(string $id, string $type, ?string $name = null) : Alias { $name = (new Target($name ?? $id))->name; if (!\preg_match('/^[a-zA-Z_\\x7f-\\xff]/', $name)) { throw new InvalidArgumentException(\sprintf('Invalid argument name "%s" for service "%s": the first character must be a letter.', $name, $id)); } return $this->setAlias($type . ' $' . $name, $id); } /** * Returns an array of ChildDefinition[] keyed by interface. * * @return array */ public function getAutoconfiguredInstanceof() { return $this->autoconfiguredInstanceof; } /** * @return array */ public function getAutoconfiguredAttributes() : array { return $this->autoconfiguredAttributes; } /** * Resolves env parameter placeholders in a string or an array. * * @param mixed $value The value to resolve * @param string|true|null $format A sprintf() format returning the replacement for each env var name or * null to resolve back to the original "%env(VAR)%" format or * true to resolve to the actual values of the referenced env vars * @param array &$usedEnvs Env vars found while resolving are added to this array * * @return mixed The value with env parameters resolved if a string or an array is passed */ public function resolveEnvPlaceholders($value, $format = null, ?array &$usedEnvs = null) { if (null === $format) { $format = '%%env(%s)%%'; } $bag = $this->getParameterBag(); if (\true === $format) { $value = $bag->resolveValue($value); } if ($value instanceof Definition) { $value = (array) $value; } if (\is_array($value)) { $result = []; foreach ($value as $k => $v) { $result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs); } return $result; } if (!\is_string($value) || 38 > \strlen($value) || !\preg_match('/env[_(]/i', $value)) { return $value; } $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; $completed = \false; foreach ($envPlaceholders as $env => $placeholders) { foreach ($placeholders as $placeholder) { if (\false !== \stripos($value, $placeholder)) { if (\true === $format) { $resolved = $bag->escapeValue($this->getEnv($env)); } else { $resolved = \sprintf($format, $env); } if ($placeholder === $value) { $value = $resolved; $completed = \true; } else { if (!\is_string($resolved) && !\is_numeric($resolved)) { throw new RuntimeException(\sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, \get_debug_type($resolved), $this->resolveEnvPlaceholders($value))); } $value = \str_ireplace($placeholder, $resolved, $value); } $usedEnvs[$env] = $env; $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1; if ($completed) { break 2; } } } } return $value; } /** * Get statistics about env usage. * * @return int[] The number of time each env vars has been resolved */ public function getEnvCounters() { $bag = $this->getParameterBag(); $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; foreach ($envPlaceholders as $env => $placeholders) { if (!isset($this->envCounters[$env])) { $this->envCounters[$env] = 0; } } return $this->envCounters; } /** * @final */ public function log(CompilerPassInterface $pass, string $message) { $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message)); } /** * Checks whether a class is available and will remain available in the "no-dev" mode of Composer. * * When parent packages are provided and if any of them is in dev-only mode, * the class will be considered available even if it is also in dev-only mode. */ public static final function willBeAvailable(string $package, string $class, array $parentPackages) : bool { $skipDeprecation = 3 < \func_num_args() && \func_get_arg(3); $hasRuntimeApi = \class_exists(InstalledVersions::class); if (!$hasRuntimeApi && !$skipDeprecation) { \trigger_deprecation('symfony/dependency-injection', '5.4', 'Calling "%s" when dependencies have been installed with Composer 1 is deprecated. Consider upgrading to Composer 2.', __METHOD__); } if (!\class_exists($class) && !\interface_exists($class, \false) && !\trait_exists($class, \false)) { return \false; } if (!$hasRuntimeApi || !InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, \false)) { return \true; } // the package is installed but in dev-mode only, check if this applies to one of the parent packages too $rootPackage = InstalledVersions::getRootPackage()['name'] ?? ''; if ('symfony/symfony' === $rootPackage) { return \true; } foreach ($parentPackages as $parentPackage) { if ($rootPackage === $parentPackage || InstalledVersions::isInstalled($parentPackage) && !InstalledVersions::isInstalled($parentPackage, \false)) { return \true; } } return \false; } /** * Gets removed binding ids. * * @return array * * @internal */ public function getRemovedBindingIds() : array { return $this->removedBindingIds; } /** * Removes bindings for a service. * * @internal */ public function removeBindings(string $id) { if ($this->hasDefinition($id)) { foreach ($this->getDefinition($id)->getBindings() as $key => $binding) { [, $bindingId] = $binding->getValues(); $this->removedBindingIds[(int) $bindingId] = \true; } } } /** * Returns the Service Conditionals. * * @param mixed $value An array of conditionals to return * * @return string[] * * @internal */ public static function getServiceConditionals($value) : array { $services = []; if (\is_array($value)) { foreach ($value as $v) { $services = \array_unique(\array_merge($services, self::getServiceConditionals($v))); } } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { $services[] = (string) $value; } return $services; } /** * Returns the initialized conditionals. * * @param mixed $value An array of conditionals to return * * @return string[] * * @internal */ public static function getInitializedConditionals($value) : array { $services = []; if (\is_array($value)) { foreach ($value as $v) { $services = \array_unique(\array_merge($services, self::getInitializedConditionals($v))); } } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) { $services[] = (string) $value; } return $services; } /** * Computes a reasonably unique hash of a value. * * @param mixed $value A serializable value * * @return string */ public static function hash($value) { $hash = \substr(\base64_encode(\hash('sha256', \serialize($value), \true)), 0, 7); return \str_replace(['/', '+'], ['.', '_'], $hash); } /** * {@inheritdoc} */ protected function getEnv(string $name) { $value = parent::getEnv($name); $bag = $this->getParameterBag(); if (!\is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) { return $value; } $envPlaceholders = $bag->getEnvPlaceholders(); if (isset($envPlaceholders[$name][$value])) { $bag = new ParameterBag($bag->all()); return $bag->unescapeValue($bag->get("env({$name})")); } foreach ($envPlaceholders as $env => $placeholders) { if (isset($placeholders[$value])) { return $this->getEnv($env); } } $this->resolving["env({$name})"] = \true; try { return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), \true)); } finally { unset($this->resolving["env({$name})"]); } } private function callMethod(object $service, array $call, array &$inlineServices) { foreach (self::getServiceConditionals($call[1]) as $s) { if (!$this->has($s)) { return $service; } } foreach (self::getInitializedConditionals($call[1]) as $s) { if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { return $service; } } $result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices)); return empty($call[2]) ? $service : $result; } /** * Shares a given service in the container. * * @param mixed $service */ private function shareService(Definition $definition, $service, ?string $id, array &$inlineServices) { $inlineServices[$id ?? \spl_object_hash($definition)] = $service; if (null !== $id && $definition->isShared()) { if ($definition->isPrivate() && $this->isCompiled()) { $this->privates[$id] = $service; } else { $this->services[$id] = $service; } unset($this->loading[$id]); } } private function getExpressionLanguage() : ExpressionLanguage { if (null === $this->expressionLanguage) { if (!\class_exists(\_ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); } return $this->expressionLanguage; } private function inVendors(string $path) : bool { if (null === $this->vendors) { $this->vendors = (new ComposerResource())->getVendors(); } $path = \realpath($path) ?: $path; foreach ($this->vendors as $vendor) { if (\str_starts_with($path, $vendor) && \false !== \strpbrk(\substr($path, \strlen($vendor), 1), '/' . \DIRECTORY_SEPARATOR)) { $this->addResource(new FileResource($vendor . '/composer/installed.json')); return \true; } } return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; /** * This definition extends another definition. * * @author Johannes M. Schmitt */ class ChildDefinition extends Definition { private $parent; /** * @param string $parent The id of Definition instance to decorate */ public function __construct(string $parent) { $this->parent = $parent; } /** * Returns the Definition to inherit from. * * @return string */ public function getParent() { return $this->parent; } /** * Sets the Definition to inherit from. * * @return $this */ public function setParent(string $parent) { $this->parent = $parent; return $this; } /** * Gets an argument to pass to the service constructor/factory method. * * If replaceArgument() has been used to replace an argument, this method * will return the replacement value. * * @param int|string $index * * @return mixed * * @throws OutOfBoundsException When the argument does not exist */ public function getArgument($index) { if (\array_key_exists('index_' . $index, $this->arguments)) { return $this->arguments['index_' . $index]; } return parent::getArgument($index); } /** * You should always use this method when overwriting existing arguments * of the parent definition. * * If you directly call setArguments() keep in mind that you must follow * certain conventions when you want to overwrite the arguments of the * parent definition, otherwise your arguments will only be appended. * * @param int|string $index * @param mixed $value * * @return $this * * @throws InvalidArgumentException when $index isn't an integer */ public function replaceArgument($index, $value) { if (\is_int($index)) { $this->arguments['index_' . $index] = $value; } elseif (\str_starts_with($index, '$')) { $this->arguments[$index] = $value; } else { throw new InvalidArgumentException('The argument must be an existing index or the name of a constructor\'s parameter.'); } return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * Reference represents a service reference. * * @author Fabien Potencier */ class Reference { private $id; private $invalidBehavior; public function __construct(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { $this->id = $id; $this->invalidBehavior = $invalidBehavior; } /** * @return string */ public function __toString() { return $this->id; } /** * Returns the behavior to be used when the service does not exist. * * @return int */ public function getInvalidBehavior() { return $this->invalidBehavior; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; /** * ParameterBagInterface is the interface implemented by objects that manage service container parameters. * * @author Fabien Potencier */ interface ParameterBagInterface { /** * Clears all parameters. * * @throws LogicException if the ParameterBagInterface cannot be cleared */ public function clear(); /** * Adds parameters to the service container parameters. * * @throws LogicException if the parameter cannot be added */ public function add(array $parameters); /** * Gets the service container parameters. * * @return array */ public function all(); /** * Gets a service container parameter. * * @return array|bool|string|int|float|\UnitEnum|null * * @throws ParameterNotFoundException if the parameter is not defined */ public function get(string $name); /** * Removes a parameter. */ public function remove(string $name); /** * Sets a service container parameter. * * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value * * @throws LogicException if the parameter cannot be set */ public function set(string $name, $value); /** * Returns true if a parameter name is defined. * * @return bool */ public function has(string $name); /** * Replaces parameter placeholders (%name%) by their values for all parameters. */ public function resolve(); /** * Replaces parameter placeholders (%name%) by their values. * * @param mixed $value A value * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist */ public function resolveValue($value); /** * Escape parameter placeholders %. * * @param mixed $value * * @return mixed */ public function escapeValue($value); /** * Unescape parameter placeholders %. * * @param mixed $value * * @return mixed */ public function unescapeValue($value); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * @author Nicolas Grekas */ class EnvPlaceholderParameterBag extends ParameterBag { private $envPlaceholderUniquePrefix; private $envPlaceholders = []; private $unusedEnvPlaceholders = []; private $providedTypes = []; private static $counter = 0; /** * {@inheritdoc} */ public function get(string $name) { if (\str_starts_with($name, 'env(') && \str_ends_with($name, ')') && 'env()' !== $name) { $env = \substr($name, 4, -1); if (isset($this->envPlaceholders[$env])) { foreach ($this->envPlaceholders[$env] as $placeholder) { return $placeholder; // return first result } } if (isset($this->unusedEnvPlaceholders[$env])) { foreach ($this->unusedEnvPlaceholders[$env] as $placeholder) { return $placeholder; // return first result } } if (!\preg_match('/^(?:[-.\\w]*+:)*+\\w++$/', $env)) { throw new InvalidArgumentException(\sprintf('Invalid %s name: only "word" characters are allowed.', $name)); } if ($this->has($name) && null !== ($defaultValue = parent::get($name)) && !\is_string($defaultValue)) { throw new RuntimeException(\sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', \get_debug_type($defaultValue), $name)); } $uniqueName = \md5($name . '_' . self::$counter++); $placeholder = \sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), \strtr($env, ':-.', '___'), $uniqueName); $this->envPlaceholders[$env][$placeholder] = $placeholder; return $placeholder; } return parent::get($name); } /** * Gets the common env placeholder prefix for env vars created by this bag. */ public function getEnvPlaceholderUniquePrefix() : string { if (null === $this->envPlaceholderUniquePrefix) { $reproducibleEntropy = \unserialize(\serialize($this->parameters)); \array_walk_recursive($reproducibleEntropy, function (&$v) { $v = null; }); $this->envPlaceholderUniquePrefix = 'env_' . \substr(\md5(\serialize($reproducibleEntropy)), -16); } return $this->envPlaceholderUniquePrefix; } /** * Returns the map of env vars used in the resolved parameter values to their placeholders. * * @return string[][] A map of env var names to their placeholders */ public function getEnvPlaceholders() { return $this->envPlaceholders; } public function getUnusedEnvPlaceholders() : array { return $this->unusedEnvPlaceholders; } public function clearUnusedEnvPlaceholders() { $this->unusedEnvPlaceholders = []; } /** * Merges the env placeholders of another EnvPlaceholderParameterBag. */ public function mergeEnvPlaceholders(self $bag) { if ($newPlaceholders = $bag->getEnvPlaceholders()) { $this->envPlaceholders += $newPlaceholders; foreach ($newPlaceholders as $env => $placeholders) { $this->envPlaceholders[$env] += $placeholders; } } if ($newUnusedPlaceholders = $bag->getUnusedEnvPlaceholders()) { $this->unusedEnvPlaceholders += $newUnusedPlaceholders; foreach ($newUnusedPlaceholders as $env => $placeholders) { $this->unusedEnvPlaceholders[$env] += $placeholders; } } } /** * Maps env prefixes to their corresponding PHP types. */ public function setProvidedTypes(array $providedTypes) { $this->providedTypes = $providedTypes; } /** * Gets the PHP types corresponding to env() parameter prefixes. * * @return string[][] */ public function getProvidedTypes() { return $this->providedTypes; } /** * {@inheritdoc} */ public function resolve() { if ($this->resolved) { return; } parent::resolve(); foreach ($this->envPlaceholders as $env => $placeholders) { if ($this->has($name = "env({$env})") && null !== ($default = $this->parameters[$name]) && !\is_string($default)) { throw new RuntimeException(\sprintf('The default value of env parameter "%s" must be a string or null, "%s" given.', $env, \get_debug_type($default))); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * Holds parameters. * * @author Fabien Potencier */ class ParameterBag implements ParameterBagInterface { protected $parameters = []; protected $resolved = \false; public function __construct(array $parameters = []) { $this->add($parameters); } /** * {@inheritdoc} */ public function clear() { $this->parameters = []; } /** * {@inheritdoc} */ public function add(array $parameters) { foreach ($parameters as $key => $value) { $this->set($key, $value); } } /** * {@inheritdoc} */ public function all() { return $this->parameters; } /** * {@inheritdoc} */ public function get(string $name) { if (!\array_key_exists($name, $this->parameters)) { if (!$name) { throw new ParameterNotFoundException($name); } $alternatives = []; foreach ($this->parameters as $key => $parameterValue) { $lev = \levenshtein($name, $key); if ($lev <= \strlen($name) / 3 || \str_contains($key, $name)) { $alternatives[] = $key; } } $nonNestedAlternative = null; if (!\count($alternatives) && \str_contains($name, '.')) { $namePartsLength = \array_map('strlen', \explode('.', $name)); $key = \substr($name, 0, -1 * (1 + \array_pop($namePartsLength))); while (\count($namePartsLength)) { if ($this->has($key)) { if (\is_array($this->get($key))) { $nonNestedAlternative = $key; } break; } $key = \substr($key, 0, -1 * (1 + \array_pop($namePartsLength))); } } throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative); } return $this->parameters[$name]; } /** * {@inheritdoc} */ public function set(string $name, $value) { $this->parameters[$name] = $value; } /** * {@inheritdoc} */ public function has(string $name) { return \array_key_exists($name, $this->parameters); } /** * {@inheritdoc} */ public function remove(string $name) { unset($this->parameters[$name]); } /** * {@inheritdoc} */ public function resolve() { if ($this->resolved) { return; } $parameters = []; foreach ($this->parameters as $key => $value) { try { $value = $this->resolveValue($value); $parameters[$key] = $this->unescapeValue($value); } catch (ParameterNotFoundException $e) { $e->setSourceKey($key); throw $e; } } $this->parameters = $parameters; $this->resolved = \true; } /** * Replaces parameter placeholders (%name%) by their values. * * @param mixed $value A value * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * * @return mixed * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist * @throws ParameterCircularReferenceException if a circular reference if detected * @throws RuntimeException when a given parameter has a type problem */ public function resolveValue($value, array $resolving = []) { if (\is_array($value)) { $args = []; foreach ($value as $k => $v) { $args[\is_string($k) ? $this->resolveValue($k, $resolving) : $k] = $this->resolveValue($v, $resolving); } return $args; } if (!\is_string($value) || 2 > \strlen($value)) { return $value; } return $this->resolveString($value, $resolving); } /** * Resolves parameters inside a string. * * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * * @return mixed * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist * @throws ParameterCircularReferenceException if a circular reference if detected * @throws RuntimeException when a given parameter has a type problem */ public function resolveString(string $value, array $resolving = []) { // we do this to deal with non string values (Boolean, integer, ...) // as the preg_replace_callback throw an exception when trying // a non-string in a parameter value if (\preg_match('/^%([^%\\s]+)%$/', $value, $match)) { $key = $match[1]; if (isset($resolving[$key])) { throw new ParameterCircularReferenceException(\array_keys($resolving)); } $resolving[$key] = \true; return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving); } return \preg_replace_callback('/%%|%([^%\\s]+)%/', function ($match) use($resolving, $value) { // skip %% if (!isset($match[1])) { return '%%'; } $key = $match[1]; if (isset($resolving[$key])) { throw new ParameterCircularReferenceException(\array_keys($resolving)); } $resolved = $this->get($key); if (!\is_string($resolved) && !\is_numeric($resolved)) { throw new RuntimeException(\sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type "%s" inside string value "%s".', $key, \get_debug_type($resolved), $value)); } $resolved = (string) $resolved; $resolving[$key] = \true; return $this->isResolved() ? $resolved : $this->resolveString($resolved, $resolving); }, $value); } public function isResolved() { return $this->resolved; } /** * {@inheritdoc} */ public function escapeValue($value) { if (\is_string($value)) { return \str_replace('%', '%%', $value); } if (\is_array($value)) { $result = []; foreach ($value as $k => $v) { $result[$k] = $this->escapeValue($v); } return $result; } return $value; } /** * {@inheritdoc} */ public function unescapeValue($value) { if (\is_string($value)) { return \str_replace('%%', '%', $value); } if (\is_array($value)) { $result = []; foreach ($value as $k => $v) { $result[$k] = $this->unescapeValue($v); } return $result; } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; /** * ContainerBagInterface is the interface implemented by objects that manage service container parameters. * * @author Nicolas Grekas */ interface ContainerBagInterface extends ContainerInterface { /** * Gets the service container parameters. * * @return array */ public function all(); /** * Replaces parameter placeholders (%name%) by their values. * * @param mixed $value A value * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist */ public function resolveValue($value); /** * Escape parameter placeholders %. * * @param mixed $value * * @return mixed */ public function escapeValue($value); /** * Unescape parameter placeholders %. * * @param mixed $value * * @return mixed */ public function unescapeValue($value); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Container; /** * @author Nicolas Grekas */ class ContainerBag extends FrozenParameterBag implements ContainerBagInterface { private $container; public function __construct(Container $container) { $this->container = $container; } /** * {@inheritdoc} */ public function all() { return $this->container->getParameterBag()->all(); } /** * {@inheritdoc} * * @return array|bool|string|int|float|\UnitEnum|null */ public function get(string $name) { return $this->container->getParameter($name); } /** * {@inheritdoc} * * @return bool */ public function has(string $name) { return $this->container->hasParameter($name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; /** * Holds read-only parameters. * * @author Fabien Potencier */ class FrozenParameterBag extends ParameterBag { /** * For performance reasons, the constructor assumes that * all keys are already lowercased. * * This is always the case when used internally. * * @param array $parameters An array of parameters */ public function __construct(array $parameters = []) { $this->parameters = $parameters; $this->resolved = \true; } /** * {@inheritdoc} */ public function clear() { throw new LogicException('Impossible to call clear() on a frozen ParameterBag.'); } /** * {@inheritdoc} */ public function add(array $parameters) { throw new LogicException('Impossible to call add() on a frozen ParameterBag.'); } /** * {@inheritdoc} */ public function set(string $name, $value) { throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } /** * {@inheritdoc} */ public function remove(string $name) { throw new LogicException('Impossible to call remove() on a frozen ParameterBag.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; /** * Definition represents a service definition. * * @author Fabien Potencier */ class Definition { private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future.'; private $class; private $file; private $factory; private $shared = \true; private $deprecation = []; private $properties = []; private $calls = []; private $instanceof = []; private $autoconfigured = \false; private $configurator; private $tags = []; private $public = \false; private $synthetic = \false; private $abstract = \false; private $lazy = \false; private $decoratedService; private $autowired = \false; private $changes = []; private $bindings = []; private $errors = []; protected $arguments = []; /** * @internal * * Used to store the name of the inner id when using service decoration together with autowiring */ public $innerServiceId; /** * @internal * * Used to store the behavior to follow when using service decoration and the decorated service is invalid */ public $decorationOnInvalid; public function __construct(?string $class = null, array $arguments = []) { if (null !== $class) { $this->setClass($class); } $this->arguments = $arguments; } /** * Returns all changes tracked for the Definition object. * * @return array */ public function getChanges() { return $this->changes; } /** * Sets the tracked changes for the Definition object. * * @param array $changes An array of changes for this Definition * * @return $this */ public function setChanges(array $changes) { $this->changes = $changes; return $this; } /** * Sets a factory. * * @param string|array|Reference|null $factory A PHP function, reference or an array containing a class/Reference and a method to call * * @return $this */ public function setFactory($factory) { $this->changes['factory'] = \true; if (\is_string($factory) && \str_contains($factory, '::')) { $factory = \explode('::', $factory, 2); } elseif ($factory instanceof Reference) { $factory = [$factory, '__invoke']; } $this->factory = $factory; return $this; } /** * Gets the factory. * * @return string|array|null The PHP function or an array containing a class/Reference and a method to call */ public function getFactory() { return $this->factory; } /** * Sets the service that this service is decorating. * * @param string|null $id The decorated service id, use null to remove decoration * @param string|null $renamedId The new decorated service id * * @return $this * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ public function setDecoratedService(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { if ($renamedId && $id === $renamedId) { throw new InvalidArgumentException(\sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); } $this->changes['decorated_service'] = \true; if (null === $id) { $this->decoratedService = null; } else { $this->decoratedService = [$id, $renamedId, $priority]; if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { $this->decoratedService[] = $invalidBehavior; } } return $this; } /** * Gets the service that this service is decorating. * * @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated */ public function getDecoratedService() { return $this->decoratedService; } /** * Sets the service class. * * @return $this */ public function setClass(?string $class) { $this->changes['class'] = \true; $this->class = $class; return $this; } /** * Gets the service class. * * @return string|null */ public function getClass() { return $this->class; } /** * Sets the arguments to pass to the service constructor/factory method. * * @return $this */ public function setArguments(array $arguments) { $this->arguments = $arguments; return $this; } /** * Sets the properties to define when creating the service. * * @return $this */ public function setProperties(array $properties) { $this->properties = $properties; return $this; } /** * Gets the properties to define when creating the service. * * @return array */ public function getProperties() { return $this->properties; } /** * Sets a specific property. * * @param mixed $value * * @return $this */ public function setProperty(string $name, $value) { $this->properties[$name] = $value; return $this; } /** * Adds an argument to pass to the service constructor/factory method. * * @param mixed $argument An argument * * @return $this */ public function addArgument($argument) { $this->arguments[] = $argument; return $this; } /** * Replaces a specific argument. * * @param int|string $index * @param mixed $argument * * @return $this * * @throws OutOfBoundsException When the replaced argument does not exist */ public function replaceArgument($index, $argument) { if (0 === \count($this->arguments)) { throw new OutOfBoundsException(\sprintf('Cannot replace arguments for class "%s" if none have been configured yet.', $this->class)); } if (\is_int($index) && ($index < 0 || $index > \count($this->arguments) - 1)) { throw new OutOfBoundsException(\sprintf('The index "%d" is not in the range [0, %d] of the arguments of class "%s".', $index, \count($this->arguments) - 1, $this->class)); } if (!\array_key_exists($index, $this->arguments)) { throw new OutOfBoundsException(\sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); } $this->arguments[$index] = $argument; return $this; } /** * Sets a specific argument. * * @param int|string $key * @param mixed $value * * @return $this */ public function setArgument($key, $value) { $this->arguments[$key] = $value; return $this; } /** * Gets the arguments to pass to the service constructor/factory method. * * @return array */ public function getArguments() { return $this->arguments; } /** * Gets an argument to pass to the service constructor/factory method. * * @param int|string $index * * @return mixed * * @throws OutOfBoundsException When the argument does not exist */ public function getArgument($index) { if (!\array_key_exists($index, $this->arguments)) { throw new OutOfBoundsException(\sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); } return $this->arguments[$index]; } /** * Sets the methods to call after service initialization. * * @return $this */ public function setMethodCalls(array $calls = []) { $this->calls = []; foreach ($calls as $call) { $this->addMethodCall($call[0], $call[1], $call[2] ?? \false); } return $this; } /** * Adds a method to call after service initialization. * * @param string $method The method name to call * @param array $arguments An array of arguments to pass to the method call * @param bool $returnsClone Whether the call returns the service instance or not * * @return $this * * @throws InvalidArgumentException on empty $method param */ public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = \false) { if (empty($method)) { throw new InvalidArgumentException('Method name cannot be empty.'); } $this->calls[] = $returnsClone ? [$method, $arguments, \true] : [$method, $arguments]; return $this; } /** * Removes a method to call after service initialization. * * @return $this */ public function removeMethodCall(string $method) { foreach ($this->calls as $i => $call) { if ($call[0] === $method) { unset($this->calls[$i]); } } return $this; } /** * Check if the current definition has a given method to call after service initialization. * * @return bool */ public function hasMethodCall(string $method) { foreach ($this->calls as $call) { if ($call[0] === $method) { return \true; } } return \false; } /** * Gets the methods to call after service initialization. * * @return array */ public function getMethodCalls() { return $this->calls; } /** * Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class. * * @param ChildDefinition[] $instanceof * * @return $this */ public function setInstanceofConditionals(array $instanceof) { $this->instanceof = $instanceof; return $this; } /** * Gets the definition templates to conditionally apply on the current definition, keyed by parent interface/class. * * @return ChildDefinition[] */ public function getInstanceofConditionals() { return $this->instanceof; } /** * Sets whether or not instanceof conditionals should be prepended with a global set. * * @return $this */ public function setAutoconfigured(bool $autoconfigured) { $this->changes['autoconfigured'] = \true; $this->autoconfigured = $autoconfigured; return $this; } /** * @return bool */ public function isAutoconfigured() { return $this->autoconfigured; } /** * Sets tags for this definition. * * @return $this */ public function setTags(array $tags) { $this->tags = $tags; return $this; } /** * Returns all tags. * * @return array */ public function getTags() { return $this->tags; } /** * Gets a tag by name. * * @return array */ public function getTag(string $name) { return $this->tags[$name] ?? []; } /** * Adds a tag for this definition. * * @return $this */ public function addTag(string $name, array $attributes = []) { $this->tags[$name][] = $attributes; return $this; } /** * Whether this definition has a tag with the given name. * * @return bool */ public function hasTag(string $name) { return isset($this->tags[$name]); } /** * Clears all tags for a given name. * * @return $this */ public function clearTag(string $name) { unset($this->tags[$name]); return $this; } /** * Clears the tags for this definition. * * @return $this */ public function clearTags() { $this->tags = []; return $this; } /** * Sets a file to require before creating the service. * * @return $this */ public function setFile(?string $file) { $this->changes['file'] = \true; $this->file = $file; return $this; } /** * Gets the file to require before creating the service. * * @return string|null */ public function getFile() { return $this->file; } /** * Sets if the service must be shared or not. * * @return $this */ public function setShared(bool $shared) { $this->changes['shared'] = \true; $this->shared = $shared; return $this; } /** * Whether this service is shared. * * @return bool */ public function isShared() { return $this->shared; } /** * Sets the visibility of this service. * * @return $this */ public function setPublic(bool $boolean) { $this->changes['public'] = \true; $this->public = $boolean; return $this; } /** * Whether this service is public facing. * * @return bool */ public function isPublic() { return $this->public; } /** * Sets if this service is private. * * @return $this * * @deprecated since Symfony 5.2, use setPublic() instead */ public function setPrivate(bool $boolean) { \trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__); return $this->setPublic(!$boolean); } /** * Whether this service is private. * * @return bool */ public function isPrivate() { return !$this->public; } /** * Sets the lazy flag of this service. * * @return $this */ public function setLazy(bool $lazy) { $this->changes['lazy'] = \true; $this->lazy = $lazy; return $this; } /** * Whether this service is lazy. * * @return bool */ public function isLazy() { return $this->lazy; } /** * Sets whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. * * @return $this */ public function setSynthetic(bool $boolean) { $this->synthetic = $boolean; if (!isset($this->changes['public'])) { $this->setPublic(\true); } return $this; } /** * Whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. * * @return bool */ public function isSynthetic() { return $this->synthetic; } /** * Whether this definition is abstract, that means it merely serves as a * template for other definitions. * * @return $this */ public function setAbstract(bool $boolean) { $this->abstract = $boolean; return $this; } /** * Whether this definition is abstract, that means it merely serves as a * template for other definitions. * * @return bool */ public function isAbstract() { return $this->abstract; } /** * Whether this definition is deprecated, that means it should not be called * anymore. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ public function setDeprecated() { $args = \func_get_args(); if (\func_num_args() < 3) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); $status = $args[0] ?? \true; if (!$status) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); } $message = (string) ($args[1] ?? null); $package = $version = ''; } else { $status = \true; $package = (string) $args[0]; $version = (string) $args[1]; $message = (string) $args[2]; } if ('' !== $message) { if (\preg_match('#[\\r\\n]|\\*/#', $message)) { throw new InvalidArgumentException('Invalid characters found in deprecation template.'); } if (!\str_contains($message, '%service_id%')) { throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.'); } } $this->changes['deprecated'] = \true; $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE] : []; return $this; } /** * Whether this definition is deprecated, that means it should not be called * anymore. * * @return bool */ public function isDeprecated() { return (bool) $this->deprecation; } /** * Message to use if this definition is deprecated. * * @deprecated since Symfony 5.1, use "getDeprecation()" instead. * * @param string $id Service id relying on this definition * * @return string */ public function getDeprecationMessage(string $id) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); return $this->getDeprecation($id)['message']; } /** * @param string $id Service id relying on this definition */ public function getDeprecation(string $id) : array { return ['package' => $this->deprecation['package'], 'version' => $this->deprecation['version'], 'message' => \str_replace('%service_id%', $id, $this->deprecation['message'])]; } /** * Sets a configurator to call after the service is fully initialized. * * @param string|array|Reference|null $configurator A PHP function, reference or an array containing a class/Reference and a method to call * * @return $this */ public function setConfigurator($configurator) { $this->changes['configurator'] = \true; if (\is_string($configurator) && \str_contains($configurator, '::')) { $configurator = \explode('::', $configurator, 2); } elseif ($configurator instanceof Reference) { $configurator = [$configurator, '__invoke']; } $this->configurator = $configurator; return $this; } /** * Gets the configurator to call after the service is fully initialized. * * @return string|array|null */ public function getConfigurator() { return $this->configurator; } /** * Is the definition autowired? * * @return bool */ public function isAutowired() { return $this->autowired; } /** * Enables/disables autowiring. * * @return $this */ public function setAutowired(bool $autowired) { $this->changes['autowired'] = \true; $this->autowired = $autowired; return $this; } /** * Gets bindings. * * @return BoundArgument[] */ public function getBindings() { return $this->bindings; } /** * Sets bindings. * * Bindings map $named or FQCN arguments to values that should be * injected in the matching parameters (of the constructor, of methods * called and of controller actions). * * @return $this */ public function setBindings(array $bindings) { foreach ($bindings as $key => $binding) { if (0 < \strpos($key, '$') && $key !== ($k = \preg_replace('/[ \\t]*\\$/', ' $', $key))) { unset($bindings[$key]); $bindings[$key = $k] = $binding; } if (!$binding instanceof BoundArgument) { $bindings[$key] = new BoundArgument($binding); } } $this->bindings = $bindings; return $this; } /** * Add an error that occurred when building this Definition. * * @param string|\Closure|self $error * * @return $this */ public function addError($error) { if ($error instanceof self) { $this->errors = \array_merge($this->errors, $error->errors); } else { $this->errors[] = $error; } return $this; } /** * Returns any errors that occurred while building this Definition. * * @return array */ public function getErrors() { foreach ($this->errors as $i => $error) { if ($error instanceof \Closure) { $this->errors[$i] = (string) $error(); } elseif (!\is_string($error)) { $this->errors[$i] = (string) $error; } } return $this->errors; } public function hasErrors() : bool { return (bool) $this->errors; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * The EnvVarProcessorInterface is implemented by objects that manage environment-like variables. * * @author Nicolas Grekas */ interface EnvVarProcessorInterface { /** * Returns the value of the given variable as managed by the current instance. * * @param string $prefix The namespace of the variable; when the empty string is passed, null values should be kept as is * @param string $name The name of the variable within the namespace * @param \Closure $getEnv A closure that allows fetching more env vars * * @return mixed * * @throws RuntimeException on error */ public function getEnv(string $prefix, string $name, \Closure $getEnv); /** * @return string[] The PHP-types managed by getEnv(), keyed by prefixes */ public static function getProvidedTypes(); } DependencyInjection Component ============================= The DependencyInjection component allows you to standardize and centralize the way objects are constructed in your application. Resources --------- * [Documentation](https://symfony.com/doc/current/components/dependency_injection.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Psr\Container\ContainerInterface as PsrContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; /** * ContainerInterface is the interface implemented by service container classes. * * @author Fabien Potencier * @author Johannes M. Schmitt */ interface ContainerInterface extends PsrContainerInterface { public const RUNTIME_EXCEPTION_ON_INVALID_REFERENCE = 0; public const EXCEPTION_ON_INVALID_REFERENCE = 1; public const NULL_ON_INVALID_REFERENCE = 2; public const IGNORE_ON_INVALID_REFERENCE = 3; public const IGNORE_ON_UNINITIALIZED_REFERENCE = 4; /** * Sets a service. */ public function set(string $id, ?object $service); /** * Gets a service. * * @param string $id The service identifier * @param int $invalidBehavior The behavior when the service does not exist * * @return object|null * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined * * @see Reference */ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); /** * @return bool */ public function has(string $id); /** * Check for whether or not a service has been initialized. * * @return bool */ public function initialized(string $id); /** * @return array|bool|string|int|float|\UnitEnum|null * * @throws InvalidArgumentException if the parameter is not defined */ public function getParameter(string $name); /** * @return bool */ public function hasParameter(string $name); /** * Sets a parameter. * * @param string $name The parameter name * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value */ public function setParameter(string $name, $value); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; use _ContaoManager\Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; use _ContaoManager\Symfony\Component\Config\Exception\LoaderLoadException; use _ContaoManager\Symfony\Component\Config\FileLocatorInterface; use _ContaoManager\Symfony\Component\Config\Loader\FileLoader as BaseFileLoader; use _ContaoManager\Symfony\Component\Config\Loader\Loader; use _ContaoManager\Symfony\Component\Config\Resource\GlobResource; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\When; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * FileLoader is the abstract class used by all built-in loaders that are file based. * * @author Fabien Potencier */ abstract class FileLoader extends BaseFileLoader { public const ANONYMOUS_ID_REGEXP = '/^\\.\\d+_[^~]*+~[._a-zA-Z\\d]{7}$/'; protected $container; protected $isLoadingInstanceof = \false; protected $instanceof = []; protected $interfaces = []; protected $singlyImplemented = []; protected $autoRegisterAliasesForSinglyImplementedInterfaces = \true; public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null) { $this->container = $container; parent::__construct($locator, $env); } /** * {@inheritdoc} * * @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found */ public function import($resource, ?string $type = null, $ignoreErrors = \false, ?string $sourceResource = null, $exclude = null) { $args = \func_get_args(); if ($ignoreNotFound = 'not_found' === $ignoreErrors) { $args[2] = \false; } elseif (!\is_bool($ignoreErrors)) { throw new \TypeError(\sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, \get_debug_type($ignoreErrors))); } try { return parent::import(...$args); } catch (LoaderLoadException $e) { if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) { throw $e; } foreach ($prev->getTrace() as $frame) { if ('import' === ($frame['function'] ?? null) && \is_a($frame['class'] ?? '', Loader::class, \true)) { break; } } if (__FILE__ !== $frame['file']) { throw $e; } } return null; } /** * Registers a set of classes as services using PSR-4 for discovery. * * @param Definition $prototype A definition to use as template * @param string $namespace The namespace prefix of classes in the scanned directory * @param string $resource The directory to look for classes, glob-patterns allowed * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude */ public function registerClasses(Definition $prototype, string $namespace, string $resource, $exclude = null) { if (!\str_ends_with($namespace, '\\')) { throw new InvalidArgumentException(\sprintf('Namespace prefix must end with a "\\": "%s".', $namespace)); } if (!\preg_match('/^(?:[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+\\\\)++$/', $namespace)) { throw new InvalidArgumentException(\sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace)); } $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes); // prepare for deep cloning $serializedPrototype = \serialize($prototype); foreach ($classes as $class => $errorMessage) { if (null === $errorMessage && $autoconfigureAttributes && $this->env) { $r = $this->container->getReflectionClass($class); $attribute = null; foreach ($r->getAttributes(When::class) as $attribute) { if ($this->env === $attribute->newInstance()->env) { $attribute = null; break; } } if (null !== $attribute) { continue; } } if (\interface_exists($class, \false)) { $this->interfaces[] = $class; } else { $this->setDefinition($class, $definition = \unserialize($serializedPrototype)); if (null !== $errorMessage) { $definition->addError($errorMessage); continue; } foreach (\class_implements($class, \false) as $interface) { $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? \false : $class; } } } if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) { $this->registerAliasesForSinglyImplementedInterfaces(); } } public function registerAliasesForSinglyImplementedInterfaces() { foreach ($this->interfaces as $interface) { if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) { $this->container->setAlias($interface, $this->singlyImplemented[$interface]); } } $this->interfaces = $this->singlyImplemented = []; } /** * Registers a definition in the container with its instanceof-conditionals. */ protected function setDefinition(string $id, Definition $definition) { $this->container->removeBindings($id); if ($this->isLoadingInstanceof) { if (!$definition instanceof ChildDefinition) { throw new InvalidArgumentException(\sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, \get_debug_type($definition))); } $this->instanceof[$id] = $definition; } else { $this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof)); } } private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes) : array { $parameterBag = $this->container->getParameterBag(); $excludePaths = []; $excludePrefix = null; $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); foreach ($excludePatterns as $excludePattern) { foreach ($this->glob($excludePattern, \true, $resource, \true, \true) as $path => $info) { if (null === $excludePrefix) { $excludePrefix = $resource->getPrefix(); } // normalize Windows slashes and remove trailing slashes $excludePaths[\rtrim(\str_replace('\\', '/', $path), '/')] = \true; } } $pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern)); $classes = []; $extRegexp = '/\\.php$/'; $prefixLen = null; foreach ($this->glob($pattern, \true, $resource, \false, \false, $excludePaths) as $path => $info) { if (null === $prefixLen) { $prefixLen = \strlen($resource->getPrefix()); if ($excludePrefix && !\str_starts_with($excludePrefix, $resource->getPrefix())) { throw new InvalidArgumentException(\sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s).', $namespace, $excludePattern, $pattern)); } } if (isset($excludePaths[\str_replace('\\', '/', $path)])) { continue; } if (!\preg_match($extRegexp, $path, $m) || !$info->isReadable()) { continue; } $class = $namespace . \ltrim(\str_replace('/', '\\', \substr($path, $prefixLen, -\strlen($m[0]))), '\\'); if (!\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+(?:\\\\[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+)*+$/', $class)) { continue; } try { $r = $this->container->getReflectionClass($class); } catch (\ReflectionException $e) { $classes[$class] = $e->getMessage(); continue; } // check to make sure the expected class exists if (!$r) { throw new InvalidArgumentException(\sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); } if ($r->isInstantiable() || $r->isInterface()) { $classes[$class] = null; } if ($autoconfigureAttributes && !$r->isInstantiable()) { $autoconfigureAttributes->processClass($this->container, $r); } } // track only for new & removed files if ($resource instanceof GlobResource) { $this->container->addResource($resource); } else { foreach ($resource as $path) { $this->container->fileExists($path, \false); } } return $classes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; use _ContaoManager\Symfony\Component\Yaml\Exception\ParseException; use _ContaoManager\Symfony\Component\Yaml\Parser as YamlParser; use _ContaoManager\Symfony\Component\Yaml\Tag\TaggedValue; use _ContaoManager\Symfony\Component\Yaml\Yaml; /** * YamlFileLoader loads YAML files service definitions. * * @author Fabien Potencier */ class YamlFileLoader extends FileLoader { private const SERVICE_KEYWORDS = ['alias' => 'alias', 'parent' => 'parent', 'class' => 'class', 'shared' => 'shared', 'synthetic' => 'synthetic', 'lazy' => 'lazy', 'public' => 'public', 'abstract' => 'abstract', 'deprecated' => 'deprecated', 'factory' => 'factory', 'file' => 'file', 'arguments' => 'arguments', 'properties' => 'properties', 'configurator' => 'configurator', 'calls' => 'calls', 'tags' => 'tags', 'decorates' => 'decorates', 'decoration_inner_name' => 'decoration_inner_name', 'decoration_priority' => 'decoration_priority', 'decoration_on_invalid' => 'decoration_on_invalid', 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind']; private const PROTOTYPE_KEYWORDS = ['resource' => 'resource', 'namespace' => 'namespace', 'exclude' => 'exclude', 'parent' => 'parent', 'shared' => 'shared', 'lazy' => 'lazy', 'public' => 'public', 'abstract' => 'abstract', 'deprecated' => 'deprecated', 'factory' => 'factory', 'arguments' => 'arguments', 'properties' => 'properties', 'configurator' => 'configurator', 'calls' => 'calls', 'tags' => 'tags', 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind']; private const INSTANCEOF_KEYWORDS = ['shared' => 'shared', 'lazy' => 'lazy', 'public' => 'public', 'properties' => 'properties', 'configurator' => 'configurator', 'calls' => 'calls', 'tags' => 'tags', 'autowire' => 'autowire', 'bind' => 'bind']; private const DEFAULTS_KEYWORDS = ['public' => 'public', 'tags' => 'tags', 'autowire' => 'autowire', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind']; private $yamlParser; private $anonymousServicesCount; private $anonymousServicesSuffix; protected $autoRegisterAliasesForSinglyImplementedInterfaces = \false; /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { $path = $this->locator->locate($resource); $content = $this->loadFile($path); $this->container->fileExists($path); // empty file if (null === $content) { return null; } $this->loadContent($content, $path); // per-env configuration if ($this->env && isset($content['when@' . $this->env])) { if (!\is_array($content['when@' . $this->env])) { throw new InvalidArgumentException(\sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path)); } $env = $this->env; $this->env = null; try { $this->loadContent($content['when@' . $env], $path); } finally { $this->env = $env; } } return null; } private function loadContent(array $content, string $path) { // imports $this->parseImports($content, $path); // parameters if (isset($content['parameters'])) { if (!\is_array($content['parameters'])) { throw new InvalidArgumentException(\sprintf('The "parameters" key should contain an array in "%s". Check your YAML syntax.', $path)); } foreach ($content['parameters'] as $key => $value) { $this->container->setParameter($key, $this->resolveServices($value, $path, \true)); } } // extensions $this->loadFromExtensions($content); // services $this->anonymousServicesCount = 0; $this->anonymousServicesSuffix = '~' . ContainerBuilder::hash($path); $this->setCurrentDir(\dirname($path)); try { $this->parseDefinitions($content, $path); } finally { $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); } } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { if (!\is_string($resource)) { return \false; } if (null === $type && \in_array(\pathinfo($resource, \PATHINFO_EXTENSION), ['yaml', 'yml'], \true)) { return \true; } return \in_array($type, ['yaml', 'yml'], \true); } private function parseImports(array $content, string $file) { if (!isset($content['imports'])) { return; } if (!\is_array($content['imports'])) { throw new InvalidArgumentException(\sprintf('The "imports" key should contain an array in "%s". Check your YAML syntax.', $file)); } $defaultDirectory = \dirname($file); foreach ($content['imports'] as $import) { if (!\is_array($import)) { $import = ['resource' => $import]; } if (!isset($import['resource'])) { throw new InvalidArgumentException(\sprintf('An import should provide a resource in "%s". Check your YAML syntax.', $file)); } $this->setCurrentDir($defaultDirectory); $this->import($import['resource'], $import['type'] ?? null, $import['ignore_errors'] ?? \false, $file); } } private function parseDefinitions(array $content, string $file, bool $trackBindings = \true) { if (!isset($content['services'])) { return; } if (!\is_array($content['services'])) { throw new InvalidArgumentException(\sprintf('The "services" key should contain an array in "%s". Check your YAML syntax.', $file)); } if (\array_key_exists('_instanceof', $content['services'])) { $instanceof = $content['services']['_instanceof']; unset($content['services']['_instanceof']); if (!\is_array($instanceof)) { throw new InvalidArgumentException(\sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', \get_debug_type($instanceof), $file)); } $this->instanceof = []; $this->isLoadingInstanceof = \true; foreach ($instanceof as $id => $service) { if (!$service || !\is_array($service)) { throw new InvalidArgumentException(\sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in "%s". Check your YAML syntax.', $id, $file)); } if (\is_string($service) && \str_starts_with($service, '@')) { throw new InvalidArgumentException(\sprintf('Type definition "%s" cannot be an alias within "_instanceof" in "%s". Check your YAML syntax.', $id, $file)); } $this->parseDefinition($id, $service, $file, [], \false, $trackBindings); } } $this->isLoadingInstanceof = \false; $defaults = $this->parseDefaults($content, $file); foreach ($content['services'] as $id => $service) { $this->parseDefinition($id, $service, $file, $defaults, \false, $trackBindings); } } /** * @throws InvalidArgumentException */ private function parseDefaults(array &$content, string $file) : array { if (!\array_key_exists('_defaults', $content['services'])) { return []; } $defaults = $content['services']['_defaults']; unset($content['services']['_defaults']); if (!\is_array($defaults)) { throw new InvalidArgumentException(\sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', \get_debug_type($defaults), $file)); } foreach ($defaults as $key => $default) { if (!isset(self::DEFAULTS_KEYWORDS[$key])) { throw new InvalidArgumentException(\sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, \implode('", "', self::DEFAULTS_KEYWORDS))); } } if (isset($defaults['tags'])) { if (!\is_array($tags = $defaults['tags'])) { throw new InvalidArgumentException(\sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file)); } foreach ($tags as $tag) { if (!\is_array($tag)) { $tag = ['name' => $tag]; } if (1 === \count($tag) && \is_array(\current($tag))) { $name = \key($tag); $tag = \current($tag); } else { if (!isset($tag['name'])) { throw new InvalidArgumentException(\sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file)); } $name = $tag['name']; unset($tag['name']); } if (!\is_string($name) || '' === $name) { throw new InvalidArgumentException(\sprintf('The tag name in "_defaults" must be a non-empty string in "%s".', $file)); } foreach ($tag as $attribute => $value) { if (!\is_scalar($value) && null !== $value) { throw new InvalidArgumentException(\sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file)); } } } } if (isset($defaults['bind'])) { if (!\is_array($defaults['bind'])) { throw new InvalidArgumentException(\sprintf('Parameter "bind" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file)); } foreach ($this->resolveServices($defaults['bind'], $file) as $argument => $value) { $defaults['bind'][$argument] = new BoundArgument($value, \true, BoundArgument::DEFAULTS_BINDING, $file); } } return $defaults; } private function isUsingShortSyntax(array $service) : bool { foreach ($service as $key => $value) { if (\is_string($key) && ('' === $key || '$' !== $key[0] && !\str_contains($key, '\\'))) { return \false; } } return \true; } /** * Parses a definition. * * @param array|string|null $service * * @throws InvalidArgumentException When tags are invalid */ private function parseDefinition(string $id, $service, string $file, array $defaults, bool $return = \false, bool $trackBindings = \true) { if (\preg_match('/^_[a-zA-Z0-9_]*$/', $id)) { throw new InvalidArgumentException(\sprintf('Service names that start with an underscore are reserved. Rename the "%s" service or define it in XML instead.', $id)); } if (\is_string($service) && \str_starts_with($service, '@')) { $alias = new Alias(\substr($service, 1)); if (isset($defaults['public'])) { $alias->setPublic($defaults['public']); } return $return ? $alias : $this->container->setAlias($id, $alias); } if (\is_array($service) && $this->isUsingShortSyntax($service)) { $service = ['arguments' => $service]; } if (null === $service) { $service = []; } if (!\is_array($service)) { throw new InvalidArgumentException(\sprintf('A service definition must be an array or a string starting with "@" but "%s" found for service "%s" in "%s". Check your YAML syntax.', \get_debug_type($service), $id, $file)); } if (isset($service['stack'])) { if (!\is_array($service['stack'])) { throw new InvalidArgumentException(\sprintf('A stack must be an array of definitions, "%s" given for service "%s" in "%s". Check your YAML syntax.', \get_debug_type($service), $id, $file)); } $stack = []; foreach ($service['stack'] as $k => $frame) { if (\is_array($frame) && 1 === \count($frame) && !isset(self::SERVICE_KEYWORDS[\key($frame)])) { $frame = ['class' => \key($frame), 'arguments' => \current($frame)]; } if (\is_array($frame) && isset($frame['stack'])) { throw new InvalidArgumentException(\sprintf('Service stack "%s" cannot contain another stack in "%s".', $id, $file)); } $definition = $this->parseDefinition($id . '" at index "' . $k, $frame, $file, $defaults, \true); if ($definition instanceof Definition) { $definition->setInstanceofConditionals($this->instanceof); } $stack[$k] = $definition; } if ($diff = \array_diff(\array_keys($service), ['stack', 'public', 'deprecated'])) { throw new InvalidArgumentException(\sprintf('Invalid attribute "%s"; supported ones are "public" and "deprecated" for service "%s" in "%s". Check your YAML syntax.', \implode('", "', $diff), $id, $file)); } $service = ['parent' => '', 'arguments' => $stack, 'tags' => ['container.stack'], 'public' => $service['public'] ?? null, 'deprecated' => $service['deprecated'] ?? null]; } $definition = isset($service[0]) && $service[0] instanceof Definition ? \array_shift($service) : null; $return = null === $definition ? $return : \true; $this->checkDefinition($id, $service, $file); if (isset($service['alias'])) { $alias = new Alias($service['alias']); if (isset($service['public'])) { $alias->setPublic($service['public']); } elseif (isset($defaults['public'])) { $alias->setPublic($defaults['public']); } foreach ($service as $key => $value) { if (!\in_array($key, ['alias', 'public', 'deprecated'])) { throw new InvalidArgumentException(\sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias", "public" and "deprecated".', $key, $id, $file)); } if ('deprecated' === $key) { $deprecation = \is_array($value) ? $value : ['message' => $value]; if (!isset($deprecation['package'])) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); } if (!isset($deprecation['version'])) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); } $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); } } return $return ? $alias : $this->container->setAlias($id, $alias); } if (null !== $definition) { // no-op } elseif ($this->isLoadingInstanceof) { $definition = new ChildDefinition(''); } elseif (isset($service['parent'])) { if ('' !== $service['parent'] && '@' === $service['parent'][0]) { throw new InvalidArgumentException(\sprintf('The value of the "parent" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['parent'], \substr($service['parent'], 1))); } $definition = new ChildDefinition($service['parent']); } else { $definition = new Definition(); } if (isset($defaults['public'])) { $definition->setPublic($defaults['public']); } if (isset($defaults['autowire'])) { $definition->setAutowired($defaults['autowire']); } if (isset($defaults['autoconfigure'])) { $definition->setAutoconfigured($defaults['autoconfigure']); } $definition->setChanges([]); if (isset($service['class'])) { $definition->setClass($service['class']); } if (isset($service['shared'])) { $definition->setShared($service['shared']); } if (isset($service['synthetic'])) { $definition->setSynthetic($service['synthetic']); } if (isset($service['lazy'])) { $definition->setLazy((bool) $service['lazy']); if (\is_string($service['lazy'])) { $definition->addTag('proxy', ['interface' => $service['lazy']]); } } if (isset($service['public'])) { $definition->setPublic($service['public']); } if (isset($service['abstract'])) { $definition->setAbstract($service['abstract']); } if (isset($service['deprecated'])) { $deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']]; if (!isset($deprecation['package'])) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); } if (!isset($deprecation['version'])) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); } $definition->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); } if (isset($service['factory'])) { $definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file)); } if (isset($service['file'])) { $definition->setFile($service['file']); } if (isset($service['arguments'])) { $definition->setArguments($this->resolveServices($service['arguments'], $file)); } if (isset($service['properties'])) { $definition->setProperties($this->resolveServices($service['properties'], $file)); } if (isset($service['configurator'])) { $definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file)); } if (isset($service['calls'])) { if (!\is_array($service['calls'])) { throw new InvalidArgumentException(\sprintf('Parameter "calls" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } foreach ($service['calls'] as $k => $call) { if (!\is_array($call) && (!\is_string($k) || !$call instanceof TaggedValue)) { throw new InvalidArgumentException(\sprintf('Invalid method call for service "%s": expected map or array, "%s" given in "%s".', $id, $call instanceof TaggedValue ? '!' . $call->getTag() : \get_debug_type($call), $file)); } if (\is_string($k)) { throw new InvalidArgumentException(\sprintf('Invalid method call for service "%s", did you forgot a leading dash before "%s: ..." in "%s"?', $id, $k, $file)); } if (isset($call['method']) && \is_string($call['method'])) { $method = $call['method']; $args = $call['arguments'] ?? []; $returnsClone = $call['returns_clone'] ?? \false; } else { if (1 === \count($call) && \is_string(\key($call))) { $method = \key($call); $args = $call[$method]; if ($args instanceof TaggedValue) { if ('returns_clone' !== $args->getTag()) { throw new InvalidArgumentException(\sprintf('Unsupported tag "!%s", did you mean "!returns_clone" for service "%s" in "%s"?', $args->getTag(), $id, $file)); } $returnsClone = \true; $args = $args->getValue(); } else { $returnsClone = \false; } } elseif (empty($call[0])) { throw new InvalidArgumentException(\sprintf('Invalid call for service "%s": the method must be defined as the first index of an array or as the only key of a map in "%s".', $id, $file)); } else { $method = $call[0]; $args = $call[1] ?? []; $returnsClone = $call[2] ?? \false; } } if (!\is_array($args)) { throw new InvalidArgumentException(\sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in "%s". Check your YAML syntax.', $method, $id, $file)); } $args = $this->resolveServices($args, $file); $definition->addMethodCall($method, $args, $returnsClone); } } $tags = $service['tags'] ?? []; if (!\is_array($tags)) { throw new InvalidArgumentException(\sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } if (isset($defaults['tags'])) { $tags = \array_merge($tags, $defaults['tags']); } foreach ($tags as $tag) { if (!\is_array($tag)) { $tag = ['name' => $tag]; } if (1 === \count($tag) && \is_array(\current($tag))) { $name = \key($tag); $tag = \current($tag); } else { if (!isset($tag['name'])) { throw new InvalidArgumentException(\sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file)); } $name = $tag['name']; unset($tag['name']); } if (!\is_string($name) || '' === $name) { throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file)); } foreach ($tag as $attribute => $value) { if (!\is_scalar($value) && null !== $value) { throw new InvalidArgumentException(\sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file)); } } $definition->addTag($name, $tag); } if (null !== ($decorates = $service['decorates'] ?? null)) { if ('' !== $decorates && '@' === $decorates[0]) { throw new InvalidArgumentException(\sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], \substr($decorates, 1))); } $decorationOnInvalid = \array_key_exists('decoration_on_invalid', $service) ? $service['decoration_on_invalid'] : 'exception'; if ('exception' === $decorationOnInvalid) { $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; } elseif ('ignore' === $decorationOnInvalid) { $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } elseif (null === $decorationOnInvalid) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } elseif ('null' === $decorationOnInvalid) { throw new InvalidArgumentException(\sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean null (without quotes) in "%s"?', $decorationOnInvalid, $id, $file)); } else { throw new InvalidArgumentException(\sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean "exception", "ignore" or null in "%s"?', $decorationOnInvalid, $id, $file)); } $renameId = $service['decoration_inner_name'] ?? null; $priority = $service['decoration_priority'] ?? 0; $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); } if (isset($service['autowire'])) { $definition->setAutowired($service['autowire']); } if (isset($defaults['bind']) || isset($service['bind'])) { // deep clone, to avoid multiple process of the same instance in the passes $bindings = $definition->getBindings(); $bindings += isset($defaults['bind']) ? \unserialize(\serialize($defaults['bind'])) : []; if (isset($service['bind'])) { if (!\is_array($service['bind'])) { throw new InvalidArgumentException(\sprintf('Parameter "bind" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } $bindings = \array_merge($bindings, $this->resolveServices($service['bind'], $file)); $bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING; foreach ($bindings as $argument => $value) { if (!$value instanceof BoundArgument) { $bindings[$argument] = new BoundArgument($value, $trackBindings, $bindingType, $file); } } } $definition->setBindings($bindings); } if (isset($service['autoconfigure'])) { $definition->setAutoconfigured($service['autoconfigure']); } if (\array_key_exists('namespace', $service) && !\array_key_exists('resource', $service)) { throw new InvalidArgumentException(\sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } if ($return) { if (\array_key_exists('resource', $service)) { throw new InvalidArgumentException(\sprintf('Invalid "resource" attribute found for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } return $definition; } if (\array_key_exists('resource', $service)) { if (!\is_string($service['resource'])) { throw new InvalidArgumentException(\sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } $exclude = $service['exclude'] ?? null; $namespace = $service['namespace'] ?? $id; $this->registerClasses($definition, $namespace, $service['resource'], $exclude); } else { $this->setDefinition($id, $definition); } } /** * Parses a callable. * * @param string|array $callable A callable reference * * @return string|array|Reference * * @throws InvalidArgumentException When errors occur */ private function parseCallable($callable, string $parameter, string $id, string $file) { if (\is_string($callable)) { if ('' !== $callable && '@' === $callable[0]) { if (!\str_contains($callable, ':')) { return [$this->resolveServices($callable, $file), '__invoke']; } throw new InvalidArgumentException(\sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s" in "%s").', $parameter, $id, $callable, \substr($callable, 1), $file)); } return $callable; } if (\is_array($callable)) { if (isset($callable[0]) && isset($callable[1])) { return [$this->resolveServices($callable[0], $file), $callable[1]]; } if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) { return $callable; } throw new InvalidArgumentException(\sprintf('Parameter "%s" must contain an array with two elements for service "%s" in "%s". Check your YAML syntax.', $parameter, $id, $file)); } throw new InvalidArgumentException(\sprintf('Parameter "%s" must be a string or an array for service "%s" in "%s". Check your YAML syntax.', $parameter, $id, $file)); } /** * Loads a YAML file. * * @return array|null * * @throws InvalidArgumentException when the given file is not a local file or when it does not exist */ protected function loadFile(string $file) { if (!\class_exists(\_ContaoManager\Symfony\Component\Yaml\Parser::class)) { throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.'); } if (!\stream_is_local($file)) { throw new InvalidArgumentException(\sprintf('This is not a local file "%s".', $file)); } if (!\is_file($file)) { throw new InvalidArgumentException(\sprintf('The file "%s" does not exist.', $file)); } if (null === $this->yamlParser) { $this->yamlParser = new YamlParser(); } try { $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS); } catch (ParseException $e) { throw new InvalidArgumentException(\sprintf('The file "%s" does not contain valid YAML: ', $file) . $e->getMessage(), 0, $e); } return $this->validate($configuration, $file); } /** * Validates a YAML file. * * @throws InvalidArgumentException When service file is not valid */ private function validate($content, string $file) : ?array { if (null === $content) { return $content; } if (!\is_array($content)) { throw new InvalidArgumentException(\sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); } foreach ($content as $namespace => $data) { if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === \strpos($namespace, 'when@')) { continue; } if (!$this->container->hasExtension($namespace)) { $extensionNamespaces = \array_filter(\array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); throw new InvalidArgumentException(\sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $file, $namespace, $extensionNamespaces ? \sprintf('"%s"', \implode('", "', $extensionNamespaces)) : 'none')); } } return $content; } /** * @return mixed */ private function resolveServices($value, string $file, bool $isParameter = \false) { if ($value instanceof TaggedValue) { $argument = $value->getValue(); if ('iterator' === $value->getTag()) { if (!\is_array($argument)) { throw new InvalidArgumentException(\sprintf('"!iterator" tag only accepts sequences in "%s".', $file)); } $argument = $this->resolveServices($argument, $file, $isParameter); try { return new IteratorArgument($argument); } catch (InvalidArgumentException $e) { throw new InvalidArgumentException(\sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file)); } } if ('service_closure' === $value->getTag()) { $argument = $this->resolveServices($argument, $file, $isParameter); if (!$argument instanceof Reference) { throw new InvalidArgumentException(\sprintf('"!service_closure" tag only accepts service references in "%s".', $file)); } return new ServiceClosureArgument($argument); } if ('service_locator' === $value->getTag()) { if (!\is_array($argument)) { throw new InvalidArgumentException(\sprintf('"!service_locator" tag only accepts maps in "%s".', $file)); } $argument = $this->resolveServices($argument, $file, $isParameter); try { return new ServiceLocatorArgument($argument); } catch (InvalidArgumentException $e) { throw new InvalidArgumentException(\sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file)); } } if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], \true)) { $forLocator = 'tagged_locator' === $value->getTag(); if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) { if ($diff = \array_diff(\array_keys($argument), ['tag', 'index_by', 'default_index_method', 'default_priority_method'])) { throw new InvalidArgumentException(\sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), \implode('", "', $diff))); } $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null); } elseif (\is_string($argument) && $argument) { $argument = new TaggedIteratorArgument($argument, null, null, $forLocator); } else { throw new InvalidArgumentException(\sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file)); } if ($forLocator) { $argument = new ServiceLocatorArgument($argument); } return $argument; } if ('service' === $value->getTag()) { if ($isParameter) { throw new InvalidArgumentException(\sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file)); } $isLoadingInstanceof = $this->isLoadingInstanceof; $this->isLoadingInstanceof = \false; $instanceof = $this->instanceof; $this->instanceof = []; $id = \sprintf('.%d_%s', ++$this->anonymousServicesCount, \preg_replace('/^.*\\\\/', '', $argument['class'] ?? '') . $this->anonymousServicesSuffix); $this->parseDefinition($id, $argument, $file, []); if (!$this->container->hasDefinition($id)) { throw new InvalidArgumentException(\sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file)); } $this->container->getDefinition($id); $this->isLoadingInstanceof = $isLoadingInstanceof; $this->instanceof = $instanceof; return new Reference($id); } if ('abstract' === $value->getTag()) { return new AbstractArgument($value->getValue()); } throw new InvalidArgumentException(\sprintf('Unsupported tag "!%s".', $value->getTag())); } if (\is_array($value)) { foreach ($value as $k => $v) { $value[$k] = $this->resolveServices($v, $file, $isParameter); } } elseif (\is_string($value) && \str_starts_with($value, '@=')) { if ($isParameter) { throw new InvalidArgumentException(\sprintf('Using expressions in parameters is not allowed in "%s".', $file)); } if (!\class_exists(Expression::class)) { throw new \LogicException('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); } return new Expression(\substr($value, 2)); } elseif (\is_string($value) && \str_starts_with($value, '@')) { if (\str_starts_with($value, '@@')) { $value = \substr($value, 1); $invalidBehavior = null; } elseif (\str_starts_with($value, '@!')) { $value = \substr($value, 2); $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; } elseif (\str_starts_with($value, '@?')) { $value = \substr($value, 2); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } else { $value = \substr($value, 1); $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; } if (null !== $invalidBehavior) { $value = new Reference($value, $invalidBehavior); } } return $value; } private function loadFromExtensions(array $content) { foreach ($content as $namespace => $values) { if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === \strpos($namespace, 'when@')) { continue; } if (!\is_array($values) && null !== $values) { $values = []; } $this->container->loadFromExtension($namespace, $values); } } private function checkDefinition(string $id, array $definition, string $file) { if ($this->isLoadingInstanceof) { $keywords = self::INSTANCEOF_KEYWORDS; } elseif (isset($definition['resource']) || isset($definition['namespace'])) { $keywords = self::PROTOTYPE_KEYWORDS; } else { $keywords = self::SERVICE_KEYWORDS; } foreach ($definition as $key => $value) { if (!isset($keywords[$key])) { throw new InvalidArgumentException(\sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, \implode('", "', $keywords))); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; use _ContaoManager\Symfony\Component\Config\Builder\ConfigBuilderGenerator; use _ContaoManager\Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; use _ContaoManager\Symfony\Component\Config\Builder\ConfigBuilderInterface; use _ContaoManager\Symfony\Component\Config\FileLocatorInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\When; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; /** * PhpFileLoader loads service definitions from a PHP file. * * The PHP file is required and the $container variable can be * used within the file to change the container. * * @author Fabien Potencier */ class PhpFileLoader extends FileLoader { protected $autoRegisterAliasesForSinglyImplementedInterfaces = \false; private $generator; public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, ?ConfigBuilderGeneratorInterface $generator = null) { parent::__construct($container, $locator, $env); $this->generator = $generator; } /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { // the container and loader variables are exposed to the included file below $container = $this->container; $loader = $this; $path = $this->locator->locate($resource); $this->setCurrentDir(\dirname($path)); $this->container->fileExists($path); // the closure forbids access to the private scope in the included file $load = \Closure::bind(function ($path, $env) use($container, $loader, $resource, $type) { return include $path; }, $this, ProtectedPhpFileLoader::class); try { $callback = $load($path, $this->env); if (\is_object($callback) && \is_callable($callback)) { $this->executeCallback($callback, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path); } } finally { $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); } return null; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { if (!\is_string($resource)) { return \false; } if (null === $type && 'php' === \pathinfo($resource, \PATHINFO_EXTENSION)) { return \true; } return 'php' === $type; } /** * Resolve the parameters to the $callback and execute it. */ private function executeCallback(callable $callback, ContainerConfigurator $containerConfigurator, string $path) { if (!$callback instanceof \Closure) { $callback = \Closure::fromCallable($callback); } $arguments = []; $configBuilders = []; $r = new \ReflectionFunction($callback); if (\PHP_VERSION_ID >= 80000) { $attribute = null; foreach ($r->getAttributes(When::class) as $attribute) { if ($this->env === $attribute->newInstance()->env) { $attribute = null; break; } } if (null !== $attribute) { return; } } foreach ($r->getParameters() as $parameter) { $reflectionType = $parameter->getType(); if (!$reflectionType instanceof \ReflectionNamedType) { throw new \InvalidArgumentException(\sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s" or "%s").', $parameter->getName(), $path, ContainerConfigurator::class, ContainerBuilder::class)); } $type = $reflectionType->getName(); switch ($type) { case ContainerConfigurator::class: $arguments[] = $containerConfigurator; break; case ContainerBuilder::class: $arguments[] = $this->container; break; case FileLoader::class: case self::class: $arguments[] = $this; break; default: try { $configBuilder = $this->configBuilder($type); } catch (InvalidArgumentException|\LogicException $e) { throw new \InvalidArgumentException(\sprintf('Could not resolve argument "%s" for "%s".', $type . ' $' . $parameter->getName(), $path), 0, $e); } $configBuilders[] = $configBuilder; $arguments[] = $configBuilder; } } // Force load ContainerConfigurator to make env(), param() etc available. \class_exists(ContainerConfigurator::class); $callback(...$arguments); /** @var ConfigBuilderInterface $configBuilder */ foreach ($configBuilders as $configBuilder) { $containerConfigurator->extension($configBuilder->getExtensionAlias(), $configBuilder->toArray()); } } /** * @param string $namespace FQCN string for a class implementing ConfigBuilderInterface */ private function configBuilder(string $namespace) : ConfigBuilderInterface { if (!\class_exists(ConfigBuilderGenerator::class)) { throw new \LogicException('You cannot use the config builder as the Config component is not installed. Try running "composer require symfony/config".'); } if (null === $this->generator) { throw new \LogicException('You cannot use the ConfigBuilders without providing a class implementing ConfigBuilderGeneratorInterface.'); } // If class exists and implements ConfigBuilderInterface if (\class_exists($namespace) && \is_subclass_of($namespace, ConfigBuilderInterface::class)) { return new $namespace(); } // If it does not start with Symfony\Config\ we dont know how to handle this if ('Symfony\\Config\\' !== \substr($namespace, 0, 15)) { throw new InvalidArgumentException(\sprintf('Could not find or generate class "%s".', $namespace)); } // Try to get the extension alias $alias = Container::underscore(\substr($namespace, 15, -6)); if (\false !== \strpos($alias, '\\')) { throw new InvalidArgumentException('You can only use "root" ConfigBuilders from "Symfony\\Config\\" namespace. Nested classes like "Symfony\\Config\\Framework\\CacheConfig" cannot be used.'); } if (!$this->container->hasExtension($alias)) { $extensions = \array_filter(\array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); throw new InvalidArgumentException(\sprintf('There is no extension able to load the configuration for "%s". Looked for namespace "%s", found "%s".', $namespace, $alias, $extensions ? \implode('", "', $extensions) : 'none')); } $extension = $this->container->getExtension($alias); if (!$extension instanceof ConfigurationExtensionInterface) { throw new \LogicException(\sprintf('You cannot use the config builder for "%s" because the extension does not implement "%s".', $namespace, ConfigurationExtensionInterface::class)); } $configuration = $extension->getConfiguration([], $this->container); $loader = $this->generator->build($configuration); return $loader(); } } /** * @internal */ final class ProtectedPhpFileLoader extends PhpFileLoader { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; use _ContaoManager\Symfony\Component\Config\Util\XmlUtils; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; /** * XmlFileLoader loads XML files service definitions. * * @author Fabien Potencier */ class XmlFileLoader extends FileLoader { public const NS = 'http://symfony.com/schema/dic/services'; protected $autoRegisterAliasesForSinglyImplementedInterfaces = \false; /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { $path = $this->locator->locate($resource); $xml = $this->parseFileToDOM($path); $this->container->fileExists($path); $this->loadXml($xml, $path); if ($this->env) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); foreach ($xpath->query(\sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) { $env = $this->env; $this->env = null; try { $this->loadXml($xml, $path, $root); } finally { $this->env = $env; } } } return null; } private function loadXml(\DOMDocument $xml, string $path, ?\DOMNode $root = null) : void { $defaults = $this->getServiceDefaults($xml, $path, $root); // anonymous services $this->processAnonymousServices($xml, $path, $root); // imports $this->parseImports($xml, $path, $root); // parameters $this->parseParameters($xml, $path, $root); // extensions $this->loadFromExtensions($xml, $root); // services try { $this->parseDefinitions($xml, $path, $defaults, $root); } finally { $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); } } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { if (!\is_string($resource)) { return \false; } if (null === $type && 'xml' === \pathinfo($resource, \PATHINFO_EXTENSION)) { return \true; } return 'xml' === $type; } private function parseParameters(\DOMDocument $xml, string $file, ?\DOMNode $root = null) { if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) { $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file)); } } private function parseImports(\DOMDocument $xml, string $file, ?\DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); if (\false === ($imports = $xpath->query('.//container:imports/container:import', $root))) { return; } $defaultDirectory = \dirname($file); foreach ($imports as $import) { $this->setCurrentDir($defaultDirectory); $this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, XmlUtils::phpize($import->getAttribute('ignore-errors')) ?: \false, $file); } } private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, ?\DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); if (\false === ($services = $xpath->query('.//container:services/container:service|.//container:services/container:prototype|.//container:services/container:stack', $root))) { return; } $this->setCurrentDir(\dirname($file)); $this->instanceof = []; $this->isLoadingInstanceof = \true; $instanceof = $xpath->query('.//container:services/container:instanceof', $root); foreach ($instanceof as $service) { $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition())); } $this->isLoadingInstanceof = \false; foreach ($services as $service) { if ('stack' === $service->tagName) { $service->setAttribute('parent', '-'); $definition = $this->parseDefinition($service, $file, $defaults)->setTags(\array_merge_recursive(['container.stack' => [[]]], $defaults->getTags())); $this->setDefinition($id = (string) $service->getAttribute('id'), $definition); $stack = []; foreach ($this->getChildren($service, 'service') as $k => $frame) { $k = $frame->getAttribute('id') ?: $k; $frame->setAttribute('id', $id . '" at index "' . $k); if ($alias = $frame->getAttribute('alias')) { $this->validateAlias($frame, $file); $stack[$k] = new Reference($alias); } else { $stack[$k] = $this->parseDefinition($frame, $file, $defaults)->setInstanceofConditionals($this->instanceof); } } $definition->setArguments($stack); } elseif (null !== ($definition = $this->parseDefinition($service, $file, $defaults))) { if ('prototype' === $service->tagName) { $excludes = \array_column($this->getChildren($service, 'exclude'), 'nodeValue'); if ($service->hasAttribute('exclude')) { if (\count($excludes) > 0) { throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); } $excludes = [$service->getAttribute('exclude')]; } $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes); } else { $this->setDefinition((string) $service->getAttribute('id'), $definition); } } } } private function getServiceDefaults(\DOMDocument $xml, string $file, ?\DOMNode $root = null) : Definition { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); if (null === ($defaultsNode = $xpath->query('.//container:services/container:defaults', $root)->item(0))) { return new Definition(); } $defaultsNode->setAttribute('id', ''); return $this->parseDefinition($defaultsNode, $file, new Definition()); } /** * Parses an individual Definition. */ private function parseDefinition(\DOMElement $service, string $file, Definition $defaults) : ?Definition { if ($alias = $service->getAttribute('alias')) { $this->validateAlias($service, $file); $this->container->setAlias($service->getAttribute('id'), $alias = new Alias($alias)); if ($publicAttr = $service->getAttribute('public')) { $alias->setPublic(XmlUtils::phpize($publicAttr)); } elseif ($defaults->getChanges()['public'] ?? \false) { $alias->setPublic($defaults->isPublic()); } if ($deprecated = $this->getChildren($service, 'deprecated')) { $message = $deprecated[0]->nodeValue ?: ''; $package = $deprecated[0]->getAttribute('package') ?: ''; $version = $deprecated[0]->getAttribute('version') ?: ''; if (!$deprecated[0]->hasAttribute('package')) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); } if (!$deprecated[0]->hasAttribute('version')) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); } $alias->setDeprecated($package, $version, $message); } return null; } if ($this->isLoadingInstanceof) { $definition = new ChildDefinition(''); } elseif ($parent = $service->getAttribute('parent')) { $definition = new ChildDefinition($parent); } else { $definition = new Definition(); } if ($defaults->getChanges()['public'] ?? \false) { $definition->setPublic($defaults->isPublic()); } $definition->setAutowired($defaults->isAutowired()); $definition->setAutoconfigured($defaults->isAutoconfigured()); $definition->setChanges([]); foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) { if ($value = $service->getAttribute($key)) { $method = 'set' . $key; $definition->{$method}($value = XmlUtils::phpize($value)); } } if ($value = $service->getAttribute('lazy')) { $definition->setLazy((bool) ($value = XmlUtils::phpize($value))); if (\is_string($value)) { $definition->addTag('proxy', ['interface' => $value]); } } if ($value = $service->getAttribute('autowire')) { $definition->setAutowired(XmlUtils::phpize($value)); } if ($value = $service->getAttribute('autoconfigure')) { $definition->setAutoconfigured(XmlUtils::phpize($value)); } if ($files = $this->getChildren($service, 'file')) { $definition->setFile($files[0]->nodeValue); } if ($deprecated = $this->getChildren($service, 'deprecated')) { $message = $deprecated[0]->nodeValue ?: ''; $package = $deprecated[0]->getAttribute('package') ?: ''; $version = $deprecated[0]->getAttribute('version') ?: ''; if ('' === $package) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); } if ('' === $version) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); } $definition->setDeprecated($package, $version, $message); } $definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, $definition instanceof ChildDefinition)); $definition->setProperties($this->getArgumentsAsPhp($service, 'property', $file)); if ($factories = $this->getChildren($service, 'factory')) { $factory = $factories[0]; if ($function = $factory->getAttribute('function')) { $definition->setFactory($function); } else { if ($childService = $factory->getAttribute('service')) { $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); } else { $class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null; } $definition->setFactory([$class, $factory->getAttribute('method') ?: '__invoke']); } } if ($configurators = $this->getChildren($service, 'configurator')) { $configurator = $configurators[0]; if ($function = $configurator->getAttribute('function')) { $definition->setConfigurator($function); } else { if ($childService = $configurator->getAttribute('service')) { $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); } else { $class = $configurator->getAttribute('class'); } $definition->setConfigurator([$class, $configurator->getAttribute('method') ?: '__invoke']); } } foreach ($this->getChildren($service, 'call') as $call) { $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone'))); } $tags = $this->getChildren($service, 'tag'); foreach ($tags as $tag) { $parameters = []; $tagName = $tag->nodeValue; foreach ($tag->attributes as $name => $node) { if ('name' === $name && '' === $tagName) { continue; } if (\str_contains($name, '-') && !\str_contains($name, '_') && !\array_key_exists($normalizedName = \str_replace('-', '_', $name), $parameters)) { $parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue); } // keep not normalized key $parameters[$name] = XmlUtils::phpize($node->nodeValue); } if ('' === $tagName && '' === ($tagName = $tag->getAttribute('name'))) { throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file)); } $definition->addTag($tagName, $parameters); } $definition->setTags(\array_merge_recursive($definition->getTags(), $defaults->getTags())); $bindings = $this->getArgumentsAsPhp($service, 'bind', $file); $bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING; foreach ($bindings as $argument => $value) { $bindings[$argument] = new BoundArgument($value, \true, $bindingType, $file); } // deep clone, to avoid multiple process of the same instance in the passes $bindings = \array_merge(\unserialize(\serialize($defaults->getBindings())), $bindings); if ($bindings) { $definition->setBindings($bindings); } if ($decorates = $service->getAttribute('decorates')) { $decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception'; if ('exception' === $decorationOnInvalid) { $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; } elseif ('ignore' === $decorationOnInvalid) { $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } elseif ('null' === $decorationOnInvalid) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } else { throw new InvalidArgumentException(\sprintf('Invalid value "%s" for attribute "decoration-on-invalid" on service "%s". Did you mean "exception", "ignore" or "null" in "%s"?', $decorationOnInvalid, $service->getAttribute('id'), $file)); } $renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null; $priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0; $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); } return $definition; } /** * Parses an XML file to a \DOMDocument. * * @throws InvalidArgumentException When loading of XML file returns error */ private function parseFileToDOM(string $file) : \DOMDocument { try { $dom = XmlUtils::loadFile($file, [$this, 'validateSchema']); } catch (\InvalidArgumentException $e) { throw new InvalidArgumentException(\sprintf('Unable to parse file "%s": ', $file) . $e->getMessage(), $e->getCode(), $e); } $this->validateExtensions($dom, $file); return $dom; } /** * Processes anonymous services. */ private function processAnonymousServices(\DOMDocument $xml, string $file, ?\DOMNode $root = null) { $definitions = []; $count = 0; $suffix = '~' . ContainerBuilder::hash($file); $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); // anonymous services as arguments/properties if (\false !== ($nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root))) { foreach ($nodes as $node) { if ($services = $this->getChildren($node, 'service')) { // give it a unique name $id = \sprintf('.%d_%s', ++$count, \preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')) . $suffix); $node->setAttribute('id', $id); $node->setAttribute('service', $id); $definitions[$id] = [$services[0], $file]; $services[0]->setAttribute('id', $id); // anonymous services are always private // we could not use the constant false here, because of XML parsing $services[0]->setAttribute('public', 'false'); } } } // anonymous services "in the wild" if (\false !== ($nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root))) { foreach ($nodes as $node) { throw new InvalidArgumentException(\sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo())); } } // resolve definitions \uksort($definitions, 'strnatcmp'); foreach (\array_reverse($definitions) as $id => [$domElement, $file]) { if (null !== ($definition = $this->parseDefinition($domElement, $file, new Definition()))) { $this->setDefinition($id, $definition); } } } private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file, bool $isChildDefinition = \false) : array { $arguments = []; foreach ($this->getChildren($node, $name) as $arg) { if ($arg->hasAttribute('name')) { $arg->setAttribute('key', $arg->getAttribute('name')); } // this is used by ChildDefinition to overwrite a specific // argument of the parent definition if ($arg->hasAttribute('index')) { $key = ($isChildDefinition ? 'index_' : '') . $arg->getAttribute('index'); } elseif (!$arg->hasAttribute('key')) { // Append an empty argument, then fetch its key to overwrite it later $arguments[] = null; $keys = \array_keys($arguments); $key = \array_pop($keys); } else { $key = $arg->getAttribute('key'); } $onInvalid = $arg->getAttribute('on-invalid'); $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; if ('ignore' == $onInvalid) { $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } elseif ('ignore_uninitialized' == $onInvalid) { $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; } elseif ('null' == $onInvalid) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } switch ($arg->getAttribute('type')) { case 'service': if ('' === $arg->getAttribute('id')) { throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file)); } $arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior); break; case 'expression': if (!\class_exists(Expression::class)) { throw new \LogicException('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); } $arguments[$key] = new Expression($arg->nodeValue); break; case 'collection': $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, $file); break; case 'iterator': $arg = $this->getArgumentsAsPhp($arg, $name, $file); try { $arguments[$key] = new IteratorArgument($arg); } catch (InvalidArgumentException $e) { throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file)); } break; case 'service_closure': if ('' === $arg->getAttribute('id')) { throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="service_closure" has no or empty "id" attribute in "%s".', $name, $file)); } $arguments[$key] = new ServiceClosureArgument(new Reference($arg->getAttribute('id'), $invalidBehavior)); break; case 'service_locator': $arg = $this->getArgumentsAsPhp($arg, $name, $file); try { $arguments[$key] = new ServiceLocatorArgument($arg); } catch (InvalidArgumentException $e) { throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="service_locator" only accepts maps of type="service" references in "%s".', $name, $file)); } break; case 'tagged': case 'tagged_iterator': case 'tagged_locator': $type = $arg->getAttribute('type'); $forLocator = 'tagged_locator' === $type; if (!$arg->getAttribute('tag')) { throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file)); } $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null); if ($forLocator) { $arguments[$key] = new ServiceLocatorArgument($arguments[$key]); } break; case 'binary': if (\false === ($value = \base64_decode($arg->nodeValue))) { throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="binary" is not a valid base64 encoded string.', $name)); } $arguments[$key] = $value; break; case 'abstract': $arguments[$key] = new AbstractArgument($arg->nodeValue); break; case 'string': $arguments[$key] = $arg->nodeValue; break; case 'constant': $arguments[$key] = \constant(\trim($arg->nodeValue)); break; default: $arguments[$key] = XmlUtils::phpize($arg->nodeValue); } } return $arguments; } /** * Get child elements by name. * * @return \DOMElement[] */ private function getChildren(\DOMNode $node, string $name) : array { $children = []; foreach ($node->childNodes as $child) { if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) { $children[] = $child; } } return $children; } /** * Validates a documents XML schema. * * @return bool * * @throws RuntimeException When extension references a non-existent XSD file */ public function validateSchema(\DOMDocument $dom) { $schemaLocations = ['http://symfony.com/schema/dic/services' => \str_replace('\\', '/', __DIR__ . '/schema/dic/services/services-1.0.xsd')]; if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) { $items = \preg_split('/\\s+/', $element); for ($i = 0, $nb = \count($items); $i < $nb; $i += 2) { if (!$this->container->hasExtension($items[$i])) { continue; } if (($extension = $this->container->getExtension($items[$i])) && \false !== $extension->getXsdValidationBasePath()) { $ns = $extension->getNamespace(); $path = \str_replace([$ns, \str_replace('http://', 'https://', $ns)], \str_replace('\\', '/', $extension->getXsdValidationBasePath()) . '/', $items[$i + 1]); if (!\is_file($path)) { throw new RuntimeException(\sprintf('Extension "%s" references a non-existent XSD file "%s".', \get_debug_type($extension), $path)); } $schemaLocations[$items[$i]] = $path; } } } $tmpfiles = []; $imports = ''; foreach ($schemaLocations as $namespace => $location) { $parts = \explode('/', $location); $locationstart = 'file:///'; if (0 === \stripos($location, 'phar://')) { $tmpfile = \tempnam(\sys_get_temp_dir(), 'symfony'); if ($tmpfile) { \copy($location, $tmpfile); $tmpfiles[] = $tmpfile; $parts = \explode('/', \str_replace('\\', '/', $tmpfile)); } else { \array_shift($parts); $locationstart = 'phar:///'; } } elseif ('\\' === \DIRECTORY_SEPARATOR && \str_starts_with($location, '\\\\')) { $locationstart = ''; } $drive = '\\' === \DIRECTORY_SEPARATOR ? \array_shift($parts) . '/' : ''; $location = $locationstart . $drive . \implode('/', \array_map('rawurlencode', $parts)); $imports .= \sprintf(' ' . "\n", $namespace, $location); } $source = << {$imports} EOF; if ($this->shouldEnableEntityLoader()) { $disableEntities = \libxml_disable_entity_loader(\false); $valid = @$dom->schemaValidateSource($source); \libxml_disable_entity_loader($disableEntities); } else { $valid = @$dom->schemaValidateSource($source); } foreach ($tmpfiles as $tmpfile) { @\unlink($tmpfile); } return $valid; } private function shouldEnableEntityLoader() : bool { // Version prior to 8.0 can be enabled without deprecation if (\PHP_VERSION_ID < 80000) { return \true; } static $dom, $schema; if (null === $dom) { $dom = new \DOMDocument(); $dom->loadXML(''); $tmpfile = \tempnam(\sys_get_temp_dir(), 'symfony'); \register_shutdown_function(static function () use($tmpfile) { @\unlink($tmpfile); }); $schema = ' '; \file_put_contents($tmpfile, ' '); } return !@$dom->schemaValidateSource($schema); } private function validateAlias(\DOMElement $alias, string $file) { foreach ($alias->attributes as $name => $node) { if (!\in_array($name, ['alias', 'id', 'public'])) { throw new InvalidArgumentException(\sprintf('Invalid attribute "%s" defined for alias "%s" in "%s".', $name, $alias->getAttribute('id'), $file)); } } foreach ($alias->childNodes as $child) { if (!$child instanceof \DOMElement || self::NS !== $child->namespaceURI) { continue; } if (!\in_array($child->localName, ['deprecated'], \true)) { throw new InvalidArgumentException(\sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file)); } } } /** * Validates an extension. * * @throws InvalidArgumentException When no extension is found corresponding to a tag */ private function validateExtensions(\DOMDocument $dom, string $file) { foreach ($dom->documentElement->childNodes as $node) { if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) { continue; } // can it be handled by an extension? if (!$this->container->hasExtension($node->namespaceURI)) { $extensionNamespaces = \array_filter(\array_map(function (ExtensionInterface $ext) { return $ext->getNamespace(); }, $this->container->getExtensions())); throw new InvalidArgumentException(\sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? \implode('", "', $extensionNamespaces) : 'none')); } } } /** * Loads from an extension. */ private function loadFromExtensions(\DOMDocument $xml) { foreach ($xml->documentElement->childNodes as $node) { if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) { continue; } $values = static::convertDomElementToArray($node); if (!\is_array($values)) { $values = []; } $this->container->loadFromExtension($node->namespaceURI, $values); } } /** * Converts a \DOMElement object to a PHP array. * * The following rules applies during the conversion: * * * Each tag is converted to a key value or an array * if there is more than one "value" * * * The content of a tag is set under a "value" key (bar) * if the tag also has some nested tags * * * The attributes are converted to keys () * * * The nested-tags are converted to keys (bar) * * @param \DOMElement $element A \DOMElement instance * * @return mixed */ public static function convertDomElementToArray(\DOMElement $element) { return XmlUtils::convertDomElementToArray($element); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; use _ContaoManager\Symfony\Component\Config\Util\XmlUtils; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * IniFileLoader loads parameters from INI files. * * @author Fabien Potencier */ class IniFileLoader extends FileLoader { /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { $path = $this->locator->locate($resource); $this->container->fileExists($path); // first pass to catch parsing errors $result = \parse_ini_file($path, \true); if (\false === $result || [] === $result) { throw new InvalidArgumentException(\sprintf('The "%s" file is not valid.', $resource)); } // real raw parsing $result = \parse_ini_file($path, \true, \INI_SCANNER_RAW); if (isset($result['parameters']) && \is_array($result['parameters'])) { foreach ($result['parameters'] as $key => $value) { $this->container->setParameter($key, $this->phpize($value)); } } if ($this->env && \is_array($result['parameters@' . $this->env] ?? null)) { foreach ($result['parameters@' . $this->env] as $key => $value) { $this->container->setParameter($key, $this->phpize($value)); } } return null; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { if (!\is_string($resource)) { return \false; } if (null === $type && 'ini' === \pathinfo($resource, \PATHINFO_EXTENSION)) { return \true; } return 'ini' === $type; } /** * Note that the following features are not supported: * * strings with escaped quotes are not supported "foo\"bar"; * * string concatenation ("foo" "bar"). * * @return mixed */ private function phpize(string $value) { // trim on the right as comments removal keep whitespaces if ($value !== ($v = \rtrim($value))) { $value = '""' === \substr_replace($v, '', 1, -1) ? \substr($v, 1, -1) : $v; } $lowercaseValue = \strtolower($value); switch (\true) { case \defined($value): return \constant($value); case 'yes' === $lowercaseValue || 'on' === $lowercaseValue: return \true; case 'no' === $lowercaseValue || 'off' === $lowercaseValue || 'none' === $lowercaseValue: return \false; case isset($value[1]) && ("'" === $value[0] && "'" === $value[\strlen($value) - 1] || '"' === $value[0] && '"' === $value[\strlen($value) - 1]): // quoted string return \substr($value, 1, -1); default: return XmlUtils::phpize($value); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; /** * @author Nicolas Grekas */ class ServicesConfigurator extends AbstractConfigurator { public const FACTORY = 'services'; private $defaults; private $container; private $loader; private $instanceof; private $path; private $anonymousHash; private $anonymousCount; public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, ?string $path = null, int &$anonymousCount = 0) { $this->defaults = new Definition(); $this->container = $container; $this->loader = $loader; $this->instanceof =& $instanceof; $this->path = $path; $this->anonymousHash = ContainerBuilder::hash($path ?: \mt_rand()); $this->anonymousCount =& $anonymousCount; $instanceof = []; } /** * Defines a set of defaults for following service definitions. */ public final function defaults() : DefaultsConfigurator { return new DefaultsConfigurator($this, $this->defaults = new Definition(), $this->path); } /** * Defines an instanceof-conditional to be applied to following service definitions. */ public final function instanceof(string $fqcn) : InstanceofConfigurator { $this->instanceof[$fqcn] = $definition = new ChildDefinition(''); return new InstanceofConfigurator($this, $definition, $fqcn, $this->path); } /** * Registers a service. * * @param string|null $id The service id, or null to create an anonymous service * @param string|null $class The class of the service, or null when $id is also the class name */ public final function set(?string $id, ?string $class = null) : ServiceConfigurator { $defaults = $this->defaults; $definition = new Definition(); if (null === $id) { if (!$class) { throw new \LogicException('Anonymous services must have a class name.'); } $id = \sprintf('.%d_%s', ++$this->anonymousCount, \preg_replace('/^.*\\\\/', '', $class) . '~' . $this->anonymousHash); } elseif (!$defaults->isPublic() || !$defaults->isPrivate()) { $definition->setPublic($defaults->isPublic() && !$defaults->isPrivate()); } $definition->setAutowired($defaults->isAutowired()); $definition->setAutoconfigured($defaults->isAutoconfigured()); // deep clone, to avoid multiple process of the same instance in the passes $definition->setBindings(\unserialize(\serialize($defaults->getBindings()))); $definition->setChanges([]); $configurator = new ServiceConfigurator($this->container, $this->instanceof, \true, $this, $definition, $id, $defaults->getTags(), $this->path); return null !== $class ? $configurator->class($class) : $configurator; } /** * Removes an already defined service definition or alias. * * @return $this */ public final function remove(string $id) : self { $this->container->removeDefinition($id); $this->container->removeAlias($id); return $this; } /** * Creates an alias. */ public final function alias(string $id, string $referencedId) : AliasConfigurator { $ref = static::processValue($referencedId, \true); $alias = new Alias((string) $ref); if (!$this->defaults->isPublic() || !$this->defaults->isPrivate()) { $alias->setPublic($this->defaults->isPublic()); } $this->container->setAlias($id, $alias); return new AliasConfigurator($this, $alias); } /** * Registers a PSR-4 namespace using a glob pattern. */ public final function load(string $namespace, string $resource) : PrototypeConfigurator { return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, \true); } /** * Gets an already defined service definition. * * @throws ServiceNotFoundException if the service definition does not exist */ public final function get(string $id) : ServiceConfigurator { $definition = $this->container->getDefinition($id); return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), \true, $this, $definition, $id, []); } /** * Registers a stack of decorator services. * * @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services */ public final function stack(string $id, array $services) : AliasConfigurator { foreach ($services as $i => $service) { if ($service instanceof InlineServiceConfigurator) { $definition = $service->definition->setInstanceofConditionals($this->instanceof); $changes = $definition->getChanges(); $definition->setAutowired((isset($changes['autowired']) ? $definition : $this->defaults)->isAutowired()); $definition->setAutoconfigured((isset($changes['autoconfigured']) ? $definition : $this->defaults)->isAutoconfigured()); $definition->setBindings(\array_merge($this->defaults->getBindings(), $definition->getBindings())); $definition->setChanges($changes); $services[$i] = $definition; } elseif (!$service instanceof ReferenceConfigurator) { throw new InvalidArgumentException(\sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY . '()' : \get_debug_type($service), $i, $id)); } } $alias = $this->alias($id, ''); $alias->definition = $this->set($id)->parent('')->args($services)->tag('container.stack')->definition; return $alias; } /** * Registers a service. */ public final function __invoke(string $id, ?string $class = null) : ServiceConfigurator { return $this->set($id, $class); } public function __destruct() { $this->loader->registerAliasesForSinglyImplementedInterfaces(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * @author Nicolas Grekas */ class DefaultsConfigurator extends AbstractServiceConfigurator { use Traits\AutoconfigureTrait; use Traits\AutowireTrait; use Traits\BindTrait; use Traits\PublicTrait; public const FACTORY = 'defaults'; private $path; public function __construct(ServicesConfigurator $parent, Definition $definition, ?string $path = null) { parent::__construct($parent, $definition, null, []); $this->path = $path; } /** * Adds a tag for this definition. * * @return $this * * @throws InvalidArgumentException when an invalid tag name or attribute is provided */ public final function tag(string $name, array $attributes = []) : self { if ('' === $name) { throw new InvalidArgumentException('The tag name in "_defaults" must be a non-empty string.'); } foreach ($attributes as $attribute => $value) { if (null !== $value && !\is_scalar($value)) { throw new InvalidArgumentException(\sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute)); } } $this->definition->addTag($name, $attributes); return $this; } /** * Defines an instanceof-conditional to be applied to following service definitions. */ public final function instanceof(string $fqcn) : InstanceofConfigurator { return $this->parent->instanceof($fqcn); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; /** * @author Nicolas Grekas */ class ReferenceConfigurator extends AbstractConfigurator { /** @internal */ protected $id; /** @internal */ protected $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; public function __construct(string $id) { $this->id = $id; } /** * @return $this */ public final function ignoreOnInvalid() : self { $this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; return $this; } /** * @return $this */ public final function nullOnInvalid() : self { $this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; return $this; } /** * @return $this */ public final function ignoreOnUninitialized() : self { $this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; return $this; } /** * @return string */ public function __toString() { return $this->id; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait ClassTrait { /** * Sets the service class. * * @return $this */ public final function class(?string $class) : self { $this->definition->setClass($class); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait CallTrait { /** * Adds a method to call after service initialization. * * @param string $method The method name to call * @param array $arguments An array of arguments to pass to the method call * @param bool $returnsClone Whether the call returns the service instance or not * * @return $this * * @throws InvalidArgumentException on empty $method param */ public final function call(string $method, array $arguments = [], bool $returnsClone = \false) : self { $this->definition->addMethodCall($method, static::processValue($arguments, \true), $returnsClone); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait SyntheticTrait { /** * Sets whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. * * @return $this */ public final function synthetic(bool $synthetic = \true) : self { $this->definition->setSynthetic($synthetic); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait PropertyTrait { /** * Sets a specific property. * * @return $this */ public final function property(string $name, $value) : self { $this->definition->setProperty($name, static::processValue($value, \true)); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait ArgumentTrait { /** * Sets the arguments to pass to the service constructor/factory method. * * @return $this */ public final function args(array $arguments) : self { $this->definition->setArguments(static::processValue($arguments, \true)); return $this; } /** * Sets one argument to pass to the service constructor/factory method. * * @param string|int $key * @param mixed $value * * @return $this */ public final function arg($key, $value) : self { $this->definition->setArgument($key, static::processValue($value, \true)); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait ConfiguratorTrait { /** * Sets a configurator to call after the service is fully initialized. * * @param string|array $configurator A PHP callable reference * * @return $this */ public final function configurator($configurator) : self { $this->definition->setConfigurator(static::processValue($configurator, \true)); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait ParentTrait { /** * Sets the Definition to inherit from. * * @return $this * * @throws InvalidArgumentException when parent cannot be set */ public final function parent(string $parent) : self { if (!$this->allowParent) { throw new InvalidArgumentException(\sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id)); } if ($this->definition instanceof ChildDefinition) { $this->definition->setParent($parent); } else { // cast Definition to ChildDefinition $definition = \serialize($this->definition); $definition = \substr_replace($definition, '68', 2, 2); $definition = \substr_replace($definition, 'Child', 59, 0); $definition = \unserialize($definition); $this->definition = $definition->setParent($parent); } return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; trait FactoryTrait { /** * Sets a factory. * * @param string|array|ReferenceConfigurator $factory A PHP callable reference * * @return $this */ public final function factory($factory) : self { if (\is_string($factory) && 1 === \substr_count($factory, ':')) { $factoryParts = \explode(':', $factory); throw new InvalidArgumentException(\sprintf('Invalid factory "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1])); } $this->definition->setFactory(static::processValue($factory, \true)); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait TagTrait { /** * Adds a tag for this definition. * * @return $this */ public final function tag(string $name, array $attributes = []) : self { if ('' === $name) { throw new InvalidArgumentException(\sprintf('The tag name for service "%s" must be a non-empty string.', $this->id)); } foreach ($attributes as $attribute => $value) { if (!\is_scalar($value) && null !== $value) { throw new InvalidArgumentException(\sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute)); } } $this->definition->addTag($name, $attributes); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait PublicTrait { /** * @return $this */ public final function public() : self { $this->definition->setPublic(\true); return $this; } /** * @return $this */ public final function private() : self { $this->definition->setPublic(\false); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait DecorateTrait { /** * Sets the service that this service is decorating. * * @param string|null $id The decorated service id, use null to remove decoration * * @return $this * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ public final function decorate(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) : self { $this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait AutoconfigureTrait { /** * Sets whether or not instanceof conditionals should be prepended with a global set. * * @return $this * * @throws InvalidArgumentException when a parent is already set */ public final function autoconfigure(bool $autoconfigured = \true) : self { $this->definition->setAutoconfigured($autoconfigured); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait DeprecateTrait { /** * Whether this definition is deprecated, that means it should not be called anymore. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ public final function deprecate() : self { $args = \func_get_args(); $package = $version = $message = ''; if (\func_num_args() < 3) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); $message = (string) ($args[0] ?? null); } else { $package = (string) $args[0]; $version = (string) $args[1]; $message = (string) $args[2]; } $this->definition->setDeprecated($package, $version, $message); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\DefaultsConfigurator; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator; trait BindTrait { /** * Sets bindings. * * Bindings map $named or FQCN arguments to values that should be * injected in the matching parameters (of the constructor, of methods * called and of controller actions). * * @param string $nameOrFqcn A parameter name with its "$" prefix, or an FQCN * @param mixed $valueOrRef The value or reference to bind * * @return $this */ public final function bind(string $nameOrFqcn, $valueOrRef) : self { $valueOrRef = static::processValue($valueOrRef, \true); $bindings = $this->definition->getBindings(); $type = $this instanceof DefaultsConfigurator ? BoundArgument::DEFAULTS_BINDING : ($this instanceof InstanceofConfigurator ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING); $bindings[$nameOrFqcn] = new BoundArgument($valueOrRef, \true, $type, $this->path ?? null); $this->definition->setBindings($bindings); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait LazyTrait { /** * Sets the lazy flag of this service. * * @param bool|string $lazy A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class * * @return $this */ public final function lazy($lazy = \true) : self { $this->definition->setLazy((bool) $lazy); if (\is_string($lazy)) { $this->definition->addTag('proxy', ['interface' => $lazy]); } return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait FileTrait { /** * Sets a file to require before creating the service. * * @return $this */ public final function file(string $file) : self { $this->definition->setFile($file); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait AbstractTrait { /** * Whether this definition is abstract, that means it merely serves as a * template for other definitions. * * @return $this */ public final function abstract(bool $abstract = \true) : self { $this->definition->setAbstract($abstract); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait ShareTrait { /** * Sets if the service must be shared or not. * * @return $this */ public final function share(bool $shared = \true) : self { $this->definition->setShared($shared); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator\Traits; trait AutowireTrait { /** * Enables/disables autowiring. * * @return $this */ public final function autowire(bool $autowired = \true) : self { $this->definition->setAutowired($autowired); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Config\Loader\ParamConfigurator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; abstract class AbstractConfigurator { public const FACTORY = 'unknown'; /** * @var callable(mixed, bool)|null */ public static $valuePreProcessor; /** @internal */ protected $definition; public function __call(string $method, array $args) { if (\method_exists($this, 'set' . $method)) { return $this->{'set' . $method}(...$args); } throw new \BadMethodCallException(\sprintf('Call to undefined method "%s::%s()".', static::class, $method)); } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } /** * Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value. * * @param mixed $value * @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars and arrays are * * @return mixed the value, optionally cast to a Definition/Reference */ public static function processValue($value, $allowServices = \false) { if (\is_array($value)) { foreach ($value as $k => $v) { $value[$k] = static::processValue($v, $allowServices); } return self::$valuePreProcessor ? (self::$valuePreProcessor)($value, $allowServices) : $value; } if (self::$valuePreProcessor) { $value = (self::$valuePreProcessor)($value, $allowServices); } if ($value instanceof ReferenceConfigurator) { $reference = new Reference($value->id, $value->invalidBehavior); return $value instanceof ClosureReferenceConfigurator ? new ServiceClosureArgument($reference) : $reference; } if ($value instanceof InlineServiceConfigurator) { $def = $value->definition; $value->definition = null; return $def; } if ($value instanceof ParamConfigurator) { return (string) $value; } if ($value instanceof self) { throw new InvalidArgumentException(\sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY)); } switch (\true) { case null === $value: case \is_scalar($value): return $value; case $value instanceof ArgumentInterface: case $value instanceof Definition: case $value instanceof Expression: case $value instanceof Parameter: case $value instanceof AbstractArgument: case $value instanceof Reference: if ($allowServices) { return $value; } } throw new InvalidArgumentException(\sprintf('Cannot use values of type "%s" in service configuration files.', \get_debug_type($value))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; class ClosureReferenceConfigurator extends ReferenceConfigurator { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Config\Loader\ParamConfigurator; class EnvConfigurator extends ParamConfigurator { /** * @var string[] */ private $stack; public function __construct(string $name) { $this->stack = \explode(':', $name); } public function __toString() : string { return '%env(' . \implode(':', $this->stack) . ')%'; } /** * @return $this */ public function __call(string $name, array $arguments) : self { $processor = \strtolower(\preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\\d])([A-Z])/'], '_ContaoManager\\1_\\2', $name)); $this->custom($processor, ...$arguments); return $this; } /** * @return $this */ public function custom(string $processor, ...$args) : self { \array_unshift($this->stack, $processor, ...$args); return $this; } /** * @return $this */ public function base64() : self { \array_unshift($this->stack, 'base64'); return $this; } /** * @return $this */ public function bool() : self { \array_unshift($this->stack, 'bool'); return $this; } /** * @return $this */ public function not() : self { \array_unshift($this->stack, 'not'); return $this; } /** * @return $this */ public function const() : self { \array_unshift($this->stack, 'const'); return $this; } /** * @return $this */ public function csv() : self { \array_unshift($this->stack, 'csv'); return $this; } /** * @return $this */ public function file() : self { \array_unshift($this->stack, 'file'); return $this; } /** * @return $this */ public function float() : self { \array_unshift($this->stack, 'float'); return $this; } /** * @return $this */ public function int() : self { \array_unshift($this->stack, 'int'); return $this; } /** * @return $this */ public function json() : self { \array_unshift($this->stack, 'json'); return $this; } /** * @return $this */ public function key(string $key) : self { \array_unshift($this->stack, 'key', $key); return $this; } /** * @return $this */ public function url() : self { \array_unshift($this->stack, 'url'); return $this; } /** * @return $this */ public function queryString() : self { \array_unshift($this->stack, 'query_string'); return $this; } /** * @return $this */ public function resolve() : self { \array_unshift($this->stack, 'resolve'); return $this; } /** * @return $this */ public function default(string $fallbackParam) : self { \array_unshift($this->stack, 'default', $fallbackParam); return $this; } /** * @return $this */ public function string() : self { \array_unshift($this->stack, 'string'); return $this; } /** * @return $this */ public function trim() : self { \array_unshift($this->stack, 'trim'); return $this; } /** * @return $this */ public function require() : self { \array_unshift($this->stack, 'require'); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; abstract class AbstractServiceConfigurator extends AbstractConfigurator { protected $parent; protected $id; private $defaultTags = []; public function __construct(ServicesConfigurator $parent, Definition $definition, ?string $id = null, array $defaultTags = []) { $this->parent = $parent; $this->definition = $definition; $this->id = $id; $this->defaultTags = $defaultTags; } public function __destruct() { // default tags should be added last foreach ($this->defaultTags as $name => $attributes) { foreach ($attributes as $attribute) { $this->definition->addTag($name, $attribute); } } $this->defaultTags = []; } /** * Registers a service. */ public final function set(?string $id, ?string $class = null) : ServiceConfigurator { $this->__destruct(); return $this->parent->set($id, $class); } /** * Creates an alias. */ public final function alias(string $id, string $referencedId) : AliasConfigurator { $this->__destruct(); return $this->parent->alias($id, $referencedId); } /** * Registers a PSR-4 namespace using a glob pattern. */ public final function load(string $namespace, string $resource) : PrototypeConfigurator { $this->__destruct(); return $this->parent->load($namespace, $resource); } /** * Gets an already defined service definition. * * @throws ServiceNotFoundException if the service definition does not exist */ public final function get(string $id) : ServiceConfigurator { $this->__destruct(); return $this->parent->get($id); } /** * Removes an already defined service definition or alias. */ public final function remove(string $id) : ServicesConfigurator { $this->__destruct(); return $this->parent->remove($id); } /** * Registers a stack of decorator services. * * @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services */ public final function stack(string $id, array $services) : AliasConfigurator { $this->__destruct(); return $this->parent->stack($id, $services); } /** * Registers a service. */ public final function __invoke(string $id, ?string $class = null) : ServiceConfigurator { $this->__destruct(); return $this->parent->set($id, $class); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * @author Nicolas Grekas */ class InstanceofConfigurator extends AbstractServiceConfigurator { use Traits\AutowireTrait; use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; use Traits\LazyTrait; use Traits\PropertyTrait; use Traits\PublicTrait; use Traits\ShareTrait; use Traits\TagTrait; public const FACTORY = 'instanceof'; private $path; public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, ?string $path = null) { parent::__construct($parent, $definition, $id, []); $this->path = $path; } /** * Defines an instanceof-conditional to be applied to following service definitions. */ public final function instanceof(string $fqcn) : self { return $this->parent->instanceof($fqcn); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\Config\Loader\ParamConfigurator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; /** * @author Nicolas Grekas */ class ContainerConfigurator extends AbstractConfigurator { public const FACTORY = 'container'; private $container; private $loader; private $instanceof; private $path; private $file; private $anonymousCount = 0; private $env; public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, ?string $env = null) { $this->container = $container; $this->loader = $loader; $this->instanceof =& $instanceof; $this->path = $path; $this->file = $file; $this->env = $env; } public final function extension(string $namespace, array $config) { if (!$this->container->hasExtension($namespace)) { $extensions = \array_filter(\array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); throw new InvalidArgumentException(\sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $this->file, $namespace, $extensions ? \implode('", "', $extensions) : 'none')); } $this->container->loadFromExtension($namespace, static::processValue($config)); } public final function import(string $resource, ?string $type = null, $ignoreErrors = \false) { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); } public final function parameters() : ParametersConfigurator { return new ParametersConfigurator($this->container); } public final function services() : ServicesConfigurator { return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount); } /** * Get the current environment to be able to write conditional configuration. */ public final function env() : ?string { return $this->env; } /** * @return static */ public final function withPath(string $path) : self { $clone = clone $this; $clone->path = $clone->file = $path; $clone->loader->setCurrentDir(\dirname($path)); return $clone; } } /** * Creates a parameter. */ function param(string $name) : ParamConfigurator { return new ParamConfigurator($name); } /** * Creates a service reference. * * @deprecated since Symfony 5.1, use service() instead. */ function ref(string $id) : ReferenceConfigurator { \trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "service()" instead.', __FUNCTION__); return new ReferenceConfigurator($id); } /** * Creates a reference to a service. */ function service(string $serviceId) : ReferenceConfigurator { return new ReferenceConfigurator($serviceId); } /** * Creates an inline service. * * @deprecated since Symfony 5.1, use inline_service() instead. */ function inline(?string $class = null) : InlineServiceConfigurator { \trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "inline_service()" instead.', __FUNCTION__); return new InlineServiceConfigurator(new Definition($class)); } /** * Creates an inline service. */ function inline_service(?string $class = null) : InlineServiceConfigurator { return new InlineServiceConfigurator(new Definition($class)); } /** * Creates a service locator. * * @param ReferenceConfigurator[] $values */ function service_locator(array $values) : ServiceLocatorArgument { return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, \true)); } /** * Creates a lazy iterator. * * @param ReferenceConfigurator[] $values */ function iterator(array $values) : IteratorArgument { return new IteratorArgument(AbstractConfigurator::processValue($values, \true)); } /** * Creates a lazy iterator by tag name. */ function tagged_iterator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null) : TaggedIteratorArgument { return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, \false, $defaultPriorityMethod); } /** * Creates a service locator by tag name. */ function tagged_locator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null) : ServiceLocatorArgument { return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, \true, $defaultPriorityMethod)); } /** * Creates an expression. */ function expr(string $expression) : Expression { return new Expression($expression); } /** * Creates an abstract argument. */ function abstract_arg(string $description) : AbstractArgument { return new AbstractArgument($description); } /** * Creates an environment variable reference. */ function env(string $name) : EnvConfigurator { return new EnvConfigurator($name); } /** * Creates a closure service reference. */ function service_closure(string $serviceId) : ClosureReferenceConfigurator { return new ClosureReferenceConfigurator($serviceId); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; /** * @author Nicolas Grekas */ class AliasConfigurator extends AbstractServiceConfigurator { use Traits\DeprecateTrait; use Traits\PublicTrait; public const FACTORY = 'alias'; public function __construct(ServicesConfigurator $parent, Alias $alias) { $this->parent = $parent; $this->definition = $alias; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * @author Nicolas Grekas */ class InlineServiceConfigurator extends AbstractConfigurator { use Traits\ArgumentTrait; use Traits\AutowireTrait; use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; use Traits\FactoryTrait; use Traits\FileTrait; use Traits\LazyTrait; use Traits\ParentTrait; use Traits\PropertyTrait; use Traits\TagTrait; public const FACTORY = 'service'; private $id = '[inline]'; private $allowParent = \true; private $path = null; public function __construct(Definition $definition) { $this->definition = $definition; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\PhpFileLoader; /** * @author Nicolas Grekas */ class PrototypeConfigurator extends AbstractServiceConfigurator { use Traits\AbstractTrait; use Traits\ArgumentTrait; use Traits\AutoconfigureTrait; use Traits\AutowireTrait; use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; use Traits\DeprecateTrait; use Traits\FactoryTrait; use Traits\LazyTrait; use Traits\ParentTrait; use Traits\PropertyTrait; use Traits\PublicTrait; use Traits\ShareTrait; use Traits\TagTrait; public const FACTORY = 'load'; private $loader; private $resource; private $excludes; private $allowParent; public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent) { $definition = new Definition(); if (!$defaults->isPublic() || !$defaults->isPrivate()) { $definition->setPublic($defaults->isPublic()); } $definition->setAutowired($defaults->isAutowired()); $definition->setAutoconfigured($defaults->isAutoconfigured()); // deep clone, to avoid multiple process of the same instance in the passes $definition->setBindings(\unserialize(\serialize($defaults->getBindings()))); $definition->setChanges([]); $this->loader = $loader; $this->resource = $resource; $this->allowParent = $allowParent; parent::__construct($parent, $definition, $namespace, $defaults->getTags()); } public function __destruct() { parent::__destruct(); if ($this->loader) { $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes); } $this->loader = null; } /** * Excludes files from registration using glob patterns. * * @param string[]|string $excludes * * @return $this */ public final function exclude($excludes) : self { $this->excludes = (array) $excludes; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; /** * @author Nicolas Grekas */ class ParametersConfigurator extends AbstractConfigurator { public const FACTORY = 'parameters'; private $container; public function __construct(ContainerBuilder $container) { $this->container = $container; } /** * Creates a parameter. * * @return $this */ public final function set(string $name, $value) : self { if ($value instanceof Expression) { throw new InvalidArgumentException(\sprintf('Using an expression in parameter "%s" is not allowed.', $name)); } $this->container->setParameter($name, static::processValue($value, \true)); return $this; } /** * Creates a parameter. * * @return $this */ public final function __invoke(string $name, $value) : self { return $this->set($name, $value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader\Configurator; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * @author Nicolas Grekas */ class ServiceConfigurator extends AbstractServiceConfigurator { use Traits\AbstractTrait; use Traits\ArgumentTrait; use Traits\AutoconfigureTrait; use Traits\AutowireTrait; use Traits\BindTrait; use Traits\CallTrait; use Traits\ClassTrait; use Traits\ConfiguratorTrait; use Traits\DecorateTrait; use Traits\DeprecateTrait; use Traits\FactoryTrait; use Traits\FileTrait; use Traits\LazyTrait; use Traits\ParentTrait; use Traits\PropertyTrait; use Traits\PublicTrait; use Traits\ShareTrait; use Traits\SyntheticTrait; use Traits\TagTrait; public const FACTORY = 'services'; private $container; private $instanceof; private $allowParent; private $path; private $destructed = \false; public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, ?string $id, array $defaultTags, ?string $path = null) { $this->container = $container; $this->instanceof = $instanceof; $this->allowParent = $allowParent; $this->path = $path; parent::__construct($parent, $definition, $id, $defaultTags); } public function __destruct() { if ($this->destructed) { return; } $this->destructed = \true; parent::__destruct(); $this->container->removeBindings($this->id); $this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; /** * DirectoryLoader is a recursive loader to go through directories. * * @author Sebastien Lavoie */ class DirectoryLoader extends FileLoader { /** * {@inheritdoc} */ public function load($file, ?string $type = null) { $file = \rtrim($file, '/'); $path = $this->locator->locate($file); $this->container->fileExists($path, \false); foreach (\scandir($path) as $dir) { if ('.' !== $dir[0]) { if (\is_dir($path . '/' . $dir)) { $dir .= '/'; // append / to allow recursion } $this->setCurrentDir($path); $this->import($dir, null, \false, $path); } } return null; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { if ('directory' === $type) { return \true; } return null === $type && \is_string($resource) && \str_ends_with($resource, '/'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; /** * GlobFileLoader loads files from a glob pattern. * * @author Nicolas Grekas */ class GlobFileLoader extends FileLoader { /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { foreach ($this->glob($resource, \false, $globResource) as $path => $info) { $this->import($path); } $this->container->addResource($globResource); return null; } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return 'glob' === $type; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Loader; use _ContaoManager\Symfony\Component\Config\Loader\Loader; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * ClosureLoader loads service definitions from a PHP closure. * * The Closure has access to the container as its first argument. * * @author Fabien Potencier */ class ClosureLoader extends Loader { private $container; public function __construct(ContainerBuilder $container, ?string $env = null) { $this->container = $container; parent::__construct($env); } /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { return $resource($this->container, $this->env); } /** * {@inheritdoc} */ public function supports($resource, ?string $type = null) { return $resource instanceof \Closure; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy; /** * @author Nicolas Grekas * * @internal */ class ProxyHelper { /** * @return string|null The FQCN or builtin name of the type hint, or null when the type hint references an invalid self|parent context */ public static function getTypeHint(\ReflectionFunctionAbstract $r, ?\ReflectionParameter $p = null, bool $noBuiltin = \false) : ?string { if ($p instanceof \ReflectionParameter) { $type = $p->getType(); } else { $type = $r->getReturnType(); } if (!$type) { return null; } return self::getTypeHintForType($type, $r, $noBuiltin); } private static function getTypeHintForType(\ReflectionType $type, \ReflectionFunctionAbstract $r, bool $noBuiltin) : ?string { $types = []; $glue = '|'; if ($type instanceof \ReflectionUnionType) { $reflectionTypes = $type->getTypes(); } elseif ($type instanceof \ReflectionIntersectionType) { $reflectionTypes = $type->getTypes(); $glue = '&'; } elseif ($type instanceof \ReflectionNamedType) { $reflectionTypes = [$type]; } else { return null; } foreach ($reflectionTypes as $type) { if ($type instanceof \ReflectionIntersectionType) { $typeHint = self::getTypeHintForType($type, $r, $noBuiltin); if (null === $typeHint) { return null; } $types[] = \sprintf('(%s)', $typeHint); continue; } if ($type->isBuiltin()) { if (!$noBuiltin) { $types[] = $type->getName(); } continue; } $lcName = \strtolower($type->getName()); $prefix = $noBuiltin ? '' : '\\'; if ('self' !== $lcName && 'parent' !== $lcName) { $types[] = $prefix . $type->getName(); continue; } if (!$r instanceof \ReflectionMethod) { continue; } if ('self' === $lcName) { $types[] = $prefix . $r->getDeclaringClass()->name; } else { $types[] = ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix . $parent->name : null; } } \sort($types); return $types ? \implode($glue, $types) : null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * Lazy proxy dumper capable of generating the instantiation logic PHP code for proxied services. * * @author Marco Pivetta */ interface DumperInterface { /** * Inspects whether the given definitions should produce proxy instantiation logic in the dumped container. * * @return bool */ public function isProxyCandidate(Definition $definition); /** * Generates the code to be used to instantiate a proxy in the dumped factory code. * * @return string */ public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode); /** * Generates the code for the lazy proxy. * * @return string */ public function getProxyCode(Definition $definition); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * Null dumper, negates any proxy code generation for any given service definition. * * @author Marco Pivetta * * @final */ class NullDumper implements DumperInterface { /** * {@inheritdoc} */ public function isProxyCandidate(Definition $definition) : bool { return \false; } /** * {@inheritdoc} */ public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode) : string { return ''; } /** * {@inheritdoc} */ public function getProxyCode(Definition $definition) : string { return ''; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\Instantiator; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * {@inheritdoc} * * Noop proxy instantiator - produces the real service instead of a proxy instance. * * @author Marco Pivetta */ class RealServiceInstantiator implements InstantiatorInterface { /** * {@inheritdoc} */ public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator) { return $realInstantiator(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\Instantiator; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * Lazy proxy instantiator, capable of instantiating a proxy given a container, the * service definitions and a callback that produces the real service instance. * * @author Marco Pivetta */ interface InstantiatorInterface { /** * Instantiates a proxy object. * * @param string $id Identifier of the requested service * @param callable $realInstantiator Zero-argument callback that is capable of producing the real service instance * * @return object */ public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Psr\Cache\CacheItemPoolInterface; use _ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; if (!\class_exists(BaseExpressionLanguage::class)) { return; } /** * Adds some function to the default ExpressionLanguage. * * @author Fabien Potencier * * @see ExpressionLanguageProvider */ class ExpressionLanguage extends BaseExpressionLanguage { /** * {@inheritdoc} */ public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [], ?callable $serviceCompiler = null) { // prepend the default provider to let users override it easily \array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler)); parent::__construct($cache, $providers); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Psr\Container\ContainerExceptionInterface; use _ContaoManager\Psr\Container\NotFoundExceptionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Contracts\Service\ServiceLocatorTrait; use _ContaoManager\Symfony\Contracts\Service\ServiceProviderInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; /** * @author Robin Chalas * @author Nicolas Grekas */ class ServiceLocator implements ServiceProviderInterface { use ServiceLocatorTrait { get as private doGet; } private $externalId; private $container; /** * {@inheritdoc} * * @return mixed */ public function get(string $id) { if (!$this->externalId) { return $this->doGet($id); } try { return $this->doGet($id); } catch (RuntimeException $e) { $what = \sprintf('service "%s" required by "%s"', $id, $this->externalId); $message = \preg_replace('/service "\\.service_locator\\.[^"]++"/', $what, $e->getMessage()); if ($e->getMessage() === $message) { $message = \sprintf('Cannot resolve %s: %s', $what, $message); } $r = new \ReflectionProperty($e, 'message'); $r->setAccessible(\true); $r->setValue($e, $message); throw $e; } } public function __invoke(string $id) { return isset($this->factories[$id]) ? $this->get($id) : null; } /** * @internal * * @return static */ public function withContext(string $externalId, Container $container) : self { $locator = clone $this; $locator->externalId = $externalId; $locator->container = $container; return $locator; } private function createNotFoundException(string $id) : NotFoundExceptionInterface { if ($this->loading) { $msg = \sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', \end($this->loading), $id, $this->formatAlternatives()); return new ServiceNotFoundException($id, \end($this->loading) ?: null, null, [], $msg); } $class = \debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4); $class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null; $externalId = $this->externalId ?: $class; $msg = []; $msg[] = \sprintf('Service "%s" not found:', $id); if (!$this->container) { $class = null; } elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) { $msg[] = 'even though it exists in the app\'s container,'; } else { try { $this->container->get($id); $class = null; } catch (ServiceNotFoundException $e) { if ($e->getAlternatives()) { $msg[] = \sprintf('did you mean %s? Anyway,', $this->formatAlternatives($e->getAlternatives(), 'or')); } else { $class = null; } } } if ($externalId) { $msg[] = \sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives()); } else { $msg[] = \sprintf('the current service locator %s', $this->formatAlternatives()); } if (!$class) { // no-op } elseif (\is_subclass_of($class, ServiceSubscriberInterface::class)) { $msg[] = \sprintf('Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', \preg_replace('/([^\\\\]++\\\\)++/', '', $class)); } else { $msg[] = 'Try using dependency injection instead.'; } return new ServiceNotFoundException($id, \end($this->loading) ?: null, null, [], \implode(' ', $msg)); } private function createCircularReferenceException(string $id, array $path) : ContainerExceptionInterface { return new ServiceCircularReferenceException($id, $path); } private function formatAlternatives(?array $alternatives = null, string $separator = 'and') : string { $format = '"%s"%s'; if (null === $alternatives) { if (!($alternatives = \array_keys($this->factories))) { return 'is empty...'; } $format = \sprintf('only knows about the %s service%s.', $format, 1 < \count($alternatives) ? 's' : ''); } $last = \array_pop($alternatives); return \sprintf($format, $alternatives ? \implode('", "', $alternatives) : $last, $alternatives ? \sprintf(' %s "%s"', $separator, $last) : ''); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * Represents a PHP type-hinted service reference. * * @author Nicolas Grekas */ class TypedReference extends Reference { private $type; private $name; /** * @param string $id The service identifier * @param string $type The PHP type of the identified service * @param int $invalidBehavior The behavior when the service does not exist * @param string|null $name The name of the argument targeting the service */ public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, ?string $name = null) { $this->name = $type === $id ? $name : null; parent::__construct($id, $invalidBehavior); $this->type = $type; } public function getType() { return $this->type; } public function getName() : ?string { return $this->name; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; class Alias { private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%alias_id%" service alias is deprecated. You should stop using it, as it will be removed in the future.'; private $id; private $public; private $deprecation = []; public function __construct(string $id, bool $public = \false) { $this->id = $id; $this->public = $public; } /** * Checks if this DI Alias should be public or not. * * @return bool */ public function isPublic() { return $this->public; } /** * Sets if this Alias is public. * * @return $this */ public function setPublic(bool $boolean) { $this->public = $boolean; return $this; } /** * Sets if this Alias is private. * * @return $this * * @deprecated since Symfony 5.2, use setPublic() instead */ public function setPrivate(bool $boolean) { \trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__); return $this->setPublic(!$boolean); } /** * Whether this alias is private. * * @return bool */ public function isPrivate() { return !$this->public; } /** * Whether this alias is deprecated, that means it should not be referenced * anymore. * * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ public function setDeprecated() { $args = \func_get_args(); if (\func_num_args() < 3) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); $status = $args[0] ?? \true; if (!$status) { \trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); } $message = (string) ($args[1] ?? null); $package = $version = ''; } else { $status = \true; $package = (string) $args[0]; $version = (string) $args[1]; $message = (string) $args[2]; } if ('' !== $message) { if (\preg_match('#[\\r\\n]|\\*/#', $message)) { throw new InvalidArgumentException('Invalid characters found in deprecation template.'); } if (!\str_contains($message, '%alias_id%')) { throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.'); } } $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE] : []; return $this; } public function isDeprecated() : bool { return (bool) $this->deprecation; } /** * @deprecated since Symfony 5.1, use "getDeprecation()" instead. */ public function getDeprecationMessage(string $id) : string { \trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); return $this->getDeprecation($id)['message']; } /** * @param string $id Service id relying on this definition */ public function getDeprecation(string $id) : array { return ['package' => $this->deprecation['package'], 'version' => $this->deprecation['version'], 'message' => \str_replace('%alias_id%', $id, $this->deprecation['message'])]; } /** * Returns the Id of this alias. * * @return string */ public function __toString() { return $this->id; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * This exception is thrown when a circular reference in a parameter is detected. * * @author Fabien Potencier */ class ParameterCircularReferenceException extends RuntimeException { private $parameters; public function __construct(array $parameters, ?\Throwable $previous = null) { parent::__construct(\sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], \implode('" > "', $parameters), $parameters[0]), 0, $previous); $this->parameters = $parameters; } public function getParameters() { return $this->parameters; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Base LogicException for Dependency Injection component. */ class LogicException extends \LogicException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * This exception is thrown when an environment variable is not found. * * @author Nicolas Grekas */ class EnvNotFoundException extends InvalidArgumentException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; use _ContaoManager\Psr\Container\NotFoundExceptionInterface; /** * This exception is thrown when a non-existent parameter is used. * * @author Fabien Potencier */ class ParameterNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface { private $key; private $sourceId; private $sourceKey; private $alternatives; private $nonNestedAlternative; /** * @param string $key The requested parameter key * @param string|null $sourceId The service id that references the non-existent parameter * @param string|null $sourceKey The parameter key that references the non-existent parameter * @param \Throwable|null $previous The previous exception * @param string[] $alternatives Some parameter name alternatives * @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters */ public function __construct(string $key, ?string $sourceId = null, ?string $sourceKey = null, ?\Throwable $previous = null, array $alternatives = [], ?string $nonNestedAlternative = null) { $this->key = $key; $this->sourceId = $sourceId; $this->sourceKey = $sourceKey; $this->alternatives = $alternatives; $this->nonNestedAlternative = $nonNestedAlternative; parent::__construct('', 0, $previous); $this->updateRepr(); } public function updateRepr() { if (null !== $this->sourceId) { $this->message = \sprintf('The service "%s" has a dependency on a non-existent parameter "%s".', $this->sourceId, $this->key); } elseif (null !== $this->sourceKey) { $this->message = \sprintf('The parameter "%s" has a dependency on a non-existent parameter "%s".', $this->sourceKey, $this->key); } else { $this->message = \sprintf('You have requested a non-existent parameter "%s".', $this->key); } if ($this->alternatives) { if (1 == \count($this->alternatives)) { $this->message .= ' Did you mean this: "'; } else { $this->message .= ' Did you mean one of these: "'; } $this->message .= \implode('", "', $this->alternatives) . '"?'; } elseif (null !== $this->nonNestedAlternative) { $this->message .= ' You cannot access nested array items, do you want to inject "' . $this->nonNestedAlternative . '" instead?'; } } public function getKey() { return $this->key; } public function getSourceId() { return $this->sourceId; } public function getSourceKey() { return $this->sourceKey; } public function setSourceId(?string $sourceId) { $this->sourceId = $sourceId; $this->updateRepr(); } public function setSourceKey(?string $sourceKey) { $this->sourceKey = $sourceKey; $this->updateRepr(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; use _ContaoManager\Psr\Container\ContainerExceptionInterface; /** * Base ExceptionInterface for Dependency Injection component. * * @author Fabien Potencier * @author Bulat Shakirzyanov */ interface ExceptionInterface extends ContainerExceptionInterface, \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Base OutOfBoundsException for Dependency Injection component. */ class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * This exception is thrown when a circular reference is detected. * * @author Johannes M. Schmitt */ class ServiceCircularReferenceException extends RuntimeException { private $serviceId; private $path; public function __construct(string $serviceId, array $path, ?\Throwable $previous = null) { parent::__construct(\sprintf('Circular reference detected for service "%s", path: "%s".', $serviceId, \implode(' -> ', $path)), 0, $previous); $this->serviceId = $serviceId; $this->path = $path; } public function getServiceId() { return $this->serviceId; } public function getPath() { return $this->path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; use _ContaoManager\Psr\Container\NotFoundExceptionInterface; /** * This exception is thrown when a non-existent service is requested. * * @author Johannes M. Schmitt */ class ServiceNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface { private $id; private $sourceId; private $alternatives; public function __construct(string $id, ?string $sourceId = null, ?\Throwable $previous = null, array $alternatives = [], ?string $msg = null) { if (null !== $msg) { // no-op } elseif (null === $sourceId) { $msg = \sprintf('You have requested a non-existent service "%s".', $id); } else { $msg = \sprintf('The service "%s" has a dependency on a non-existent service "%s".', $sourceId, $id); } if ($alternatives) { if (1 == \count($alternatives)) { $msg .= ' Did you mean this: "'; } else { $msg .= ' Did you mean one of these: "'; } $msg .= \implode('", "', $alternatives) . '"?'; } parent::__construct($msg, 0, $previous); $this->id = $id; $this->sourceId = $sourceId; $this->alternatives = $alternatives; } public function getId() { return $this->id; } public function getSourceId() { return $this->sourceId; } public function getAlternatives() { return $this->alternatives; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Base RuntimeException for Dependency Injection component. * * @author Johannes M. Schmitt */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Base InvalidArgumentException for Dependency Injection component. * * @author Bulat Shakirzyanov */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Base BadMethodCallException for Dependency Injection component. */ class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Thrown when a definition cannot be autowired. */ class AutowiringFailedException extends RuntimeException { private $serviceId; private $messageCallback; public function __construct(string $serviceId, $message = '', int $code = 0, ?\Throwable $previous = null) { $this->serviceId = $serviceId; if ($message instanceof \Closure && \function_exists('xdebug_is_enabled') && \xdebug_is_enabled()) { $message = $message(); } if (!$message instanceof \Closure) { parent::__construct($message, $code, $previous); return; } $this->messageCallback = $message; parent::__construct('', $code, $previous); $this->message = new class($this->message, $this->messageCallback) { private $message; private $messageCallback; public function __construct(&$message, &$messageCallback) { $this->message =& $message; $this->messageCallback =& $messageCallback; } public function __toString() : string { $messageCallback = $this->messageCallback; $this->messageCallback = null; try { return $this->message = $messageCallback(); } catch (\Throwable $e) { return $this->message = $e->getMessage(); } } }; } public function getMessageCallback() : ?\Closure { return $this->messageCallback; } public function getServiceId() { return $this->serviceId; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * Thrown when trying to inject a parameter into a constructor/method with an incompatible type. * * @author Nicolas Grekas * @author Julien Maulny */ class InvalidParameterTypeException extends InvalidArgumentException { public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter) { $acceptedType = $parameter->getType(); $acceptedType = $acceptedType instanceof \ReflectionNamedType ? $acceptedType->getName() : (string) $acceptedType; $this->code = $type; $function = $parameter->getDeclaringFunction(); $functionName = $function instanceof \ReflectionMethod ? \sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) : $function->getName(); parent::__construct(\sprintf('Invalid definition for service "%s": argument %d of "%s()" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $functionName, $acceptedType, $type)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Exception; /** * This exception wraps exceptions whose messages contain a reference to an env parameter. * * @author Nicolas Grekas */ class EnvParameterException extends InvalidArgumentException { public function __construct(array $envs, ?\Throwable $previous = null, string $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.') { parent::__construct(\sprintf($message, \implode('", "', $envs)), 0, $previous); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * TaggedContainerInterface is the interface implemented when a container knows how to deals with tags. * * @author Fabien Potencier */ interface TaggedContainerInterface extends ContainerInterface { /** * Returns service ids for a given tag. * * @param string $name The tag name * * @return array */ public function findTaggedServiceIds(string $name); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * Parameter represents a parameter reference. * * @author Fabien Potencier */ class Parameter { private $id; public function __construct(string $id) { $this->id = $id; } /** * @return string */ public function __toString() { return $this->id; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Overwrites a service but keeps the overridden one. * * @author Christophe Coevoet * @author Fabien Potencier * @author Diego Saint Esteben */ class DecoratorServicePass extends AbstractRecursivePass { private $innerId = '.inner'; public function __construct(?string $innerId = '.inner') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->innerId = $innerId; } public function process(ContainerBuilder $container) { $definitions = new \SplPriorityQueue(); $order = \PHP_INT_MAX; foreach ($container->getDefinitions() as $id => $definition) { if (!($decorated = $definition->getDecoratedService())) { continue; } $definitions->insert([$id, $definition], [$decorated[2], --$order]); } $decoratingDefinitions = []; $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') ? $container->getParameter('container.behavior_describing_tags') : ['proxy', 'container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator']; foreach ($definitions as [$id, $definition]) { $decoratedService = $definition->getDecoratedService(); [$inner, $renamedId] = $decoratedService; $invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; $definition->setDecoratedService(null); if (!$renamedId) { $renamedId = $id . '.inner'; } $this->currentId = $renamedId; $this->processValue($definition); $definition->innerServiceId = $renamedId; $definition->decorationOnInvalid = $invalidBehavior; // we create a new alias/service for the service we are replacing // to be able to reference it in the new one if ($container->hasAlias($inner)) { $alias = $container->getAlias($inner); $public = $alias->isPublic(); $container->setAlias($renamedId, new Alias((string) $alias, \false)); $decoratedDefinition = $container->findDefinition($alias); } elseif ($container->hasDefinition($inner)) { $decoratedDefinition = $container->getDefinition($inner); $public = $decoratedDefinition->isPublic(); $decoratedDefinition->setPublic(\false); $container->setDefinition($renamedId, $decoratedDefinition); $decoratingDefinitions[$inner] = $decoratedDefinition; } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { $container->removeDefinition($id); continue; } elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { $public = $definition->isPublic(); $decoratedDefinition = null; } else { throw new ServiceNotFoundException($inner, $id); } if ($decoratedDefinition && $decoratedDefinition->isSynthetic()) { throw new InvalidArgumentException(\sprintf('A synthetic service cannot be decorated: service "%s" cannot decorate "%s".', $id, $inner)); } if (isset($decoratingDefinitions[$inner])) { $decoratingDefinition = $decoratingDefinitions[$inner]; $decoratingTags = $decoratingDefinition->getTags(); $resetTags = []; // Behavior-describing tags must not be transferred out to decorators foreach ($tagsToKeep as $containerTag) { if (isset($decoratingTags[$containerTag])) { $resetTags[$containerTag] = $decoratingTags[$containerTag]; unset($decoratingTags[$containerTag]); } } $definition->setTags(\array_merge($decoratingTags, $definition->getTags())); $decoratingDefinition->setTags($resetTags); $decoratingDefinitions[$inner] = $definition; } $container->setAlias($inner, $id)->setPublic($public); } } protected function processValue($value, bool $isRoot = \false) { if ($value instanceof Reference && $this->innerId === (string) $value) { return new Reference($this->currentId, $value->getInvalidBehavior()); } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; /** * Emulates the invalid behavior if the reference is not found within the * container. * * @author Johannes M. Schmitt */ class ResolveInvalidReferencesPass implements CompilerPassInterface { private $container; private $signalingException; private $currentId; /** * Process the ContainerBuilder to resolve invalid references. */ public function process(ContainerBuilder $container) { $this->container = $container; $this->signalingException = new RuntimeException('Invalid reference.'); try { foreach ($container->getDefinitions() as $this->currentId => $definition) { $this->processValue($definition); } } finally { $this->container = $this->signalingException = null; } } /** * Processes arguments to determine invalid references. * * @return mixed * * @throws RuntimeException When an invalid reference is found */ private function processValue($value, int $rootLevel = 0, int $level = 0) { if ($value instanceof ServiceClosureArgument) { $value->setValues($this->processValue($value->getValues(), 1, 1)); } elseif ($value instanceof ArgumentInterface) { $value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level)); } elseif ($value instanceof Definition) { if ($value->isSynthetic() || $value->isAbstract()) { return $value; } $value->setArguments($this->processValue($value->getArguments(), 0)); $value->setProperties($this->processValue($value->getProperties(), 1)); $value->setMethodCalls($this->processValue($value->getMethodCalls(), 2)); } elseif (\is_array($value)) { $i = 0; foreach ($value as $k => $v) { try { if (\false !== $i && $k !== $i++) { $i = \false; } if ($v !== ($processedValue = $this->processValue($v, $rootLevel, 1 + $level))) { $value[$k] = $processedValue; } } catch (RuntimeException $e) { if ($rootLevel < $level || $rootLevel && !$level) { unset($value[$k]); } elseif ($rootLevel) { throw $e; } else { $value[$k] = null; } } } // Ensure numerically indexed arguments have sequential numeric keys. if (\false !== $i) { $value = \array_values($value); } } elseif ($value instanceof Reference) { if ($this->container->has($id = (string) $value)) { return $value; } $currentDefinition = $this->container->getDefinition($this->currentId); // resolve decorated service behavior depending on decorator service if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) { return null; } $invalidBehavior = $value->getInvalidBehavior(); if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) { $e = new ServiceNotFoundException($id, $this->currentId); // since the error message varies by $id and $this->currentId, so should the id of the dummy errored definition $this->container->register($id = \sprintf('.errored.%s.%s', $this->currentId, $id), $value->getType())->addError($e->getMessage()); return new TypedReference($id, $value->getType(), $value->getInvalidBehavior()); } // resolve invalid behavior if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { $value = null; } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { if (0 < $level || $rootLevel) { throw $this->signalingException; } $value = null; } } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\Config\Definition\BaseNode; use _ContaoManager\Symfony\Component\Config\Definition\ConfigurationInterface; use _ContaoManager\Symfony\Component\Config\Definition\Processor; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** * Validates environment variable placeholders used in extension configuration with dummy values. * * @author Roland Franssen */ class ValidateEnvPlaceholdersPass implements CompilerPassInterface { private const TYPE_FIXTURES = ['array' => [], 'bool' => \false, 'float' => 0.0, 'int' => 0, 'string' => '']; private $extensionConfig = []; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->extensionConfig = []; if (!\class_exists(BaseNode::class) || !($extensions = $container->getExtensions())) { return; } $resolvingBag = $container->getParameterBag(); if (!$resolvingBag instanceof EnvPlaceholderParameterBag) { return; } $defaultBag = new ParameterBag($resolvingBag->all()); $envTypes = $resolvingBag->getProvidedTypes(); foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) { $values = []; if (\false === ($i = \strpos($env, ':'))) { $default = $defaultBag->has("env({$env})") ? $defaultBag->get("env({$env})") : self::TYPE_FIXTURES['string']; $defaultType = null !== $default ? \get_debug_type($default) : 'string'; $values[$defaultType] = $default; } else { $prefix = \substr($env, 0, $i); foreach ($envTypes[$prefix] ?? ['string'] as $type) { $values[$type] = self::TYPE_FIXTURES[$type] ?? null; } } foreach ($placeholders as $placeholder) { BaseNode::setPlaceholder($placeholder, $values); } } $processor = new Processor(); foreach ($extensions as $name => $extension) { if (!($extension instanceof ConfigurationExtensionInterface || $extension instanceof ConfigurationInterface) || !($config = \array_filter($container->getExtensionConfig($name)))) { // this extension has no semantic configuration or was not called continue; } $config = $resolvingBag->resolveValue($config); if ($extension instanceof ConfigurationInterface) { $configuration = $extension; } elseif (null === ($configuration = $extension->getConfiguration($config, $container))) { continue; } $this->extensionConfig[$name] = $processor->processConfiguration($configuration, $config); } $resolvingBag->clearUnusedEnvPlaceholders(); } /** * @internal */ public function getExtensionConfig() : array { try { return $this->extensionConfig; } finally { $this->extensionConfig = []; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\EnvVarProcessor; use _ContaoManager\Symfony\Component\DependencyInjection\EnvVarProcessorInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Creates the container.env_var_processors_locator service. * * @author Nicolas Grekas */ class RegisterEnvVarProcessorsPass implements CompilerPassInterface { private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string']; public function process(ContainerBuilder $container) { $bag = $container->getParameterBag(); $types = []; $processors = []; foreach ($container->findTaggedServiceIds('container.env_var_processor') as $id => $tags) { if (!($r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass()))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } elseif (!$r->isSubclassOf(EnvVarProcessorInterface::class)) { throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class)); } foreach ($class::getProvidedTypes() as $prefix => $type) { $processors[$prefix] = new Reference($id); $types[$prefix] = self::validateProvidedTypes($type, $class); } } if ($bag instanceof EnvPlaceholderParameterBag) { foreach (EnvVarProcessor::getProvidedTypes() as $prefix => $type) { if (!isset($types[$prefix])) { $types[$prefix] = self::validateProvidedTypes($type, EnvVarProcessor::class); } } $bag->setProvidedTypes($types); } if ($processors) { $container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors))->setPublic(\true); } } private static function validateProvidedTypes(string $types, string $class) : array { $types = \explode('|', $types); foreach ($types as $type) { if (!\in_array($type, self::ALLOWED_TYPES)) { throw new InvalidArgumentException(\sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, \implode('", "', self::ALLOWED_TYPES))); } } return $types; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\YamlFileLoader; /** * Reads #[Autoconfigure] attributes on definitions that are autoconfigured * and don't have the "container.ignore_attributes" tag. * * @author Nicolas Grekas */ final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface { private static $registerForAutoconfiguration; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { if (80000 > \PHP_VERSION_ID) { return; } foreach ($container->getDefinitions() as $id => $definition) { if ($this->accept($definition) && ($class = $container->getReflectionClass($definition->getClass(), \false))) { $this->processClass($container, $class); } } } public function accept(Definition $definition) : bool { return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes'); } public function processClass(ContainerBuilder $container, \ReflectionClass $class) { foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { self::registerForAutoconfiguration($container, $class, $attribute); } } private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) { if (self::$registerForAutoconfiguration) { return (self::$registerForAutoconfiguration)($container, $class, $attribute); } $parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions'); $parseDefinitions->setAccessible(\true); $yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor(); self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use($parseDefinitions, $yamlLoader) { $attribute = (array) $attribute->newInstance(); foreach ($attribute['tags'] ?? [] as $i => $tag) { if (\is_array($tag) && [0] === \array_keys($tag)) { $attribute['tags'][$i] = [$class->name => $tag[0]]; } } $parseDefinitions->invoke($yamlLoader, ['services' => ['_instanceof' => [$class->name => [$container->registerForAutoconfiguration($class->name)] + $attribute]]], $class->getFileName(), \false); }; return (self::$registerForAutoconfiguration)($container, $class, $attribute); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\EnvParameterException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\FileLoader; /** * This pass validates each definition individually only taking the information * into account which is contained in the definition itself. * * Later passes can rely on the following, and specifically do not need to * perform these checks themselves: * * - non synthetic, non abstract services always have a class set * - synthetic services are always public * * @author Johannes M. Schmitt */ class CheckDefinitionValidityPass implements CompilerPassInterface { /** * Processes the ContainerBuilder to validate the Definition. * * @throws RuntimeException When the Definition is invalid */ public function process(ContainerBuilder $container) { foreach ($container->getDefinitions() as $id => $definition) { // synthetic service is public if ($definition->isSynthetic() && !$definition->isPublic()) { throw new RuntimeException(\sprintf('A synthetic service ("%s") must be public.', $id)); } // non-synthetic, non-abstract service has class if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && !$definition->hasTag('container.service_locator') && (!$definition->getFactory() || !\preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) { if ($definition->getFactory()) { throw new RuntimeException(\sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id)); } if (\class_exists($id) || \interface_exists($id, \false)) { if (\str_starts_with($id, '\\') && 1 < \substr_count($id, '\\')) { throw new RuntimeException(\sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, \substr($id, 1))); } throw new RuntimeException(\sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface in the global namespace. Leaving out the "class" attribute is only allowed for namespaced classes. Please specify the class attribute explicitly to get rid of this error.', $id)); } throw new RuntimeException(\sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id)); } // tag attribute values must be scalars foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { foreach ($attributes as $attribute => $value) { if (!\is_scalar($value) && null !== $value) { throw new RuntimeException(\sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute)); } } } } if ($definition->isPublic() && !$definition->isPrivate()) { $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); if (null !== $usedEnvs) { throw new EnvParameterException([$resolvedId], null, 'A service name ("%s") cannot contain dynamic values.'); } } } foreach ($container->getAliases() as $id => $alias) { if ($alias->isPublic() && !$alias->isPrivate()) { $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); if (null !== $usedEnvs) { throw new EnvParameterException([$resolvedId], null, 'An alias name ("%s") cannot contain dynamic values.'); } } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * Compiler Pass Configuration. * * This class has a default configuration embedded. * * @author Johannes M. Schmitt */ class PassConfig { public const TYPE_AFTER_REMOVING = 'afterRemoving'; public const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization'; public const TYPE_BEFORE_REMOVING = 'beforeRemoving'; public const TYPE_OPTIMIZE = 'optimization'; public const TYPE_REMOVE = 'removing'; private $mergePass; private $afterRemovingPasses = []; private $beforeOptimizationPasses = []; private $beforeRemovingPasses = []; private $optimizationPasses; private $removingPasses; public function __construct() { $this->mergePass = new MergeExtensionConfigurationPass(); $this->beforeOptimizationPasses = [100 => [new ResolveClassPass(), new RegisterAutoconfigureAttributesPass(), new AttributeAutoconfigurationPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass()], -1000 => [new ExtensionCompilerPass()]]; $this->optimizationPasses = [[$autoAliasServicePass = new AutoAliasServicePass(), new ValidateEnvPlaceholdersPass(), new ResolveDecoratorStackPass(), new ResolveChildDefinitionsPass(), new RegisterServiceSubscribersPass(), new ResolveParameterPlaceHoldersPass(\false, \false), new ResolveFactoryClassPass(), new ResolveNamedArgumentsPass(), new AutowireRequiredMethodsPass(), new AutowireRequiredPropertiesPass(), new ResolveBindingsPass(), new ServiceLocatorTagPass(), new DecoratorServicePass(), new CheckDefinitionValidityPass(), new AutowirePass(\false), new ServiceLocatorTagPass(), new ResolveTaggedIteratorArgumentPass(), new ResolveServiceSubscribersPass(), new ResolveReferencesToAliasesPass(), new ResolveInvalidReferencesPass(), new AnalyzeServiceReferencesPass(\true), new CheckCircularReferencesPass(), new CheckReferenceValidityPass(), new CheckArgumentsValidityPass(\false)]]; $this->removingPasses = [[new RemovePrivateAliasesPass(), (new ReplaceAliasByActualDefinitionPass())->setAutoAliasServicePass($autoAliasServicePass), new RemoveAbstractDefinitionsPass(), new RemoveUnusedDefinitionsPass(), new AnalyzeServiceReferencesPass(), new CheckExceptionOnInvalidReferenceBehaviorPass(), new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()), new AnalyzeServiceReferencesPass(), new DefinitionErrorExceptionPass()]]; $this->afterRemovingPasses = [[new ResolveHotPathPass(), new ResolveNoPreloadPass(), new AliasDeprecatedPublicServicesPass()]]; } /** * Returns all passes in order to be processed. * * @return CompilerPassInterface[] */ public function getPasses() { return \array_merge([$this->mergePass], $this->getBeforeOptimizationPasses(), $this->getOptimizationPasses(), $this->getBeforeRemovingPasses(), $this->getRemovingPasses(), $this->getAfterRemovingPasses()); } /** * Adds a pass. * * @throws InvalidArgumentException when a pass type doesn't exist */ public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { $property = $type . 'Passes'; if (!isset($this->{$property})) { throw new InvalidArgumentException(\sprintf('Invalid type "%s".', $type)); } $passes =& $this->{$property}; if (!isset($passes[$priority])) { $passes[$priority] = []; } $passes[$priority][] = $pass; } /** * Gets all passes for the AfterRemoving pass. * * @return CompilerPassInterface[] */ public function getAfterRemovingPasses() { return $this->sortPasses($this->afterRemovingPasses); } /** * Gets all passes for the BeforeOptimization pass. * * @return CompilerPassInterface[] */ public function getBeforeOptimizationPasses() { return $this->sortPasses($this->beforeOptimizationPasses); } /** * Gets all passes for the BeforeRemoving pass. * * @return CompilerPassInterface[] */ public function getBeforeRemovingPasses() { return $this->sortPasses($this->beforeRemovingPasses); } /** * Gets all passes for the Optimization pass. * * @return CompilerPassInterface[] */ public function getOptimizationPasses() { return $this->sortPasses($this->optimizationPasses); } /** * Gets all passes for the Removing pass. * * @return CompilerPassInterface[] */ public function getRemovingPasses() { return $this->sortPasses($this->removingPasses); } /** * Gets the Merge pass. * * @return CompilerPassInterface */ public function getMergePass() { return $this->mergePass; } public function setMergePass(CompilerPassInterface $pass) { $this->mergePass = $pass; } /** * Sets the AfterRemoving passes. * * @param CompilerPassInterface[] $passes */ public function setAfterRemovingPasses(array $passes) { $this->afterRemovingPasses = [$passes]; } /** * Sets the BeforeOptimization passes. * * @param CompilerPassInterface[] $passes */ public function setBeforeOptimizationPasses(array $passes) { $this->beforeOptimizationPasses = [$passes]; } /** * Sets the BeforeRemoving passes. * * @param CompilerPassInterface[] $passes */ public function setBeforeRemovingPasses(array $passes) { $this->beforeRemovingPasses = [$passes]; } /** * Sets the Optimization passes. * * @param CompilerPassInterface[] $passes */ public function setOptimizationPasses(array $passes) { $this->optimizationPasses = [$passes]; } /** * Sets the Removing passes. * * @param CompilerPassInterface[] $passes */ public function setRemovingPasses(array $passes) { $this->removingPasses = [$passes]; } /** * Sort passes by priority. * * @param array $passes CompilerPassInterface instances with their priority as key * * @return CompilerPassInterface[] */ private function sortPasses(array $passes) : array { if (0 === \count($passes)) { return []; } \krsort($passes); // Flatten the array return \array_merge(...$passes); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * This is a directed graph of your services. * * This information can be used by your compiler passes instead of collecting * it themselves which improves performance quite a lot. * * @author Johannes M. Schmitt * * @final */ class ServiceReferenceGraph { /** * @var ServiceReferenceGraphNode[] */ private $nodes = []; public function hasNode(string $id) : bool { return isset($this->nodes[$id]); } /** * Gets a node by identifier. * * @throws InvalidArgumentException if no node matches the supplied identifier */ public function getNode(string $id) : ServiceReferenceGraphNode { if (!isset($this->nodes[$id])) { throw new InvalidArgumentException(\sprintf('There is no node with id "%s".', $id)); } return $this->nodes[$id]; } /** * Returns all nodes. * * @return ServiceReferenceGraphNode[] */ public function getNodes() : array { return $this->nodes; } /** * Clears all nodes. */ public function clear() { foreach ($this->nodes as $node) { $node->clear(); } $this->nodes = []; } /** * Connects 2 nodes together in the Graph. */ public function connect(?string $sourceId, $sourceValue, ?string $destId, $destValue = null, ?Reference $reference = null, bool $lazy = \false, bool $weak = \false, bool $byConstructor = \false) { if (null === $sourceId || null === $destId) { return; } $sourceNode = $this->createNode($sourceId, $sourceValue); $destNode = $this->createNode($destId, $destValue); $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor); $sourceNode->addOutEdge($edge); $destNode->addInEdge($edge); } private function createNode(string $id, $value) : ServiceReferenceGraphNode { if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) { return $this->nodes[$id]; } return $this->nodes[$id] = new ServiceReferenceGraphNode($id, $value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Propagate the "container.no_preload" tag. * * @author Nicolas Grekas */ class ResolveNoPreloadPass extends AbstractRecursivePass { private const DO_PRELOAD_TAG = '.container.do_preload'; private $tagName; private $resolvedIds = []; public function __construct(string $tagName = 'container.no_preload') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->tagName = $tagName; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->container = $container; try { foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) { $this->resolvedIds[$id] = \true; $this->processValue($definition, \true); } } foreach ($container->getAliases() as $alias) { if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->hasDefinition($id)) { $this->resolvedIds[$id] = \true; $this->processValue($container->getDefinition($id), \true); } } } finally { $this->resolvedIds = []; $this->container = null; } foreach ($container->getDefinitions() as $definition) { if ($definition->hasTag(self::DO_PRELOAD_TAG)) { $definition->clearTag(self::DO_PRELOAD_TAG); } elseif (!$definition->isDeprecated() && !$definition->hasErrors()) { $definition->addTag($this->tagName); } } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { $definition = $this->container->getDefinition($id); if (!isset($this->resolvedIds[$id]) && (!$definition->isPublic() || $definition->isPrivate())) { $this->resolvedIds[$id] = \true; $this->processValue($definition, \true); } return $value; } if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) { return $value; } if ($isRoot) { $value->addTag(self::DO_PRELOAD_TAG); } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * Represents a node in your service graph. * * Value is typically a definition, or an alias. * * @author Johannes M. Schmitt */ class ServiceReferenceGraphNode { private $id; private $inEdges = []; private $outEdges = []; private $value; /** * @param string $id The node identifier * @param mixed $value The node value */ public function __construct(string $id, $value) { $this->id = $id; $this->value = $value; } public function addInEdge(ServiceReferenceGraphEdge $edge) { $this->inEdges[] = $edge; } public function addOutEdge(ServiceReferenceGraphEdge $edge) { $this->outEdges[] = $edge; } /** * Checks if the value of this node is an Alias. * * @return bool */ public function isAlias() { return $this->value instanceof Alias; } /** * Checks if the value of this node is a Definition. * * @return bool */ public function isDefinition() { return $this->value instanceof Definition; } /** * Returns the identifier. * * @return string */ public function getId() { return $this->id; } /** * Returns the in edges. * * @return ServiceReferenceGraphEdge[] */ public function getInEdges() { return $this->inEdges; } /** * Returns the out edges. * * @return ServiceReferenceGraphEdge[] */ public function getOutEdges() { return $this->outEdges; } /** * Returns the value of this Node. * * @return mixed */ public function getValue() { return $this->value; } /** * Clears all edges. */ public function clear() { $this->inEdges = $this->outEdges = []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\ExpressionLanguage; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; /** * Checks whether injected parameters are compatible with type declarations. * * This pass should be run after all optimization passes. * * It can be added either: * * before removing passes to check all services even if they are not currently used, * * after removing passes to check only services are used in the app. * * @author Nicolas Grekas * @author Julien Maulny */ final class CheckTypeDeclarationsPass extends AbstractRecursivePass { private const SCALAR_TYPES = ['int' => \true, 'float' => \true, 'bool' => \true, 'string' => \true]; private const BUILTIN_TYPES = ['array' => \true, 'bool' => \true, 'callable' => \true, 'float' => \true, 'int' => \true, 'iterable' => \true, 'object' => \true, 'string' => \true]; private $autoload; private $skippedIds; private $expressionLanguage; /** * @param bool $autoload Whether services who's class in not loaded should be checked or not. * Defaults to false to save loading code during compilation. * @param array $skippedIds An array indexed by the service ids to skip */ public function __construct(bool $autoload = \false, array $skippedIds = []) { $this->autoload = $autoload; $this->skippedIds = $skippedIds; } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if (isset($this->skippedIds[$this->currentId])) { return $value; } if (!$value instanceof Definition || $value->hasErrors() || $value->isDeprecated()) { return parent::processValue($value, $isRoot); } if (!$this->autoload) { if (!($class = $value->getClass())) { return parent::processValue($value, $isRoot); } if (!\class_exists($class, \false) && !\interface_exists($class, \false)) { return parent::processValue($value, $isRoot); } } if (ServiceLocator::class === $value->getClass()) { return parent::processValue($value, $isRoot); } if ($constructor = $this->getConstructor($value, \false)) { $this->checkTypeDeclarations($value, $constructor, $value->getArguments()); } foreach ($value->getMethodCalls() as $methodCall) { try { $reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]); } catch (RuntimeException $e) { if ($value->getFactory()) { continue; } throw $e; } $this->checkTypeDeclarations($value, $reflectionMethod, $methodCall[1]); } return parent::processValue($value, $isRoot); } /** * @throws InvalidArgumentException When not enough parameters are defined for the method */ private function checkTypeDeclarations(Definition $checkedDefinition, \ReflectionFunctionAbstract $reflectionFunction, array $values) : void { $numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters(); if (\count($values) < $numberOfRequiredParameters) { throw new InvalidArgumentException(\sprintf('Invalid definition for service "%s": "%s::%s()" requires %d arguments, %d passed.', $this->currentId, $reflectionFunction->class, $reflectionFunction->name, $numberOfRequiredParameters, \count($values))); } $reflectionParameters = $reflectionFunction->getParameters(); $checksCount = \min($reflectionFunction->getNumberOfParameters(), \count($values)); $envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null; for ($i = 0; $i < $checksCount; ++$i) { $p = $reflectionParameters[$i]; if (!$p->hasType() || $p->isVariadic()) { continue; } if (\array_key_exists($p->name, $values)) { $i = $p->name; } elseif (!\array_key_exists($i, $values)) { continue; } $this->checkType($checkedDefinition, $values[$i], $p, $envPlaceholderUniquePrefix); } if ($reflectionFunction->isVariadic() && ($lastParameter = \end($reflectionParameters))->hasType()) { $variadicParameters = \array_slice($values, $lastParameter->getPosition()); foreach ($variadicParameters as $variadicParameter) { $this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix); } } } /** * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type */ private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, ?\ReflectionType $reflectionType = null) : void { $reflectionType = $reflectionType ?? $parameter->getType(); if ($reflectionType instanceof \ReflectionUnionType) { foreach ($reflectionType->getTypes() as $t) { try { $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); return; } catch (InvalidParameterTypeException $e) { } } throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter); } if ($reflectionType instanceof \ReflectionIntersectionType) { foreach ($reflectionType->getTypes() as $t) { $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); } return; } if (!$reflectionType instanceof \ReflectionNamedType) { return; } $type = $reflectionType->getName(); if ($value instanceof Reference) { if (!$this->container->has($value = (string) $value)) { return; } if ('service_container' === $value && \is_a($type, Container::class, \true)) { return; } $value = $this->container->findDefinition($value); } if ('self' === $type) { $type = $parameter->getDeclaringClass()->getName(); } if ('static' === $type) { $type = $checkedDefinition->getClass(); } $class = null; if ($value instanceof Definition) { if ($value->hasErrors() || $value->getFactory()) { return; } $class = $value->getClass(); if ($class && isset(self::BUILTIN_TYPES[\strtolower($class)])) { $class = \strtolower($class); } elseif (!$class || !$this->autoload && !\class_exists($class, \false) && !\interface_exists($class, \false)) { return; } } elseif ($value instanceof Parameter) { $value = $this->container->getParameter($value); } elseif ($value instanceof Expression) { try { $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]); } catch (\Exception $e) { // If a service from the expression cannot be fetched from the container, we skip the validation. return; } } elseif (\is_string($value)) { if ('%' === ($value[0] ?? '') && \preg_match('/^%([^%]+)%$/', $value, $match)) { $value = $this->container->getParameter(\substr($value, 1, -1)); } if ($envPlaceholderUniquePrefix && \is_string($value) && \str_contains($value, 'env_')) { // If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it. // We don't need to change the value because it is already a string. if ('' === \preg_replace('/' . $envPlaceholderUniquePrefix . '_\\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) { try { $value = $this->container->resolveEnvPlaceholders($value, \true); } catch (\Exception $e) { // If an env placeholder cannot be resolved, we skip the validation. return; } } } } if (null === $value && $parameter->allowsNull()) { return; } if (null === $class) { if ($value instanceof IteratorArgument) { $class = RewindableGenerator::class; } elseif ($value instanceof ServiceClosureArgument) { $class = \Closure::class; } elseif ($value instanceof ServiceLocatorArgument) { $class = ServiceLocator::class; } elseif (\is_object($value)) { $class = \get_class($value); } else { $class = \gettype($value); $class = ['integer' => 'int', 'double' => 'float', 'boolean' => 'bool'][$class] ?? $class; } } if (isset(self::SCALAR_TYPES[$type]) && isset(self::SCALAR_TYPES[$class])) { return; } if ('string' === $type && \method_exists($class, '__toString')) { return; } if ('callable' === $type && (\Closure::class === $class || \method_exists($class, '__invoke'))) { return; } if ('callable' === $type && \is_array($value) && isset($value[0]) && ($value[0] instanceof Reference || $value[0] instanceof Definition || \is_string($value[0]))) { return; } if ('iterable' === $type && (\is_array($value) || 'array' === $class || \is_subclass_of($class, \Traversable::class))) { return; } if ($type === $class) { return; } if ('object' === $type && !isset(self::BUILTIN_TYPES[$class])) { return; } if ('mixed' === $type) { return; } if (\is_a($class, $type, \true)) { return; } if ('false' === $type) { if (\false === $value) { return; } } elseif ('true' === $type) { if (\true === $value) { return; } } elseif ($reflectionType->isBuiltin()) { $checkFunction = \sprintf('is_%s', $type); if ($checkFunction($value)) { return; } } throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : \get_debug_type($value), $parameter); } private function getExpressionLanguage() : ExpressionLanguage { if (null === $this->expressionLanguage) { $this->expressionLanguage = new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders()); } return $this->expressionLanguage; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Inline service definitions where this is possible. * * @author Johannes M. Schmitt */ class InlineServiceDefinitionsPass extends AbstractRecursivePass { private $analyzingPass; private $cloningIds = []; private $connectedIds = []; private $notInlinedIds = []; private $inlinedIds = []; private $notInlinableIds = []; private $graph; public function __construct(?AnalyzeServiceReferencesPass $analyzingPass = null) { $this->analyzingPass = $analyzingPass; } public function process(ContainerBuilder $container) { $this->container = $container; if ($this->analyzingPass) { $analyzedContainer = new ContainerBuilder(); $analyzedContainer->setAliases($container->getAliases()); $analyzedContainer->setDefinitions($container->getDefinitions()); foreach ($container->getExpressionLanguageProviders() as $provider) { $analyzedContainer->addExpressionLanguageProvider($provider); } } else { $analyzedContainer = $container; } try { $remainingInlinedIds = []; $this->connectedIds = $this->notInlinedIds = $container->getDefinitions(); do { if ($this->analyzingPass) { $analyzedContainer->setDefinitions(\array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds)); $this->analyzingPass->process($analyzedContainer); } $this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph(); $notInlinedIds = $this->notInlinedIds; $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; foreach ($analyzedContainer->getDefinitions() as $id => $definition) { if (!$this->graph->hasNode($id)) { continue; } foreach ($this->graph->getNode($id)->getOutEdges() as $edge) { if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) { $this->currentId = $id; $this->processValue($definition, \true); break; } } } foreach ($this->inlinedIds as $id => $isPublicOrNotShared) { if ($isPublicOrNotShared) { $remainingInlinedIds[$id] = $id; } else { $container->removeDefinition($id); $analyzedContainer->removeDefinition($id); } } } while ($this->inlinedIds && $this->analyzingPass); foreach ($remainingInlinedIds as $id) { if (isset($this->notInlinableIds[$id])) { continue; } $definition = $container->getDefinition($id); if (!$definition->isShared() && !$definition->isPublic()) { $container->removeDefinition($id); } } } finally { $this->container = null; $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; $this->notInlinableIds = []; $this->graph = null; } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof ArgumentInterface) { // References found in ArgumentInterface::getValues() are not inlineable return $value; } if ($value instanceof Definition && $this->cloningIds) { if ($value->isShared()) { return $value; } $value = clone $value; } if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); } elseif (!$this->container->hasDefinition($id = (string) $value)) { return $value; } $definition = $this->container->getDefinition($id); if (!$this->isInlineableDefinition($id, $definition)) { $this->notInlinableIds[$id] = \true; return $value; } $this->container->log($this, \sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); $this->inlinedIds[$id] = $definition->isPublic() || !$definition->isShared(); $this->notInlinedIds[$this->currentId] = \true; if ($definition->isShared()) { return $definition; } if (isset($this->cloningIds[$id])) { $ids = \array_keys($this->cloningIds); $ids[] = $id; throw new ServiceCircularReferenceException($id, \array_slice($ids, \array_search($id, $ids))); } $this->cloningIds[$id] = \true; try { return $this->processValue($definition); } finally { unset($this->cloningIds[$id]); } } /** * Checks if the definition is inlineable. */ private function isInlineableDefinition(string $id, Definition $definition) : bool { if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) { return \false; } if (!$definition->isShared()) { if (!$this->graph->hasNode($id)) { return \true; } foreach ($this->graph->getNode($id)->getInEdges() as $edge) { $srcId = $edge->getSourceNode()->getId(); $this->connectedIds[$srcId] = \true; if ($edge->isWeak() || $edge->isLazy()) { return !($this->connectedIds[$id] = \true); } } return \true; } if ($definition->isPublic()) { return \false; } if (!$this->graph->hasNode($id)) { return \true; } if ($this->currentId == $id) { return \false; } $this->connectedIds[$id] = \true; $srcIds = []; $srcCount = 0; foreach ($this->graph->getNode($id)->getInEdges() as $edge) { $srcId = $edge->getSourceNode()->getId(); $this->connectedIds[$srcId] = \true; if ($edge->isWeak() || $edge->isLazy()) { return \false; } $srcIds[$srcId] = \true; ++$srcCount; } if (1 !== \count($srcIds)) { $this->notInlinedIds[$id] = \true; return \false; } if ($srcCount > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { return \false; } return $this->container->getDefinition($srcId)->isShared(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * @author Maxime Steinhausser */ class ResolveFactoryClassPass extends AbstractRecursivePass { /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof Definition && \is_array($factory = $value->getFactory()) && null === $factory[0]) { if (null === ($class = $value->getClass())) { throw new RuntimeException(\sprintf('The "%s" service is defined to be created by a factory, but is missing the factory class. Did you forget to define the factory or service class?', $this->currentId)); } $factory[0] = $class; $value->setFactory($factory); } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\Target; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; /** * @author Guilhem Niot */ class ResolveBindingsPass extends AbstractRecursivePass { private $usedBindings = []; private $unusedBindings = []; private $errorMessages = []; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->usedBindings = $container->getRemovedBindingIds(); try { parent::process($container); foreach ($this->unusedBindings as [$key, $serviceId, $bindingType, $file]) { $argumentType = $argumentName = $message = null; if (\str_contains($key, ' ')) { [$argumentType, $argumentName] = \explode(' ', $key, 2); } elseif ('$' === $key[0]) { $argumentName = $key; } else { $argumentType = $key; } if ($argumentType) { $message .= \sprintf('of type "%s" ', $argumentType); } if ($argumentName) { $message .= \sprintf('named "%s" ', $argumentName); } if (BoundArgument::DEFAULTS_BINDING === $bindingType) { $message .= 'under "_defaults"'; } elseif (BoundArgument::INSTANCEOF_BINDING === $bindingType) { $message .= 'under "_instanceof"'; } else { $message .= \sprintf('for service "%s"', $serviceId); } if ($file) { $message .= \sprintf(' in file "%s"', $file); } $message = \sprintf('A binding is configured for an argument %s, but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.', $message); if ($this->errorMessages) { $message .= \sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : ''); } foreach ($this->errorMessages as $m) { $message .= "\n - " . $m; } throw new InvalidArgumentException($message); } } finally { $this->usedBindings = []; $this->unusedBindings = []; $this->errorMessages = []; } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof TypedReference && $value->getType() === (string) $value) { // Already checked $bindings = $this->container->getDefinition($this->currentId)->getBindings(); $name = $value->getName(); if (isset($name, $bindings[$name = $value . ' $' . $name])) { return $this->getBindingValue($bindings[$name]); } if (isset($bindings[$value->getType()])) { return $this->getBindingValue($bindings[$value->getType()]); } return parent::processValue($value, $isRoot); } if (!$value instanceof Definition || !($bindings = $value->getBindings())) { return parent::processValue($value, $isRoot); } $bindingNames = []; foreach ($bindings as $key => $binding) { [$bindingValue, $bindingId, $used, $bindingType, $file] = $binding->getValues(); if ($used) { $this->usedBindings[$bindingId] = \true; unset($this->unusedBindings[$bindingId]); } elseif (!isset($this->usedBindings[$bindingId])) { $this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file]; } if (\preg_match('/^(?:(?:array|bool|float|int|string|iterable|([^ $]++)) )\\$/', $key, $m)) { $bindingNames[\substr($key, \strlen($m[0]))] = $binding; } if (!isset($m[1])) { continue; } if (\is_subclass_of($m[1], \UnitEnum::class)) { $bindingNames[\substr($key, \strlen($m[0]))] = $binding; continue; } if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) { throw new InvalidArgumentException(\sprintf('Invalid value for binding key "%s" for service "%s": expected "%s", "%s", "%s", "%s" or null, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, ServiceLocatorArgument::class, \get_debug_type($bindingValue))); } } if ($value->isAbstract()) { return parent::processValue($value, $isRoot); } $calls = $value->getMethodCalls(); try { if ($constructor = $this->getConstructor($value, \false)) { $calls[] = [$constructor, $value->getArguments()]; } } catch (RuntimeException $e) { $this->errorMessages[] = $e->getMessage(); $this->container->getDefinition($this->currentId)->addError($e->getMessage()); return parent::processValue($value, $isRoot); } foreach ($calls as $i => $call) { [$method, $arguments] = $call; if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; } else { try { $reflectionMethod = $this->getReflectionMethod($value, $method); } catch (RuntimeException $e) { if ($value->getFactory()) { continue; } throw $e; } } $names = []; foreach ($reflectionMethod->getParameters() as $key => $parameter) { $names[$key] = $parameter->name; if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) { continue; } if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) { continue; } $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter); $name = Target::parseName($parameter); if ($typeHint && \array_key_exists($k = \ltrim($typeHint, '\\') . ' $' . $name, $bindings)) { $arguments[$key] = $this->getBindingValue($bindings[$k]); continue; } if (\array_key_exists('$' . $name, $bindings)) { $arguments[$key] = $this->getBindingValue($bindings['$' . $name]); continue; } if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = \substr($typeHint, 1)])) { $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); continue; } if (isset($bindingNames[$name]) || isset($bindingNames[$parameter->name])) { $bindingKey = \array_search($binding, $bindings, \true); $argumentType = \substr($bindingKey, 0, \strpos($bindingKey, ' ')); $this->errorMessages[] = \sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name); } } foreach ($names as $key => $name) { if (\array_key_exists($name, $arguments) && (0 === $key || \array_key_exists($key - 1, $arguments))) { $arguments[$key] = $arguments[$name]; unset($arguments[$name]); } } if ($arguments !== $call[1]) { \ksort($arguments, \SORT_NATURAL); $calls[$i][1] = $arguments; } } if ($constructor) { [, $arguments] = \array_pop($calls); if ($arguments !== $value->getArguments()) { $value->setArguments($arguments); } } if ($calls !== $value->getMethodCalls()) { $value->setMethodCalls($calls); } return parent::processValue($value, $isRoot); } /** * @return mixed */ private function getBindingValue(BoundArgument $binding) { [$bindingValue, $bindingId] = $binding->getValues(); $this->usedBindings[$bindingId] = \true; unset($this->unusedBindings[$bindingId]); return $bindingValue; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; /** * Trait that allows a generic method to find and sort service by priority option in the tag. * * @author Iltar van der Berg */ trait PriorityTaggedServiceTrait { /** * Finds all services with the given tag name and order them by their priority. * * The order of additions must be respected for services having the same priority, * and knowing that the \SplPriorityQueue class does not respect the FIFO method, * we should not use that class. * * @see https://bugs.php.net/53710 * @see https://bugs.php.net/60926 * * @param string|TaggedIteratorArgument $tagName * * @return Reference[] */ private function findAndSortTaggedServices($tagName, ContainerBuilder $container) : array { $indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null; if ($tagName instanceof TaggedIteratorArgument) { $indexAttribute = $tagName->getIndexAttribute(); $defaultIndexMethod = $tagName->getDefaultIndexMethod(); $needsIndexes = $tagName->needsIndexes(); $defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority'; $tagName = $tagName->getTag(); } $i = 0; $services = []; foreach ($container->findTaggedServiceIds($tagName, \true) as $serviceId => $attributes) { $defaultPriority = null; $defaultIndex = null; $definition = $container->getDefinition($serviceId); $class = $definition->getClass(); $class = $container->getParameterBag()->resolveValue($class) ?: null; $checkTaggedItem = !$definition->hasTag(80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName); foreach ($attributes as $attribute) { $index = $priority = null; if (isset($attribute['priority'])) { $priority = $attribute['priority']; } elseif (null === $defaultPriority && $defaultPriorityMethod && $class) { $defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem); } $priority = $priority ?? $defaultPriority ?? ($defaultPriority = 0); if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) { $services[] = [$priority, ++$i, null, $serviceId, null]; continue 2; } if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { $index = $attribute[$indexAttribute]; } elseif (null === $defaultIndex && $defaultPriorityMethod && $class) { $defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem); } $index = $index ?? $defaultIndex ?? ($defaultIndex = $serviceId); $services[] = [$priority, ++$i, $index, $serviceId, $class]; } } \uasort($services, static function ($a, $b) { return $b[0] <=> $a[0] ?: $a[1] <=> $b[1]; }); $refs = []; foreach ($services as [, , $index, $serviceId, $class]) { if (!$class) { $reference = new Reference($serviceId); } elseif ($index === $serviceId) { $reference = new TypedReference($serviceId, $class); } else { $reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index); } if (null === $index) { $refs[] = $reference; } else { $refs[$index] = $reference; } } return $refs; } } /** * @internal */ class PriorityTaggedServiceUtil { /** * @return string|int|null */ public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem) { if (!($r = $container->getReflectionClass($class)) || !$checkTaggedItem && !$r->hasMethod($defaultMethod)) { return null; } if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) { foreach ($r->getAttributes(AsTaggedItem::class) as $attribute) { return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index; } return null; } if (null !== $indexAttribute) { $service = $class !== $serviceId ? \sprintf('service "%s"', $serviceId) : 'on the corresponding service'; $message = [\sprintf('Either method "%s::%s()" should ', $class, $defaultMethod), \sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)]; } else { $message = [\sprintf('Method "%s::%s()" should ', $class, $defaultMethod), '.']; } if (!($rm = $r->getMethod($defaultMethod))->isStatic()) { throw new InvalidArgumentException(\implode('be static', $message)); } if (!$rm->isPublic()) { throw new InvalidArgumentException(\implode('be public', $message)); } $default = $rm->invoke(null); if ('priority' === $indexAttribute) { if (!\is_int($default)) { throw new InvalidArgumentException(\implode(\sprintf('return int (got "%s")', \get_debug_type($default)), $message)); } return $default; } if (\is_int($default)) { $default = (string) $default; } if (!\is_string($default)) { throw new InvalidArgumentException(\implode(\sprintf('return string|int (got "%s")', \get_debug_type($default)), $message)); } return $default; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Nicolas Grekas */ class RegisterReverseContainerPass implements CompilerPassInterface { private $beforeRemoving; private $serviceId; private $tagName; public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible') { if (1 < \func_num_args()) { \trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->beforeRemoving = $beforeRemoving; $this->serviceId = $serviceId; $this->tagName = $tagName; } public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->serviceId)) { return; } $refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; $services = []; foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) { $services[$id] = new Reference($id, $refType); } if ($this->beforeRemoving) { // prevent inlining of the reverse container $services[$this->serviceId] = new Reference($this->serviceId, $refType); } $locator = $container->getDefinition($this->serviceId)->getArgument(1); if ($locator instanceof Reference) { $locator = $container->getDefinition((string) $locator); } if ($locator instanceof Definition) { foreach ($services as $id => $ref) { $services[$id] = new ServiceClosureArgument($ref); } $locator->replaceArgument(0, $services); } else { $locator->setValues($services); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\ExpressionLanguage; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; /** * @author Nicolas Grekas */ abstract class AbstractRecursivePass implements CompilerPassInterface { /** * @var ContainerBuilder */ protected $container; protected $currentId; private $processExpressions = \false; private $expressionLanguage; private $inExpression = \false; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->container = $container; try { $this->processValue($container->getDefinitions(), \true); } finally { $this->container = null; } } protected function enableExpressionProcessing() { $this->processExpressions = \true; } protected function inExpression(bool $reset = \true) : bool { $inExpression = $this->inExpression; if ($reset) { $this->inExpression = \false; } return $inExpression; } /** * Processes a value found in a definition tree. * * @param mixed $value * * @return mixed */ protected function processValue($value, bool $isRoot = \false) { if (\is_array($value)) { foreach ($value as $k => $v) { if ($isRoot) { $this->currentId = $k; } if ($v !== ($processedValue = $this->processValue($v, $isRoot))) { $value[$k] = $processedValue; } } } elseif ($value instanceof ArgumentInterface) { $value->setValues($this->processValue($value->getValues())); } elseif ($value instanceof Expression && $this->processExpressions) { $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); } elseif ($value instanceof Definition) { $value->setArguments($this->processValue($value->getArguments())); $value->setProperties($this->processValue($value->getProperties())); $value->setMethodCalls($this->processValue($value->getMethodCalls())); $changes = $value->getChanges(); if (isset($changes['factory'])) { $value->setFactory($this->processValue($value->getFactory())); } if (isset($changes['configurator'])) { $value->setConfigurator($this->processValue($value->getConfigurator())); } } return $value; } /** * @return \ReflectionFunctionAbstract|null * * @throws RuntimeException */ protected function getConstructor(Definition $definition, bool $required) { if ($definition->isSynthetic()) { return null; } if (\is_string($factory = $definition->getFactory())) { if (!\function_exists($factory)) { throw new RuntimeException(\sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory)); } $r = new \ReflectionFunction($factory); if (\false !== $r->getFileName() && \file_exists($r->getFileName())) { $this->container->fileExists($r->getFileName()); } return $r; } if ($factory) { [$class, $method] = $factory; if ('__construct' === $method) { throw new RuntimeException(\sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId)); } if ($class instanceof Reference) { $factoryDefinition = $this->container->findDefinition((string) $class); while (null === ($class = $factoryDefinition->getClass()) && $factoryDefinition instanceof ChildDefinition) { $factoryDefinition = $this->container->findDefinition($factoryDefinition->getParent()); } } elseif ($class instanceof Definition) { $class = $class->getClass(); } elseif (null === $class) { $class = $definition->getClass(); } return $this->getReflectionMethod(new Definition($class), $method); } while (null === ($class = $definition->getClass()) && $definition instanceof ChildDefinition) { $definition = $this->container->findDefinition($definition->getParent()); } try { if (!($r = $this->container->getReflectionClass($class))) { if (null === $class) { throw new RuntimeException(\sprintf('Invalid service "%s": the class is not set.', $this->currentId)); } throw new RuntimeException(\sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); } } catch (\ReflectionException $e) { throw new RuntimeException(\sprintf('Invalid service "%s": ', $this->currentId) . \lcfirst($e->getMessage())); } if (!($r = $r->getConstructor())) { if ($required) { throw new RuntimeException(\sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, \sprintf($class !== $this->currentId ? ' "%s"' : '', $class))); } } elseif (!$r->isPublic()) { throw new RuntimeException(\sprintf('Invalid service "%s": ', $this->currentId) . \sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class) . ' must be public.'); } return $r; } /** * @return \ReflectionFunctionAbstract * * @throws RuntimeException */ protected function getReflectionMethod(Definition $definition, string $method) { if ('__construct' === $method) { return $this->getConstructor($definition, \true); } while (null === ($class = $definition->getClass()) && $definition instanceof ChildDefinition) { $definition = $this->container->findDefinition($definition->getParent()); } if (null === $class) { throw new RuntimeException(\sprintf('Invalid service "%s": the class is not set.', $this->currentId)); } if (!($r = $this->container->getReflectionClass($class))) { throw new RuntimeException(\sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); } if (!$r->hasMethod($method)) { if ($r->hasMethod('__call') && ($r = $r->getMethod('__call')) && $r->isPublic()) { return new \ReflectionMethod(static function (...$arguments) { }, '__invoke'); } throw new RuntimeException(\sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class . '::' . $method : $method)); } $r = $r->getMethod($method); if (!$r->isPublic()) { throw new RuntimeException(\sprintf('Invalid service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class . '::' . $method : $method)); } return $r; } private function getExpressionLanguage() : ExpressionLanguage { if (null === $this->expressionLanguage) { if (!\class_exists(ExpressionLanguage::class)) { throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } $providers = $this->container->getExpressionLanguageProviders(); $this->expressionLanguage = new ExpressionLanguage(null, $providers, function (string $arg) : string { if ('""' === \substr_replace($arg, '', 1, -1)) { $id = \stripcslashes(\substr($arg, 1, -1)); $this->inExpression = \true; $arg = $this->processValue(new Reference($id)); $this->inExpression = \false; if (!$arg instanceof Reference) { throw new RuntimeException(\sprintf('"%s::processValue()" must return a Reference when processing an expression, "%s" returned for service("%s").', static::class, \get_debug_type($arg), $id)); } $arg = \sprintf('"%s"', $arg); } return \sprintf('$this->get(%s)', $arg); }); } return $this->expressionLanguage; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass { private $tagName; private $aliases = []; public function __construct(string $tagName = 'container.private') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->tagName = $tagName; } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) { return new Reference($this->aliases[$id], $value->getInvalidBehavior()); } return parent::processValue($value, $isRoot); } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) { if (null === ($package = $tags[0]['package'] ?? null)) { throw new InvalidArgumentException(\sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id)); } if (null === ($version = $tags[0]['version'] ?? null)) { throw new InvalidArgumentException(\sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id)); } $definition = $container->getDefinition($id); if (!$definition->isPublic() || $definition->isPrivate()) { continue; } $container->setAlias($id, $aliasId = '.' . $this->tagName . '.' . $id)->setPublic(\true)->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); $container->setDefinition($aliasId, $definition); $this->aliases[$id] = $aliasId; } parent::process($container); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * Applies instanceof conditionals to definitions. * * @author Nicolas Grekas */ class ResolveInstanceofConditionalsPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) { if ($definition->getArguments()) { throw new InvalidArgumentException(\sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface)); } } $tagsToKeep = []; if ($container->hasParameter('container.behavior_describing_tags')) { $tagsToKeep = $container->getParameter('container.behavior_describing_tags'); } foreach ($container->getDefinitions() as $id => $definition) { $container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep)); } if ($container->hasParameter('container.behavior_describing_tags')) { $container->getParameterBag()->remove('container.behavior_describing_tags'); } } private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep) : Definition { $instanceofConditionals = $definition->getInstanceofConditionals(); $autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : []; if (!$instanceofConditionals && !$autoconfiguredInstanceof) { return $definition; } if (!($class = $container->getParameterBag()->resolveValue($definition->getClass()))) { return $definition; } $conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container); $definition->setInstanceofConditionals([]); $shared = null; $instanceofTags = []; $instanceofCalls = []; $instanceofBindings = []; $reflectionClass = null; $parent = $definition instanceof ChildDefinition ? $definition->getParent() : null; foreach ($conditionals as $interface => $instanceofDefs) { if ($interface !== $class && !($reflectionClass ?? ($reflectionClass = $container->getReflectionClass($class, \false) ?: \false))) { continue; } if ($interface !== $class && !\is_subclass_of($class, $interface)) { continue; } foreach ($instanceofDefs as $key => $instanceofDef) { /** @var ChildDefinition $instanceofDef */ $instanceofDef = clone $instanceofDef; $instanceofDef->setAbstract(\true)->setParent($parent ?: '.abstract.instanceof.' . $id); $parent = '.instanceof.' . $interface . '.' . $key . '.' . $id; $container->setDefinition($parent, $instanceofDef); $instanceofTags[] = [$interface, $instanceofDef->getTags()]; $instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings; foreach ($instanceofDef->getMethodCalls() as $methodCall) { $instanceofCalls[] = $methodCall; } $instanceofDef->setTags([]); $instanceofDef->setMethodCalls([]); $instanceofDef->setBindings([]); if (isset($instanceofDef->getChanges()['shared'])) { $shared = $instanceofDef->isShared(); } } } if ($parent) { $bindings = $definition->getBindings(); $abstract = $container->setDefinition('.abstract.instanceof.' . $id, $definition); $definition->setBindings([]); $definition = \serialize($definition); if (Definition::class === \get_class($abstract)) { // cast Definition to ChildDefinition $definition = \substr_replace($definition, '68', 2, 2); $definition = \substr_replace($definition, 'Child', 59, 0); } /** @var ChildDefinition $definition */ $definition = \unserialize($definition); $definition->setParent($parent); if (null !== $shared && !isset($definition->getChanges()['shared'])) { $definition->setShared($shared); } // Don't add tags to service decorators $i = \count($instanceofTags); while (0 <= --$i) { [$interface, $tags] = $instanceofTags[$i]; foreach ($tags as $k => $v) { if (null === $definition->getDecoratedService() || $interface === $definition->getClass() || \in_array($k, $tagsToKeep, \true)) { foreach ($v as $v) { if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { continue; } $definition->addTag($k, $v); } } } } $definition->setMethodCalls(\array_merge($instanceofCalls, $definition->getMethodCalls())); $definition->setBindings($bindings + $instanceofBindings); // reset fields with "merge" behavior $abstract->setBindings([])->setArguments([])->setMethodCalls([])->setDecoratedService(null)->setTags([])->setAbstract(\true); } return $definition; } private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container) : array { // make each value an array of ChildDefinition $conditionals = \array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof); foreach ($instanceofConditionals as $interface => $instanceofDef) { // make sure the interface/class exists (but don't validate automaticInstanceofConditionals) if (!$container->getReflectionClass($interface)) { throw new RuntimeException(\sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface)); } if (!isset($autoconfiguredInstanceof[$interface])) { $conditionals[$interface] = []; } $conditionals[$interface][] = $instanceofDef; } return $conditionals; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Replaces all references to aliases with references to the actual service. * * @author Johannes M. Schmitt */ class ResolveReferencesToAliasesPass extends AbstractRecursivePass { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { parent::process($container); foreach ($container->getAliases() as $id => $alias) { $aliasId = (string) $alias; $this->currentId = $id; if ($aliasId !== ($defId = $this->getDefinitionId($aliasId, $container))) { $container->setAlias($id, $defId)->setPublic($alias->isPublic()); } } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); } $defId = $this->getDefinitionId($id = (string) $value, $this->container); return $defId !== $id ? new Reference($defId, $value->getInvalidBehavior()) : $value; } private function getDefinitionId(string $id, ContainerBuilder $container) : string { if (!$container->hasAlias($id)) { return $id; } $alias = $container->getAlias($id); if ($alias->isDeprecated()) { $referencingDefinition = $container->hasDefinition($this->currentId) ? $container->getDefinition($this->currentId) : $container->getAlias($this->currentId); if (!$referencingDefinition->isDeprecated()) { $deprecation = $alias->getDeprecation($id); \trigger_deprecation($deprecation['package'], $deprecation['version'], \rtrim($deprecation['message'], '. ') . '. It is being referenced by the "%s" ' . ($container->hasDefinition($this->currentId) ? 'service.' : 'alias.'), $this->currentId); } } $seen = []; do { if (isset($seen[$id])) { throw new ServiceCircularReferenceException($id, \array_merge(\array_keys($seen), [$id])); } $seen[$id] = \true; $id = (string) $container->getAlias($id); } while ($container->hasAlias($id)); return $id; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * Checks if arguments of methods are properly configured. * * @author Kévin Dunglas * @author Nicolas Grekas */ class CheckArgumentsValidityPass extends AbstractRecursivePass { private $throwExceptions; public function __construct(bool $throwExceptions = \true) { $this->throwExceptions = $throwExceptions; } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } $i = 0; $hasNamedArgs = \false; foreach ($value->getArguments() as $k => $v) { if (\PHP_VERSION_ID >= 80000 && \preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $k)) { $hasNamedArgs = \true; continue; } if ($k !== $i++) { if (!\is_int($k)) { $msg = \sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k); $value->addError($msg); if ($this->throwExceptions) { throw new RuntimeException($msg); } break; } $msg = \sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i); $value->addError($msg); if ($this->throwExceptions) { throw new RuntimeException($msg); } } if ($hasNamedArgs) { $msg = \sprintf('Invalid constructor argument for service "%s": cannot use positional argument after named argument. Check your service definition.', $this->currentId); $value->addError($msg); if ($this->throwExceptions) { throw new RuntimeException($msg); } break; } } foreach ($value->getMethodCalls() as $methodCall) { $i = 0; $hasNamedArgs = \false; foreach ($methodCall[1] as $k => $v) { if (\PHP_VERSION_ID >= 80000 && \preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $k)) { $hasNamedArgs = \true; continue; } if ($k !== $i++) { if (!\is_int($k)) { $msg = \sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k); $value->addError($msg); if ($this->throwExceptions) { throw new RuntimeException($msg); } break; } $msg = \sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i); $value->addError($msg); if ($this->throwExceptions) { throw new RuntimeException($msg); } } if ($hasNamedArgs) { $msg = \sprintf('Invalid argument for method call "%s" of service "%s": cannot use positional argument after named argument. Check your service definition.', $methodCall[0], $this->currentId); $value->addError($msg); if ($this->throwExceptions) { throw new RuntimeException($msg); } break; } } } return null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; /** * @author Alexander M. Turek */ final class AttributeAutoconfigurationPass extends AbstractRecursivePass { private $classAttributeConfigurators = []; private $methodAttributeConfigurators = []; private $propertyAttributeConfigurators = []; private $parameterAttributeConfigurators = []; public function process(ContainerBuilder $container) : void { if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) { return; } foreach ($container->getAutoconfiguredAttributes() as $attributeName => $callable) { $callableReflector = new \ReflectionFunction(\Closure::fromCallable($callable)); if ($callableReflector->getNumberOfParameters() <= 2) { $this->classAttributeConfigurators[$attributeName] = $callable; continue; } $reflectorParameter = $callableReflector->getParameters()[2]; $parameterType = $reflectorParameter->getType(); $types = []; if ($parameterType instanceof \ReflectionUnionType) { foreach ($parameterType->getTypes() as $type) { $types[] = $type->getName(); } } elseif ($parameterType instanceof \ReflectionNamedType) { $types[] = $parameterType->getName(); } else { throw new LogicException(\sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\\ReflectionClass|\\ReflectionMethod|\\ReflectionProperty|\\ReflectionParameter|\\Reflector" in "%s" on line "%d".', $reflectorParameter->getName(), $callableReflector->getFileName(), $callableReflector->getStartLine())); } try { $attributeReflector = new \ReflectionClass($attributeName); } catch (\ReflectionException $e) { continue; } $targets = $attributeReflector->getAttributes(\Attribute::class)[0] ?? 0; $targets = $targets ? $targets->getArguments()[0] ?? -1 : 0; foreach (['class', 'method', 'property', 'parameter'] as $symbol) { if (['Reflector'] !== $types) { if (!\in_array('Reflection' . \ucfirst($symbol), $types, \true)) { continue; } if (!($targets & \constant('Attribute::TARGET_' . \strtoupper($symbol)))) { throw new LogicException(\sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a ' . $symbol . ' in "%s" on line "%d".', \ucfirst($symbol), $reflectorParameter->getName(), $attributeName, $callableReflector->getFileName(), $callableReflector->getStartLine())); } } $this->{$symbol . 'AttributeConfigurators'}[$attributeName] = $callable; } } parent::process($container); } protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Definition || !$value->isAutoconfigured() || $value->isAbstract() || $value->hasTag('container.ignore_attributes') || !($classReflector = $this->container->getReflectionClass($value->getClass(), \false))) { return parent::processValue($value, $isRoot); } $instanceof = $value->getInstanceofConditionals(); $conditionals = $instanceof[$classReflector->getName()] ?? new ChildDefinition(''); if ($this->classAttributeConfigurators) { foreach ($classReflector->getAttributes() as $attribute) { if ($configurator = $this->classAttributeConfigurators[$attribute->getName()] ?? null) { $configurator($conditionals, $attribute->newInstance(), $classReflector); } } } if ($this->parameterAttributeConfigurators) { try { $constructorReflector = $this->getConstructor($value, \false); } catch (RuntimeException $e) { $constructorReflector = null; } if ($constructorReflector) { foreach ($constructorReflector->getParameters() as $parameterReflector) { foreach ($parameterReflector->getAttributes() as $attribute) { if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) { $configurator($conditionals, $attribute->newInstance(), $parameterReflector); } } } } } if ($this->methodAttributeConfigurators || $this->parameterAttributeConfigurators) { foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflector) { if ($methodReflector->isStatic() || $methodReflector->isConstructor() || $methodReflector->isDestructor()) { continue; } if ($this->methodAttributeConfigurators) { foreach ($methodReflector->getAttributes() as $attribute) { if ($configurator = $this->methodAttributeConfigurators[$attribute->getName()] ?? null) { $configurator($conditionals, $attribute->newInstance(), $methodReflector); } } } if ($this->parameterAttributeConfigurators) { foreach ($methodReflector->getParameters() as $parameterReflector) { foreach ($parameterReflector->getAttributes() as $attribute) { if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) { $configurator($conditionals, $attribute->newInstance(), $parameterReflector); } } } } } } if ($this->propertyAttributeConfigurators) { foreach ($classReflector->getProperties(\ReflectionProperty::IS_PUBLIC) as $propertyReflector) { if ($propertyReflector->isStatic()) { continue; } foreach ($propertyReflector->getAttributes() as $attribute) { if ($configurator = $this->propertyAttributeConfigurators[$attribute->getName()] ?? null) { $configurator($conditionals, $attribute->newInstance(), $propertyReflector); } } } } if (!isset($instanceof[$classReflector->getName()]) && new ChildDefinition('') != $conditionals) { $instanceof[$classReflector->getName()] = $conditionals; $value->setInstanceofConditionals($instanceof); } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; \trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s" class is deprecated.', ResolvePrivatesPass::class); use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * @author Nicolas Grekas * * @deprecated since Symfony 5.2 */ class ResolvePrivatesPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isPrivate()) { $definition->setPublic(\false); $definition->setPrivate(\true); } } foreach ($container->getAliases() as $id => $alias) { if ($alias->isPrivate()) { $alias->setPublic(\false); $alias->setPrivate(\true); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; use _ContaoManager\Symfony\Contracts\Service\Attribute\Required; /** * Looks for definitions with autowiring enabled and registers their corresponding "@required" properties. * * @author Sebastien Morel (Plopix) * @author Nicolas Grekas */ class AutowireRequiredPropertiesPass extends AbstractRecursivePass { /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if (\PHP_VERSION_ID < 70400) { return $value; } $value = parent::processValue($value, $isRoot); if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { return $value; } if (!($reflectionClass = $this->container->getReflectionClass($value->getClass(), \false))) { return $value; } $properties = $value->getProperties(); foreach ($reflectionClass->getProperties() as $reflectionProperty) { if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) { continue; } if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class)) && (\false === ($doc = $reflectionProperty->getDocComment()) || \false === \stripos($doc, '@required') || !\preg_match('#(?:^/\\*\\*|\\n\\s*+\\*)\\s*+@required(?:\\s|\\*/$)#i', $doc))) { continue; } if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) { continue; } $type = $type->getName(); $value->setProperty($name, new TypedReference($type, $type, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name)); } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Resolves named arguments to their corresponding numeric index. * * @author Kévin Dunglas */ class ResolveNamedArgumentsPass extends AbstractRecursivePass { /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof AbstractArgument && $value->getText() . '.' === $value->getTextWithContext()) { $value->setContext(\sprintf('A value found in service "%s"', $this->currentId)); } if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } $calls = $value->getMethodCalls(); $calls[] = ['__construct', $value->getArguments()]; foreach ($calls as $i => $call) { [$method, $arguments] = $call; $parameters = null; $resolvedKeys = []; $resolvedArguments = []; foreach ($arguments as $key => $argument) { if ($argument instanceof AbstractArgument && $argument->getText() . '.' === $argument->getTextWithContext()) { $argument->setContext(\sprintf('Argument ' . (\is_int($key) ? 1 + $key : '"%3$s"') . ' of ' . ('__construct' === $method ? 'service "%s"' : 'method call "%s::%s()"'), $this->currentId, $method, $key)); } if (\is_int($key)) { $resolvedKeys[$key] = $key; $resolvedArguments[$key] = $argument; continue; } if (null === $parameters) { $r = $this->getReflectionMethod($value, $method); $class = $r instanceof \ReflectionMethod ? $r->class : $this->currentId; $method = $r->getName(); $parameters = $r->getParameters(); } if (isset($key[0]) && '$' !== $key[0] && !\class_exists($key) && !\interface_exists($key, \false)) { throw new InvalidArgumentException(\sprintf('Invalid service "%s": did you forget to add the "$" prefix to argument "%s"?', $this->currentId, $key)); } if (isset($key[0]) && '$' === $key[0]) { foreach ($parameters as $j => $p) { if ($key === '$' . $p->name) { if ($p->isVariadic() && \is_array($argument)) { foreach ($argument as $variadicArgument) { $resolvedKeys[$j] = $j; $resolvedArguments[$j++] = $variadicArgument; } } else { $resolvedKeys[$j] = $p->name; $resolvedArguments[$j] = $argument; } continue 2; } } throw new InvalidArgumentException(\sprintf('Invalid service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class . '::' . $method : $method, $key)); } if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) { throw new InvalidArgumentException(\sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of "%s" or an instance of "%s", "%s" given.', $this->currentId, $key, $class !== $this->currentId ? $class . '::' . $method : $method, Reference::class, Definition::class, \get_debug_type($argument))); } $typeFound = \false; foreach ($parameters as $j => $p) { if (!\array_key_exists($j, $resolvedArguments) && ProxyHelper::getTypeHint($r, $p, \true) === $key) { $resolvedKeys[$j] = $p->name; $resolvedArguments[$j] = $argument; $typeFound = \true; } } if (!$typeFound) { throw new InvalidArgumentException(\sprintf('Invalid service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class . '::' . $method : $method, $key)); } } if ($resolvedArguments !== $call[1]) { \ksort($resolvedArguments); if (!$value->isAutowired() && !\array_is_list($resolvedArguments)) { \ksort($resolvedKeys); $resolvedArguments = \array_combine($resolvedKeys, $resolvedArguments); } $calls[$i][1] = $resolvedArguments; } } [, $arguments] = \array_pop($calls); if ($arguments !== $value->getArguments()) { $value->setArguments($arguments); } if ($calls !== $value->getMethodCalls()) { $value->setMethodCalls($calls); } foreach ($value->getProperties() as $key => $argument) { if ($argument instanceof AbstractArgument && $argument->getText() . '.' === $argument->getTextWithContext()) { $argument->setContext(\sprintf('Property "%s" of service "%s"', $key, $this->currentId)); } } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Remove private aliases from the container. They were only used to establish * dependencies between services, and these dependencies have been resolved in * one of the previous passes. * * @author Johannes M. Schmitt */ class RemovePrivateAliasesPass implements CompilerPassInterface { /** * Removes private aliases from the ContainerBuilder. */ public function process(ContainerBuilder $container) { foreach ($container->getAliases() as $id => $alias) { if ($alias->isPublic()) { continue; } $container->removeAlias($id); $container->log($this, \sprintf('Removed service "%s"; reason: private alias.', $id)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\Config\Definition\BaseNode; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\Extension; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; /** * Merges extension configs into the container builder. * * @author Fabien Potencier */ class MergeExtensionConfigurationPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $parameters = $container->getParameterBag()->all(); $definitions = $container->getDefinitions(); $aliases = $container->getAliases(); $exprLangProviders = $container->getExpressionLanguageProviders(); $configAvailable = \class_exists(BaseNode::class); foreach ($container->getExtensions() as $extension) { if ($extension instanceof PrependExtensionInterface) { $extension->prepend($container); } } foreach ($container->getExtensions() as $name => $extension) { if (!($config = $container->getExtensionConfig($name))) { // this extension was not called continue; } $resolvingBag = $container->getParameterBag(); if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) { // create a dedicated bag so that we can track env vars per-extension $resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag); if ($configAvailable) { BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix()); } } $config = $resolvingBag->resolveValue($config); try { $tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag); $tmpContainer->setResourceTracking($container->isTrackingResources()); $tmpContainer->addObjectResource($extension); if ($extension instanceof ConfigurationExtensionInterface && null !== ($configuration = $extension->getConfiguration($config, $tmpContainer))) { $tmpContainer->addObjectResource($configuration); } foreach ($exprLangProviders as $provider) { $tmpContainer->addExpressionLanguageProvider($provider); } $extension->load($config, $tmpContainer); } catch (\Exception $e) { if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { $container->getParameterBag()->mergeEnvPlaceholders($resolvingBag); } throw $e; } if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { // don't keep track of env vars that are *overridden* when configs are merged $resolvingBag->freezeAfterProcessing($extension, $tmpContainer); } $container->merge($tmpContainer); $container->getParameterBag()->add($parameters); } $container->addDefinitions($definitions); $container->addAliases($aliases); } } /** * @internal */ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag { private $processedEnvPlaceholders; public function __construct(parent $parameterBag) { parent::__construct($parameterBag->all()); $this->mergeEnvPlaceholders($parameterBag); } public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container) { if (!($config = $extension->getProcessedConfigs())) { // Extension::processConfiguration() wasn't called, we cannot know how configs were merged return; } $this->processedEnvPlaceholders = []; // serialize config and container to catch env vars nested in object graphs $config = \serialize($config) . \serialize($container->getDefinitions()) . \serialize($container->getAliases()) . \serialize($container->getParameterBag()->all()); foreach (parent::getEnvPlaceholders() as $env => $placeholders) { foreach ($placeholders as $placeholder) { if (\false !== \stripos($config, $placeholder)) { $this->processedEnvPlaceholders[$env] = $placeholders; break; } } } } /** * {@inheritdoc} */ public function getEnvPlaceholders() : array { return $this->processedEnvPlaceholders ?? parent::getEnvPlaceholders(); } public function getUnusedEnvPlaceholders() : array { return null === $this->processedEnvPlaceholders ? [] : \array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders); } } /** * A container builder preventing using methods that wouldn't have any effect from extensions. * * @internal */ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder { private $extensionClass; public function __construct(ExtensionInterface $extension, ?ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); $this->extensionClass = \get_class($extension); } /** * {@inheritdoc} */ public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) : self { throw new LogicException(\sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', \get_debug_type($pass), $this->extensionClass)); } /** * {@inheritdoc} */ public function registerExtension(ExtensionInterface $extension) { throw new LogicException(\sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', \get_debug_type($extension), $this->extensionClass)); } /** * {@inheritdoc} */ public function compile(bool $resolveEnvPlaceholders = \false) { throw new LogicException(\sprintf('Cannot compile the container in extension "%s".', $this->extensionClass)); } /** * {@inheritdoc} */ public function resolveEnvPlaceholders($value, $format = null, ?array &$usedEnvs = null) { if (\true !== $format || !\is_string($value)) { return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); } $bag = $this->getParameterBag(); $value = $bag->resolveValue($value); if (!$bag instanceof EnvPlaceholderParameterBag) { return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); } foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { if (!\str_contains($env, ':')) { continue; } foreach ($placeholders as $placeholder) { if (\false !== \stripos($value, $placeholder)) { throw new RuntimeException(\sprintf('Using a cast in "env(%s)" is incompatible with resolution at compile time in "%s". The logic in the extension should be moved to a compiler pass, or an env parameter with no cast should be used instead.', $env, $this->extensionClass)); } } } return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; /** * Represents an edge in your service graph. * * Value is typically a reference. * * @author Johannes M. Schmitt */ class ServiceReferenceGraphEdge { private $sourceNode; private $destNode; private $value; private $lazy; private $weak; private $byConstructor; public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, bool $lazy = \false, bool $weak = \false, bool $byConstructor = \false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; $this->value = $value; $this->lazy = $lazy; $this->weak = $weak; $this->byConstructor = $byConstructor; } /** * Returns the value of the edge. * * @return mixed */ public function getValue() { return $this->value; } /** * Returns the source node. * * @return ServiceReferenceGraphNode */ public function getSourceNode() { return $this->sourceNode; } /** * Returns the destination node. * * @return ServiceReferenceGraphNode */ public function getDestNode() { return $this->destNode; } /** * Returns true if the edge is lazy, meaning it's a dependency not requiring direct instantiation. * * @return bool */ public function isLazy() { return $this->lazy; } /** * Returns true if the edge is weak, meaning it shouldn't prevent removing the target service. * * @return bool */ public function isWeak() { return $this->weak; } /** * Returns true if the edge links with a constructor argument. * * @return bool */ public function isReferencedByConstructor() { return $this->byConstructor; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Throws an exception for any Definitions that have errors and still exist. * * @author Ryan Weaver */ class DefinitionErrorExceptionPass extends AbstractRecursivePass { private $erroredDefinitions = []; private $sourceReferences = []; /** * @return void */ public function process(ContainerBuilder $container) { try { parent::process($container); $visitedIds = []; foreach ($this->erroredDefinitions as $id => $definition) { if ($this->isErrorForRuntime($id, $visitedIds)) { continue; } // only show the first error so the user can focus on it $errors = $definition->getErrors(); throw new RuntimeException(\reset($errors)); } } finally { $this->erroredDefinitions = []; $this->sourceReferences = []; } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof ArgumentInterface) { parent::processValue($value->getValues()); return $value; } if ($value instanceof Reference && $this->currentId !== ($targetId = (string) $value)) { if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { $this->sourceReferences[$targetId][$this->currentId] ?? ($this->sourceReferences[$targetId][$this->currentId] = \true); } else { $this->sourceReferences[$targetId][$this->currentId] = \false; } return $value; } if (!$value instanceof Definition || !$value->hasErrors()) { return parent::processValue($value, $isRoot); } $this->erroredDefinitions[$this->currentId] = $value; return parent::processValue($value); } private function isErrorForRuntime(string $id, array &$visitedIds) : bool { if (!isset($this->sourceReferences[$id])) { return \false; } if (isset($visitedIds[$id])) { return $visitedIds[$id]; } $visitedIds[$id] = \true; foreach ($this->sourceReferences[$id] as $sourceId => $isRuntime) { if ($visitedIds[$sourceId] ?? ($visitedIds[$sourceId] = $this->isErrorForRuntime($sourceId, $visitedIds))) { continue; } if (!$isRuntime) { return \false; } } return \true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Removes unused service definitions from the container. * * @author Johannes M. Schmitt * @author Nicolas Grekas */ class RemoveUnusedDefinitionsPass extends AbstractRecursivePass { private $connectedIds = []; /** * Processes the ContainerBuilder to remove unused definitions. */ public function process(ContainerBuilder $container) { try { $this->enableExpressionProcessing(); $this->container = $container; $connectedIds = []; $aliases = $container->getAliases(); foreach ($aliases as $id => $alias) { if ($alias->isPublic()) { $this->connectedIds[] = (string) $aliases[$id]; } } foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isPublic()) { $connectedIds[$id] = \true; $this->processValue($definition); } } while ($this->connectedIds) { $ids = $this->connectedIds; $this->connectedIds = []; foreach ($ids as $id) { if (!isset($connectedIds[$id]) && $container->hasDefinition($id)) { $connectedIds[$id] = \true; $this->processValue($container->getDefinition($id)); } } } foreach ($container->getDefinitions() as $id => $definition) { if (!isset($connectedIds[$id])) { $container->removeDefinition($id); $container->resolveEnvPlaceholders(!$definition->hasErrors() ? \serialize($definition) : $definition); $container->log($this, \sprintf('Removed service "%s"; reason: unused.', $id)); } } } finally { $this->container = null; $this->connectedIds = []; } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); } if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) { $this->connectedIds[] = (string) $value; } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\Config\Resource\ClassExistenceResource; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\TaggedIterator; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\TaggedLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Attribute\Target; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; /** * Inspects existing service definitions and wires the autowired ones using the type hints of their classes. * * @author Kévin Dunglas * @author Nicolas Grekas */ class AutowirePass extends AbstractRecursivePass { private $types; private $ambiguousServiceTypes; private $autowiringAliases; private $lastFailure; private $throwOnAutowiringException; private $decoratedClass; private $decoratedId; private $methodCalls; private $defaultArgument; private $getPreviousValue; private $decoratedMethodIndex; private $decoratedMethodArgumentIndex; private $typesClone; public function __construct(bool $throwOnAutowireException = \true) { $this->throwOnAutowiringException = $throwOnAutowireException; $this->defaultArgument = new class { public $value; public $names; public $bag; public function withValue(\ReflectionParameter $parameter) : self { $clone = clone $this; $clone->value = $this->bag->escapeValue($parameter->getDefaultValue()); return $clone; } }; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->defaultArgument->bag = $container->getParameterBag(); try { $this->typesClone = clone $this; parent::process($container); } finally { $this->decoratedClass = null; $this->decoratedId = null; $this->methodCalls = null; $this->defaultArgument->bag = null; $this->defaultArgument->names = null; $this->getPreviousValue = null; $this->decoratedMethodIndex = null; $this->decoratedMethodArgumentIndex = null; $this->typesClone = null; } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { try { return $this->doProcessValue($value, $isRoot); } catch (AutowiringFailedException $e) { if ($this->throwOnAutowiringException) { throw $e; } $this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage()); return parent::processValue($value, $isRoot); } } /** * @return mixed */ private function doProcessValue($value, bool $isRoot = \false) { if ($value instanceof TypedReference) { if ($ref = $this->getAutowiredReference($value, \true)) { return $ref; } if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { $message = $this->createTypeNotFoundMessageCallback($value, 'it'); // since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition $this->container->register($id = \sprintf('.errored.%s.%s', $this->currentId, (string) $value), $value->getType())->addError($message); return new TypedReference($id, $value->getType(), $value->getInvalidBehavior(), $value->getName()); } } $value = parent::processValue($value, $isRoot); if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { return $value; } if (!($reflectionClass = $this->container->getReflectionClass($value->getClass(), \false))) { $this->container->log($this, \sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); return $value; } $this->methodCalls = $value->getMethodCalls(); try { $constructor = $this->getConstructor($value, \false); } catch (RuntimeException $e) { throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); } if ($constructor) { \array_unshift($this->methodCalls, [$constructor, $value->getArguments()]); } $checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes'); $this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes); if ($constructor) { [, $arguments] = \array_shift($this->methodCalls); if ($arguments !== $value->getArguments()) { $value->setArguments($arguments); } } if ($this->methodCalls !== $value->getMethodCalls()) { $value->setMethodCalls($this->methodCalls); } return $value; } private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes) : array { $this->decoratedId = null; $this->decoratedClass = null; $this->getPreviousValue = null; if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) { $this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass(); } $patchedIndexes = []; foreach ($this->methodCalls as $i => $call) { [$method, $arguments] = $call; if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; } else { $definition = new Definition($reflectionClass->name); try { $reflectionMethod = $this->getReflectionMethod($definition, $method); } catch (RuntimeException $e) { if ($definition->getFactory()) { continue; } throw $e; } } $arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes, $i); if ($arguments !== $call[1]) { $this->methodCalls[$i][1] = $arguments; $patchedIndexes[] = $i; } } // use named arguments to skip complex default values foreach ($patchedIndexes as $i) { $namedArguments = null; $arguments = $this->methodCalls[$i][1]; foreach ($arguments as $j => $value) { if ($namedArguments && !$value instanceof $this->defaultArgument) { unset($arguments[$j]); $arguments[$namedArguments[$j]] = $value; } if (!$value instanceof $this->defaultArgument) { continue; } if (\PHP_VERSION_ID >= 80100 && (\is_array($value->value) ? $value->value : \is_object($value->value))) { $namedArguments = $value->names; } if ($namedArguments) { unset($arguments[$j]); } else { $arguments[$j] = $value->value; } } $this->methodCalls[$i][1] = $arguments; } return $this->methodCalls; } /** * Autowires the constructor or a method. * * @throws AutowiringFailedException */ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes, int $methodIndex) : array { $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId; $method = $reflectionMethod->name; $parameters = $reflectionMethod->getParameters(); if ($reflectionMethod->isVariadic()) { \array_pop($parameters); } $this->defaultArgument->names = new \ArrayObject(); foreach ($parameters as $index => $parameter) { $this->defaultArgument->names[$index] = $parameter->name; if (\array_key_exists($parameter->name, $arguments)) { $arguments[$index] = $arguments[$parameter->name]; unset($arguments[$parameter->name]); } if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) { continue; } $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, \true); if ($checkAttributes) { foreach ($parameter->getAttributes() as $attribute) { if (TaggedIterator::class === $attribute->getName()) { $attribute = $attribute->newInstance(); $arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, \false, $attribute->defaultPriorityMethod); break; } if (TaggedLocator::class === $attribute->getName()) { $attribute = $attribute->newInstance(); $arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, \true, $attribute->defaultPriorityMethod)); break; } } if ('' !== ($arguments[$index] ?? '')) { continue; } } if (!$type) { if (isset($arguments[$index])) { continue; } // no default value? Then fail if (!$parameter->isDefaultValueAvailable()) { // For core classes, isDefaultValueAvailable() can // be false when isOptional() returns true. If the // argument *is* optional, allow it to be missing if ($parameter->isOptional()) { --$index; break; } $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, \false); $type = $type ? \sprintf('is type-hinted "%s"', \ltrim($type, '\\')) : 'has no type-hint'; throw new AutowiringFailedException($this->currentId, \sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class . '::' . $method : $method, $type)); } // specifically pass the default value $arguments[$index] = $this->defaultArgument->withValue($parameter); continue; } $getValue = function () use($type, $parameter, $class, $method) { if (!($value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), \true))) { $failureMessage = $this->createTypeNotFoundMessageCallback($ref, \sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class . '::' . $method : $method)); if ($parameter->isDefaultValueAvailable()) { $value = $this->defaultArgument->withValue($parameter); } elseif (!$parameter->allowsNull()) { throw new AutowiringFailedException($this->currentId, $failureMessage); } } return $value; }; if ($this->decoratedClass && ($isDecorated = \is_a($this->decoratedClass, $type, \true))) { if ($this->getPreviousValue) { // The inner service is injected only if there is only 1 argument matching the type of the decorated class // across all arguments of all autowired methods. // If a second matching argument is found, the default behavior is restored. $getPreviousValue = $this->getPreviousValue; $this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue(); $this->decoratedClass = null; // Prevent further checks } else { $arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass); $this->getPreviousValue = $getValue; $this->decoratedMethodIndex = $methodIndex; $this->decoratedMethodArgumentIndex = $index; continue; } } $arguments[$index] = $getValue(); } if ($parameters && !isset($arguments[++$index])) { while (0 <= --$index) { if (!$arguments[$index] instanceof $this->defaultArgument) { break; } unset($arguments[$index]); } } // it's possible index 1 was set, then index 0, then 2, etc // make sure that we re-order so they're injected as expected \ksort($arguments, \SORT_NATURAL); return $arguments; } /** * Returns a reference to the service matching the given type, if any. */ private function getAutowiredReference(TypedReference $reference, bool $filterType) : ?TypedReference { $this->lastFailure = null; $type = $reference->getType(); if ($type !== (string) $reference) { return $reference; } if ($filterType && \false !== ($m = \strpbrk($type, '&|'))) { $types = \array_diff(\explode($m[0], $type), ['int', 'string', 'array', 'bool', 'float', 'iterable', 'object', 'callable', 'null']); \sort($types); $type = \implode($m[0], $types); } if (null !== ($name = $reference->getName())) { if ($this->container->has($alias = $type . ' $' . $name) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } if (null !== ($alias = $this->getCombinedAlias($type, $name) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) { foreach ($this->container->getAliases() as $id => $alias) { if ($name === (string) $alias && \str_starts_with($id, $type . ' $')) { return new TypedReference($name, $type, $reference->getInvalidBehavior()); } } } } if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { return new TypedReference($type, $type, $reference->getInvalidBehavior()); } if (null !== ($alias = $this->getCombinedAlias($type) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } return null; } /** * Populates the list of available types. */ private function populateAvailableTypes(ContainerBuilder $container) { $this->types = []; $this->ambiguousServiceTypes = []; $this->autowiringAliases = []; foreach ($container->getDefinitions() as $id => $definition) { $this->populateAvailableType($container, $id, $definition); } foreach ($container->getAliases() as $id => $alias) { $this->populateAutowiringAlias($id); } } /** * Populates the list of available types for a given definition. */ private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition) { // Never use abstract services if ($definition->isAbstract()) { return; } if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !($reflectionClass = $container->getReflectionClass($definition->getClass(), \false))) { return; } foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { $this->set($reflectionInterface->name, $id); } do { $this->set($reflectionClass->name, $id); } while ($reflectionClass = $reflectionClass->getParentClass()); $this->populateAutowiringAlias($id); } /** * Associates a type and a service id if applicable. */ private function set(string $type, string $id) { // is this already a type/class that is known to match multiple services? if (isset($this->ambiguousServiceTypes[$type])) { $this->ambiguousServiceTypes[$type][] = $id; return; } // check to make sure the type doesn't match multiple services if (!isset($this->types[$type]) || $this->types[$type] === $id) { $this->types[$type] = $id; return; } // keep an array of all services matching this type if (!isset($this->ambiguousServiceTypes[$type])) { $this->ambiguousServiceTypes[$type] = [$this->types[$type]]; unset($this->types[$type]); } $this->ambiguousServiceTypes[$type][] = $id; } private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label) : \Closure { if (null === $this->typesClone->container) { $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag()); $this->typesClone->container->setAliases($this->container->getAliases()); $this->typesClone->container->setDefinitions($this->container->getDefinitions()); $this->typesClone->container->setResourceTracking(\false); } $currentId = $this->currentId; return (function () use($reference, $label, $currentId) { return $this->createTypeNotFoundMessage($reference, $label, $currentId); })->bindTo($this->typesClone); } private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId) : string { if (!($r = $this->container->getReflectionClass($type = $reference->getType(), \false))) { // either $type does not exist or a parent class does not exist try { $resource = new ClassExistenceResource($type, \false); // isFresh() will explode ONLY if a parent class/trait does not exist $resource->isFresh(0); $parentMsg = \false; } catch (\ReflectionException $e) { $parentMsg = $e->getMessage(); } $message = \sprintf('has type "%s" but this class %s.', $type, $parentMsg ? \sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); } else { $alternatives = $this->createTypeAlternatives($this->container, $reference); $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; $message = \sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); if ($r->isInterface() && !$alternatives) { $message .= ' Did you create a class that implements this interface?'; } } $message = \sprintf('Cannot autowire service "%s": %s %s', $currentId, $label, $message); if (null !== $this->lastFailure) { $message = $this->lastFailure . "\n" . $message; $this->lastFailure = null; } return $message; } private function createTypeAlternatives(ContainerBuilder $container, TypedReference $reference) : string { // try suggesting available aliases first if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) { return ' ' . $message; } if (null === $this->ambiguousServiceTypes) { $this->populateAvailableTypes($container); } $servicesAndAliases = $container->getServiceIds(); if (null !== ($autowiringAliases = $this->autowiringAliases[$type] ?? null) && !isset($autowiringAliases[''])) { return \sprintf(' Available autowiring aliases for this %s are: "$%s".', \class_exists($type, \false) ? 'class' : 'interface', \implode('", "$', $autowiringAliases)); } if (!$container->has($type) && \false !== ($key = \array_search(\strtolower($type), \array_map('strtolower', $servicesAndAliases)))) { return \sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]); } elseif (isset($this->ambiguousServiceTypes[$type])) { $message = \sprintf('one of these existing services: "%s"', \implode('", "', $this->ambiguousServiceTypes[$type])); } elseif (isset($this->types[$type])) { $message = \sprintf('the existing "%s" service', $this->types[$type]); } else { return ''; } return \sprintf(' You should maybe alias this %s to %s.', \class_exists($type, \false) ? 'class' : 'interface', $message); } private function getAliasesSuggestionForType(ContainerBuilder $container, string $type) : ?string { $aliases = []; foreach (\class_parents($type) + \class_implements($type) as $parent) { if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) { $aliases[] = $parent; } } if (1 < ($len = \count($aliases))) { $message = 'Try changing the type-hint to one of its parents: '; for ($i = 0, --$len; $i < $len; ++$i) { $message .= \sprintf('%s "%s", ', \class_exists($aliases[$i], \false) ? 'class' : 'interface', $aliases[$i]); } $message .= \sprintf('or %s "%s".', \class_exists($aliases[$i], \false) ? 'class' : 'interface', $aliases[$i]); return $message; } if ($aliases) { return \sprintf('Try changing the type-hint to "%s" instead.', $aliases[0]); } return null; } private function populateAutowiringAlias(string $id) : void { if (!\preg_match('/(?(DEFINE)(?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \\$((?&V)))?$/', $id, $m)) { return; } $type = $m[2]; $name = $m[3] ?? ''; if (\class_exists($type, \false) || \interface_exists($type, \false)) { $this->autowiringAliases[$type][$name] = $name; } } private function getCombinedAlias(string $type, ?string $name = null) : ?string { if (\str_contains($type, '&')) { $types = \explode('&', $type); } elseif (\str_contains($type, '|')) { $types = \explode('|', $type); } else { return null; } $alias = null; $suffix = $name ? ' $' . $name : ''; foreach ($types as $type) { if (!$this->container->hasAlias($type . $suffix)) { return null; } if (null === $alias) { $alias = (string) $this->container->getAlias($type . $suffix); } elseif ((string) $this->container->getAlias($type . $suffix) !== $alias) { return null; } } return $alias; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator; /** * Applies the "container.service_locator" tag by wrapping references into ServiceClosureArgument instances. * * @author Nicolas Grekas */ final class ServiceLocatorTagPass extends AbstractRecursivePass { use PriorityTaggedServiceTrait; protected function processValue($value, bool $isRoot = \false) { if ($value instanceof ServiceLocatorArgument) { if ($value->getTaggedIteratorArgument()) { $value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container)); } return self::register($this->container, $value->getValues()); } if ($value instanceof Definition) { $value->setBindings(parent::processValue($value->getBindings())); } if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) { return parent::processValue($value, $isRoot); } if (!$value->getClass()) { $value->setClass(ServiceLocator::class); } $services = $value->getArguments()[0] ?? null; if ($services instanceof TaggedIteratorArgument) { $services = $this->findAndSortTaggedServices($services, $this->container); } if (!\is_array($services)) { throw new InvalidArgumentException(\sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId)); } $i = 0; foreach ($services as $k => $v) { if ($v instanceof ServiceClosureArgument) { continue; } if (!$v instanceof Reference) { throw new InvalidArgumentException(\sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, \get_debug_type($v), $k)); } if ($i === $k) { unset($services[$k]); $k = (string) $v; ++$i; } elseif (\is_int($k)) { $i = null; } $services[$k] = new ServiceClosureArgument($v); } \ksort($services); $value->setArgument(0, $services); $id = '.service_locator.' . ContainerBuilder::hash($value); if ($isRoot) { if ($id !== $this->currentId) { $this->container->setAlias($id, new Alias($this->currentId, \false)); } return $value; } $this->container->setDefinition($id, $value->setPublic(\false)); return new Reference($id); } /** * @param Reference[] $refMap */ public static function register(ContainerBuilder $container, array $refMap, ?string $callerId = null) : Reference { foreach ($refMap as $id => $ref) { if (!$ref instanceof Reference) { throw new InvalidArgumentException(\sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', \get_debug_type($ref), $id)); } $refMap[$id] = new ServiceClosureArgument($ref); } $locator = (new Definition(ServiceLocator::class))->addArgument($refMap)->addTag('container.service_locator'); if (null !== $callerId && $container->hasDefinition($callerId)) { $locator->setBindings($container->getDefinition($callerId)->getBindings()); } if (!$container->hasDefinition($id = '.service_locator.' . ContainerBuilder::hash($locator))) { $container->setDefinition($id, $locator); } if (null !== $callerId) { $locatorId = $id; // Locators are shared when they hold the exact same list of factories; // to have them specialized per consumer service, we use a cloning factory // to derivate customized instances from the prototype one. $container->register($id .= '.' . $callerId, ServiceLocator::class)->setFactory([new Reference($locatorId), 'withContext'])->addTag('container.service_locator_context', ['id' => $callerId])->addArgument($callerId)->addArgument(new Reference('service_container')); } return new Reference($id); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; /** * Resolves all TaggedIteratorArgument arguments. * * @author Roland Franssen */ class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass { use PriorityTaggedServiceTrait; /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof TaggedIteratorArgument) { return parent::processValue($value, $isRoot); } $value->setValues($this->findAndSortTaggedServices($value, $this->container)); return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; /** * Replaces env var placeholders by their current values. */ class ResolveEnvPlaceholdersPass extends AbstractRecursivePass { protected function processValue($value, bool $isRoot = \false) { if (\is_string($value)) { return $this->container->resolveEnvPlaceholders($value, \true); } if ($value instanceof Definition) { $changes = $value->getChanges(); if (isset($changes['class'])) { $value->setClass($this->container->resolveEnvPlaceholders($value->getClass(), \true)); } if (isset($changes['file'])) { $value->setFile($this->container->resolveEnvPlaceholders($value->getFile(), \true)); } } $value = parent::processValue($value, $isRoot); if ($value && \is_array($value) && !$isRoot) { $value = \array_combine($this->container->resolveEnvPlaceholders(\array_keys($value), \true), $value); } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; /** * Checks your services for circular references. * * References from method calls are ignored since we might be able to resolve * these references depending on the order in which services are called. * * Circular reference from method calls will only be detected at run-time. * * @author Johannes M. Schmitt */ class CheckCircularReferencesPass implements CompilerPassInterface { private $currentPath; private $checkedNodes; /** * Checks the ContainerBuilder object for circular references. */ public function process(ContainerBuilder $container) { $graph = $container->getCompiler()->getServiceReferenceGraph(); $this->checkedNodes = []; foreach ($graph->getNodes() as $id => $node) { $this->currentPath = [$id]; $this->checkOutEdges($node->getOutEdges()); } } /** * Checks for circular references. * * @param ServiceReferenceGraphEdge[] $edges An array of Edges * * @throws ServiceCircularReferenceException when a circular reference is found */ private function checkOutEdges(array $edges) { foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); if (empty($this->checkedNodes[$id])) { // Don't check circular references for lazy edges if (!$node->getValue() || !$edge->isLazy() && !$edge->isWeak()) { $searchKey = \array_search($id, $this->currentPath); $this->currentPath[] = $id; if (\false !== $searchKey) { throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey)); } $this->checkOutEdges($node->getOutEdges()); } $this->checkedNodes[$id] = \true; \array_pop($this->currentPath); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Checks that all references are pointing to a valid service. * * @author Johannes M. Schmitt */ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass { private $serviceLocatorContextIds = []; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { $this->serviceLocatorContextIds = []; foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) { $this->serviceLocatorContextIds[$id] = $tags[0]['id']; $container->getDefinition($id)->clearTag('container.service_locator_context'); } try { return parent::process($container); } finally { $this->serviceLocatorContextIds = []; } } protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); } if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has($id = (string) $value)) { return $value; } $currentId = $this->currentId; $graph = $this->container->getCompiler()->getServiceReferenceGraph(); if (isset($this->serviceLocatorContextIds[$currentId])) { $currentId = $this->serviceLocatorContextIds[$currentId]; $locator = $this->container->getDefinition($this->currentId)->getFactory()[0]; foreach ($locator->getArgument(0) as $k => $v) { if ($v->getValues()[0] === $value) { if ($k !== $id) { $currentId = $k . '" in the container provided to "' . $currentId; } throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id)); } } } if ('.' === $currentId[0] && $graph->hasNode($currentId)) { foreach ($graph->getNode($currentId)->getInEdges() as $edge) { if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) { continue; } $sourceId = $edge->getSourceNode()->getId(); if ('.' !== $sourceId[0]) { $currentId = $sourceId; break; } } } throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id)); } private function getAlternatives(string $id) : array { $alternatives = []; foreach ($this->container->getServiceIds() as $knownId) { if ('' === $knownId || '.' === $knownId[0]) { continue; } $lev = \levenshtein($id, $knownId); if ($lev <= \strlen($id) / 3 || \false !== \strpos($knownId, $id)) { $alternatives[] = $knownId; } } return $alternatives; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * Sets a service to be an alias of another one, given a format pattern. */ class AutoAliasServicePass implements CompilerPassInterface { private $privateAliases = []; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) { foreach ($tags as $tag) { if (!isset($tag['format'])) { throw new InvalidArgumentException(\sprintf('Missing tag information "format" on auto_alias service "%s".', $serviceId)); } $aliasId = $container->getParameterBag()->resolveValue($tag['format']); if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) { $alias = new Alias($aliasId, $container->getDefinition($serviceId)->isPublic()); $container->setAlias($serviceId, $alias); if (!$alias->isPublic()) { $alias->setPublic(\true); $this->privateAliases[] = $alias; } } } } } /** * @internal to be removed in Symfony 6.0 */ public function getPrivateAliases() : array { $privateAliases = $this->privateAliases; $this->privateAliases = []; return $privateAliases; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; /** * Resolves all parameter placeholders "%somevalue%" to their real values. * * @author Johannes M. Schmitt */ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass { private $bag; private $resolveArrays; private $throwOnResolveException; public function __construct($resolveArrays = \true, $throwOnResolveException = \true) { $this->resolveArrays = $resolveArrays; $this->throwOnResolveException = $throwOnResolveException; } /** * {@inheritdoc} * * @throws ParameterNotFoundException */ public function process(ContainerBuilder $container) { $this->bag = $container->getParameterBag(); try { parent::process($container); $aliases = []; foreach ($container->getAliases() as $name => $target) { $this->currentId = $name; $aliases[$this->bag->resolveValue($name)] = $target; } $container->setAliases($aliases); } catch (ParameterNotFoundException $e) { $e->setSourceId($this->currentId); throw $e; } $this->bag->resolve(); $this->bag = null; } protected function processValue($value, bool $isRoot = \false) { if (\is_string($value)) { try { $v = $this->bag->resolveValue($value); } catch (ParameterNotFoundException $e) { if ($this->throwOnResolveException) { throw $e; } $v = null; $this->container->getDefinition($this->currentId)->addError($e->getMessage()); } return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value; } if ($value instanceof Definition) { $value->setBindings($this->processValue($value->getBindings())); $changes = $value->getChanges(); if (isset($changes['class'])) { $value->setClass($this->bag->resolveValue($value->getClass())); } if (isset($changes['file'])) { $value->setFile($this->bag->resolveValue($value->getFile())); } $tags = $value->getTags(); if (isset($tags['proxy'])) { $tags['proxy'] = $this->bag->resolveValue($tags['proxy']); $value->setTags($tags); } } $value = parent::processValue($value, $isRoot); if ($value && \is_array($value)) { $value = \array_combine($this->bag->resolveValue(\array_keys($value)), $value); } return $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * @author Nicolas Grekas */ class ResolveClassPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isSynthetic() || null !== $definition->getClass()) { continue; } if (\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+(?:\\\\[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+)++$/', $id)) { if ($definition instanceof ChildDefinition && !\class_exists($id)) { throw new InvalidArgumentException(\sprintf('Service definition "%s" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id)); } $definition->setClass($id); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Removes abstract Definitions. */ class RemoveAbstractDefinitionsPass implements CompilerPassInterface { /** * Removes abstract definitions from the ContainerBuilder. */ public function process(ContainerBuilder $container) { foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isAbstract()) { $container->removeDefinition($id); $container->log($this, \sprintf('Removed service "%s"; reason: abstract.', $id)); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Checks the validity of references. * * The following checks are performed by this pass: * - target definitions are not abstract * * @author Johannes M. Schmitt */ class CheckReferenceValidityPass extends AbstractRecursivePass { protected function processValue($value, bool $isRoot = \false) { if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) { return $value; } if ($value instanceof Reference && $this->container->hasDefinition((string) $value)) { $targetDefinition = $this->container->getDefinition((string) $value); if ($targetDefinition->isAbstract()) { throw new RuntimeException(\sprintf('The definition "%s" has a reference to an abstract definition "%s". Abstract definitions cannot be the target of references.', $this->currentId, $value)); } } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Psr\Container\ContainerInterface as PsrContainerInterface; use _ContaoManager\Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\BoundArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; use _ContaoManager\Symfony\Component\HttpFoundation\Session\SessionInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceProviderInterface; use _ContaoManager\Symfony\Contracts\Service\ServiceSubscriberInterface; /** * Compiler pass to register tagged services that require a service locator. * * @author Nicolas Grekas */ class RegisterServiceSubscribersPass extends AbstractRecursivePass { protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) { return parent::processValue($value, $isRoot); } $serviceMap = []; $autowire = $value->isAutowired(); foreach ($value->getTag('container.service_subscriber') as $attributes) { if (!$attributes) { $autowire = \true; continue; } \ksort($attributes); if ([] !== \array_diff(\array_keys($attributes), ['id', 'key'])) { throw new InvalidArgumentException(\sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', \implode('", "', \array_keys($attributes)), $this->currentId)); } if (!\array_key_exists('id', $attributes)) { throw new InvalidArgumentException(\sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $this->currentId)); } if (!\array_key_exists('key', $attributes)) { $attributes['key'] = $attributes['id']; } if (isset($serviceMap[$attributes['key']])) { continue; } $serviceMap[$attributes['key']] = new Reference($attributes['id']); } $class = $value->getClass(); if (!($r = $this->container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId)); } if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) { throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class)); } $class = $r->name; $replaceDeprecatedSession = $this->container->has('.session.deprecated') && $r->isSubclassOf(AbstractController::class); $subscriberMap = []; foreach ($class::getSubscribedServices() as $key => $type) { if (!\is_string($type) || !\preg_match('/(?(DEFINE)(?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\\??(?&fqcn)(?:(?:\\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { throw new InvalidArgumentException(\sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : \get_debug_type($type))); } if ($optionalBehavior = '?' === $type[0]) { $type = \substr($type, 1); $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } if (\is_int($name = $key)) { $key = $type; $name = null; } if (!isset($serviceMap[$key])) { if (!$autowire) { throw new InvalidArgumentException(\sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class)); } if ($replaceDeprecatedSession && SessionInterface::class === $type) { // This prevents triggering the deprecation when building the container // Should be removed in Symfony 6.0 $type = '.session.deprecated'; } $serviceMap[$key] = new Reference($type); } if ($name) { if (\false !== ($i = \strpos($name, '::get'))) { $name = \lcfirst(\substr($name, 5 + $i)); } elseif (\str_contains($name, '::')) { $name = null; } } if (null !== $name && !$this->container->has($name) && !$this->container->has($type . ' $' . $name)) { $camelCaseName = \lcfirst(\str_replace(' ', '', \ucwords(\preg_replace('/[^a-zA-Z0-9\\x7f-\\xff]++/', ' ', $name)))); $name = $this->container->has($type . ' $' . $camelCaseName) ? $camelCaseName : $name; } $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name); unset($serviceMap[$key]); } if ($serviceMap = \array_keys($serviceMap)) { $message = \sprintf(1 < \count($serviceMap) ? 'keys "%s" do' : 'key "%s" does', \str_replace('%', '%%', \implode('", "', $serviceMap))); throw new InvalidArgumentException(\sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId)); } $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId); $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]); $value->setBindings([PsrContainerInterface::class => new BoundArgument($locatorRef, \false), ServiceProviderInterface::class => new BoundArgument($locatorRef, \false)] + $value->getBindings()); return parent::processValue($value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Propagate "container.hot_path" tags to referenced services. * * @author Nicolas Grekas */ class ResolveHotPathPass extends AbstractRecursivePass { private $tagName; private $resolvedIds = []; public function __construct(string $tagName = 'container.hot_path') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->tagName = $tagName; } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { try { parent::process($container); $container->getDefinition('service_container')->clearTag($this->tagName); } finally { $this->resolvedIds = []; } } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof ArgumentInterface) { return $value; } if ($value instanceof Definition && $isRoot) { if ($value->isDeprecated()) { return $value->clearTag($this->tagName); } $this->resolvedIds[$this->currentId] = \true; if (!$value->hasTag($this->tagName)) { return $value; } } if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { $definition = $this->container->getDefinition($id); if ($definition->isDeprecated() || $definition->hasTag($this->tagName)) { return $value; } $definition->addTag($this->tagName); if (isset($this->resolvedIds[$id])) { parent::processValue($definition, \false); } return $value; } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * @author Nicolas Grekas */ class ResolveDecoratorStackPass implements CompilerPassInterface { private $tag; public function __construct(string $tag = 'container.stack') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->tag = $tag; } public function process(ContainerBuilder $container) { $stacks = []; foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) { $definition = $container->getDefinition($id); if (!$definition instanceof ChildDefinition) { throw new InvalidArgumentException(\sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag)); } if (!($stack = $definition->getArguments())) { throw new InvalidArgumentException(\sprintf('Invalid service "%s": the stack of decorators is empty.', $id)); } $stacks[$id] = $stack; } if (!$stacks) { return; } $resolvedDefinitions = []; foreach ($container->getDefinitions() as $id => $definition) { if (!isset($stacks[$id])) { $resolvedDefinitions[$id] = $definition; continue; } foreach (\array_reverse($this->resolveStack($stacks, [$id]), \true) as $k => $v) { $resolvedDefinitions[$k] = $v; } $alias = $container->setAlias($id, $k); if ($definition->getChanges()['public'] ?? \false) { $alias->setPublic($definition->isPublic()); } if ($definition->isDeprecated()) { $alias->setDeprecated(...\array_values($definition->getDeprecation('%alias_id%'))); } } $container->setDefinitions($resolvedDefinitions); } private function resolveStack(array $stacks, array $path) : array { $definitions = []; $id = \end($path); $prefix = '.' . $id . '.'; if (!isset($stacks[$id])) { return [$id => new ChildDefinition($id)]; } if (\key($path) !== ($searchKey = \array_search($id, $path))) { throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey)); } foreach ($stacks[$id] as $k => $definition) { if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) { $path[] = $definition->getParent(); $definition = \unserialize(\serialize($definition)); // deep clone } elseif ($definition instanceof Definition) { $definitions[$decoratedId = $prefix . $k] = $definition; continue; } elseif ($definition instanceof Reference || $definition instanceof Alias) { $path[] = (string) $definition; } else { throw new InvalidArgumentException(\sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, \get_debug_type($definition))); } $p = $prefix . $k; foreach ($this->resolveStack($stacks, $path) as $k => $v) { $definitions[$decoratedId = $p . $k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k); $definition = null; } \array_pop($path); } if (1 === \count($path)) { foreach ($definitions as $k => $definition) { $definition->setPublic(\false)->setTags([])->setDecoratedService($decoratedId); } $definition->setDecoratedService(null); } return $definitions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Contracts\Service\ServiceProviderInterface; /** * Compiler pass to inject their service locator to service subscribers. * * @author Nicolas Grekas */ class ResolveServiceSubscribersPass extends AbstractRecursivePass { private $serviceLocator; protected function processValue($value, bool $isRoot = \false) { if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], \true)) { return new Reference($this->serviceLocator); } if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } $serviceLocator = $this->serviceLocator; $this->serviceLocator = null; if ($value->hasTag('container.service_subscriber.locator')) { $this->serviceLocator = $value->getTag('container.service_subscriber.locator')[0]['id']; $value->clearTag('container.service_subscriber.locator'); } try { return parent::processValue($value); } finally { $this->serviceLocator = $serviceLocator; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Interface that must be implemented by compilation passes. * * @author Johannes M. Schmitt */ interface CompilerPassInterface { /** * You can modify the container here before it is dumped to PHP code. */ public function process(ContainerBuilder $container); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Replaces aliases with actual service definitions, effectively removing these * aliases. * * @author Johannes M. Schmitt */ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass { private $replacements; private $autoAliasServicePass; /** * @internal to be removed in Symfony 6.0 * * @return $this */ public function setAutoAliasServicePass(AutoAliasServicePass $autoAliasServicePass) : self { $this->autoAliasServicePass = $autoAliasServicePass; return $this; } /** * Process the Container to replace aliases with service definitions. * * @throws InvalidArgumentException if the service definition does not exist */ public function process(ContainerBuilder $container) { // First collect all alias targets that need to be replaced $seenAliasTargets = []; $replacements = []; $privateAliases = $this->autoAliasServicePass ? $this->autoAliasServicePass->getPrivateAliases() : []; foreach ($privateAliases as $target) { $target->setDeprecated('symfony/dependency-injection', '5.4', 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); } foreach ($container->getAliases() as $definitionId => $target) { $targetId = (string) $target; // Special case: leave this target alone if ('service_container' === $targetId) { continue; } // Check if target needs to be replaced if (isset($replacements[$targetId])) { $container->setAlias($definitionId, $replacements[$targetId])->setPublic($target->isPublic()); if ($target->isDeprecated()) { $container->getAlias($definitionId)->setDeprecated(...\array_values($target->getDeprecation('%alias_id%'))); } } // No need to process the same target twice if (isset($seenAliasTargets[$targetId])) { continue; } // Process new target $seenAliasTargets[$targetId] = \true; try { $definition = $container->getDefinition($targetId); } catch (ServiceNotFoundException $e) { if ('' !== $e->getId() && '@' === $e->getId()[0]) { throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [\substr($e->getId(), 1)]); } throw $e; } if ($definition->isPublic()) { continue; } // Remove private definition and schedule for replacement $definition->setPublic($target->isPublic()); $container->setDefinition($definitionId, $definition); $container->removeDefinition($targetId); $replacements[$targetId] = $definitionId; if ($target->isPublic() && $target->isDeprecated()) { $definition->addTag('container.private', $target->getDeprecation('%service_id%')); } } $this->replacements = $replacements; parent::process($container); $this->replacements = []; } /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) { // Perform the replacement $newId = $this->replacements[$referenceId]; $value = new Reference($newId, $value->getInvalidBehavior()); $this->container->log($this, \sprintf('Changed reference of service "%s" previously pointing to "%s" to "%s".', $this->currentId, $referenceId, $newId)); } return parent::processValue($value, $isRoot); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ChildDefinition; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; /** * This replaces all ChildDefinition instances with their equivalent fully * merged Definition instance. * * @author Johannes M. Schmitt * @author Nicolas Grekas */ class ResolveChildDefinitionsPass extends AbstractRecursivePass { private $currentPath; protected function processValue($value, bool $isRoot = \false) { if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } if ($isRoot) { // yes, we are specifically fetching the definition from the // container to ensure we are not operating on stale data $value = $this->container->getDefinition($this->currentId); } if ($value instanceof ChildDefinition) { $this->currentPath = []; $value = $this->resolveDefinition($value); if ($isRoot) { $this->container->setDefinition($this->currentId, $value); } } return parent::processValue($value, $isRoot); } /** * Resolves the definition. * * @throws RuntimeException When the definition is invalid */ private function resolveDefinition(ChildDefinition $definition) : Definition { try { return $this->doResolveDefinition($definition); } catch (ServiceCircularReferenceException $e) { throw $e; } catch (ExceptionInterface $e) { $r = new \ReflectionProperty($e, 'message'); $r->setAccessible(\true); $r->setValue($e, \sprintf('Service "%s": %s', $this->currentId, $e->getMessage())); throw $e; } } private function doResolveDefinition(ChildDefinition $definition) : Definition { if (!$this->container->has($parent = $definition->getParent())) { throw new RuntimeException(\sprintf('Parent definition "%s" does not exist.', $parent)); } $searchKey = \array_search($parent, $this->currentPath); $this->currentPath[] = $parent; if (\false !== $searchKey) { throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey)); } $parentDef = $this->container->findDefinition($parent); if ($parentDef instanceof ChildDefinition) { $id = $this->currentId; $this->currentId = $parent; $parentDef = $this->resolveDefinition($parentDef); $this->container->setDefinition($parent, $parentDef); $this->currentId = $id; } $this->container->log($this, \sprintf('Resolving inheritance for "%s" (parent: %s).', $this->currentId, $parent)); $def = new Definition(); // merge in parent definition // purposely ignored attributes: abstract, shared, tags, autoconfigured $def->setClass($parentDef->getClass()); $def->setArguments($parentDef->getArguments()); $def->setMethodCalls($parentDef->getMethodCalls()); $def->setProperties($parentDef->getProperties()); if ($parentDef->isDeprecated()) { $deprecation = $parentDef->getDeprecation('%service_id%'); $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); } $def->setFactory($parentDef->getFactory()); $def->setConfigurator($parentDef->getConfigurator()); $def->setFile($parentDef->getFile()); $def->setPublic($parentDef->isPublic()); $def->setLazy($parentDef->isLazy()); $def->setAutowired($parentDef->isAutowired()); $def->setChanges($parentDef->getChanges()); $def->setBindings($definition->getBindings() + $parentDef->getBindings()); $def->setSynthetic($definition->isSynthetic()); // overwrite with values specified in the decorator $changes = $definition->getChanges(); if (isset($changes['class'])) { $def->setClass($definition->getClass()); } if (isset($changes['factory'])) { $def->setFactory($definition->getFactory()); } if (isset($changes['configurator'])) { $def->setConfigurator($definition->getConfigurator()); } if (isset($changes['file'])) { $def->setFile($definition->getFile()); } if (isset($changes['public'])) { $def->setPublic($definition->isPublic()); } else { $def->setPublic($parentDef->isPublic()); } if (isset($changes['lazy'])) { $def->setLazy($definition->isLazy()); } if (isset($changes['deprecated'])) { if ($definition->isDeprecated()) { $deprecation = $definition->getDeprecation('%service_id%'); $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); } else { $def->setDeprecated(\false); } } if (isset($changes['autowired'])) { $def->setAutowired($definition->isAutowired()); } if (isset($changes['shared'])) { $def->setShared($definition->isShared()); } if (isset($changes['decorated_service'])) { $decoratedService = $definition->getDecoratedService(); if (null === $decoratedService) { $def->setDecoratedService($decoratedService); } else { $def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); } } // merge arguments foreach ($definition->getArguments() as $k => $v) { if (\is_numeric($k)) { $def->addArgument($v); } elseif (\str_starts_with($k, 'index_')) { $def->replaceArgument((int) \substr($k, \strlen('index_')), $v); } else { $def->setArgument($k, $v); } } // merge properties foreach ($definition->getProperties() as $k => $v) { $def->setProperty($k, $v); } // append method calls if ($calls = $definition->getMethodCalls()) { $def->setMethodCalls(\array_merge($def->getMethodCalls(), $calls)); } $def->addError($parentDef); $def->addError($definition); // these attributes are always taken from the child $def->setAbstract($definition->isAbstract()); $def->setTags($definition->getTags()); // autoconfigure is never taken from parent (on purpose) // and it's not legal on an instanceof $def->setAutoconfigured($definition->isAutoconfigured()); if (!$def->hasTag('proxy')) { foreach ($parentDef->getTag('proxy') as $v) { $def->addTag('proxy', $v); } } return $def; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\EnvParameterException; /** * This class is used to remove circular dependencies between individual passes. * * @author Johannes M. Schmitt */ class Compiler { private $passConfig; private $log = []; private $serviceReferenceGraph; public function __construct() { $this->passConfig = new PassConfig(); $this->serviceReferenceGraph = new ServiceReferenceGraph(); } /** * @return PassConfig */ public function getPassConfig() { return $this->passConfig; } /** * @return ServiceReferenceGraph */ public function getServiceReferenceGraph() { return $this->serviceReferenceGraph; } public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { $this->passConfig->addPass($pass, $type, $priority); } /** * @final */ public function log(CompilerPassInterface $pass, string $message) { if (\str_contains($message, "\n")) { $message = \str_replace("\n", "\n" . \get_class($pass) . ': ', \trim($message)); } $this->log[] = \get_class($pass) . ': ' . $message; } /** * @return array */ public function getLog() { return $this->log; } /** * Run the Compiler and process all Passes. */ public function compile(ContainerBuilder $container) { try { foreach ($this->passConfig->getPasses() as $pass) { $pass->process($container); } } catch (\Exception $e) { $usedEnvs = []; $prev = $e; do { $msg = $prev->getMessage(); if ($msg !== ($resolvedMsg = $container->resolveEnvPlaceholders($msg, null, $usedEnvs))) { $r = new \ReflectionProperty($prev, 'message'); $r->setAccessible(\true); $r->setValue($prev, $resolvedMsg); } } while ($prev = $prev->getPrevious()); if ($usedEnvs) { $e = new EnvParameterException($usedEnvs, $e); } throw $e; } finally { $this->getServiceReferenceGraph()->clear(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * A pass to automatically process extensions if they implement * CompilerPassInterface. * * @author Wouter J */ class ExtensionCompilerPass implements CompilerPassInterface { /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach ($container->getExtensions() as $extension) { if (!$extension instanceof CompilerPassInterface) { continue; } $extension->process($container); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * Run this pass before passes that need to know more about the relation of * your services. * * This class will populate the ServiceReferenceGraph with information. You can * retrieve the graph in other passes from the compiler. * * @author Johannes M. Schmitt * @author Nicolas Grekas */ class AnalyzeServiceReferencesPass extends AbstractRecursivePass { private $graph; private $currentDefinition; private $onlyConstructorArguments; private $hasProxyDumper; private $lazy; private $byConstructor; private $byFactory; private $definitions; private $aliases; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls */ public function __construct(bool $onlyConstructorArguments = \false, bool $hasProxyDumper = \true) { $this->onlyConstructorArguments = $onlyConstructorArguments; $this->hasProxyDumper = $hasProxyDumper; $this->enableExpressionProcessing(); } /** * Processes a ContainerBuilder object to populate the service reference graph. */ public function process(ContainerBuilder $container) { $this->container = $container; $this->graph = $container->getCompiler()->getServiceReferenceGraph(); $this->graph->clear(); $this->lazy = \false; $this->byConstructor = \false; $this->byFactory = \false; $this->definitions = $container->getDefinitions(); $this->aliases = $container->getAliases(); foreach ($this->aliases as $id => $alias) { $targetId = $this->getDefinitionId((string) $alias); $this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null); } try { parent::process($container); } finally { $this->aliases = $this->definitions = []; } } protected function processValue($value, bool $isRoot = \false) { $lazy = $this->lazy; $inExpression = $this->inExpression(); if ($value instanceof ArgumentInterface) { $this->lazy = !$this->byFactory || !$value instanceof IteratorArgument; parent::processValue($value->getValues()); $this->lazy = $lazy; return $value; } if ($value instanceof Reference) { $targetId = $this->getDefinitionId((string) $value); $targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null; $this->graph->connect($this->currentId, $this->currentDefinition, $targetId, $targetDefinition, $value, $this->lazy || $this->hasProxyDumper && $targetDefinition && $targetDefinition->isLazy(), ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(), $this->byConstructor); if ($inExpression) { $this->graph->connect('.internal.reference_in_expression', null, $targetId, $targetDefinition, $value, $this->lazy || $targetDefinition && $targetDefinition->isLazy(), \true); } return $value; } if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } if ($isRoot) { if ($value->isSynthetic() || $value->isAbstract()) { return $value; } $this->currentDefinition = $value; } elseif ($this->currentDefinition === $value) { return $value; } $this->lazy = \false; $byConstructor = $this->byConstructor; $this->byConstructor = $isRoot || $byConstructor; $byFactory = $this->byFactory; $this->byFactory = \true; $this->processValue($value->getFactory()); $this->byFactory = $byFactory; $this->processValue($value->getArguments()); $properties = $value->getProperties(); $setters = $value->getMethodCalls(); // Any references before a "wither" are part of the constructor-instantiation graph $lastWitherIndex = null; foreach ($setters as $k => $call) { if ($call[2] ?? \false) { $lastWitherIndex = $k; } } if (null !== $lastWitherIndex) { $this->processValue($properties); $setters = $properties = []; foreach ($value->getMethodCalls() as $k => $call) { if (null === $lastWitherIndex) { $setters[] = $call; continue; } if ($lastWitherIndex === $k) { $lastWitherIndex = null; } $this->processValue($call); } } $this->byConstructor = $byConstructor; if (!$this->onlyConstructorArguments) { $this->processValue($properties); $this->processValue($setters); $this->processValue($value->getConfigurator()); } $this->lazy = $lazy; return $value; } private function getDefinitionId(string $id) : ?string { while (isset($this->aliases[$id])) { $id = (string) $this->aliases[$id]; } return isset($this->definitions[$id]) ? $id : null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Compiler; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Contracts\Service\Attribute\Required; /** * Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters. * * @author Nicolas Grekas */ class AutowireRequiredMethodsPass extends AbstractRecursivePass { /** * {@inheritdoc} */ protected function processValue($value, bool $isRoot = \false) { $value = parent::processValue($value, $isRoot); if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { return $value; } if (!($reflectionClass = $this->container->getReflectionClass($value->getClass(), \false))) { return $value; } $alreadyCalledMethods = []; $withers = []; foreach ($value->getMethodCalls() as [$method]) { $alreadyCalledMethods[\strtolower($method)] = \true; } foreach ($reflectionClass->getMethods() as $reflectionMethod) { $r = $reflectionMethod; if ($r->isConstructor() || isset($alreadyCalledMethods[\strtolower($r->name)])) { continue; } while (\true) { if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) { if ($this->isWither($r, $r->getDocComment() ?: '')) { $withers[] = [$r->name, [], \true]; } else { $value->addMethodCall($r->name, []); } break; } if (\false !== ($doc = $r->getDocComment())) { if (\false !== \stripos($doc, '@required') && \preg_match('#(?:^/\\*\\*|\\n\\s*+\\*)\\s*+@required(?:\\s|\\*/$)#i', $doc)) { if ($this->isWither($reflectionMethod, $doc)) { $withers[] = [$reflectionMethod->name, [], \true]; } else { $value->addMethodCall($reflectionMethod->name, []); } break; } if (\false === \stripos($doc, '@inheritdoc') || !\preg_match('#(?:^/\\*\\*|\\n\\s*+\\*)\\s*+(?:\\{@inheritdoc\\}|@inheritdoc)(?:\\s|\\*/$)#i', $doc)) { break; } } try { $r = $r->getPrototype(); } catch (\ReflectionException $e) { break; // method has no prototype } } } if ($withers) { // Prepend withers to prevent creating circular loops $setters = $value->getMethodCalls(); $value->setMethodCalls($withers); foreach ($setters as $call) { $value->addMethodCall($call[0], $call[1], $call[2] ?? \false); } } return $value; } private function isWither(\ReflectionMethod $reflectionMethod, string $doc) : bool { $match = \preg_match('#(?:^/\\*\\*|\\n\\s*+\\*)\\s*+@return\\s++(static|\\$this)[\\s\\*]#i', $doc, $matches); if ($match && 'static' === $matches[1]) { return \true; } if ($match && '$this' === $matches[1]) { return \false; } $reflectionType = $reflectionMethod->hasReturnType() ? $reflectionMethod->getReturnType() : null; return $reflectionType instanceof \ReflectionNamedType && 'static' === $reflectionType->getName(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; /** * GraphvizDumper dumps a service container as a graphviz file. * * You can convert the generated dot file with the dot utility (http://www.graphviz.org/): * * dot -Tpng container.dot > foo.png * * @author Fabien Potencier */ class GraphvizDumper extends Dumper { private $nodes; private $edges; // All values should be strings private $options = ['graph' => ['ratio' => 'compress'], 'node' => ['fontsize' => '11', 'fontname' => 'Arial', 'shape' => 'record'], 'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => '0.5'], 'node.instance' => ['fillcolor' => '#9999ff', 'style' => 'filled'], 'node.definition' => ['fillcolor' => '#eeeeee'], 'node.missing' => ['fillcolor' => '#ff9999', 'style' => 'filled']]; /** * Dumps the service container as a graphviz graph. * * Available options: * * * graph: The default options for the whole graph * * node: The default options for nodes * * edge: The default options for edges * * node.instance: The default options for services that are defined directly by object instances * * node.definition: The default options for services that are defined via service definition instances * * node.missing: The default options for missing services * * @return string */ public function dump(array $options = []) { foreach (['graph', 'node', 'edge', 'node.instance', 'node.definition', 'node.missing'] as $key) { if (isset($options[$key])) { $this->options[$key] = \array_merge($this->options[$key], $options[$key]); } } $this->nodes = $this->findNodes(); $this->edges = []; foreach ($this->container->getDefinitions() as $id => $definition) { $this->edges[$id] = \array_merge($this->findEdges($id, $definition->getArguments(), \true, ''), $this->findEdges($id, $definition->getProperties(), \false, '')); foreach ($definition->getMethodCalls() as $call) { $this->edges[$id] = \array_merge($this->edges[$id], $this->findEdges($id, $call[1], \false, $call[0] . '()')); } } return $this->container->resolveEnvPlaceholders($this->startDot() . $this->addNodes() . $this->addEdges() . $this->endDot(), '__ENV_%s__'); } private function addNodes() : string { $code = ''; foreach ($this->nodes as $id => $node) { $aliases = $this->getAliases($id); $code .= \sprintf(" node_%s [label=\"%s\\n%s\\n\", shape=%s%s];\n", $this->dotize($id), $id . ($aliases ? ' (' . \implode(', ', $aliases) . ')' : ''), $node['class'], $this->options['node']['shape'], $this->addAttributes($node['attributes'])); } return $code; } private function addEdges() : string { $code = ''; foreach ($this->edges as $id => $edges) { foreach ($edges as $edge) { $code .= \sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"%s];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed', $edge['lazy'] ? ' color="#9999ff"' : ''); } } return $code; } /** * Finds all edges belonging to a specific service id. */ private function findEdges(string $id, array $arguments, bool $required, string $name, bool $lazy = \false) : array { $edges = []; foreach ($arguments as $argument) { if ($argument instanceof Parameter) { $argument = $this->container->hasParameter($argument) ? $this->container->getParameter($argument) : null; } elseif (\is_string($argument) && \preg_match('/^%([^%]+)%$/', $argument, $match)) { $argument = $this->container->hasParameter($match[1]) ? $this->container->getParameter($match[1]) : null; } if ($argument instanceof Reference) { $lazyEdge = $lazy; if (!$this->container->has((string) $argument)) { $this->nodes[(string) $argument] = ['name' => $name, 'required' => $required, 'class' => '', 'attributes' => $this->options['node.missing']]; } elseif ('service_container' !== (string) $argument) { $lazyEdge = $lazy || $this->container->getDefinition((string) $argument)->isLazy(); } $edges[] = [['name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge]]; } elseif ($argument instanceof ArgumentInterface) { $edges[] = $this->findEdges($id, $argument->getValues(), $required, $name, \true); } elseif ($argument instanceof Definition) { $edges[] = $this->findEdges($id, $argument->getArguments(), $required, ''); $edges[] = $this->findEdges($id, $argument->getProperties(), \false, ''); foreach ($argument->getMethodCalls() as $call) { $edges[] = $this->findEdges($id, $call[1], \false, $call[0] . '()'); } } elseif (\is_array($argument)) { $edges[] = $this->findEdges($id, $argument, $required, $name, $lazy); } } return \array_merge([], ...$edges); } private function findNodes() : array { $nodes = []; $container = $this->cloneContainer(); foreach ($container->getDefinitions() as $id => $definition) { $class = $definition->getClass(); if ('\\' === \substr($class, 0, 1)) { $class = \substr($class, 1); } try { $class = $this->container->getParameterBag()->resolveValue($class); } catch (ParameterNotFoundException $e) { } $nodes[$id] = ['class' => \str_replace('\\', '\\\\', $class), 'attributes' => \array_merge($this->options['node.definition'], ['style' => $definition->isShared() ? 'filled' : 'dotted'])]; $container->setDefinition($id, new Definition('stdClass')); } foreach ($container->getServiceIds() as $id) { if (\array_key_exists($id, $container->getAliases())) { continue; } if (!$container->hasDefinition($id)) { $nodes[$id] = ['class' => \str_replace('\\', '\\\\', \get_class($container->get($id))), 'attributes' => $this->options['node.instance']]; } } return $nodes; } private function cloneContainer() : ContainerBuilder { $parameterBag = new ParameterBag($this->container->getParameterBag()->all()); $container = new ContainerBuilder($parameterBag); $container->setDefinitions($this->container->getDefinitions()); $container->setAliases($this->container->getAliases()); $container->setResources($this->container->getResources()); foreach ($this->container->getExtensions() as $extension) { $container->registerExtension($extension); } return $container; } private function startDot() : string { return \sprintf("digraph sc {\n %s\n node [%s];\n edge [%s];\n\n", $this->addOptions($this->options['graph']), $this->addOptions($this->options['node']), $this->addOptions($this->options['edge'])); } private function endDot() : string { return "}\n"; } private function addAttributes(array $attributes) : string { $code = []; foreach ($attributes as $k => $v) { $code[] = \sprintf('%s="%s"', $k, $v); } return $code ? ', ' . \implode(', ', $code) : ''; } private function addOptions(array $options) : string { $code = []; foreach ($options as $k => $v) { $code[] = \sprintf('%s="%s"', $k, $v); } return \implode(' ', $code); } private function dotize(string $id) : string { return \preg_replace('/\\W/i', '_', $id); } private function getAliases(string $id) : array { $aliases = []; foreach ($this->container->getAliases() as $alias => $origin) { if ($id == $origin) { $aliases[] = $alias; } } return $aliases; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; use _ContaoManager\Symfony\Component\Yaml\Dumper as YmlDumper; use _ContaoManager\Symfony\Component\Yaml\Parser; use _ContaoManager\Symfony\Component\Yaml\Tag\TaggedValue; use _ContaoManager\Symfony\Component\Yaml\Yaml; /** * YamlDumper dumps a service container as a YAML string. * * @author Fabien Potencier */ class YamlDumper extends Dumper { private $dumper; /** * Dumps the service container as an YAML string. * * @return string */ public function dump(array $options = []) { if (!\class_exists(\_ContaoManager\Symfony\Component\Yaml\Dumper::class)) { throw new LogicException('Unable to dump the container as the Symfony Yaml Component is not installed.'); } if (null === $this->dumper) { $this->dumper = new YmlDumper(); } return $this->container->resolveEnvPlaceholders($this->addParameters() . "\n" . $this->addServices()); } private function addService(string $id, Definition $definition) : string { $code = " {$id}:\n"; if ($class = $definition->getClass()) { if ('\\' === \substr($class, 0, 1)) { $class = \substr($class, 1); } $code .= \sprintf(" class: %s\n", $this->dumper->dump($class)); } if (!$definition->isPrivate()) { $code .= \sprintf(" public: %s\n", $definition->isPublic() ? 'true' : 'false'); } $tagsCode = ''; foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { $att = []; foreach ($attributes as $key => $value) { $att[] = \sprintf('%s: %s', $this->dumper->dump($key), $this->dumper->dump($value)); } $att = $att ? ': { ' . \implode(', ', $att) . ' }' : ''; $tagsCode .= \sprintf(" - %s%s\n", $this->dumper->dump($name), $att); } } if ($tagsCode) { $code .= " tags:\n" . $tagsCode; } if ($definition->getFile()) { $code .= \sprintf(" file: %s\n", $this->dumper->dump($definition->getFile())); } if ($definition->isSynthetic()) { $code .= " synthetic: true\n"; } if ($definition->isDeprecated()) { $code .= " deprecated:\n"; foreach ($definition->getDeprecation('%service_id%') as $key => $value) { if ('' !== $value) { $code .= \sprintf(" %s: %s\n", $key, $this->dumper->dump($value)); } } } if ($definition->isAutowired()) { $code .= " autowire: true\n"; } if ($definition->isAutoconfigured()) { $code .= " autoconfigure: true\n"; } if ($definition->isAbstract()) { $code .= " abstract: true\n"; } if ($definition->isLazy()) { $code .= " lazy: true\n"; } if ($definition->getArguments()) { $code .= \sprintf(" arguments: %s\n", $this->dumper->dump($this->dumpValue($definition->getArguments()), 0)); } if ($definition->getProperties()) { $code .= \sprintf(" properties: %s\n", $this->dumper->dump($this->dumpValue($definition->getProperties()), 0)); } if ($definition->getMethodCalls()) { $code .= \sprintf(" calls:\n%s\n", $this->dumper->dump($this->dumpValue($definition->getMethodCalls()), 1, 12)); } if (!$definition->isShared()) { $code .= " shared: false\n"; } if (null !== ($decoratedService = $definition->getDecoratedService())) { [$decorated, $renamedId, $priority] = $decoratedService; $code .= \sprintf(" decorates: %s\n", $decorated); if (null !== $renamedId) { $code .= \sprintf(" decoration_inner_name: %s\n", $renamedId); } if (0 !== $priority) { $code .= \sprintf(" decoration_priority: %s\n", $priority); } $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE])) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; $code .= \sprintf(" decoration_on_invalid: %s\n", $invalidBehavior); } } if ($callable = $definition->getFactory()) { $code .= \sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); } if ($callable = $definition->getConfigurator()) { $code .= \sprintf(" configurator: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); } return $code; } private function addServiceAlias(string $alias, Alias $id) : string { $deprecated = ''; if ($id->isDeprecated()) { $deprecated = " deprecated:\n"; foreach ($id->getDeprecation('%alias_id%') as $key => $value) { if ('' !== $value) { $deprecated .= \sprintf(" %s: %s\n", $key, $value); } } } if (!$id->isDeprecated() && $id->isPrivate()) { return \sprintf(" %s: '@%s'\n", $alias, $id); } if ($id->isPublic()) { $deprecated = " public: true\n" . $deprecated; } return \sprintf(" %s:\n alias: %s\n%s", $alias, $id, $deprecated); } private function addServices() : string { if (!$this->container->getDefinitions()) { return ''; } $code = "services:\n"; foreach ($this->container->getDefinitions() as $id => $definition) { $code .= $this->addService($id, $definition); } $aliases = $this->container->getAliases(); foreach ($aliases as $alias => $id) { while (isset($aliases[(string) $id])) { $id = $aliases[(string) $id]; } $code .= $this->addServiceAlias($alias, $id); } return $code; } private function addParameters() : string { if (!$this->container->getParameterBag()->all()) { return ''; } $parameters = $this->prepareParameters($this->container->getParameterBag()->all(), $this->container->isCompiled()); return $this->dumper->dump(['parameters' => $parameters], 2); } /** * Dumps callable to YAML format. * * @param mixed $callable * * @return mixed */ private function dumpCallable($callable) { if (\is_array($callable)) { if ($callable[0] instanceof Reference) { $callable = [$this->getServiceCall((string) $callable[0], $callable[0]), $callable[1]]; } else { $callable = [$callable[0], $callable[1]]; } } return $callable; } /** * Dumps the value to YAML format. * * @return mixed * * @throws RuntimeException When trying to dump object or resource */ private function dumpValue($value) { if ($value instanceof ServiceClosureArgument) { $value = $value->getValues()[0]; return new TaggedValue('service_closure', $this->getServiceCall((string) $value, $value)); } if ($value instanceof ArgumentInterface) { $tag = $value; if ($value instanceof TaggedIteratorArgument || $value instanceof ServiceLocatorArgument && ($tag = $value->getTaggedIteratorArgument())) { if (null === $tag->getIndexAttribute()) { $content = $tag->getTag(); } else { $content = ['tag' => $tag->getTag(), 'index_by' => $tag->getIndexAttribute()]; if (null !== $tag->getDefaultIndexMethod()) { $content['default_index_method'] = $tag->getDefaultIndexMethod(); } if (null !== $tag->getDefaultPriorityMethod()) { $content['default_priority_method'] = $tag->getDefaultPriorityMethod(); } } return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content); } if ($value instanceof IteratorArgument) { $tag = 'iterator'; } elseif ($value instanceof ServiceLocatorArgument) { $tag = 'service_locator'; } else { throw new RuntimeException(\sprintf('Unspecified Yaml tag for type "%s".', \get_debug_type($value))); } return new TaggedValue($tag, $this->dumpValue($value->getValues())); } if (\is_array($value)) { $code = []; foreach ($value as $k => $v) { $code[$k] = $this->dumpValue($v); } return $code; } elseif ($value instanceof Reference) { return $this->getServiceCall((string) $value, $value); } elseif ($value instanceof Parameter) { return $this->getParameterCall((string) $value); } elseif ($value instanceof Expression) { return $this->getExpressionCall((string) $value); } elseif ($value instanceof Definition) { return new TaggedValue('service', (new Parser())->parse("_:\n" . $this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']); } elseif ($value instanceof \UnitEnum) { return new TaggedValue('php/const', \sprintf('%s::%s', \get_class($value), $value->name)); } elseif ($value instanceof AbstractArgument) { return new TaggedValue('abstract', $value->getText()); } elseif (\is_object($value) || \is_resource($value)) { throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); } return $value; } private function getServiceCall(string $id, ?Reference $reference = null) : string { if (null !== $reference) { switch ($reference->getInvalidBehavior()) { case ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: break; case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break; case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return \sprintf('@!%s', $id); default: return \sprintf('@?%s', $id); } } return \sprintf('@%s', $id); } private function getParameterCall(string $id) : string { return \sprintf('%%%s%%', $id); } private function getExpressionCall(string $expression) : string { return \sprintf('@=%s', $expression); } private function prepareParameters(array $parameters, bool $escape = \true) : array { $filtered = []; foreach ($parameters as $key => $value) { if (\is_array($value)) { $value = $this->prepareParameters($value, $escape); } elseif ($value instanceof Reference || \is_string($value) && \str_starts_with($value, '@')) { $value = '@' . $value; } $filtered[$key] = $value; } return $escape ? $this->escape($filtered) : $filtered; } private function escape(array $arguments) : array { $args = []; foreach ($arguments as $k => $v) { if (\is_array($v)) { $args[$k] = $this->escape($v); } elseif (\is_string($v)) { $args[$k] = \str_replace('%', '%%', $v); } else { $args[$k] = $v; } } return $args; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; /** * @author Nicolas Grekas */ final class Preloader { public static function append(string $file, array $list) : void { if (!\file_exists($file)) { throw new \LogicException(\sprintf('File "%s" does not exist.', $file)); } $cacheDir = \dirname($file); $classes = []; foreach ($list as $item) { if (0 === \strpos($item, $cacheDir)) { \file_put_contents($file, \sprintf("require_once __DIR__.%s;\n", \var_export(\strtr(\substr($item, \strlen($cacheDir)), \DIRECTORY_SEPARATOR, '/'), \true)), \FILE_APPEND); continue; } $classes[] = \sprintf("\$classes[] = %s;\n", \var_export($item, \true)); } \file_put_contents($file, \sprintf("\n\$classes = [];\n%s\$preloaded = Preloader::preload(\$classes, \$preloaded);\n", \implode('', $classes)), \FILE_APPEND); } public static function preload(array $classes, array $preloaded = []) : array { \set_error_handler(function ($t, $m, $f, $l) { if (\error_reporting() & $t) { if (__FILE__ !== $f) { throw new \ErrorException($m, 0, $t, $f, $l); } throw new \ReflectionException($m); } }); $prev = []; try { while ($prev !== $classes) { $prev = $classes; foreach ($classes as $c) { if (!isset($preloaded[$c])) { self::doPreload($c, $preloaded); } } $classes = \array_merge(\get_declared_classes(), \get_declared_interfaces(), \get_declared_traits()); } } finally { \restore_error_handler(); } return $preloaded; } private static function doPreload(string $class, array &$preloaded) : void { if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], \true)) { return; } $preloaded[$class] = \true; try { if (!\class_exists($class) && !\interface_exists($class, \false) && !\trait_exists($class, \false)) { return; } $r = new \ReflectionClass($class); if ($r->isInternal()) { return; } $r->getConstants(); $r->getDefaultProperties(); if (\PHP_VERSION_ID >= 70400) { foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) { self::preloadType($p->getType(), $preloaded); } } foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) { foreach ($m->getParameters() as $p) { if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) { $c = $p->getDefaultValueConstantName(); if ($i = \strpos($c, '::')) { self::doPreload(\substr($c, 0, $i), $preloaded); } } self::preloadType($p->getType(), $preloaded); } self::preloadType($m->getReturnType(), $preloaded); } } catch (\Throwable $e) { // ignore missing classes } } private static function preloadType(?\ReflectionType $t, array &$preloaded) : void { if (!$t) { return; } foreach ($t instanceof \ReflectionUnionType || $t instanceof \ReflectionIntersectionType ? $t->getTypes() : [$t] as $t) { if (!$t->isBuiltin()) { self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; use _ContaoManager\Symfony\Component\DependencyInjection\Alias; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; /** * XmlDumper dumps a service container as an XML string. * * @author Fabien Potencier * @author Martin Hasoň */ class XmlDumper extends Dumper { /** * @var \DOMDocument */ private $document; /** * Dumps the service container as an XML string. * * @return string */ public function dump(array $options = []) { $this->document = new \DOMDocument('1.0', 'utf-8'); $this->document->formatOutput = \true; $container = $this->document->createElementNS('http://symfony.com/schema/dic/services', 'container'); $container->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); $container->setAttribute('xsi:schemaLocation', 'http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd'); $this->addParameters($container); $this->addServices($container); $this->document->appendChild($container); $xml = $this->document->saveXML(); $this->document = null; return $this->container->resolveEnvPlaceholders($xml); } private function addParameters(\DOMElement $parent) { $data = $this->container->getParameterBag()->all(); if (!$data) { return; } if ($this->container->isCompiled()) { $data = $this->escape($data); } $parameters = $this->document->createElement('parameters'); $parent->appendChild($parameters); $this->convertParameters($data, 'parameter', $parameters); } private function addMethodCalls(array $methodcalls, \DOMElement $parent) { foreach ($methodcalls as $methodcall) { $call = $this->document->createElement('call'); $call->setAttribute('method', $methodcall[0]); if (\count($methodcall[1])) { $this->convertParameters($methodcall[1], 'argument', $call); } if ($methodcall[2] ?? \false) { $call->setAttribute('returns-clone', 'true'); } $parent->appendChild($call); } } private function addService(Definition $definition, ?string $id, \DOMElement $parent) { $service = $this->document->createElement('service'); if (null !== $id) { $service->setAttribute('id', $id); } if ($class = $definition->getClass()) { if ('\\' === \substr($class, 0, 1)) { $class = \substr($class, 1); } $service->setAttribute('class', $class); } if (!$definition->isShared()) { $service->setAttribute('shared', 'false'); } if ($definition->isPublic()) { $service->setAttribute('public', 'true'); } if ($definition->isSynthetic()) { $service->setAttribute('synthetic', 'true'); } if ($definition->isLazy()) { $service->setAttribute('lazy', 'true'); } if (null !== ($decoratedService = $definition->getDecoratedService())) { [$decorated, $renamedId, $priority] = $decoratedService; $service->setAttribute('decorates', $decorated); $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], \true)) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; $service->setAttribute('decoration-on-invalid', $invalidBehavior); } if (null !== $renamedId) { $service->setAttribute('decoration-inner-name', $renamedId); } if (0 !== $priority) { $service->setAttribute('decoration-priority', $priority); } } foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { $tag = $this->document->createElement('tag'); if (!\array_key_exists('name', $attributes)) { $tag->setAttribute('name', $name); } else { $tag->appendChild($this->document->createTextNode($name)); } foreach ($attributes as $key => $value) { $tag->setAttribute($key, $value ?? ''); } $service->appendChild($tag); } } if ($definition->getFile()) { $file = $this->document->createElement('file'); $file->appendChild($this->document->createTextNode($definition->getFile())); $service->appendChild($file); } if ($parameters = $definition->getArguments()) { $this->convertParameters($parameters, 'argument', $service); } if ($parameters = $definition->getProperties()) { $this->convertParameters($parameters, 'property', $service, 'name'); } $this->addMethodCalls($definition->getMethodCalls(), $service); if ($callable = $definition->getFactory()) { $factory = $this->document->createElement('factory'); if (\is_array($callable) && $callable[0] instanceof Definition) { $this->addService($callable[0], null, $factory); $factory->setAttribute('method', $callable[1]); } elseif (\is_array($callable)) { if (null !== $callable[0]) { $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); } $factory->setAttribute('method', $callable[1]); } else { $factory->setAttribute('function', $callable); } $service->appendChild($factory); } if ($definition->isDeprecated()) { $deprecation = $definition->getDeprecation('%service_id%'); $deprecated = $this->document->createElement('deprecated'); $deprecated->appendChild($this->document->createTextNode($definition->getDeprecation('%service_id%')['message'])); $deprecated->setAttribute('package', $deprecation['package']); $deprecated->setAttribute('version', $deprecation['version']); $service->appendChild($deprecated); } if ($definition->isAutowired()) { $service->setAttribute('autowire', 'true'); } if ($definition->isAutoconfigured()) { $service->setAttribute('autoconfigure', 'true'); } if ($definition->isAbstract()) { $service->setAttribute('abstract', 'true'); } if ($callable = $definition->getConfigurator()) { $configurator = $this->document->createElement('configurator'); if (\is_array($callable) && $callable[0] instanceof Definition) { $this->addService($callable[0], null, $configurator); $configurator->setAttribute('method', $callable[1]); } elseif (\is_array($callable)) { $configurator->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); $configurator->setAttribute('method', $callable[1]); } else { $configurator->setAttribute('function', $callable); } $service->appendChild($configurator); } $parent->appendChild($service); } private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent) { $service = $this->document->createElement('service'); $service->setAttribute('id', $alias); $service->setAttribute('alias', $id); if ($id->isPublic()) { $service->setAttribute('public', 'true'); } if ($id->isDeprecated()) { $deprecation = $id->getDeprecation('%alias_id%'); $deprecated = $this->document->createElement('deprecated'); $deprecated->appendChild($this->document->createTextNode($deprecation['message'])); $deprecated->setAttribute('package', $deprecation['package']); $deprecated->setAttribute('version', $deprecation['version']); $service->appendChild($deprecated); } $parent->appendChild($service); } private function addServices(\DOMElement $parent) { $definitions = $this->container->getDefinitions(); if (!$definitions) { return; } $services = $this->document->createElement('services'); foreach ($definitions as $id => $definition) { $this->addService($definition, $id, $services); } $aliases = $this->container->getAliases(); foreach ($aliases as $alias => $id) { while (isset($aliases[(string) $id])) { $id = $aliases[(string) $id]; } $this->addServiceAlias($alias, $id, $services); } $parent->appendChild($services); } private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key') { $withKeys = !\array_is_list($parameters); foreach ($parameters as $key => $value) { $element = $this->document->createElement($type); if ($withKeys) { $element->setAttribute($keyAttribute, $key); } if (\is_array($tag = $value)) { $element->setAttribute('type', 'collection'); $this->convertParameters($value, $type, $element, 'key'); } elseif ($value instanceof TaggedIteratorArgument || $value instanceof ServiceLocatorArgument && ($tag = $value->getTaggedIteratorArgument())) { $element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator'); $element->setAttribute('tag', $tag->getTag()); if (null !== $tag->getIndexAttribute()) { $element->setAttribute('index-by', $tag->getIndexAttribute()); if (null !== $tag->getDefaultIndexMethod()) { $element->setAttribute('default-index-method', $tag->getDefaultIndexMethod()); } if (null !== $tag->getDefaultPriorityMethod()) { $element->setAttribute('default-priority-method', $tag->getDefaultPriorityMethod()); } } } elseif ($value instanceof IteratorArgument) { $element->setAttribute('type', 'iterator'); $this->convertParameters($value->getValues(), $type, $element, 'key'); } elseif ($value instanceof ServiceLocatorArgument) { $element->setAttribute('type', 'service_locator'); $this->convertParameters($value->getValues(), $type, $element, 'key'); } elseif ($value instanceof Reference || $value instanceof ServiceClosureArgument) { $element->setAttribute('type', 'service'); if ($value instanceof ServiceClosureArgument) { $element->setAttribute('type', 'service_closure'); $value = $value->getValues()[0]; } $element->setAttribute('id', (string) $value); $behavior = $value->getInvalidBehavior(); if (ContainerInterface::NULL_ON_INVALID_REFERENCE == $behavior) { $element->setAttribute('on-invalid', 'null'); } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE == $behavior) { $element->setAttribute('on-invalid', 'ignore'); } elseif (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE == $behavior) { $element->setAttribute('on-invalid', 'ignore_uninitialized'); } } elseif ($value instanceof Definition) { $element->setAttribute('type', 'service'); $this->addService($value, null, $element); } elseif ($value instanceof Expression) { $element->setAttribute('type', 'expression'); $text = $this->document->createTextNode(self::phpToXml((string) $value)); $element->appendChild($text); } elseif (\is_string($value) && !\preg_match('/^[^\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]*+$/u', $value)) { $element->setAttribute('type', 'binary'); $text = $this->document->createTextNode(self::phpToXml(\base64_encode($value))); $element->appendChild($text); } elseif ($value instanceof \UnitEnum) { $element->setAttribute('type', 'constant'); $element->appendChild($this->document->createTextNode(self::phpToXml($value))); } elseif ($value instanceof AbstractArgument) { $element->setAttribute('type', 'abstract'); $text = $this->document->createTextNode(self::phpToXml($value->getText())); $element->appendChild($text); } else { if (\in_array($value, ['null', 'true', 'false'], \true)) { $element->setAttribute('type', 'string'); } if (\is_string($value) && (\is_numeric($value) || \preg_match('/^0b[01]*$/', $value) || \preg_match('/^0x[0-9a-f]++$/i', $value))) { $element->setAttribute('type', 'string'); } $text = $this->document->createTextNode(self::phpToXml($value)); $element->appendChild($text); } $parent->appendChild($element); } } /** * Escapes arguments. */ private function escape(array $arguments) : array { $args = []; foreach ($arguments as $k => $v) { if (\is_array($v)) { $args[$k] = $this->escape($v); } elseif (\is_string($v)) { $args[$k] = \str_replace('%', '%%', $v); } else { $args[$k] = $v; } } return $args; } /** * Converts php types to xml types. * * @param mixed $value Value to convert * * @throws RuntimeException When trying to dump object or resource */ public static function phpToXml($value) : string { switch (\true) { case null === $value: return 'null'; case \true === $value: return 'true'; case \false === $value: return 'false'; case $value instanceof Parameter: return '%' . $value . '%'; case $value instanceof \UnitEnum: return \sprintf('%s::%s', \get_class($value), $value->name); case \is_object($value) || \is_resource($value): throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); default: return (string) $value; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; /** * DumperInterface is the interface implemented by service container dumper classes. * * @author Fabien Potencier */ interface DumperInterface { /** * Dumps the service container. * * @return string|array */ public function dump(array $options = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; /** * Dumper is the abstract class for all built-in dumpers. * * @author Fabien Potencier */ abstract class Dumper implements DumperInterface { protected $container; public function __construct(ContainerBuilder $container) { $this->container = $container; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection\Dumper; use Composer\Autoload\ClassLoader; use _ContaoManager\Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\AbstractArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\IteratorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocator; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode; use _ContaoManager\Symfony\Component\DependencyInjection\Container; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\EnvParameterException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\LogicException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\RuntimeException; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use _ContaoManager\Symfony\Component\DependencyInjection\ExpressionLanguage; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; use _ContaoManager\Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; use _ContaoManager\Symfony\Component\DependencyInjection\Loader\FileLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Parameter; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; use _ContaoManager\Symfony\Component\DependencyInjection\Variable; use _ContaoManager\Symfony\Component\ErrorHandler\DebugClassLoader; use _ContaoManager\Symfony\Component\ExpressionLanguage\Expression; use _ContaoManager\Symfony\Component\HttpKernel\Kernel; /** * PhpDumper dumps a service container as a PHP class. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class PhpDumper extends Dumper { /** * Characters that might appear in the generated variable name as first character. */ public const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; /** * Characters that might appear in the generated variable name as any but the first character. */ public const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; /** * @var \SplObjectStorage|null */ private $definitionVariables; private $referenceVariables; private $variableCount; private $inlinedDefinitions; private $serviceCalls; private $reservedVariables = ['instance', 'class', 'this', 'container']; private $expressionLanguage; private $targetDirRegex; private $targetDirMaxMatches; private $docStar; private $serviceIdToMethodNameMap; private $usedMethodNames; private $namespace; private $asFiles; private $hotPathTag; private $preloadTags; private $inlineFactories; private $inlineRequires; private $inlinedRequires = []; private $circularReferences = []; private $singleUsePrivateIds = []; private $preload = []; private $addThrow = \false; private $addGetService = \false; private $locatedIds = []; private $serviceLocatorTag; private $exportedVariables = []; private $dynamicParameters = []; private $baseClass; /** * @var DumperInterface */ private $proxyDumper; private $hasProxyDumper = \false; /** * {@inheritdoc} */ public function __construct(ContainerBuilder $container) { if (!$container->isCompiled()) { throw new LogicException('Cannot dump an uncompiled container.'); } parent::__construct($container); } /** * Sets the dumper to be used when dumping proxies in the generated container. */ public function setProxyDumper(DumperInterface $proxyDumper) { $this->proxyDumper = $proxyDumper; $this->hasProxyDumper = !$proxyDumper instanceof NullDumper; } /** * Dumps the service container as a PHP class. * * Available options: * * * class: The class name * * base_class: The base class name * * namespace: The class namespace * * as_files: To split the container in several files * * @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set * * @throws EnvParameterException When an env var exists but has not been dumped */ public function dump(array $options = []) { $this->locatedIds = []; $this->targetDirRegex = null; $this->inlinedRequires = []; $this->exportedVariables = []; $this->dynamicParameters = []; $options = \array_merge(['class' => 'ProjectServiceContainer', 'base_class' => 'Container', 'namespace' => '', 'as_files' => \false, 'debug' => \true, 'hot_path_tag' => 'container.hot_path', 'preload_tags' => ['container.preload', 'container.no_preload'], 'inline_factories_parameter' => 'container.dumper.inline_factories', 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', 'preload_classes' => [], 'service_locator_tag' => 'container.service_locator', 'build_time' => \time()], $options); $this->addThrow = $this->addGetService = \false; $this->namespace = $options['namespace']; $this->asFiles = $options['as_files']; $this->hotPathTag = $options['hot_path_tag']; $this->preloadTags = $options['preload_tags']; $this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']); $this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : \PHP_VERSION_ID < 70400 || $options['debug']); $this->serviceLocatorTag = $options['service_locator_tag']; if (!\str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { $baseClass = \sprintf('%s\\%s', $options['namespace'] ? '\\' . $options['namespace'] : '', $baseClass); $this->baseClass = $baseClass; } elseif ('Container' === $baseClass) { $this->baseClass = Container::class; } else { $this->baseClass = $baseClass; } $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); if (!$this->hasProxyDumper) { (new AnalyzeServiceReferencesPass(\true, \false))->process($this->container); try { (new CheckCircularReferencesPass())->process($this->container); } catch (ServiceCircularReferenceException $e) { $path = $e->getPath(); \end($path); $path[\key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge'; throw new ServiceCircularReferenceException($e->getServiceId(), $path); } } $this->analyzeReferences(); $this->docStar = $options['debug'] ? '*' : ''; if (!empty($options['file']) && \is_dir($dir = \dirname($options['file']))) { // Build a regexp where the first root dirs are mandatory, // but every other sub-dir is optional up to the full path in $dir // Mandate at least 1 root dir and not more than 5 optional dirs. $dir = \explode(\DIRECTORY_SEPARATOR, \realpath($dir)); $i = \count($dir); if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) { $regex = ''; $lastOptionalDir = $i > 8 ? $i - 5 : 2 + (int) ('\\' === \DIRECTORY_SEPARATOR); $this->targetDirMaxMatches = $i - $lastOptionalDir; while (--$i >= $lastOptionalDir) { $regex = \sprintf('(%s%s)?', \preg_quote(\DIRECTORY_SEPARATOR . $dir[$i], '#'), $regex); } do { $regex = \preg_quote(\DIRECTORY_SEPARATOR . $dir[$i], '#') . $regex; } while (0 < --$i); $this->targetDirRegex = '#(^|file://|[:;, \\|\\r\\n])' . \preg_quote($dir[0], '#') . $regex . '#'; } } $proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null; if ($options['preload_classes']) { $this->preload = \array_combine($options['preload_classes'], $options['preload_classes']); } $code = $this->addDefaultParametersMethod(); $code = $this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses) . $this->addServices($services) . $this->addDeprecatedAliases() . $code; $proxyClasses = $proxyClasses ?? $this->generateProxyClasses(); if ($this->addGetService) { $code = \preg_replace("/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s", "\n protected \$getService;\$1 \$this->getService = \\Closure::fromCallable([\$this, 'getService']);\n", $code, 1); } if ($this->asFiles) { $fileTemplate = <<docStar} * @internal This class has been auto-generated by the Symfony Dependency Injection Component. */ class %s extends {$options['class']} {%s} EOF; $files = []; $preloadedFiles = []; $ids = $this->container->getRemovedIds(); foreach ($this->container->getDefinitions() as $id => $definition) { if (!$definition->isPublic()) { $ids[$id] = \true; } } if ($ids = \array_keys($ids)) { \sort($ids); $c = "doExport($id) . " => true,\n"; } $files['removed-ids.php'] = $c . "];\n"; } if (!$this->inlineFactories) { foreach ($this->generateServiceFiles($services) as $file => [$c, $preload]) { $files[$file] = \sprintf($fileTemplate, \substr($file, 0, -4), $c); if ($preload) { $preloadedFiles[$file] = $file; } } foreach ($proxyClasses as $file => $c) { $files[$file] = "endClass(); if ($this->inlineFactories && $proxyClasses) { $files['proxy-classes.php'] = " $c) { $code["Container{$hash}/{$file}"] = \substr_replace($c, "namespace ? "\nnamespace {$this->namespace};\n" : ''; $time = $options['build_time']; $id = \hash('crc32', $hash . $time); $this->asFiles = \false; if ($this->preload && null !== ($autoloadFile = $this->getAutoloadFile())) { $autoloadFile = \trim($this->export($autoloadFile), '()\\'); $preloadedFiles = \array_reverse($preloadedFiles); if ('' !== ($preloadedFiles = \implode("';\nrequire __DIR__.'/", $preloadedFiles))) { $preloadedFiles = "require __DIR__.'/{$preloadedFiles}';\n"; } $code[$options['class'] . '.preload.php'] = <<= 7.4 when preloading is desired use _ContaoManager\\Symfony\\Component\\DependencyInjection\\Dumper\\Preloader; if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) { return; } require {$autoloadFile}; (require __DIR__.'/{$options['class']}.php')->set(\\Container{$hash}\\{$options['class']}::class, null); {$preloadedFiles} \$classes = []; EOF; foreach ($this->preload as $class) { if (!$class || \str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], \true)) { continue; } if (!(\class_exists($class, \false) || \interface_exists($class, \false) || \trait_exists($class, \false)) || (new \ReflectionClass($class))->isUserDefined() && !\in_array($class, ['Attribute', 'JsonException', 'ReturnTypeWillChange', 'Stringable', 'UnhandledMatchError', 'ValueError'], \true)) { $code[$options['class'] . '.preload.php'] .= \sprintf("\$classes[] = '%s';\n", $class); } } $code[$options['class'] . '.preload.php'] .= <<<'EOF' $preloaded = Preloader::preload($classes); EOF; } $code[$options['class'] . '.php'] = << '{$hash}', 'container.build_id' => '{$id}', 'container.build_time' => {$time}, ], __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}'); EOF; } else { $code .= $this->endClass(); foreach ($proxyClasses as $c) { $code .= $c; } } $this->targetDirRegex = null; $this->inlinedRequires = []; $this->circularReferences = []; $this->locatedIds = []; $this->exportedVariables = []; $this->dynamicParameters = []; $this->preload = []; $unusedEnvs = []; foreach ($this->container->getEnvCounters() as $env => $use) { if (!$use) { $unusedEnvs[] = $env; } } if ($unusedEnvs) { throw new EnvParameterException($unusedEnvs, null, 'Environment variables "%s" are never used. Please, check your container\'s configuration.'); } return $code; } /** * Retrieves the currently set proxy dumper or instantiates one. */ private function getProxyDumper() : DumperInterface { if (!$this->proxyDumper) { $this->proxyDumper = new NullDumper(); } return $this->proxyDumper; } private function analyzeReferences() { (new AnalyzeServiceReferencesPass(\false, $this->hasProxyDumper))->process($this->container); $checkedNodes = []; $this->circularReferences = []; $this->singleUsePrivateIds = []; foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { if (!$node->getValue() instanceof Definition) { continue; } if ($this->isSingleUsePrivateNode($node)) { $this->singleUsePrivateIds[$id] = $id; } $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes); } $this->container->getCompiler()->getServiceReferenceGraph()->clear(); $this->singleUsePrivateIds = \array_diff_key($this->singleUsePrivateIds, $this->circularReferences); } private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = \true) : void { $path[$sourceId] = $byConstructor; $checkedNodes[$sourceId] = \true; foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) { continue; } if (isset($path[$id])) { $loop = null; $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); $pathInLoop = [$id, []]; foreach ($path as $k => $pathByConstructor) { if (null !== $loop) { $loop[] = $k; $pathInLoop[1][$k] = $pathByConstructor; $loops[$k][] =& $pathInLoop; $loopByConstructor = $loopByConstructor && $pathByConstructor; } elseif ($k === $id) { $loop = []; } } $this->addCircularReferences($id, $loop, $loopByConstructor); } elseif (!isset($checkedNodes[$id])) { $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor() && !$edge->isLazy()); } elseif (isset($loops[$id])) { // we already had detected loops for this edge // let's check if we have a common ancestor in one of the detected loops foreach ($loops[$id] as [$first, $loopPath]) { if (!isset($path[$first])) { continue; } // We have a common ancestor, let's fill the current path $fillPath = null; foreach ($loopPath as $k => $pathByConstructor) { if (null !== $fillPath) { $fillPath[$k] = $pathByConstructor; } elseif ($k === $id) { $fillPath = $path; $fillPath[$k] = $pathByConstructor; } } // we can now build the loop $loop = null; $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); foreach ($fillPath as $k => $pathByConstructor) { if (null !== $loop) { $loop[] = $k; $loopByConstructor = $loopByConstructor && $pathByConstructor; } elseif ($k === $first) { $loop = []; } } $this->addCircularReferences($first, $loop, $loopByConstructor); break; } } } unset($path[$sourceId]); } private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor) { $currentId = $sourceId; $currentPath = \array_reverse($currentPath); $currentPath[] = $currentId; foreach ($currentPath as $parentId) { if (empty($this->circularReferences[$parentId][$currentId])) { $this->circularReferences[$parentId][$currentId] = $byConstructor; } $currentId = $parentId; } } private function collectLineage(string $class, array &$lineage) { if (isset($lineage[$class])) { return; } if (!($r = $this->container->getReflectionClass($class, \false))) { return; } if (\is_a($class, $this->baseClass, \true)) { return; } $file = $r->getFileName(); if (\str_ends_with($file, ') : eval()\'d code')) { $file = \substr($file, 0, \strrpos($file, '(', -17)); } if (!$file || $this->doExport($file) === ($exportedFile = $this->export($file))) { return; } $lineage[$class] = \substr($exportedFile, 1, -1); if ($parent = $r->getParentClass()) { $this->collectLineage($parent->name, $lineage); } foreach ($r->getInterfaces() as $parent) { $this->collectLineage($parent->name, $lineage); } foreach ($r->getTraits() as $parent) { $this->collectLineage($parent->name, $lineage); } unset($lineage[$class]); $lineage[$class] = \substr($exportedFile, 1, -1); } private function generateProxyClasses() : array { $proxyClasses = []; $alreadyGenerated = []; $definitions = $this->container->getDefinitions(); $strip = '' === $this->docStar && \method_exists(Kernel::class, 'stripComments'); $proxyDumper = $this->getProxyDumper(); \ksort($definitions); foreach ($definitions as $definition) { if (!$proxyDumper->isProxyCandidate($definition)) { continue; } if (isset($alreadyGenerated[$class = $definition->getClass()])) { continue; } $alreadyGenerated[$class] = \true; // register class' reflector for resource tracking $this->container->getReflectionClass($class); if ("\n" === ($proxyCode = "\n" . $proxyDumper->getProxyCode($definition))) { continue; } if ($this->inlineRequires) { $lineage = []; $this->collectLineage($class, $lineage); $code = ''; foreach (\array_diff_key(\array_flip($lineage), $this->inlinedRequires) as $file => $class) { if ($this->inlineFactories) { $this->inlinedRequires[$file] = \true; } $code .= \sprintf("include_once %s;\n", $file); } $proxyCode = $code . $proxyCode; } if ($strip) { $proxyCode = "inlineRequires ? \substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1]; if ($this->asFiles || $this->namespace) { $proxyCode .= "\nif (!\\class_exists('{$proxyClass}', false)) {\n \\class_alias(__NAMESPACE__.'\\\\{$proxyClass}', '{$proxyClass}', false);\n}\n"; } $proxyClasses[$proxyClass . '.php'] = $proxyCode; } return $proxyClasses; } private function addServiceInclude(string $cId, Definition $definition) : string { $code = ''; if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) { $lineage = []; foreach ($this->inlinedDefinitions as $def) { if (!$def->isDeprecated()) { foreach ($this->getClasses($def, $cId) as $class) { $this->collectLineage($class, $lineage); } } } foreach ($this->serviceCalls as $id => [$callCount, $behavior]) { if ('service_container' !== $id && $id !== $cId && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior && $this->container->has($id) && $this->isTrivialInstance($def = $this->container->findDefinition($id))) { foreach ($this->getClasses($def, $cId) as $class) { $this->collectLineage($class, $lineage); } } } foreach (\array_diff_key(\array_flip($lineage), $this->inlinedRequires) as $file => $class) { $code .= \sprintf(" include_once %s;\n", $file); } } foreach ($this->inlinedDefinitions as $def) { if ($file = $def->getFile()) { $file = $this->dumpValue($file); $file = '(' === $file[0] ? \substr($file, 1, -1) : $file; $code .= \sprintf(" include_once %s;\n", $file); } } if ('' !== $code) { $code .= "\n"; } return $code; } /** * @throws InvalidArgumentException * @throws RuntimeException */ private function addServiceInstance(string $id, Definition $definition, bool $isSimpleInstance) : string { $class = $this->dumpValue($definition->getClass()); if (\str_starts_with($class, "'") && !\str_contains($class, '$') && !\preg_match('/^\'(?:\\\\{2})?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*(?:\\\\{2}[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)*\'$/', $class)) { throw new InvalidArgumentException(\sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); } $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); $instantiation = ''; $lastWitherIndex = null; foreach ($definition->getMethodCalls() as $k => $call) { if ($call[2] ?? \false) { $lastWitherIndex = $k; } } if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { $instantiation = \sprintf('$this->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); } elseif (!$isSimpleInstance) { $instantiation = '$instance'; } $return = ''; if ($isSimpleInstance) { $return = 'return '; } else { $instantiation .= ' = '; } return $this->addNewInstance($definition, ' ' . $return . $instantiation, $id); } private function isTrivialInstance(Definition $definition) : bool { if ($definition->hasErrors()) { return \true; } if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { return \false; } if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < \count($definition->getArguments())) { return \false; } foreach ($definition->getArguments() as $arg) { if (!$arg || $arg instanceof Parameter) { continue; } if (\is_array($arg) && 3 >= \count($arg)) { foreach ($arg as $k => $v) { if ($this->dumpValue($k) !== $this->dumpValue($k, \false)) { return \false; } if (!$v || $v instanceof Parameter) { continue; } if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) { continue; } if (!\is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, \false)) { return \false; } } } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) { continue; } elseif (!\is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, \false)) { return \false; } } return \true; } private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId) : string { $lastWitherIndex = null; foreach ($definition->getMethodCalls() as $k => $call) { if ($call[2] ?? \false) { $lastWitherIndex = $k; } } $calls = ''; foreach ($definition->getMethodCalls() as $k => $call) { $arguments = []; foreach ($call[1] as $i => $value) { $arguments[] = (\is_string($i) ? $i . ': ' : '') . $this->dumpValue($value); } $witherAssignation = ''; if ($call[2] ?? \false) { if (null !== $sharedNonLazyId && $lastWitherIndex === $k && 'instance' === $variableName) { $witherAssignation = \sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); } $witherAssignation .= \sprintf('$%s = ', $variableName); } $calls .= $this->wrapServiceConditionals($call[1], \sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], \implode(', ', $arguments))); } return $calls; } private function addServiceProperties(Definition $definition, string $variableName = 'instance') : string { $code = ''; foreach ($definition->getProperties() as $name => $value) { $code .= \sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value)); } return $code; } private function addServiceConfigurator(Definition $definition, string $variableName = 'instance') : string { if (!($callable = $definition->getConfigurator())) { return ''; } if (\is_array($callable)) { if ($callable[0] instanceof Reference || $callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) { return \sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } $class = $this->dumpValue($callable[0]); // If the class is a string we can optimize away if (\str_starts_with($class, "'") && !\str_contains($class, '$')) { return \sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName); } if (\str_starts_with($class, 'new ')) { return \sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } return \sprintf(" [%s, '%s'](\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } return \sprintf(" %s(\$%s);\n", $callable, $variableName); } private function addService(string $id, Definition $definition) : array { $this->definitionVariables = new \SplObjectStorage(); $this->referenceVariables = []; $this->variableCount = 0; $this->referenceVariables[$id] = new Variable('instance'); $return = []; if ($class = $definition->getClass()) { $class = $class instanceof Parameter ? '%' . $class . '%' : $this->container->resolveEnvPlaceholders($class); $return[] = \sprintf(\str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \\%s', \ltrim($class, '\\')); } elseif ($definition->getFactory()) { $factory = $definition->getFactory(); if (\is_string($factory)) { $return[] = \sprintf('@return object An instance returned by %s()', $factory); } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { $class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0]; $class = $class instanceof Parameter ? '%' . $class . '%' : $this->container->resolveEnvPlaceholders($class); $return[] = \sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]); } } if ($definition->isDeprecated()) { if ($return && \str_starts_with($return[\count($return) - 1], '@return')) { $return[] = ''; } $deprecation = $definition->getDeprecation($id); $return[] = \sprintf('@deprecated %s', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '') . $deprecation['message']); } $return = \str_replace("\n * \n", "\n *\n", \implode("\n * ", $return)); $return = $this->container->resolveEnvPlaceholders($return); $shared = $definition->isShared() ? ' shared' : ''; $public = $definition->isPublic() ? 'public' : 'private'; $autowired = $definition->isAutowired() ? ' autowired' : ''; $asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition); $methodName = $this->generateMethodName($id); if ($asFile || $definition->isLazy()) { $lazyInitialization = '$lazyLoad = true'; } else { $lazyInitialization = ''; } $code = <<docStar} * Gets the {$public} '{$id}'{$shared}{$autowired} service. * * {$return} EOF; $code = \str_replace('*/', ' ', $code) . <<hasErrors() && ($e = $definition->getErrors())) { $this->addThrow = \true; $code .= \sprintf(" \$this->throw(%s);\n", $this->export(\reset($e))); } else { $this->serviceCalls = []; $this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls); if ($definition->isDeprecated()) { $deprecation = $definition->getDeprecation($id); $code .= \sprintf(" trigger_deprecation(%s, %s, %s);\n\n", $this->export($deprecation['package']), $this->export($deprecation['version']), $this->export($deprecation['message'])); } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { foreach ($this->inlinedDefinitions as $def) { foreach ($this->getClasses($def, $id) as $class) { $this->preload[$class] = $class; } } } if (!$definition->isShared()) { $factory = \sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); } if ($isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition)) { if (!$definition->isShared()) { $code .= \sprintf(' %s = %1$s ?? ', $factory); if ($asFile) { $code .= "function () {\n"; $code .= " return self::do(\$container);\n"; $code .= " };\n\n"; } else { $code .= \sprintf("\\Closure::fromCallable([\$this, '%s']);\n\n", $methodName); } } $factoryCode = $asFile ? 'self::do($container, false)' : \sprintf('$this->%s(false)', $methodName); $factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); $code .= $asFile ? \preg_replace('/function \\(([^)]*+)\\)( {|:)/', 'function (\\1) use ($container)\\2', $factoryCode) : $factoryCode; } $c = $this->addServiceInclude($id, $definition); if ('' !== $c && $isProxyCandidate && !$definition->isShared()) { $c = \implode("\n", \array_map(function ($line) { return $line ? ' ' . $line : $line; }, \explode("\n", $c))); $code .= " static \$include = true;\n\n"; $code .= " if (\$include) {\n"; $code .= $c; $code .= " \$include = false;\n"; $code .= " }\n\n"; } else { $code .= $c; } $c = $this->addInlineService($id, $definition); if (!$isProxyCandidate && !$definition->isShared()) { $c = \implode("\n", \array_map(function ($line) { return $line ? ' ' . $line : $line; }, \explode("\n", $c))); $lazyloadInitialization = $definition->isLazy() ? '$lazyLoad = true' : ''; $c = \sprintf(" %s = function (%s) {\n%s };\n\n return %1\$s();\n", $factory, $lazyloadInitialization, $c); } $code .= $c; } if ($asFile) { $code = \str_replace('$this', '$container', $code); $code = \preg_replace('/function \\(([^)]*+)\\)( {|:)/', 'function (\\1) use ($container)\\2', $code); } $code .= " }\n"; $this->definitionVariables = $this->inlinedDefinitions = null; $this->referenceVariables = $this->serviceCalls = null; return [$file, $code]; } private function addInlineVariables(string $id, Definition $definition, array $arguments, bool $forConstructor) : string { $code = ''; foreach ($arguments as $argument) { if (\is_array($argument)) { $code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor); } elseif ($argument instanceof Reference) { $code .= $this->addInlineReference($id, $definition, $argument, $forConstructor); } elseif ($argument instanceof Definition) { $code .= $this->addInlineService($id, $definition, $argument, $forConstructor); } } return $code; } private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor) : string { while ($this->container->hasAlias($targetId)) { $targetId = (string) $this->container->getAlias($targetId); } [$callCount, $behavior] = $this->serviceCalls[$targetId]; if ($id === $targetId) { return $this->addInlineService($id, $definition, $definition); } if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { return ''; } if ($this->container->hasDefinition($targetId) && ($def = $this->container->getDefinition($targetId)) && !$def->isShared()) { return ''; } $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]) && !($this->hasProxyDumper && $definition->isLazy()); if ($hasSelfRef && !$forConstructor && !($forConstructor = !$this->circularReferences[$id][$targetId])) { $code = $this->addInlineService($id, $definition, $definition); } else { $code = ''; } if (isset($this->referenceVariables[$targetId]) || 2 > $callCount && (!$hasSelfRef || !$forConstructor)) { return $code; } $name = $this->getNextVariableName(); $this->referenceVariables[$targetId] = new Variable($name); $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null; $code .= \sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference)); if (!$hasSelfRef || !$forConstructor) { return $code; } $code .= \sprintf(<<<'EOTXT' if (isset($this->%s[%s])) { return $this->%1$s[%2$s]; } EOTXT , $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id)); return $code; } private function addInlineService(string $id, Definition $definition, ?Definition $inlineDef = null, bool $forConstructor = \true) : string { $code = ''; if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) { $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); } } } if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) { return $code; } $arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()]; $code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor); if ($arguments = \array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) { $isSimpleInstance = \false; } elseif ($definition !== $inlineDef && 2 > $this->inlinedDefinitions[$inlineDef]) { return $code; } if (isset($this->definitionVariables[$inlineDef])) { $isSimpleInstance = \false; } else { $name = $definition === $inlineDef ? 'instance' : $this->getNextVariableName(); $this->definitionVariables[$inlineDef] = new Variable($name); $code .= '' !== $code ? "\n" : ''; if ('instance' === $name) { $code .= $this->addServiceInstance($id, $definition, $isSimpleInstance); } else { $code .= $this->addNewInstance($inlineDef, ' $' . $name . ' = ', $id); } if ('' !== ($inline = $this->addInlineVariables($id, $definition, $arguments, \false))) { $code .= "\n" . $inline . "\n"; } elseif ($arguments && 'instance' === $name) { $code .= "\n"; } $code .= $this->addServiceProperties($inlineDef, $name); $code .= $this->addServiceMethodCalls($inlineDef, $name, !$this->getProxyDumper()->isProxyCandidate($inlineDef) && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null); $code .= $this->addServiceConfigurator($inlineDef, $name); } if ($isRootInstance && !$isSimpleInstance) { $code .= "\n return \$instance;\n"; } return $code; } private function addServices(?array &$services = null) : string { $publicServices = $privateServices = ''; $definitions = $this->container->getDefinitions(); \ksort($definitions); foreach ($definitions as $id => $definition) { if (!$definition->isSynthetic()) { $services[$id] = $this->addService($id, $definition); } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { $services[$id] = null; foreach ($this->getClasses($definition, $id) as $class) { $this->preload[$class] = $class; } } } foreach ($definitions as $id => $definition) { if (!([$file, $code] = $services[$id]) || null !== $file) { continue; } if ($definition->isPublic()) { $publicServices .= $code; } elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) { $privateServices .= $code; } } return $publicServices . $privateServices; } private function generateServiceFiles(array $services) : iterable { $definitions = $this->container->getDefinitions(); \ksort($definitions); foreach ($definitions as $id => $definition) { if (([$file, $code] = $services[$id]) && null !== $file && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) { (yield $file => [$code, $definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1]) && !$definition->isDeprecated() && !$definition->hasErrors()]); } } } private function addNewInstance(Definition $definition, string $return = '', ?string $id = null) : string { $tail = $return ? ";\n" : ''; if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) { $arguments = []; foreach ($definition->getArgument(0) as $k => $argument) { $arguments[$k] = $argument->getValues()[0]; } return $return . $this->dumpValue(new ServiceLocatorArgument($arguments)) . $tail; } $arguments = []; foreach ($definition->getArguments() as $i => $value) { $arguments[] = (\is_string($i) ? $i . ': ' : '') . $this->dumpValue($value); } if (null !== $definition->getFactory()) { $callable = $definition->getFactory(); if (\is_array($callable)) { if (!\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $callable[1])) { throw new RuntimeException(\sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a')); } if ($callable[0] instanceof Reference || $callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) { return $return . \sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? \implode(', ', $arguments) : '') . $tail; } $class = $this->dumpValue($callable[0]); // If the class is a string we can optimize away if (\str_starts_with($class, "'") && !\str_contains($class, '$')) { if ("''" === $class) { throw new RuntimeException(\sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "' . $id . '"' : 'inline')); } return $return . \sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? \implode(', ', $arguments) : '') . $tail; } if (\str_starts_with($class, 'new ')) { return $return . \sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? \implode(', ', $arguments) : '') . $tail; } return $return . \sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? \implode(', ', $arguments) : '') . $tail; } return $return . \sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? \implode(', ', $arguments) : '') . $tail; } if (null === ($class = $definition->getClass())) { throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); } return $return . \sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), \implode(', ', $arguments)) . $tail; } private function startClass(string $class, string $baseClass, bool $hasProxyClasses) : string { $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; $code = <<docStar} * @internal This class has been auto-generated by the Symfony Dependency Injection Component. */ class {$class} extends {$baseClass} { protected \$parameters = []; public function __construct() { EOF; if ($this->asFiles) { $code = \str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code); $code = \str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code); $code .= " \$this->buildParameters = \$buildParameters;\n"; $code .= " \$this->containerDir = \$containerDir;\n"; if (null !== $this->targetDirRegex) { $code = \str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code); $code .= ' $this->targetDir = \\dirname($containerDir);' . "\n"; } } if (Container::class !== $this->baseClass) { $r = $this->container->getReflectionClass($this->baseClass, \false); if (null !== $r && null !== ($constructor = $r->getConstructor()) && 0 === $constructor->getNumberOfRequiredParameters() && Container::class !== $constructor->getDeclaringClass()->name) { $code .= " parent::__construct();\n"; $code .= " \$this->parameterBag = null;\n\n"; } } if ($this->container->getParameterBag()->all()) { $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; } $code .= " \$this->services = \$this->privates = [];\n"; $code .= $this->addSyntheticIds(); $code .= $this->addMethodMap(); $code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : ''; $code .= $this->addAliases(); $code .= $this->addInlineRequires($hasProxyClasses); $code .= <<addRemovedIds(); if ($this->asFiles && !$this->inlineFactories) { $code .= <<<'EOF' protected function load($file, $lazyLoad = true) { if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { return $class::do($this, $lazyLoad); } if ('.' === $file[-4]) { $class = substr($class, 0, -4); } else { $file .= '.php'; } $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; } EOF; } $proxyDumper = $this->getProxyDumper(); foreach ($this->container->getDefinitions() as $definition) { if (!$proxyDumper->isProxyCandidate($definition)) { continue; } if ($this->asFiles && !$this->inlineFactories) { $proxyLoader = "class_exists(\$class, false) || require __DIR__.'/'.\$class.'.php';\n\n "; } else { $proxyLoader = ''; } $code .= <<container->getDefinitions(); \ksort($definitions); foreach ($definitions as $id => $definition) { if ($definition->isSynthetic() && 'service_container' !== $id) { $code .= ' ' . $this->doExport($id) . " => true,\n"; } } return $code ? " \$this->syntheticIds = [\n{$code} ];\n" : ''; } private function addRemovedIds() : string { $ids = $this->container->getRemovedIds(); foreach ($this->container->getDefinitions() as $id => $definition) { if (!$definition->isPublic()) { $ids[$id] = \true; } } if (!$ids) { return ''; } if ($this->asFiles) { $code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'"; } else { $code = ''; $ids = \array_keys($ids); \sort($ids); foreach ($ids as $id) { if (\preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) { continue; } $code .= ' ' . $this->doExport($id) . " => true,\n"; } $code = "[\n{$code} ]"; } return <<container->getDefinitions(); \ksort($definitions); foreach ($definitions as $id => $definition) { if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) { $code .= ' ' . $this->doExport($id) . ' => ' . $this->doExport($this->generateMethodName($id)) . ",\n"; } } $aliases = $this->container->getAliases(); foreach ($aliases as $alias => $id) { if (!$id->isDeprecated()) { continue; } $code .= ' ' . $this->doExport($alias) . ' => ' . $this->doExport($this->generateMethodName($alias)) . ",\n"; } return $code ? " \$this->methodMap = [\n{$code} ];\n" : ''; } private function addFileMap() : string { $code = ''; $definitions = $this->container->getDefinitions(); \ksort($definitions); foreach ($definitions as $id => $definition) { if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) { $code .= \sprintf(" %s => '%s',\n", $this->doExport($id), $this->generateMethodName($id)); } } return $code ? " \$this->fileMap = [\n{$code} ];\n" : ''; } private function addAliases() : string { if (!($aliases = $this->container->getAliases())) { return "\n \$this->aliases = [];\n"; } $code = " \$this->aliases = [\n"; \ksort($aliases); foreach ($aliases as $alias => $id) { if ($id->isDeprecated()) { continue; } $id = (string) $id; while (isset($aliases[$id])) { $id = (string) $aliases[$id]; } $code .= ' ' . $this->doExport($alias) . ' => ' . $this->doExport($id) . ",\n"; } return $code . " ];\n"; } private function addDeprecatedAliases() : string { $code = ''; $aliases = $this->container->getAliases(); foreach ($aliases as $alias => $definition) { if (!$definition->isDeprecated()) { continue; } $public = $definition->isPublic() ? 'public' : 'private'; $id = (string) $definition; $methodNameAlias = $this->generateMethodName($alias); $idExported = $this->export($id); $deprecation = $definition->getDeprecation($alias); $packageExported = $this->export($deprecation['package']); $versionExported = $this->export($deprecation['version']); $messageExported = $this->export($deprecation['message']); $code .= <<docStar} * Gets the {$public} '{$alias}' alias. * * @return object The "{$id}" service. */ protected function {$methodNameAlias}() { trigger_deprecation({$packageExported}, {$versionExported}, {$messageExported}); return \$this->get({$idExported}); } EOF; } return $code; } private function addInlineRequires(bool $hasProxyClasses) : string { $lineage = []; $hotPathServices = $this->hotPathTag && $this->inlineRequires ? $this->container->findTaggedServiceIds($this->hotPathTag) : []; foreach ($hotPathServices as $id => $tags) { $definition = $this->container->getDefinition($id); if ($this->getProxyDumper()->isProxyCandidate($definition)) { continue; } $inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]); foreach ($inlinedDefinitions as $def) { foreach ($this->getClasses($def, $id) as $class) { $this->collectLineage($class, $lineage); } } } $code = ''; foreach ($lineage as $file) { if (!isset($this->inlinedRequires[$file])) { $this->inlinedRequires[$file] = \true; $code .= \sprintf("\n include_once %s;", $file); } } if ($hasProxyClasses) { $code .= "\n include_once __DIR__.'/proxy-classes.php';"; } return $code ? \sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : ''; } private function addDefaultParametersMethod() : string { if (!$this->container->getParameterBag()->all()) { return ''; } $php = []; $dynamicPhp = []; foreach ($this->container->getParameterBag()->all() as $key => $value) { if ($key !== ($resolvedKey = $this->container->resolveEnvPlaceholders($key))) { throw new InvalidArgumentException(\sprintf('Parameter name cannot use env parameters: "%s".', $resolvedKey)); } $hasEnum = \false; $export = $this->exportParameters([$value], '', 12, $hasEnum); $export = \explode('0 => ', \substr(\rtrim($export, " ]\n"), 2, -1), 2); if ($hasEnum || \preg_match("/\\\$this->(?:getEnv\\('(?:[-.\\w]*+:)*+\\w++'\\)|targetDir\\.'')/", $export[1])) { $dynamicPhp[$key] = \sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); $this->dynamicParameters[$key] = \true; } else { $php[] = \sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); } } $parameters = \sprintf("[\n%s\n%s]", \implode("\n", $php), \str_repeat(' ', 8)); $code = <<<'EOF' /** * @return array|bool|float|int|string|\UnitEnum|null */ public function getParameter(string $name) { if (isset($this->buildParameters[$name])) { return $this->buildParameters[$name]; } if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); } if (isset($this->loadedDynamicParameters[$name])) { return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); } return $this->parameters[$name]; } public function hasParameter(string $name): bool { if (isset($this->buildParameters[$name])) { return true; } return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); } public function setParameter(string $name, $value): void { throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } public function getParameterBag(): ParameterBagInterface { if (null === $this->parameterBag) { $parameters = $this->parameters; foreach ($this->loadedDynamicParameters as $name => $loaded) { $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); } foreach ($this->buildParameters as $name => $value) { $parameters[$name] = $value; } $this->parameterBag = new FrozenParameterBag($parameters); } return $this->parameterBag; } EOF; if (!$this->asFiles) { $code = \preg_replace('/^.*buildParameters.*\\n.*\\n.*\\n\\n?/m', '', $code); } if ($dynamicPhp) { $loadedDynamicParameters = $this->exportParameters(\array_combine(\array_keys($dynamicPhp), \array_fill(0, \count($dynamicPhp), \false)), '', 8); $getDynamicParameter = <<<'EOF' switch ($name) { %s default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%%s" must be defined.', $name)); } $this->loadedDynamicParameters[$name] = true; return $this->dynamicParameters[$name] = $value; EOF; $getDynamicParameter = \sprintf($getDynamicParameter, \implode("\n", $dynamicPhp)); } else { $loadedDynamicParameters = '[]'; $getDynamicParameter = \str_repeat(' ', 8) . 'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));'; } $code .= << $value) { if (\is_array($value)) { $value = $this->exportParameters($value, $path . '/' . $key, $indent + 4, $hasEnum); } elseif ($value instanceof ArgumentInterface) { throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', \get_debug_type($value), $path . '/' . $key)); } elseif ($value instanceof Variable) { throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path . '/' . $key)); } elseif ($value instanceof Definition) { throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path . '/' . $key)); } elseif ($value instanceof Reference) { throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path . '/' . $key)); } elseif ($value instanceof Expression) { throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path . '/' . $key)); } elseif ($value instanceof \UnitEnum) { $hasEnum = \true; $value = \sprintf('\\%s::%s', \get_class($value), $value->name); } else { $value = $this->export($value); } $php[] = \sprintf('%s%s => %s,', \str_repeat(' ', $indent), $this->export($key), $value); } return \sprintf("[\n%s\n%s]", \implode("\n", $php), \str_repeat(' ', $indent - 4)); } private function endClass() : string { if ($this->addThrow) { return <<<'EOF' protected function throw($message) { throw new RuntimeException($message); } } EOF; } return <<<'EOF' } EOF; } private function wrapServiceConditionals($value, string $code) : string { if (!($condition = $this->getServiceConditionals($value))) { return $code; } // re-indent the wrapped code $code = \implode("\n", \array_map(function ($line) { return $line ? ' ' . $line : $line; }, \explode("\n", $code))); return \sprintf(" if (%s) {\n%s }\n", $condition, $code); } private function getServiceConditionals($value) : string { $conditions = []; foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { if (!$this->container->hasDefinition($service)) { return 'false'; } $conditions[] = \sprintf('isset($this->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); } foreach (ContainerBuilder::getServiceConditionals($value) as $service) { if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { continue; } $conditions[] = \sprintf('$this->has(%s)', $this->doExport($service)); } if (!$conditions) { return ''; } return \implode(' && ', $conditions); } private function getDefinitionsFromArguments(array $arguments, ?\SplObjectStorage $definitions = null, array &$calls = [], ?bool $byConstructor = null) : \SplObjectStorage { if (null === $definitions) { $definitions = new \SplObjectStorage(); } foreach ($arguments as $argument) { if (\is_array($argument)) { $this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor); } elseif ($argument instanceof Reference) { $id = (string) $argument; while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); } if (!isset($calls[$id])) { $calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor]; } else { $calls[$id][1] = \min($calls[$id][1], $argument->getInvalidBehavior()); } ++$calls[$id][0]; } elseif (!$argument instanceof Definition) { // no-op } elseif (isset($definitions[$argument])) { $definitions[$argument] = 1 + $definitions[$argument]; } else { $definitions[$argument] = 1; $arguments = [$argument->getArguments(), $argument->getFactory()]; $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor); $arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()]; $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor); } } return $definitions; } /** * @throws RuntimeException */ private function dumpValue($value, bool $interpolate = \true) : string { if (\is_array($value)) { if ($value && $interpolate && \false !== ($param = \array_search($value, $this->container->getParameterBag()->all(), \true))) { return $this->dumpValue("%{$param}%"); } $code = []; foreach ($value as $k => $v) { $code[] = \sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); } return \sprintf('[%s]', \implode(', ', $code)); } elseif ($value instanceof ArgumentInterface) { $scope = [$this->definitionVariables, $this->referenceVariables]; $this->definitionVariables = $this->referenceVariables = null; try { if ($value instanceof ServiceClosureArgument) { $value = $value->getValues()[0]; $code = $this->dumpValue($value, $interpolate); $returnedType = ''; if ($value instanceof TypedReference) { $returnedType = \sprintf(': %s\\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', \str_replace(['|', '&'], ['|\\', '&\\'], $value->getType())); } $code = \sprintf('return %s;', $code); return \sprintf("function ()%s {\n %s\n }", $returnedType, $code); } if ($value instanceof IteratorArgument) { $operands = [0]; $code = []; $code[] = 'new RewindableGenerator(function () {'; if (!($values = $value->getValues())) { $code[] = ' return new \\EmptyIterator();'; } else { $countCode = []; $countCode[] = 'function () {'; foreach ($values as $k => $v) { ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ({$c})" : ++$operands[0]; $v = $this->wrapServiceConditionals($v, \sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); foreach (\explode("\n", $v) as $v) { if ($v) { $code[] = ' ' . $v; } } } $countCode[] = \sprintf(' return %s;', \implode(' + ', $operands)); $countCode[] = ' }'; } $code[] = \sprintf(' }, %s)', \count($operands) > 1 ? \implode("\n", $countCode) : $operands[0]); return \implode("\n", $code); } if ($value instanceof ServiceLocatorArgument) { $serviceMap = ''; $serviceTypes = ''; foreach ($value->getValues() as $k => $v) { if (!$v) { continue; } $id = (string) $v; while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); } $definition = $this->container->getDefinition($id); $load = !($definition->hasErrors() && ($e = $definition->getErrors())) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : \reset($e); $serviceMap .= \sprintf("\n %s => [%s, %s, %s, %s],", $this->export($k), $this->export($definition->isShared() ? $definition->isPublic() ? 'services' : 'privates' : \false), $this->doExport($id), $this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id) : null), $this->export($load)); $serviceTypes .= \sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?')); $this->locatedIds[$id] = \true; } $this->addGetService = \true; return \sprintf('new \\%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); } } finally { [$this->definitionVariables, $this->referenceVariables] = $scope; } } elseif ($value instanceof Definition) { if ($value->hasErrors() && ($e = $value->getErrors())) { $this->addThrow = \true; return \sprintf('$this->throw(%s)', $this->export(\reset($e))); } if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { return $this->dumpValue($this->definitionVariables[$value], $interpolate); } if ($value->getMethodCalls()) { throw new RuntimeException('Cannot dump definitions which have method calls.'); } if ($value->getProperties()) { throw new RuntimeException('Cannot dump definitions which have properties.'); } if (null !== $value->getConfigurator()) { throw new RuntimeException('Cannot dump definitions which have a configurator.'); } return $this->addNewInstance($value); } elseif ($value instanceof Variable) { return '$' . $value; } elseif ($value instanceof Reference) { $id = (string) $value; while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); } if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) { return $this->dumpValue($this->referenceVariables[$id], $interpolate); } return $this->getServiceCall($id, $value); } elseif ($value instanceof Expression) { return $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); } elseif ($value instanceof Parameter) { return $this->dumpParameter($value); } elseif (\true === $interpolate && \is_string($value)) { if (\preg_match('/^%([^%]+)%$/', $value, $match)) { // we do this to deal with non string values (Boolean, integer, ...) // the preg_replace_callback converts them to strings return $this->dumpParameter($match[1]); } else { $replaceParameters = function ($match) { return "'." . $this->dumpParameter($match[2]) . ".'"; }; $code = \str_replace('%%', '%', \preg_replace_callback('/(?export($value))); return $code; } } elseif ($value instanceof \UnitEnum) { return \sprintf('\\%s::%s', \get_class($value), $value->name); } elseif ($value instanceof AbstractArgument) { throw new RuntimeException($value->getTextWithContext()); } elseif (\is_object($value) || \is_resource($value)) { throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); } return $this->export($value); } /** * Dumps a string to a literal (aka PHP Code) class value. * * @throws RuntimeException */ private function dumpLiteralClass(string $class) : string { if (\str_contains($class, '$')) { return \sprintf('${($_ = %s) && false ?: "_"}', $class); } if (!\str_starts_with($class, "'") || !\preg_match('/^\'(?:\\\\{2})?[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*(?:\\\\{2}[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)*\'$/', $class)) { throw new RuntimeException(\sprintf('Cannot dump definition because of invalid class name (%s).', $class ?: 'n/a')); } $class = \substr(\str_replace('\\\\', '\\', $class), 1, -1); return \str_starts_with($class, '\\') ? $class : '\\' . $class; } private function dumpParameter(string $name) : string { if (!$this->container->hasParameter($name) || ($this->dynamicParameters[$name] ?? \false)) { return \sprintf('$this->getParameter(%s)', $this->doExport($name)); } $value = $this->container->getParameter($name); $dumpedValue = $this->dumpValue($value, \false); if (!$value || !\is_array($value)) { return $dumpedValue; } return \sprintf('$this->parameters[%s]', $this->doExport($name)); } private function getServiceCall(string $id, ?Reference $reference = null) : string { while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); } if ('service_container' === $id) { return '$this'; } if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id))) { if ($definition->isSynthetic()) { $code = \sprintf('$this->get(%s%s)', $this->doExport($id), null !== $reference ? ', ' . $reference->getInvalidBehavior() : ''); } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { $code = 'null'; if (!$definition->isShared()) { return $code; } } elseif ($this->isTrivialInstance($definition)) { if ($definition->hasErrors() && ($e = $definition->getErrors())) { $this->addThrow = \true; return \sprintf('$this->throw(%s)', $this->export(\reset($e))); } $code = $this->addNewInstance($definition, '', $id); if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $code = \sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } $code = "({$code})"; } else { $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$this->load('%s')" : '$this->%s()'; $code = \sprintf($code, $this->generateMethodName($id)); if (!$definition->isShared()) { $factory = \sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); $code = \sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code); } } if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $code = \sprintf('($this->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } return $code; } if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { return 'null'; } if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) { $code = \sprintf('$this->get(%s, /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $this->doExport($id), ContainerInterface::NULL_ON_INVALID_REFERENCE); } else { $code = \sprintf('$this->get(%s)', $this->doExport($id)); } return \sprintf('($this->services[%s] ?? %s)', $this->doExport($id), $code); } /** * Initializes the method names map to avoid conflicts with the Container methods. */ private function initializeMethodNamesMap(string $class) { $this->serviceIdToMethodNameMap = []; $this->usedMethodNames = []; if ($reflectionClass = $this->container->getReflectionClass($class)) { foreach ($reflectionClass->getMethods() as $method) { $this->usedMethodNames[\strtolower($method->getName())] = \true; } } } /** * @throws InvalidArgumentException */ private function generateMethodName(string $id) : string { if (isset($this->serviceIdToMethodNameMap[$id])) { return $this->serviceIdToMethodNameMap[$id]; } $i = \strrpos($id, '\\'); $name = Container::camelize(\false !== $i && isset($id[1 + $i]) ? \substr($id, 1 + $i) : $id); $name = \preg_replace('/[^a-zA-Z0-9_\\x7f-\\xff]/', '', $name); $methodName = 'get' . $name . 'Service'; $suffix = 1; while (isset($this->usedMethodNames[\strtolower($methodName)])) { ++$suffix; $methodName = 'get' . $name . $suffix . 'Service'; } $this->serviceIdToMethodNameMap[$id] = $methodName; $this->usedMethodNames[\strtolower($methodName)] = \true; return $methodName; } private function getNextVariableName() : string { $firstChars = self::FIRST_CHARS; $firstCharsLength = \strlen($firstChars); $nonFirstChars = self::NON_FIRST_CHARS; $nonFirstCharsLength = \strlen($nonFirstChars); while (\true) { $name = ''; $i = $this->variableCount; if ('' === $name) { $name .= $firstChars[$i % $firstCharsLength]; $i = (int) ($i / $firstCharsLength); } while ($i > 0) { --$i; $name .= $nonFirstChars[$i % $nonFirstCharsLength]; $i = (int) ($i / $nonFirstCharsLength); } ++$this->variableCount; // check that the name is not reserved if (\in_array($name, $this->reservedVariables, \true)) { continue; } return $name; } } private function getExpressionLanguage() : ExpressionLanguage { if (null === $this->expressionLanguage) { if (!\class_exists(\_ContaoManager\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $providers = $this->container->getExpressionLanguageProviders(); $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { $id = '""' === \substr_replace($arg, '', 1, -1) ? \stripcslashes(\substr($arg, 1, -1)) : null; if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) { return $this->getServiceCall($id); } return \sprintf('$this->get(%s)', $arg); }); if ($this->container->isTrackingResources()) { foreach ($providers as $provider) { $this->container->addObjectResource($provider); } } } return $this->expressionLanguage; } private function isHotPath(Definition $definition) : bool { return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated(); } private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node) : bool { if ($node->getValue()->isPublic()) { return \false; } $ids = []; foreach ($node->getInEdges() as $edge) { if (!($value = $edge->getSourceNode()->getValue())) { continue; } if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) { return \false; } $ids[$edge->getSourceNode()->getId()] = \true; } return 1 === \count($ids); } /** * @return mixed */ private function export($value) { if (null !== $this->targetDirRegex && \is_string($value) && \preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) { $suffix = $matches[0][1] + \strlen($matches[0][0]); $matches[0][1] += \strlen($matches[1][0]); $prefix = $matches[0][1] ? $this->doExport(\substr($value, 0, $matches[0][1]), \true) . '.' : ''; if ('\\' === \DIRECTORY_SEPARATOR && isset($value[$suffix])) { $cookie = '\\' . \random_int(100000, \PHP_INT_MAX); $suffix = '.' . $this->doExport(\str_replace('\\', $cookie, \substr($value, $suffix)), \true); $suffix = \str_replace('\\' . $cookie, "'.\\DIRECTORY_SEPARATOR.'", $suffix); } else { $suffix = isset($value[$suffix]) ? '.' . $this->doExport(\substr($value, $suffix), \true) : ''; } $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__'; $offset = 2 + $this->targetDirMaxMatches - \count($matches); if (0 < $offset) { $dirname = \sprintf('\\dirname(__DIR__, %d)', $offset + (int) $this->asFiles); } elseif ($this->asFiles) { $dirname = "\$this->targetDir.''"; // empty string concatenation on purpose } if ($prefix || $suffix) { return \sprintf('(%s%s%s)', $prefix, $dirname, $suffix); } return $dirname; } return $this->doExport($value, \true); } /** * @return mixed */ private function doExport($value, bool $resolveEnv = \false) { $shouldCacheValue = $resolveEnv && \is_string($value); if ($shouldCacheValue && isset($this->exportedVariables[$value])) { return $this->exportedVariables[$value]; } if (\is_string($value) && \str_contains($value, "\n")) { $cleanParts = \explode("\n", $value); $cleanParts = \array_map(function ($part) { return \var_export($part, \true); }, $cleanParts); $export = \implode('."\\n".', $cleanParts); } else { $export = \var_export($value, \true); } if ($this->asFiles) { if (\false !== \strpos($export, '$this')) { $export = \str_replace('$this', "\$'.'this", $export); } if (\false !== \strpos($export, 'function () {')) { $export = \str_replace('function () {', "function ('.') {", $export); } } if ($resolveEnv && "'" === $export[0] && $export !== ($resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'"))) { $export = $resolvedExport; if (\str_ends_with($export, ".''")) { $export = \substr($export, 0, -3); if ("'" === $export[1]) { $export = \substr_replace($export, '', 18, 7); } } if ("'" === $export[1]) { $export = \substr($export, 3); } } if ($shouldCacheValue) { $this->exportedVariables[$value] = $export; } return $export; } private function getAutoloadFile() : ?string { $file = null; foreach (\spl_autoload_functions() as $autoloader) { if (!\is_array($autoloader)) { continue; } if ($autoloader[0] instanceof DebugClassLoader || $autoloader[0] instanceof LegacyDebugClassLoader) { $autoloader = $autoloader[0]->getClassLoader(); } if (!\is_array($autoloader) || !$autoloader[0] instanceof ClassLoader || !$autoloader[0]->findFile(__CLASS__)) { continue; } foreach (\get_declared_classes() as $class) { if (\str_starts_with($class, 'ComposerAutoloaderInit') && $class::getLoader() === $autoloader[0]) { $file = \dirname((new \ReflectionClass($class))->getFileName(), 2) . '/autoload.php'; if (null !== $this->targetDirRegex && \preg_match($this->targetDirRegex . 'A', $file)) { return $file; } } } } return $file; } private function getClasses(Definition $definition, string $id) : array { $classes = []; while ($definition instanceof Definition) { foreach ($definition->getTag($this->preloadTags[0]) as $tag) { if (!isset($tag['class'])) { throw new InvalidArgumentException(\sprintf('Missing attribute "class" on tag "%s" for service "%s".', $this->preloadTags[0], $id)); } $classes[] = \trim($tag['class'], '\\'); } if ($class = $definition->getClass()) { $classes[] = \trim($class, '\\'); } $factory = $definition->getFactory(); if (!\is_array($factory)) { $factory = [$factory]; } if (\is_string($factory[0])) { if (\false !== ($i = \strrpos($factory[0], '::'))) { $factory[0] = \substr($factory[0], 0, $i); } $classes[] = \trim($factory[0], '\\'); } $definition = $factory[0]; } return $classes; } } { "name": "symfony\/dependency-injection", "type": "library", "description": "Allows you to standardize and centralize the way objects are constructed in your application", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "psr\/container": "^1.1.1", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.16", "symfony\/polyfill-php81": "^1.22", "symfony\/service-contracts": "^1.1.6|^2" }, "require-dev": { "symfony\/yaml": "^4.4.26|^5.0|^6.0", "symfony\/config": "^5.3|^6.0", "symfony\/expression-language": "^4.4|^5.0|^6.0" }, "suggest": { "symfony\/yaml": "", "symfony\/config": "", "symfony\/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", "symfony\/expression-language": "For using expressions in service container configuration", "symfony\/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { "ext-psr": "<1.1|>=2", "symfony\/config": "<5.3", "symfony\/finder": "<4.4", "symfony\/proxy-manager-bridge": "<4.4", "symfony\/yaml": "<4.4.26" }, "provide": { "psr\/container-implementation": "1.0", "symfony\/service-implementation": "1.0|2.0" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\DependencyInjection\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * EnvVarLoaderInterface objects return key/value pairs that are added to the list of available env vars. * * @author Nicolas Grekas */ interface EnvVarLoaderInterface { /** * @return string[] Key/value pairs that can be accessed using the regular "%env()%" syntax */ public function loadEnvVars() : array; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\DependencyInjection; /** * ContainerAwareInterface should be implemented by classes that depends on a Container. * * @author Fabien Potencier */ interface ContainerAwareInterface { /** * Sets the container. */ public function setContainer(?ContainerInterface $container = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; /** * Provides password hashing capabilities. * * @author Robin Chalas * @author Fabien Potencier * @author Nicolas Grekas */ interface PasswordHasherInterface { public const MAX_PASSWORD_LENGTH = 4096; /** * Hashes a plain password. * * @throws InvalidPasswordException When the plain password is invalid, e.g. excessively long */ public function hash(string $plainPassword) : string; /** * Verifies a plain password against a hash. */ public function verify(string $hashedPassword, string $plainPassword) : bool; /** * Checks if a password hash would benefit from rehashing. */ public function needsRehash(string $hashedPassword) : bool; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\LogicException; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; /** * Hashes passwords using libsodium. * * @author Robin Chalas * @author Zan Baldwin * @author Dominik Müller */ final class SodiumPasswordHasher implements PasswordHasherInterface { use CheckPasswordLengthTrait; private $opsLimit; private $memLimit; public function __construct(?int $opsLimit = null, ?int $memLimit = null) { if (!self::isSupported()) { throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); } $this->opsLimit = $opsLimit ?? \max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $this->memLimit = $memLimit ?? \max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); if (3 > $this->opsLimit) { throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); } if (10 * 1024 > $this->memLimit) { throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); } } public static function isSupported() : bool { return \version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : \phpversion('libsodium'), '1.0.14', '>='); } public function hash(string $plainPassword) : string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } if (\function_exists('sodium_crypto_pwhash_str')) { return \sodium_crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit); } if (\extension_loaded('libsodium')) { return \Sodium\crypto_pwhash_str($plainPassword, $this->opsLimit, $this->memLimit); } throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); } public function verify(string $hashedPassword, string $plainPassword) : bool { if ('' === $plainPassword) { return \false; } if ($this->isPasswordTooLong($plainPassword)) { return \false; } if (0 !== \strpos($hashedPassword, '$argon')) { if (0 === \strpos($hashedPassword, '$2') && (72 < \strlen($plainPassword) || \false !== \strpos($plainPassword, "\x00"))) { $plainPassword = \base64_encode(\hash('sha512', $plainPassword, \true)); } // Accept validating non-argon passwords for seamless migrations return \password_verify($plainPassword, $hashedPassword); } if (\function_exists('sodium_crypto_pwhash_str_verify')) { return \sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword); } if (\extension_loaded('libsodium')) { return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword); } return \false; } public function needsRehash(string $hashedPassword) : bool { if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) { return \sodium_crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit); } if (\extension_loaded('libsodium')) { return \_ContaoManager\Sodium\crypto_pwhash_str_needs_rehash($hashedPassword, $this->opsLimit, $this->memLimit); } throw new LogicException('Libsodium is not available. You should either install the sodium extension or use a different password hasher.'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; /** * @author Robin Chalas */ trait CheckPasswordLengthTrait { private function isPasswordTooLong(string $password) : bool { return PasswordHasherInterface::MAX_PASSWORD_LENGTH < \strlen($password); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use _ContaoManager\Symfony\Component\Security\Core\User\UserInterface; /** * Hashes passwords based on the user and the PasswordHasherFactory. * * @author Ariel Ferrandini * * @final */ class UserPasswordHasher implements UserPasswordHasherInterface { private $hasherFactory; public function __construct(PasswordHasherFactoryInterface $hasherFactory) { $this->hasherFactory = $hasherFactory; } /** * @param PasswordAuthenticatedUserInterface $user */ public function hashPassword($user, string $plainPassword) : string { if (!$user instanceof PasswordAuthenticatedUserInterface) { if (!$user instanceof UserInterface) { throw new \TypeError(\sprintf('Expected an instance of "%s" as first argument, but got "%s".', UserInterface::class, \get_debug_type($user))); } \trigger_deprecation('symfony/password-hasher', '5.3', 'The "%s()" method expects a "%s" instance as first argument. Not implementing it in class "%s" is deprecated.', __METHOD__, PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $salt = null; if ($user instanceof LegacyPasswordAuthenticatedUserInterface) { $salt = $user->getSalt(); } elseif ($user instanceof UserInterface) { $salt = \method_exists($user, 'getSalt') ? $user->getSalt() : null; if ($salt) { \trigger_deprecation('symfony/password-hasher', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } } $hasher = $this->hasherFactory->getPasswordHasher($user); return $hasher->hash($plainPassword, $salt); } /** * @param PasswordAuthenticatedUserInterface $user */ public function isPasswordValid($user, string $plainPassword) : bool { if (!$user instanceof PasswordAuthenticatedUserInterface) { if (!$user instanceof UserInterface) { throw new \TypeError(\sprintf('Expected an instance of "%s" as first argument, but got "%s".', UserInterface::class, \get_debug_type($user))); } \trigger_deprecation('symfony/password-hasher', '5.3', 'The "%s()" method expects a "%s" instance as first argument. Not implementing it in class "%s" is deprecated.', __METHOD__, PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $salt = null; if ($user instanceof LegacyPasswordAuthenticatedUserInterface) { $salt = $user->getSalt(); } elseif ($user instanceof UserInterface) { $salt = $user->getSalt(); if (null !== $salt) { \trigger_deprecation('symfony/password-hasher', '5.3', 'Returning a string from "getSalt()" without implementing the "%s" interface is deprecated, the "%s" class should implement it.', LegacyPasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } } if (null === $user->getPassword()) { return \false; } $hasher = $this->hasherFactory->getPasswordHasher($user); return $hasher->verify($user->getPassword(), $plainPassword, $salt); } /** * @param PasswordAuthenticatedUserInterface $user */ public function needsRehash($user) : bool { if (null === $user->getPassword()) { return \false; } if (!$user instanceof PasswordAuthenticatedUserInterface) { if (!$user instanceof UserInterface) { throw new \TypeError(\sprintf('Expected an instance of "%s" as first argument, but got "%s".', UserInterface::class, \get_debug_type($user))); } \trigger_deprecation('symfony/password-hasher', '5.3', 'The "%s()" method expects a "%s" instance as first argument. Not implementing it in class "%s" is deprecated.', __METHOD__, PasswordAuthenticatedUserInterface::class, \get_debug_type($user)); } $hasher = $this->hasherFactory->getPasswordHasher($user); return $hasher->needsRehash($user->getPassword()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\LogicException; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; /** * Pbkdf2PasswordHasher uses the PBKDF2 (Password-Based Key Derivation Function 2). * * Providing a high level of Cryptographic security, * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). * * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. * PBKDF2 should be used with caution and care. * * @author Sebastiaan Stok * @author Andrew Johnson * @author Fabien Potencier */ final class Pbkdf2PasswordHasher implements LegacyPasswordHasherInterface { use CheckPasswordLengthTrait; private $algorithm; private $encodeHashAsBase64; private $iterations = 1; private $length; private $encodedLength = -1; /** * @param string $algorithm The digest algorithm to use * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash * @param int $iterations The number of iterations to use to stretch the password hash * @param int $length Length of derived key to create */ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = \true, int $iterations = 1000, int $length = 40) { $this->algorithm = $algorithm; $this->encodeHashAsBase64 = $encodeHashAsBase64; $this->length = $length; try { $this->encodedLength = \strlen($this->hash('', 'salt')); } catch (\LogicException $e) { // ignore unsupported algorithm } $this->iterations = $iterations; } public function hash(string $plainPassword, ?string $salt = null) : string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } if (!\in_array($this->algorithm, \hash_algos(), \true)) { throw new LogicException(\sprintf('The algorithm "%s" is not supported.', $this->algorithm)); } $digest = \hash_pbkdf2($this->algorithm, $plainPassword, $salt ?? '', $this->iterations, $this->length, \true); return $this->encodeHashAsBase64 ? \base64_encode($digest) : \bin2hex($digest); } public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null) : bool { if (\strlen($hashedPassword) !== $this->encodedLength || \false !== \strpos($hashedPassword, '$')) { return \false; } return !$this->isPasswordTooLong($plainPassword) && \hash_equals($hashedPassword, $this->hash($plainPassword, $salt)); } public function needsRehash(string $hashedPassword) : bool { return \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; /** * @author Christophe Coevoet */ interface PasswordHasherAwareInterface { /** * Gets the name of the password hasher used to hash the password. * * If the method returns null, the standard way to retrieve the password hasher * will be used instead. */ public function getPasswordHasherName() : ?string; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; /** * Hashes passwords using password_hash(). * * @author Elnur Abdurrakhimov * @author Terje Bråten * @author Nicolas Grekas */ final class NativePasswordHasher implements PasswordHasherInterface { use CheckPasswordLengthTrait; private $algorithm = \PASSWORD_BCRYPT; private $options; /** * @param string|null $algorithm An algorithm supported by password_hash() or null to use the best available algorithm */ public function __construct(?int $opsLimit = null, ?int $memLimit = null, ?int $cost = null, ?string $algorithm = null) { $cost = $cost ?? 13; $opsLimit = $opsLimit ?? \max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $memLimit = $memLimit ?? \max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); if (3 > $opsLimit) { throw new \InvalidArgumentException('$opsLimit must be 3 or greater.'); } if (10 * 1024 > $memLimit) { throw new \InvalidArgumentException('$memLimit must be 10k or greater.'); } if ($cost < 4 || 31 < $cost) { throw new \InvalidArgumentException('$cost must be in the range of 4-31.'); } if (null !== $algorithm) { $algorithms = [1 => \PASSWORD_BCRYPT, '2y' => \PASSWORD_BCRYPT]; if (\defined('PASSWORD_ARGON2I')) { $algorithms[2] = $algorithms['argon2i'] = \PASSWORD_ARGON2I; } if (\defined('PASSWORD_ARGON2ID')) { $algorithms[3] = $algorithms['argon2id'] = \PASSWORD_ARGON2ID; } $this->algorithm = $algorithms[$algorithm] ?? $algorithm; } $this->options = ['cost' => $cost, 'time_cost' => $opsLimit, 'memory_cost' => $memLimit >> 10, 'threads' => 1]; } public function hash(string $plainPassword) : string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } if (\PASSWORD_BCRYPT === $this->algorithm && (72 < \strlen($plainPassword) || \false !== \strpos($plainPassword, "\x00"))) { $plainPassword = \base64_encode(\hash('sha512', $plainPassword, \true)); } return \password_hash($plainPassword, $this->algorithm, $this->options); } public function verify(string $hashedPassword, string $plainPassword) : bool { if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) { return \false; } if (0 !== \strpos($hashedPassword, '$argon')) { // Bcrypt cuts on NUL chars and after 72 bytes if (0 === \strpos($hashedPassword, '$2') && (72 < \strlen($plainPassword) || \false !== \strpos($plainPassword, "\x00"))) { $plainPassword = \base64_encode(\hash('sha512', $plainPassword, \true)); } return \password_verify($plainPassword, $hashedPassword); } if (\extension_loaded('sodium') && \version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) { return \sodium_crypto_pwhash_str_verify($hashedPassword, $plainPassword); } if (\extension_loaded('libsodium') && \version_compare(\phpversion('libsodium'), '1.0.14', '>=')) { return \Sodium\crypto_pwhash_str_verify($hashedPassword, $plainPassword); } return \password_verify($plainPassword, $hashedPassword); } /** * {@inheritdoc} */ public function needsRehash(string $hashedPassword) : bool { return \password_needs_rehash($hashedPassword, $this->algorithm, $this->options); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\LogicException; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; /** * MessageDigestPasswordHasher uses a message digest algorithm. * * @author Fabien Potencier */ class MessageDigestPasswordHasher implements LegacyPasswordHasherInterface { use CheckPasswordLengthTrait; private $algorithm; private $encodeHashAsBase64; private $iterations = 1; private $hashLength = -1; /** * @param string $algorithm The digest algorithm to use * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash * @param int $iterations The number of iterations to use to stretch the password hash */ public function __construct(string $algorithm = 'sha512', bool $encodeHashAsBase64 = \true, int $iterations = 5000) { $this->algorithm = $algorithm; $this->encodeHashAsBase64 = $encodeHashAsBase64; try { $this->hashLength = \strlen($this->hash('', 'salt')); } catch (\LogicException $e) { // ignore algorithm not supported } $this->iterations = $iterations; } public function hash(string $plainPassword, ?string $salt = null) : string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } if (!\in_array($this->algorithm, \hash_algos(), \true)) { throw new LogicException(\sprintf('The algorithm "%s" is not supported.', $this->algorithm)); } $salted = $this->mergePasswordAndSalt($plainPassword, $salt); $digest = \hash($this->algorithm, $salted, \true); // "stretch" hash for ($i = 1; $i < $this->iterations; ++$i) { $digest = \hash($this->algorithm, $digest . $salted, \true); } return $this->encodeHashAsBase64 ? \base64_encode($digest) : \bin2hex($digest); } public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null) : bool { if (\strlen($hashedPassword) !== $this->hashLength || \false !== \strpos($hashedPassword, '$')) { return \false; } return !$this->isPasswordTooLong($plainPassword) && \hash_equals($hashedPassword, $this->hash($plainPassword, $salt)); } public function needsRehash(string $hashedPassword) : bool { return \false; } private function mergePasswordAndSalt(string $password, ?string $salt) : string { if (!$salt) { return $password; } if (\false !== \strrpos($salt, '{') || \false !== \strrpos($salt, '}')) { throw new \InvalidArgumentException('Cannot use { or } in salt.'); } return $password . '{' . $salt . '}'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; /** * Interface for the user password hasher service. * * @author Ariel Ferrandini * * @method string hashPassword(PasswordAuthenticatedUserInterface $user, string $plainPassword) Hashes the plain password for the given user. * @method bool isPasswordValid(PasswordAuthenticatedUserInterface $user, string $plainPassword) Checks if the plaintext password matches the user's password. * @method bool needsRehash(PasswordAuthenticatedUserInterface $user) Checks if an encoded password would benefit from rehashing. */ interface UserPasswordHasherInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; /** * PlaintextPasswordHasher does not do any hashing but is useful in testing environments. * * As this hasher is not cryptographically secure, usage of it in production environments is discouraged. * * @author Fabien Potencier */ class PlaintextPasswordHasher implements LegacyPasswordHasherInterface { use CheckPasswordLengthTrait; private $ignorePasswordCase; /** * @param bool $ignorePasswordCase Compare password case-insensitive */ public function __construct(bool $ignorePasswordCase = \false) { $this->ignorePasswordCase = $ignorePasswordCase; } /** * {@inheritdoc} */ public function hash(string $plainPassword, ?string $salt = null) : string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } return $this->mergePasswordAndSalt($plainPassword, $salt); } public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null) : bool { if ($this->isPasswordTooLong($plainPassword)) { return \false; } $pass2 = $this->mergePasswordAndSalt($plainPassword, $salt); if (!$this->ignorePasswordCase) { return \hash_equals($hashedPassword, $pass2); } return \hash_equals(\strtolower($hashedPassword), \strtolower($pass2)); } public function needsRehash(string $hashedPassword) : bool { return \false; } private function mergePasswordAndSalt(string $password, ?string $salt) : string { if (empty($salt)) { return $password; } if (\false !== \strrpos($salt, '{') || \false !== \strrpos($salt, '}')) { throw new \InvalidArgumentException('Cannot use { or } in salt.'); } return $password . '{' . $salt . '}'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; /** * PasswordHasherFactoryInterface to support different password hashers for different user accounts. * * @author Robin Chalas * @author Johannes M. Schmitt */ interface PasswordHasherFactoryInterface { /** * Returns the password hasher to use for the given user. * * @param PasswordHasherAwareInterface|PasswordAuthenticatedUserInterface|string $user * * @throws \RuntimeException When no password hasher could be found for the user */ public function getPasswordHasher($user) : PasswordHasherInterface; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; /** * Hashes passwords using the best available hasher. * Verifies them using a chain of hashers. * * /!\ Don't put a PlaintextPasswordHasher in the list as that'd mean a leaked hash * could be used to authenticate successfully without knowing the cleartext password. * * @author Nicolas Grekas */ final class MigratingPasswordHasher implements PasswordHasherInterface { private $bestHasher; private $extraHashers; public function __construct(PasswordHasherInterface $bestHasher, PasswordHasherInterface ...$extraHashers) { $this->bestHasher = $bestHasher; $this->extraHashers = $extraHashers; } public function hash(string $plainPassword, ?string $salt = null) : string { return $this->bestHasher->hash($plainPassword, $salt); } public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null) : bool { if ($this->bestHasher->verify($hashedPassword, $plainPassword, $salt)) { return \true; } if (!$this->bestHasher->needsRehash($hashedPassword)) { return \false; } foreach ($this->extraHashers as $hasher) { if ($hasher->verify($hashedPassword, $plainPassword, $salt)) { return \true; } } return \false; } public function needsRehash(string $hashedPassword) : bool { return $this->bestHasher->needsRehash($hashedPassword); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Hasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\LogicException; use _ContaoManager\Symfony\Component\PasswordHasher\PasswordHasherInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use _ContaoManager\Symfony\Component\Security\Core\Encoder\PasswordHasherAdapter; /** * A generic hasher factory implementation. * * @author Nicolas Grekas * @author Robin Chalas */ class PasswordHasherFactory implements PasswordHasherFactoryInterface { private $passwordHashers; /** * @param array $passwordHashers */ public function __construct(array $passwordHashers) { $this->passwordHashers = $passwordHashers; } /** * {@inheritdoc} */ public function getPasswordHasher($user) : PasswordHasherInterface { $hasherKey = null; if ($user instanceof PasswordHasherAwareInterface && null !== ($hasherName = $user->getPasswordHasherName()) || $user instanceof EncoderAwareInterface && null !== ($hasherName = $user->getEncoderName())) { if (!\array_key_exists($hasherName, $this->passwordHashers)) { throw new \RuntimeException(\sprintf('The password hasher "%s" was not configured.', $hasherName)); } $hasherKey = $hasherName; } else { foreach ($this->passwordHashers as $class => $hasher) { if (\is_object($user) && $user instanceof $class || !\is_object($user) && (\is_subclass_of($user, $class) || $user == $class)) { $hasherKey = $class; break; } } } if (null === $hasherKey) { throw new \RuntimeException(\sprintf('No password hasher has been configured for account "%s".', \is_object($user) ? \get_debug_type($user) : $user)); } return $this->createHasherUsingAdapter($hasherKey); } /** * Creates the actual hasher instance. * * @throws \InvalidArgumentException */ private function createHasher(array $config, bool $isExtra = \false) : PasswordHasherInterface { if (isset($config['instance'])) { if (!isset($config['migrate_from'])) { return $config['instance']; } $config = $this->getMigratingPasswordConfig($config); } if (isset($config['algorithm'])) { $rawConfig = $config; $config = $this->getHasherConfigFromAlgorithm($config); } if (!isset($config['class'])) { throw new \InvalidArgumentException('"class" must be set in ' . \json_encode($config)); } if (!isset($config['arguments'])) { throw new \InvalidArgumentException('"arguments" must be set in ' . \json_encode($config)); } $hasher = new $config['class'](...$config['arguments']); if (!$hasher instanceof PasswordHasherInterface && $hasher instanceof PasswordEncoderInterface) { $hasher = new PasswordHasherAdapter($hasher); } if ($isExtra || !\in_array($config['class'], [NativePasswordHasher::class, SodiumPasswordHasher::class], \true)) { return $hasher; } if ($rawConfig ?? null) { $extrapasswordHashers = \array_map(function (string $algo) use($rawConfig) : PasswordHasherInterface { $rawConfig['algorithm'] = $algo; return $this->createHasher($rawConfig); }, ['pbkdf2', $rawConfig['hash_algorithm'] ?? 'sha512']); } else { $extrapasswordHashers = [new Pbkdf2PasswordHasher(), new MessageDigestPasswordHasher()]; } return new MigratingPasswordHasher($hasher, ...$extrapasswordHashers); } private function createHasherUsingAdapter(string $hasherKey) : PasswordHasherInterface { if (!$this->passwordHashers[$hasherKey] instanceof PasswordHasherInterface) { $this->passwordHashers[$hasherKey] = $this->passwordHashers[$hasherKey] instanceof PasswordEncoderInterface ? new PasswordHasherAdapter($this->passwordHashers[$hasherKey]) : $this->createHasher($this->passwordHashers[$hasherKey]); } return $this->passwordHashers[$hasherKey]; } private function getHasherConfigFromAlgorithm(array $config) : array { if ('auto' === $config['algorithm']) { // "plaintext" is not listed as any leaked hashes could then be used to authenticate directly if (SodiumPasswordHasher::isSupported()) { $algorithms = ['native', 'sodium', 'pbkdf2']; } else { $algorithms = ['native', 'pbkdf2']; } if ($config['hash_algorithm'] ?? '') { $algorithms[] = $config['hash_algorithm']; } $hasherChain = []; foreach ($algorithms as $algorithm) { $config['algorithm'] = $algorithm; $hasherChain[] = $this->createHasher($config, \true); } return ['class' => MigratingPasswordHasher::class, 'arguments' => $hasherChain]; } if ($config['migrate_from'] ?? \false) { return $this->getMigratingPasswordConfig($config); } switch ($config['algorithm']) { case 'plaintext': return ['class' => PlaintextPasswordHasher::class, 'arguments' => [$config['ignore_case'] ?? \false]]; case 'pbkdf2': return ['class' => Pbkdf2PasswordHasher::class, 'arguments' => [$config['hash_algorithm'] ?? 'sha512', $config['encode_as_base64'] ?? \true, $config['iterations'] ?? 1000, $config['key_length'] ?? 40]]; case 'bcrypt': $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_BCRYPT; return $this->getHasherConfigFromAlgorithm($config); case 'native': return ['class' => NativePasswordHasher::class, 'arguments' => [$config['time_cost'] ?? null, ($config['memory_cost'] ?? 0) << 10 ?: null, $config['cost'] ?? null] + (isset($config['native_algorithm']) ? [3 => $config['native_algorithm']] : [])]; case 'sodium': return ['class' => SodiumPasswordHasher::class, 'arguments' => [$config['time_cost'] ?? null, ($config['memory_cost'] ?? 0) << 10 ?: null]]; case 'argon2i': if (SodiumPasswordHasher::isSupported() && !\defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2I')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2I; } else { throw new LogicException(\sprintf('Algorithm "argon2i" is not available. Use "%s" instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id" or "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); case 'argon2id': if (($hasSodium = SodiumPasswordHasher::isSupported()) && \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13')) { $config['algorithm'] = 'sodium'; } elseif (\defined('PASSWORD_ARGON2ID')) { $config['algorithm'] = 'native'; $config['native_algorithm'] = \PASSWORD_ARGON2ID; } else { throw new LogicException(\sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->getHasherConfigFromAlgorithm($config); } return ['class' => MessageDigestPasswordHasher::class, 'arguments' => [$config['algorithm'], $config['encode_as_base64'] ?? \true, $config['iterations'] ?? 5000]]; } private function getMigratingPasswordConfig(array $config) : array { $frompasswordHashers = $config['migrate_from']; unset($config['migrate_from']); $hasherChain = [$this->createHasher($config, \true)]; foreach ($frompasswordHashers as $name) { if ($this->passwordHashers[$name] ?? \false) { $hasher = $this->createHasherUsingAdapter($name); } else { $hasher = $this->createHasher(['algorithm' => $name], \true); } $hasherChain[] = $hasher; } return ['class' => MigratingPasswordHasher::class, 'arguments' => $hasherChain]; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5.3 --- * Add the component * Use `bcrypt` as default algorithm in `NativePasswordHasher` * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher; use _ContaoManager\Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; /** * Provides password hashing and verification capabilities for "legacy" hashers that require external salts. * * @author Fabien Potencier * @author Nicolas Grekas * @author Robin Chalas */ interface LegacyPasswordHasherInterface extends PasswordHasherInterface { /** * Hashes a plain password. * * @throws InvalidPasswordException If the plain password is invalid, e.g. excessively long */ public function hash(string $plainPassword, ?string $salt = null) : string; /** * Checks that a plain password and a salt match a password hash. */ public function verify(string $hashedPassword, string $plainPassword, ?string $salt = null) : bool; } PasswordHasher Component ======================== The PasswordHasher component provides secure password hashing utilities. Getting Started --------------- ``` $ composer require symfony/password-hasher ``` ```php use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; // Configure different password hashers via the factory $factory = new PasswordHasherFactory([ 'common' => ['algorithm' => 'bcrypt'], 'memory-hard' => ['algorithm' => 'sodium'], ]); // Retrieve the right password hasher by its name $passwordHasher = $factory->getPasswordHasher('common'); // Hash a plain password $hash = $passwordHasher->hash('plain'); // returns a bcrypt hash // Verify that a given plain password matches the hash $passwordHasher->verify($hash, 'wrong'); // returns false $passwordHasher->verify($hash, 'plain'); // returns true (valid) ``` Resources --------- * [Documentation](https://symfony.com/doc/current/security.html#c-hashing-passwords) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Command; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Question\Question; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use _ContaoManager\Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; /** * Hashes a user's password. * * @author Sarah Khalil * @author Robin Chalas * * @final */ class UserPasswordHashCommand extends Command { protected static $defaultName = 'security:hash-password'; protected static $defaultDescription = 'Hash a user password'; private $hasherFactory; private $userClasses; public function __construct(PasswordHasherFactoryInterface $hasherFactory, array $userClasses = []) { $this->hasherFactory = $hasherFactory; $this->userClasses = $userClasses; parent::__construct(); } /** * {@inheritdoc} */ protected function configure() { $this->setDescription(self::$defaultDescription)->addArgument('password', InputArgument::OPTIONAL, 'The plain password to hash.')->addArgument('user-class', InputArgument::OPTIONAL, 'The User entity class path associated with the hasher used to hash the password.')->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the hasher generate one.')->setHelp(<<%command.name%
    command hashes passwords according to your security configuration. This command is mainly used to generate passwords for the in_memory user provider type and for changing passwords in the database while developing the application. Suppose that you have the following security configuration in your application: # app/config/security.yml security: password_hashers: Symfony\\Component\\Security\\Core\\User\\InMemoryUser: plaintext App\\Entity\\User: auto If you execute the command non-interactively, the first available configured user class under the security.password_hashers key is used and a random salt is generated to hash the password: php %command.full_name% --no-interaction [password] Pass the full user class path as the second argument to hash passwords for your own entities: php %command.full_name% --no-interaction [password] 'App\\Entity\\User' Executing the command interactively allows you to generate a random salt for hashing the password: php %command.full_name% [password] 'App\\Entity\\User' In case your hasher doesn't require a salt, add the empty-salt option: php %command.full_name% --empty-salt [password] 'App\\Entity\\User' EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) : int { $io = new SymfonyStyle($input, $output); $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; $input->isInteractive() ? $errorIo->title('Symfony Password Hash Utility') : $errorIo->newLine(); $password = $input->getArgument('password'); $userClass = $this->getUserClass($input, $io); $emptySalt = $input->getOption('empty-salt'); $hasher = $this->hasherFactory->getPasswordHasher($userClass); $saltlessWithoutEmptySalt = !$emptySalt && !$hasher instanceof LegacyPasswordHasherInterface; if ($saltlessWithoutEmptySalt) { $emptySalt = \true; } if (!$password) { if (!$input->isInteractive()) { $errorIo->error('The password must not be empty.'); return 1; } $passwordQuestion = $this->createPasswordQuestion(); $password = $errorIo->askQuestion($passwordQuestion); } $salt = null; if ($input->isInteractive() && !$emptySalt) { $emptySalt = \true; $errorIo->note('The command will take care of generating a salt for you. Be aware that some hashers advise to let them generate their own salt. If you\'re using one of those hashers, please answer \'no\' to the question below. ' . \PHP_EOL . 'Provide the \'empty-salt\' option in order to let the hasher handle the generation itself.'); if ($errorIo->confirm('Confirm salt generation ?')) { $salt = $this->generateSalt(); $emptySalt = \false; } } elseif (!$emptySalt) { $salt = $this->generateSalt(); } $hashedPassword = $hasher->hash($password, $salt); $rows = [['Hasher used', \get_class($hasher)], ['Password hash', $hashedPassword]]; if (!$emptySalt) { $rows[] = ['Generated salt', $salt]; } $io->table(['Key', 'Value'], $rows); if (!$emptySalt) { $errorIo->note(\sprintf('Make sure that your salt storage field fits the salt length: %s chars', \strlen($salt))); } elseif ($saltlessWithoutEmptySalt) { $errorIo->note('Self-salting hasher used: the hasher generated its own built-in salt.'); } $errorIo->success('Password hashing succeeded'); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('user-class')) { $suggestions->suggestValues($this->userClasses); return; } } /** * Create the password question to ask the user for the password to be hashed. */ private function createPasswordQuestion() : Question { $passwordQuestion = new Question('Type in your password to be hashed'); return $passwordQuestion->setValidator(function ($value) { if ('' === \trim($value)) { throw new InvalidArgumentException('The password must not be empty.'); } return $value; })->setHidden(\true)->setMaxAttempts(20); } private function generateSalt() : string { return \base64_encode(\random_bytes(30)); } private function getUserClass(InputInterface $input, SymfonyStyle $io) : string { if (null !== ($userClass = $input->getArgument('user-class'))) { return $userClass; } if (!$this->userClasses) { throw new RuntimeException('There are no configured password hashers for the "security" extension.'); } if (!$input->isInteractive() || 1 === \count($this->userClasses)) { return \reset($this->userClasses); } $userClasses = $this->userClasses; \natcasesort($userClasses); $userClasses = \array_values($userClasses); return $io->choice('For which user class would you like to hash a password?', $userClasses, \reset($userClasses)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Exception; /** * @author Robin Chalas */ class LogicException extends \LogicException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Exception; /** * Interface for exceptions thrown by the password-hasher component. * * @author Robin Chalas */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\PasswordHasher\Exception; /** * @author Robin Chalas */ class InvalidPasswordException extends \RuntimeException implements ExceptionInterface { public function __construct(string $message = 'Invalid password.', int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } } { "name": "symfony\/password-hasher", "type": "library", "description": "Provides password hashing utilities", "keywords": [ "password", "hashing" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Robin Chalas", "email": "robin.chalas@gmail.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-php80": "^1.15" }, "require-dev": { "symfony\/security-core": "^5.3|^6.0", "symfony\/console": "^5.3|^6.0" }, "conflict": { "symfony\/security-core": "<5.3" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\PasswordHasher\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" }Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process; /** * Generic executable finder. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class ExecutableFinder { private $suffixes = ['.exe', '.bat', '.cmd', '.com']; /** * Replaces default suffixes of executable. */ public function setSuffixes(array $suffixes) { $this->suffixes = $suffixes; } /** * Adds new possible suffix to check for executable. */ public function addSuffix(string $suffix) { $this->suffixes[] = $suffix; } /** * Finds an executable by name. * * @param string $name The executable name (without the extension) * @param string|null $default The default to return if no executable is found * @param array $extraDirs Additional dirs to check into * * @return string|null */ public function find(string $name, ?string $default = null, array $extraDirs = []) { if (\ini_get('open_basedir')) { $searchPath = \array_merge(\explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); $dirs = []; foreach ($searchPath as $path) { // Silencing against https://bugs.php.net/69240 if (@\is_dir($path)) { $dirs[] = $path; } else { if (\basename($path) == $name && @\is_executable($path)) { return $path; } } } } else { $dirs = \array_merge(\explode(\PATH_SEPARATOR, \getenv('PATH') ?: \getenv('Path')), $extraDirs); } $suffixes = ['']; if ('\\' === \DIRECTORY_SEPARATOR) { $pathExt = \getenv('PATHEXT'); $suffixes = \array_merge($pathExt ? \explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); } foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { if (@\is_file($file = $dir . \DIRECTORY_SEPARATOR . $name . $suffix) && ('\\' === \DIRECTORY_SEPARATOR || @\is_executable($file))) { return $file; } } } return $default; } } CHANGELOG ========= 5.2.0 ----- * added `Process::setOptions()` to set `Process` specific options * added option `create_new_console` to allow a subprocess to continue to run after the main script exited, both on Linux and on Windows 5.1.0 ----- * added `Process::getStartTime()` to retrieve the start time of the process as float 5.0.0 ----- * removed `Process::inheritEnvironmentVariables()` * removed `PhpProcess::setPhpBinary()` * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell * removed `Process::setCommandLine()` 4.4.0 ----- * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited. * added `Process::getLastOutputTime()` method 4.2.0 ----- * added the `Process::fromShellCommandline()` to run commands in a shell wrapper * deprecated passing a command as string when creating a `Process` instance * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods * added the `Process::waitUntil()` method to wait for the process only for a specific output, then continue the normal execution of your application 4.1.0 ----- * added the `Process::isTtySupported()` method that allows to check for TTY support * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary * added the `ProcessSignaledException` class to properly catch signaled process errors 4.0.0 ----- * environment variables will always be inherited * added a second `array $env = []` argument to the `start()`, `run()`, `mustRun()`, and `restart()` methods of the `Process` class * added a second `array $env = []` argument to the `start()` method of the `PhpProcess` class * the `ProcessUtils::escapeArgument()` method has been removed * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()` methods of the `Process` class have been removed * support for passing `proc_open()` options has been removed * removed the `ProcessBuilder` class, use the `Process` class instead * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not supported anymore 3.4.0 ----- * deprecated the ProcessBuilder class * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor) 3.3.0 ----- * added command line arrays in the `Process` class * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods * deprecated the `ProcessUtils::escapeArgument()` method * deprecated not inheriting environment variables * deprecated configuring `proc_open()` options * deprecated configuring enhanced Windows compatibility * deprecated configuring enhanced sigchild compatibility 2.5.0 ----- * added support for PTY mode * added the convenience method "mustRun" * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types 2.4.0 ----- * added the ability to define an idle timeout 2.3.0 ----- * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows * added Process::signal() * added Process::getPid() * added support for a TTY mode 2.2.0 ----- * added ProcessBuilder::setArguments() to reset the arguments on a builder * added a way to retrieve the standard and error output incrementally * added Process:restart() 2.1.0 ----- * added support for non-blocking processes (start(), wait(), isRunning(), stop()) * enhanced Windows compatibility * added Process::getExitCodeText() that returns a string representation for the exit code returned by the process * added ProcessBuilder Process Component ================= The Process component executes commands in sub-processes. Sponsor ------- The Process component for Symfony 5.4/6.0 is [backed][1] by [SensioLabs][2]. As the creator of Symfony, SensioLabs supports companies using Symfony, with an offering encompassing consultancy, expertise, services, training, and technical assistance to ensure the success of web application development projects. Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/process.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://symfony.com/backers [2]: https://sensiolabs.com [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Pipes; use _ContaoManager\Symfony\Component\Process\Process; /** * UnixPipes implementation uses unix pipes as handles. * * @author Romain Neutron * * @internal */ class UnixPipes extends AbstractPipes { private $ttyMode; private $ptyMode; private $haveReadSupport; public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport) { $this->ttyMode = $ttyMode; $this->ptyMode = $ptyMode; $this->haveReadSupport = $haveReadSupport; parent::__construct($input); } public function __sleep() : array { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { $this->close(); } /** * {@inheritdoc} */ public function getDescriptors() : array { if (!$this->haveReadSupport) { $nullstream = \fopen('/dev/null', 'c'); return [['pipe', 'r'], $nullstream, $nullstream]; } if ($this->ttyMode) { return [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']]; } if ($this->ptyMode && Process::isPtySupported()) { return [['pty'], ['pty'], ['pty']]; } return [ ['pipe', 'r'], ['pipe', 'w'], // stdout ['pipe', 'w'], ]; } /** * {@inheritdoc} */ public function getFiles() : array { return []; } /** * {@inheritdoc} */ public function readAndWrite(bool $blocking, bool $close = \false) : array { $this->unblock(); $w = $this->write(); $read = $e = []; $r = $this->pipes; unset($r[0]); // let's have a look if something changed in streams \set_error_handler([$this, 'handleError']); if (($r || $w) && \false === \stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1000000.0 : 0)) { \restore_error_handler(); // if a system call has been interrupted, forget about it, let's try again // otherwise, an error occurred, let's reset pipes if (!$this->hasSystemCallBeenInterrupted()) { $this->pipes = []; } return $read; } \restore_error_handler(); foreach ($r as $pipe) { // prior PHP 5.4 the array passed to stream_select is modified and // lose key association, we have to find back the key $read[$type = \array_search($pipe, $this->pipes, \true)] = ''; do { $data = @\fread($pipe, self::CHUNK_SIZE); $read[$type] .= $data; } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); if (!isset($read[$type][0])) { unset($read[$type]); } if ($close && \feof($pipe)) { \fclose($pipe); unset($this->pipes[$type]); } } return $read; } /** * {@inheritdoc} */ public function haveReadSupport() : bool { return $this->haveReadSupport; } /** * {@inheritdoc} */ public function areOpen() : bool { return (bool) $this->pipes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Pipes; use _ContaoManager\Symfony\Component\Process\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Process\Process; /** * WindowsPipes implementation uses temporary files as handles. * * @see https://bugs.php.net/51800 * @see https://bugs.php.net/65650 * * @author Romain Neutron * * @internal */ class WindowsPipes extends AbstractPipes { private $files = []; private $fileHandles = []; private $lockHandles = []; private $readBytes = [Process::STDOUT => 0, Process::STDERR => 0]; private $haveReadSupport; public function __construct($input, bool $haveReadSupport) { $this->haveReadSupport = $haveReadSupport; if ($this->haveReadSupport) { // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. // Workaround for this problem is to use temporary files instead of pipes on Windows platform. // // @see https://bugs.php.net/51800 $pipes = [Process::STDOUT => Process::OUT, Process::STDERR => Process::ERR]; $tmpDir = \sys_get_temp_dir(); $lastError = 'unknown reason'; \set_error_handler(function ($type, $msg) use(&$lastError) { $lastError = $msg; }); for ($i = 0;; ++$i) { foreach ($pipes as $pipe => $name) { $file = \sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); if (!($h = \fopen($file . '.lock', 'w'))) { if (\file_exists($file . '.lock')) { continue 2; } \restore_error_handler(); throw new RuntimeException('A temporary file could not be opened to write the process output: ' . $lastError); } if (!\flock($h, \LOCK_EX | \LOCK_NB)) { continue 2; } if (isset($this->lockHandles[$pipe])) { \flock($this->lockHandles[$pipe], \LOCK_UN); \fclose($this->lockHandles[$pipe]); } $this->lockHandles[$pipe] = $h; if (!($h = \fopen($file, 'w')) || !\fclose($h) || !($h = \fopen($file, 'r'))) { \flock($this->lockHandles[$pipe], \LOCK_UN); \fclose($this->lockHandles[$pipe]); unset($this->lockHandles[$pipe]); continue 2; } $this->fileHandles[$pipe] = $h; $this->files[$pipe] = $file; } break; } \restore_error_handler(); } parent::__construct($input); } public function __sleep() : array { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { $this->close(); } /** * {@inheritdoc} */ public function getDescriptors() : array { if (!$this->haveReadSupport) { $nullstream = \fopen('NUL', 'c'); return [['pipe', 'r'], $nullstream, $nullstream]; } // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 // So we redirect output within the commandline and pass the nul device to the process return [['pipe', 'r'], ['file', 'NUL', 'w'], ['file', 'NUL', 'w']]; } /** * {@inheritdoc} */ public function getFiles() : array { return $this->files; } /** * {@inheritdoc} */ public function readAndWrite(bool $blocking, bool $close = \false) : array { $this->unblock(); $w = $this->write(); $read = $r = $e = []; if ($blocking) { if ($w) { @\stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1000000.0); } elseif ($this->fileHandles) { \usleep((int) (Process::TIMEOUT_PRECISION * 1000000.0)); } } foreach ($this->fileHandles as $type => $fileHandle) { $data = \stream_get_contents($fileHandle, -1, $this->readBytes[$type]); if (isset($data[0])) { $this->readBytes[$type] += \strlen($data); $read[$type] = $data; } if ($close) { \ftruncate($fileHandle, 0); \fclose($fileHandle); \flock($this->lockHandles[$type], \LOCK_UN); \fclose($this->lockHandles[$type]); unset($this->fileHandles[$type], $this->lockHandles[$type]); } } return $read; } /** * {@inheritdoc} */ public function haveReadSupport() : bool { return $this->haveReadSupport; } /** * {@inheritdoc} */ public function areOpen() : bool { return $this->pipes && $this->fileHandles; } /** * {@inheritdoc} */ public function close() { parent::close(); foreach ($this->fileHandles as $type => $handle) { \ftruncate($handle, 0); \fclose($handle); \flock($this->lockHandles[$type], \LOCK_UN); \fclose($this->lockHandles[$type]); } $this->fileHandles = $this->lockHandles = []; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Pipes; use _ContaoManager\Symfony\Component\Process\Exception\InvalidArgumentException; /** * @author Romain Neutron * * @internal */ abstract class AbstractPipes implements PipesInterface { public $pipes = []; private $inputBuffer = ''; private $input; private $blocked = \true; private $lastError; /** * @param resource|string|int|float|bool|\Iterator|null $input */ public function __construct($input) { if (\is_resource($input) || $input instanceof \Iterator) { $this->input = $input; } elseif (\is_string($input)) { $this->inputBuffer = $input; } else { $this->inputBuffer = (string) $input; } } /** * {@inheritdoc} */ public function close() { foreach ($this->pipes as $pipe) { if (\is_resource($pipe)) { \fclose($pipe); } } $this->pipes = []; } /** * Returns true if a system call has been interrupted. */ protected function hasSystemCallBeenInterrupted() : bool { $lastError = $this->lastError; $this->lastError = null; // stream_select returns false when the `select` system call is interrupted by an incoming signal return null !== $lastError && \false !== \stripos($lastError, 'interrupted system call'); } /** * Unblocks streams. */ protected function unblock() { if (!$this->blocked) { return; } foreach ($this->pipes as $pipe) { \stream_set_blocking($pipe, 0); } if (\is_resource($this->input)) { \stream_set_blocking($this->input, 0); } $this->blocked = \false; } /** * Writes input to stdin. * * @throws InvalidArgumentException When an input iterator yields a non supported value */ protected function write() : ?array { if (!isset($this->pipes[0])) { return null; } $input = $this->input; if ($input instanceof \Iterator) { if (!$input->valid()) { $input = null; } elseif (\is_resource($input = $input->current())) { \stream_set_blocking($input, 0); } elseif (!isset($this->inputBuffer[0])) { if (!\is_string($input)) { if (!\is_scalar($input)) { throw new InvalidArgumentException(\sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_debug_type($this->input), \get_debug_type($input))); } $input = (string) $input; } $this->inputBuffer = $input; $this->input->next(); $input = null; } else { $input = null; } } $r = $e = []; $w = [$this->pipes[0]]; // let's have a look if something changed in streams if (\false === @\stream_select($r, $w, $e, 0, 0)) { return null; } foreach ($w as $stdin) { if (isset($this->inputBuffer[0])) { $written = \fwrite($stdin, $this->inputBuffer); $this->inputBuffer = \substr($this->inputBuffer, $written); if (isset($this->inputBuffer[0])) { return [$this->pipes[0]]; } } if ($input) { while (\true) { $data = \fread($input, self::CHUNK_SIZE); if (!isset($data[0])) { break; } $written = \fwrite($stdin, $data); $data = \substr($data, $written); if (isset($data[0])) { $this->inputBuffer = $data; return [$this->pipes[0]]; } } if (\feof($input)) { if ($this->input instanceof \Iterator) { $this->input->next(); } else { $this->input = null; } } } } // no input to read on resource, buffer is empty if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { $this->input = null; \fclose($this->pipes[0]); unset($this->pipes[0]); } elseif (!$w) { return [$this->pipes[0]]; } return null; } /** * @internal */ public function handleError(int $type, string $msg) { $this->lastError = $msg; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Pipes; /** * PipesInterface manages descriptors and pipes for the use of proc_open. * * @author Romain Neutron * * @internal */ interface PipesInterface { public const CHUNK_SIZE = 16384; /** * Returns an array of descriptors for the use of proc_open. */ public function getDescriptors() : array; /** * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. * * @return string[] */ public function getFiles() : array; /** * Reads data in file handles and pipes. * * @param bool $blocking Whether to use blocking calls or not * @param bool $close Whether to close pipes if they've reached EOF * * @return string[] An array of read data indexed by their fd */ public function readAndWrite(bool $blocking, bool $close = \false) : array; /** * Returns if the current state has open file handles or pipes. */ public function areOpen() : bool; /** * Returns if pipes are able to read output. */ public function haveReadSupport() : bool; /** * Closes file handles and pipes. */ public function close(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process; use _ContaoManager\Symfony\Component\Process\Exception\RuntimeException; /** * Provides a way to continuously write to the input of a Process until the InputStream is closed. * * @author Nicolas Grekas * * @implements \IteratorAggregate */ class InputStream implements \IteratorAggregate { /** @var callable|null */ private $onEmpty = null; private $input = []; private $open = \true; /** * Sets a callback that is called when the write buffer becomes empty. */ public function onEmpty(?callable $onEmpty = null) { $this->onEmpty = $onEmpty; } /** * Appends an input to the write buffer. * * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar, * stream resource or \Traversable */ public function write($input) { if (null === $input) { return; } if ($this->isClosed()) { throw new RuntimeException(\sprintf('"%s" is closed.', static::class)); } $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); } /** * Closes the write buffer. */ public function close() { $this->open = \false; } /** * Tells whether the write buffer is closed or not. */ public function isClosed() { return !$this->open; } /** * @return \Traversable */ #[\ReturnTypeWillChange] public function getIterator() { $this->open = \true; while ($this->open || $this->input) { if (!$this->input) { (yield ''); continue; } $current = \array_shift($this->input); if ($current instanceof \Iterator) { yield from $current; } else { (yield $current); } if (!$this->input && $this->open && null !== ($onEmpty = $this->onEmpty)) { $this->write($onEmpty($this)); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process; use _ContaoManager\Symfony\Component\Process\Exception\LogicException; use _ContaoManager\Symfony\Component\Process\Exception\RuntimeException; /** * PhpProcess runs a PHP script in an independent process. * * $p = new PhpProcess(''); * $p->run(); * print $p->getOutput()."\n"; * * @author Fabien Potencier */ class PhpProcess extends Process { /** * @param string $script The PHP script to run (as a string) * @param string|null $cwd The working directory or null to use the working dir of the current PHP process * @param array|null $env The environment variables or null to use the same environment as the current PHP process * @param int $timeout The timeout in seconds * @param array|null $php Path to the PHP binary to use with any additional arguments */ public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null) { if (null === $php) { $executableFinder = new PhpExecutableFinder(); $php = $executableFinder->find(\false); $php = \false === $php ? null : \array_merge([$php], $executableFinder->findArguments()); } if ('phpdbg' === \PHP_SAPI) { $file = \tempnam(\sys_get_temp_dir(), 'dbg'); \file_put_contents($file, $script); \register_shutdown_function('unlink', $file); $php[] = $file; $script = null; } parent::__construct($php, $cwd, $env, $script, $timeout); } /** * {@inheritdoc} */ public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) { throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); } /** * {@inheritdoc} */ public function start(?callable $callback = null, array $env = []) { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); } parent::start($callback, $env); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process; /** * An executable finder specifically designed for the PHP executable. * * @author Fabien Potencier * @author Johannes M. Schmitt */ class PhpExecutableFinder { private $executableFinder; public function __construct() { $this->executableFinder = new ExecutableFinder(); } /** * Finds The PHP executable. * * @return string|false */ public function find(bool $includeArgs = \true) { if ($php = \getenv('PHP_BINARY')) { if (!\is_executable($php)) { $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; if ($php = \strtok(\exec($command . ' ' . \escapeshellarg($php)), \PHP_EOL)) { if (!\is_executable($php)) { return \false; } } else { return \false; } } if (@\is_dir($php)) { return \false; } return $php; } $args = $this->findArguments(); $args = $includeArgs && $args ? ' ' . \implode(' ', $args) : ''; // PHP_BINARY return the current sapi executable if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], \true)) { return \PHP_BINARY . $args; } if ($php = \getenv('PHP_PATH')) { if (!@\is_executable($php) || @\is_dir($php)) { return \false; } return $php; } if ($php = \getenv('PHP_PEAR_PHP_BIN')) { if (@\is_executable($php) && !@\is_dir($php)) { return $php; } } if (@\is_executable($php = \PHP_BINDIR . ('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@\is_dir($php)) { return $php; } $dirs = [\PHP_BINDIR]; if ('\\' === \DIRECTORY_SEPARATOR) { $dirs[] = 'C:\\xampp\\php\\'; } return $this->executableFinder->find('php', \false, $dirs); } /** * Finds the PHP executable arguments. * * @return array */ public function findArguments() { $arguments = []; if ('phpdbg' === \PHP_SAPI) { $arguments[] = '-qrr'; } return $arguments; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; use _ContaoManager\Symfony\Component\Process\Process; /** * Exception that is thrown when a process has been signaled. * * @author Sullivan Senechal */ final class ProcessSignaledException extends RuntimeException { private $process; public function __construct(Process $process) { $this->process = $process; parent::__construct(\sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); } public function getProcess() : Process { return $this->process; } public function getSignal() : int { return $this->getProcess()->getTermSignal(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; /** * LogicException for the Process Component. * * @author Romain Neutron */ class LogicException extends \LogicException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; /** * Marker Interface for the Process Component. * * @author Johannes M. Schmitt */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; use _ContaoManager\Symfony\Component\Process\Process; /** * Exception for failed processes. * * @author Johannes M. Schmitt */ class ProcessFailedException extends RuntimeException { private $process; public function __construct(Process $process) { if ($process->isSuccessful()) { throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); } $error = \sprintf('The command "%s" failed.' . "\n\nExit Code: %s(%s)\n\nWorking directory: %s", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText(), $process->getWorkingDirectory()); if (!$process->isOutputDisabled()) { $error .= \sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); } parent::__construct($error); $this->process = $process; } public function getProcess() { return $this->process; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; /** * RuntimeException for the Process Component. * * @author Johannes M. Schmitt */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; use _ContaoManager\Symfony\Component\Process\Process; /** * Exception that is thrown when a process times out. * * @author Johannes M. Schmitt */ class ProcessTimedOutException extends RuntimeException { public const TYPE_GENERAL = 1; public const TYPE_IDLE = 2; private $process; private $timeoutType; public function __construct(Process $process, int $timeoutType) { $this->process = $process; $this->timeoutType = $timeoutType; parent::__construct(\sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); } public function getProcess() { return $this->process; } public function isGeneralTimeout() { return self::TYPE_GENERAL === $this->timeoutType; } public function isIdleTimeout() { return self::TYPE_IDLE === $this->timeoutType; } public function getExceededTimeout() { switch ($this->timeoutType) { case self::TYPE_GENERAL: return $this->process->getTimeout(); case self::TYPE_IDLE: return $this->process->getIdleTimeout(); default: throw new \LogicException(\sprintf('Unknown timeout type "%d".', $this->timeoutType)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process\Exception; /** * InvalidArgumentException for the Process Component. * * @author Romain Neutron */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } { "name": "symfony\/process", "type": "library", "description": "Executes commands in sub-processes", "keywords": [], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process; use _ContaoManager\Symfony\Component\Process\Exception\InvalidArgumentException; /** * ProcessUtils is a bunch of utility methods. * * This class contains static methods only and is not meant to be instantiated. * * @author Martin Hasoň */ class ProcessUtils { /** * This class should not be instantiated. */ private function __construct() { } /** * Validates and normalizes a Process input. * * @param string $caller The name of method call that validates the input * @param mixed $input The input to validate * * @return mixed * * @throws InvalidArgumentException In case the input is not valid */ public static function validateInput(string $caller, $input) { if (null !== $input) { if (\is_resource($input)) { return $input; } if (\is_string($input)) { return $input; } if (\is_scalar($input)) { return (string) $input; } if ($input instanceof Process) { return $input->getIterator($input::ITER_SKIP_ERR); } if ($input instanceof \Iterator) { return $input; } if ($input instanceof \Traversable) { return new \IteratorIterator($input); } throw new InvalidArgumentException(\sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); } return $input; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Process; use _ContaoManager\Symfony\Component\Process\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Process\Exception\LogicException; use _ContaoManager\Symfony\Component\Process\Exception\ProcessFailedException; use _ContaoManager\Symfony\Component\Process\Exception\ProcessSignaledException; use _ContaoManager\Symfony\Component\Process\Exception\ProcessTimedOutException; use _ContaoManager\Symfony\Component\Process\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Process\Pipes\PipesInterface; use _ContaoManager\Symfony\Component\Process\Pipes\UnixPipes; use _ContaoManager\Symfony\Component\Process\Pipes\WindowsPipes; /** * Process is a thin wrapper around proc_* functions to easily * start independent PHP processes. * * @author Fabien Potencier * @author Romain Neutron * * @implements \IteratorAggregate */ class Process implements \IteratorAggregate { public const ERR = 'err'; public const OUT = 'out'; public const STATUS_READY = 'ready'; public const STATUS_STARTED = 'started'; public const STATUS_TERMINATED = 'terminated'; public const STDIN = 0; public const STDOUT = 1; public const STDERR = 2; // Timeout Precision in seconds. public const TIMEOUT_PRECISION = 0.2; public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating private $callback; private $hasCallback = \false; private $commandline; private $cwd; private $env = []; private $input; private $starttime; private $lastOutputTime; private $timeout; private $idleTimeout; private $exitcode; private $fallbackStatus = []; private $processInformation; private $outputDisabled = \false; private $stdout; private $stderr; private $process; private $status = self::STATUS_READY; private $incrementalOutputOffset = 0; private $incrementalErrorOutputOffset = 0; private $tty = \false; private $pty; private $options = ['suppress_errors' => \true, 'bypass_shell' => \true]; private $useFileHandles = \false; /** @var PipesInterface */ private $processPipes; private $latestSignal; private static $sigchild; /** * Exit codes translation table. * * User-defined errors must use exit codes in the 64-113 range. */ public static $exitCodes = [ 0 => 'OK', 1 => 'General error', 2 => 'Misuse of shell builtins', 126 => 'Invoked command cannot execute', 127 => 'Command not found', 128 => 'Invalid exit argument', // signals 129 => 'Hangup', 130 => 'Interrupt', 131 => 'Quit and dump core', 132 => 'Illegal instruction', 133 => 'Trace/breakpoint trap', 134 => 'Process aborted', 135 => 'Bus error: "access to undefined portion of memory object"', 136 => 'Floating point exception: "erroneous arithmetic operation"', 137 => 'Kill (terminate immediately)', 138 => 'User-defined 1', 139 => 'Segmentation violation', 140 => 'User-defined 2', 141 => 'Write to pipe with no one reading', 142 => 'Signal raised by alarm', 143 => 'Termination (request to terminate)', // 144 - not defined 145 => 'Child process terminated, stopped (or continued*)', 146 => 'Continue if stopped', 147 => 'Stop executing temporarily', 148 => 'Terminal stop signal', 149 => 'Background process attempting to read from tty ("in")', 150 => 'Background process attempting to write to tty ("out")', 151 => 'Urgent data available on socket', 152 => 'CPU time limit exceeded', 153 => 'File size limit exceeded', 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', 155 => 'Profiling timer expired', // 156 - not defined 157 => 'Pollable event', // 158 - not defined 159 => 'Bad syscall', ]; /** * @param array $command The command to run and its arguments listed as separate entries * @param string|null $cwd The working directory or null to use the working dir of the current PHP process * @param array|null $env The environment variables or null to use the same environment as the current PHP process * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input * @param int|float|null $timeout The timeout in seconds or null to disable * * @throws LogicException When proc_open is not installed */ public function __construct(array $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) { if (!\function_exists('proc_open')) { throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); } $this->commandline = $command; $this->cwd = $cwd; // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected // @see : https://bugs.php.net/51800 // @see : https://bugs.php.net/50524 if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { $this->cwd = \getcwd(); } if (null !== $env) { $this->setEnv($env); } $this->setInput($input); $this->setTimeout($timeout); $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; $this->pty = \false; } /** * Creates a Process instance as a command-line to be run in a shell wrapper. * * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.) * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the * shell wrapper and not to your commands. * * In order to inject dynamic values into command-lines, we strongly recommend using placeholders. * This will save escaping values, which is not portable nor secure anyway: * * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"'); * $process->run(null, ['MY_VAR' => $theValue]); * * @param string $command The command line to pass to the shell of the OS * @param string|null $cwd The working directory or null to use the working dir of the current PHP process * @param array|null $env The environment variables or null to use the same environment as the current PHP process * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input * @param int|float|null $timeout The timeout in seconds or null to disable * * @return static * * @throws LogicException When proc_open is not installed */ public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) { $process = new static([], $cwd, $env, $input, $timeout); $process->commandline = $command; return $process; } /** * @return array */ public function __sleep() { throw new \BadMethodCallException('Cannot serialize ' . __CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__); } public function __destruct() { if ($this->options['create_new_console'] ?? \false) { $this->processPipes->close(); } else { $this->stop(0); } } public function __clone() { $this->resetProcessData(); } /** * Runs the process. * * The callback receives the type of output (out or err) and * some bytes from the output in real-time. It allows to have feedback * from the independent process during execution. * * The STDOUT and STDERR are also available after the process is finished * via the getOutput() and getErrorOutput() methods. * * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @return int The exit status code * * @throws RuntimeException When process can't be launched * @throws RuntimeException When process is already running * @throws ProcessTimedOutException When process timed out * @throws ProcessSignaledException When process stopped after receiving signal * @throws LogicException In case a callback is provided and output has been disabled * * @final */ public function run(?callable $callback = null, array $env = []) : int { $this->start($callback, $env); return $this->wait(); } /** * Runs the process. * * This is identical to run() except that an exception is thrown if the process * exits with a non-zero exit code. * * @return $this * * @throws ProcessFailedException if the process didn't terminate successfully * * @final */ public function mustRun(?callable $callback = null, array $env = []) : self { if (0 !== $this->run($callback, $env)) { throw new ProcessFailedException($this); } return $this; } /** * Starts the process and returns after writing the input to STDIN. * * This method blocks until all STDIN data is sent to the process then it * returns while the process runs in the background. * * The termination of the process can be awaited with wait(). * * The callback receives the type of output (out or err) and some bytes from * the output in real-time while writing the standard input to the process. * It allows to have feedback from the independent process during execution. * * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @throws RuntimeException When process can't be launched * @throws RuntimeException When process is already running * @throws LogicException In case a callback is provided and output has been disabled */ public function start(?callable $callback = null, array $env = []) { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); } $this->resetProcessData(); $this->starttime = $this->lastOutputTime = \microtime(\true); $this->callback = $this->buildCallback($callback); $this->hasCallback = null !== $callback; $descriptors = $this->getDescriptors(); if ($this->env) { $env += '\\' === \DIRECTORY_SEPARATOR ? \array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; } $env += '\\' === \DIRECTORY_SEPARATOR ? \array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv(); if (\is_array($commandline = $this->commandline)) { $commandline = \implode(' ', \array_map([$this, 'escapeArgument'], $commandline)); if ('\\' !== \DIRECTORY_SEPARATOR) { // exec is mandatory to deal with sending a signal to the process $commandline = 'exec ' . $commandline; } } else { $commandline = $this->replacePlaceholders($commandline, $env); } if ('\\' === \DIRECTORY_SEPARATOR) { $commandline = $this->prepareWindowsCommandLine($commandline, $env); } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) { // last exit code is output on the fourth pipe and caught to work around --enable-sigchild $descriptors[3] = ['pipe', 'w']; // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input $commandline = '{ (' . $commandline . ') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code'; // Workaround for the bug, when PTS functionality is enabled. // @see : https://bugs.php.net/69442 $ptsWorkaround = \fopen(__FILE__, 'r'); } $envPairs = []; foreach ($env as $k => $v) { if (\false !== $v && \false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], \true)) { $envPairs[] = $k . '=' . $v; } } if (!\is_dir($this->cwd)) { throw new RuntimeException(\sprintf('The provided cwd "%s" does not exist.', $this->cwd)); } $this->process = @\proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); if (!\is_resource($this->process)) { throw new RuntimeException('Unable to launch a new process.'); } $this->status = self::STATUS_STARTED; if (isset($descriptors[3])) { $this->fallbackStatus['pid'] = (int) \fgets($this->processPipes->pipes[3]); } if ($this->tty) { return; } $this->updateStatus(\false); $this->checkTimeout(); } /** * Restarts the process. * * Be warned that the process is cloned before being started. * * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @return static * * @throws RuntimeException When process can't be launched * @throws RuntimeException When process is already running * * @see start() * * @final */ public function restart(?callable $callback = null, array $env = []) : self { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); } $process = clone $this; $process->start($callback, $env); return $process; } /** * Waits for the process to terminate. * * The callback receives the type of output (out or err) and some bytes * from the output in real-time while writing the standard input to the process. * It allows to have feedback from the independent process during execution. * * @param callable|null $callback A valid PHP callback * * @return int The exitcode of the process * * @throws ProcessTimedOutException When process timed out * @throws ProcessSignaledException When process stopped after receiving signal * @throws LogicException When process is not yet started */ public function wait(?callable $callback = null) { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(\false); if (null !== $callback) { if (!$this->processPipes->haveReadSupport()) { $this->stop(0); throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); } $this->callback = $this->buildCallback($callback); } do { $this->checkTimeout(); $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen()); $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); } while ($running); while ($this->isRunning()) { $this->checkTimeout(); \usleep(1000); } if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { throw new ProcessSignaledException($this); } return $this->exitcode; } /** * Waits until the callback returns true. * * The callback receives the type of output (out or err) and some bytes * from the output in real-time while writing the standard input to the process. * It allows to have feedback from the independent process during execution. * * @throws RuntimeException When process timed out * @throws LogicException When process is not yet started * @throws ProcessTimedOutException In case the timeout was reached */ public function waitUntil(callable $callback) : bool { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(\false); if (!$this->processPipes->haveReadSupport()) { $this->stop(0); throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); } $callback = $this->buildCallback($callback); $ready = \false; while (\true) { $this->checkTimeout(); $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); foreach ($output as $type => $data) { if (3 !== $type) { $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } if ($ready) { return \true; } if (!$running) { return \false; } \usleep(1000); } } /** * Returns the Pid (process identifier), if applicable. * * @return int|null The process id if running, null otherwise */ public function getPid() { return $this->isRunning() ? $this->processInformation['pid'] : null; } /** * Sends a POSIX signal to the process. * * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) * * @return $this * * @throws LogicException In case the process is not running * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ public function signal(int $signal) { $this->doSignal($signal, \true); return $this; } /** * Disables fetching output and error output from the underlying process. * * @return $this * * @throws RuntimeException In case the process is already running * @throws LogicException if an idle timeout is set */ public function disableOutput() { if ($this->isRunning()) { throw new RuntimeException('Disabling output while the process is running is not possible.'); } if (null !== $this->idleTimeout) { throw new LogicException('Output cannot be disabled while an idle timeout is set.'); } $this->outputDisabled = \true; return $this; } /** * Enables fetching output and error output from the underlying process. * * @return $this * * @throws RuntimeException In case the process is already running */ public function enableOutput() { if ($this->isRunning()) { throw new RuntimeException('Enabling output while the process is running is not possible.'); } $this->outputDisabled = \false; return $this; } /** * Returns true in case the output is disabled, false otherwise. * * @return bool */ public function isOutputDisabled() { return $this->outputDisabled; } /** * Returns the current output of the process (STDOUT). * * @return string * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ public function getOutput() { $this->readPipesForOutput(__FUNCTION__); if (\false === ($ret = \stream_get_contents($this->stdout, -1, 0))) { return ''; } return $ret; } /** * Returns the output incrementally. * * In comparison with the getOutput method which always return the whole * output, this one returns the new output since the last call. * * @return string * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ public function getIncrementalOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = \stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); $this->incrementalOutputOffset = \ftell($this->stdout); if (\false === $latest) { return ''; } return $latest; } /** * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). * * @param int $flags A bit field of Process::ITER_* flags * * @return \Generator * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ #[\ReturnTypeWillChange] public function getIterator(int $flags = 0) { $this->readPipesForOutput(__FUNCTION__, \false); $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); $blocking = !(self::ITER_NON_BLOCKING & $flags); $yieldOut = !(self::ITER_SKIP_OUT & $flags); $yieldErr = !(self::ITER_SKIP_ERR & $flags); while (null !== $this->callback || $yieldOut && !\feof($this->stdout) || $yieldErr && !\feof($this->stderr)) { if ($yieldOut) { $out = \stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); if (isset($out[0])) { if ($clearOutput) { $this->clearOutput(); } else { $this->incrementalOutputOffset = \ftell($this->stdout); } (yield self::OUT => $out); } } if ($yieldErr) { $err = \stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); if (isset($err[0])) { if ($clearOutput) { $this->clearErrorOutput(); } else { $this->incrementalErrorOutputOffset = \ftell($this->stderr); } (yield self::ERR => $err); } } if (!$blocking && !isset($out[0]) && !isset($err[0])) { (yield self::OUT => ''); } $this->checkTimeout(); $this->readPipesForOutput(__FUNCTION__, $blocking); } } /** * Clears the process output. * * @return $this */ public function clearOutput() { \ftruncate($this->stdout, 0); \fseek($this->stdout, 0); $this->incrementalOutputOffset = 0; return $this; } /** * Returns the current error output of the process (STDERR). * * @return string * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ public function getErrorOutput() { $this->readPipesForOutput(__FUNCTION__); if (\false === ($ret = \stream_get_contents($this->stderr, -1, 0))) { return ''; } return $ret; } /** * Returns the errorOutput incrementally. * * In comparison with the getErrorOutput method which always return the * whole error output, this one returns the new error output since the last * call. * * @return string * * @throws LogicException in case the output has been disabled * @throws LogicException In case the process is not started */ public function getIncrementalErrorOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = \stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); $this->incrementalErrorOutputOffset = \ftell($this->stderr); if (\false === $latest) { return ''; } return $latest; } /** * Clears the process output. * * @return $this */ public function clearErrorOutput() { \ftruncate($this->stderr, 0); \fseek($this->stderr, 0); $this->incrementalErrorOutputOffset = 0; return $this; } /** * Returns the exit code returned by the process. * * @return int|null The exit status code, null if the Process is not terminated */ public function getExitCode() { $this->updateStatus(\false); return $this->exitcode; } /** * Returns a string representation for the exit code returned by the process. * * This method relies on the Unix exit code status standardization * and might not be relevant for other operating systems. * * @return string|null A string representation for the exit status code, null if the Process is not terminated * * @see http://tldp.org/LDP/abs/html/exitcodes.html * @see http://en.wikipedia.org/wiki/Unix_signal */ public function getExitCodeText() { if (null === ($exitcode = $this->getExitCode())) { return null; } return self::$exitCodes[$exitcode] ?? 'Unknown error'; } /** * Checks if the process ended successfully. * * @return bool */ public function isSuccessful() { return 0 === $this->getExitCode(); } /** * Returns true if the child process has been terminated by an uncaught signal. * * It always returns false on Windows. * * @return bool * * @throws LogicException In case the process is not terminated */ public function hasBeenSignaled() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['signaled']; } /** * Returns the number of the signal that caused the child process to terminate its execution. * * It is only meaningful if hasBeenSignaled() returns true. * * @return int * * @throws RuntimeException In case --enable-sigchild is activated * @throws LogicException In case the process is not terminated */ public function getTermSignal() { $this->requireProcessIsTerminated(__FUNCTION__); if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.'); } return $this->processInformation['termsig']; } /** * Returns true if the child process has been stopped by a signal. * * It always returns false on Windows. * * @return bool * * @throws LogicException In case the process is not terminated */ public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopped']; } /** * Returns the number of the signal that caused the child process to stop its execution. * * It is only meaningful if hasBeenStopped() returns true. * * @return int * * @throws LogicException In case the process is not terminated */ public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopsig']; } /** * Checks if the process is currently running. * * @return bool */ public function isRunning() { if (self::STATUS_STARTED !== $this->status) { return \false; } $this->updateStatus(\false); return $this->processInformation['running']; } /** * Checks if the process has been started with no regard to the current state. * * @return bool */ public function isStarted() { return self::STATUS_READY != $this->status; } /** * Checks if the process is terminated. * * @return bool */ public function isTerminated() { $this->updateStatus(\false); return self::STATUS_TERMINATED == $this->status; } /** * Gets the process status. * * The status is one of: ready, started, terminated. * * @return string */ public function getStatus() { $this->updateStatus(\false); return $this->status; } /** * Stops the process. * * @param int|float $timeout The timeout in seconds * @param int|null $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) * * @return int|null The exit-code of the process or null if it's not running */ public function stop(float $timeout = 10, ?int $signal = null) { $timeoutMicro = \microtime(\true) + $timeout; if ($this->isRunning()) { // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here $this->doSignal(15, \false); do { \usleep(1000); } while ($this->isRunning() && \microtime(\true) < $timeoutMicro); if ($this->isRunning()) { // Avoid exception here: process is supposed to be running, but it might have stopped just // after this line. In any case, let's silently discard the error, we cannot do anything. $this->doSignal($signal ?: 9, \false); } } if ($this->isRunning()) { if (isset($this->fallbackStatus['pid'])) { unset($this->fallbackStatus['pid']); return $this->stop(0, $signal); } $this->close(); } return $this->exitcode; } /** * Adds a line to the STDOUT stream. * * @internal */ public function addOutput(string $line) { $this->lastOutputTime = \microtime(\true); \fseek($this->stdout, 0, \SEEK_END); \fwrite($this->stdout, $line); \fseek($this->stdout, $this->incrementalOutputOffset); } /** * Adds a line to the STDERR stream. * * @internal */ public function addErrorOutput(string $line) { $this->lastOutputTime = \microtime(\true); \fseek($this->stderr, 0, \SEEK_END); \fwrite($this->stderr, $line); \fseek($this->stderr, $this->incrementalErrorOutputOffset); } /** * Gets the last output time in seconds. */ public function getLastOutputTime() : ?float { return $this->lastOutputTime; } /** * Gets the command line to be executed. * * @return string */ public function getCommandLine() { return \is_array($this->commandline) ? \implode(' ', \array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; } /** * Gets the process timeout in seconds (max. runtime). * * @return float|null */ public function getTimeout() { return $this->timeout; } /** * Gets the process idle timeout in seconds (max. time since last output). * * @return float|null */ public function getIdleTimeout() { return $this->idleTimeout; } /** * Sets the process timeout (max. runtime) in seconds. * * To disable the timeout, set this value to null. * * @return $this * * @throws InvalidArgumentException if the timeout is negative */ public function setTimeout(?float $timeout) { $this->timeout = $this->validateTimeout($timeout); return $this; } /** * Sets the process idle timeout (max. time since last output) in seconds. * * To disable the timeout, set this value to null. * * @return $this * * @throws LogicException if the output is disabled * @throws InvalidArgumentException if the timeout is negative */ public function setIdleTimeout(?float $timeout) { if (null !== $timeout && $this->outputDisabled) { throw new LogicException('Idle timeout cannot be set while the output is disabled.'); } $this->idleTimeout = $this->validateTimeout($timeout); return $this; } /** * Enables or disables the TTY mode. * * @return $this * * @throws RuntimeException In case the TTY mode is not supported */ public function setTty(bool $tty) { if ('\\' === \DIRECTORY_SEPARATOR && $tty) { throw new RuntimeException('TTY mode is not supported on Windows platform.'); } if ($tty && !self::isTtySupported()) { throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); } $this->tty = $tty; return $this; } /** * Checks if the TTY mode is enabled. * * @return bool */ public function isTty() { return $this->tty; } /** * Sets PTY mode. * * @return $this */ public function setPty(bool $bool) { $this->pty = $bool; return $this; } /** * Returns PTY state. * * @return bool */ public function isPty() { return $this->pty; } /** * Gets the working directory. * * @return string|null */ public function getWorkingDirectory() { if (null === $this->cwd) { // getcwd() will return false if any one of the parent directories does not have // the readable or search mode set, even if the current directory does return \getcwd() ?: null; } return $this->cwd; } /** * Sets the current working directory. * * @return $this */ public function setWorkingDirectory(string $cwd) { $this->cwd = $cwd; return $this; } /** * Gets the environment variables. * * @return array */ public function getEnv() { return $this->env; } /** * Sets the environment variables. * * @param array $env The new environment variables * * @return $this */ public function setEnv(array $env) { $this->env = $env; return $this; } /** * Gets the Process input. * * @return resource|string|\Iterator|null */ public function getInput() { return $this->input; } /** * Sets the input. * * This content will be passed to the underlying process standard input. * * @param string|int|float|bool|resource|\Traversable|null $input The content * * @return $this * * @throws LogicException In case the process is running */ public function setInput($input) { if ($this->isRunning()) { throw new LogicException('Input cannot be set while the process is running.'); } $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } /** * Performs a check between the timeout definition and the time the process started. * * In case you run a background process (with the start method), you should * trigger this method regularly to ensure the process timeout * * @throws ProcessTimedOutException In case the timeout was reached */ public function checkTimeout() { if (self::STATUS_STARTED !== $this->status) { return; } if (null !== $this->timeout && $this->timeout < \microtime(\true) - $this->starttime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); } if (null !== $this->idleTimeout && $this->idleTimeout < \microtime(\true) - $this->lastOutputTime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); } } /** * @throws LogicException in case process is not started */ public function getStartTime() : float { if (!$this->isStarted()) { throw new LogicException('Start time is only available after process start.'); } return $this->starttime; } /** * Defines options to pass to the underlying proc_open(). * * @see https://php.net/proc_open for the options supported by PHP. * * Enabling the "create_new_console" option allows a subprocess to continue * to run after the main process exited, on both Windows and *nix */ public function setOptions(array $options) { if ($this->isRunning()) { throw new RuntimeException('Setting options while the process is running is not possible.'); } $defaultOptions = $this->options; $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console']; foreach ($options as $key => $value) { if (!\in_array($key, $existingOptions)) { $this->options = $defaultOptions; throw new LogicException(\sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, \implode('", "', $existingOptions))); } $this->options[$key] = $value; } } /** * Returns whether TTY is supported on the current operating system. */ public static function isTtySupported() : bool { static $isTtySupported; if (null === $isTtySupported) { $isTtySupported = (bool) @\proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); } return $isTtySupported; } /** * Returns whether PTY is supported on the current operating system. * * @return bool */ public static function isPtySupported() { static $result; if (null !== $result) { return $result; } if ('\\' === \DIRECTORY_SEPARATOR) { return $result = \false; } return $result = (bool) @\proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); } /** * Creates the descriptors needed by the proc_open. */ private function getDescriptors() : array { if ($this->input instanceof \Iterator) { $this->input->rewind(); } if ('\\' === \DIRECTORY_SEPARATOR) { $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); } else { $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); } return $this->processPipes->getDescriptors(); } /** * Builds up the callback used by wait(). * * The callbacks adds all occurred output to the specific buffer and calls * the user callback (if present) with the received output. * * @param callable|null $callback The user defined PHP callback * * @return \Closure */ protected function buildCallback(?callable $callback = null) { if ($this->outputDisabled) { return function ($type, $data) use($callback) : bool { return null !== $callback && $callback($type, $data); }; } $out = self::OUT; return function ($type, $data) use($callback, $out) : bool { if ($out == $type) { $this->addOutput($data); } else { $this->addErrorOutput($data); } return null !== $callback && $callback($type, $data); }; } /** * Updates the status of the process, reads pipes. * * @param bool $blocking Whether to use a blocking read call */ protected function updateStatus(bool $blocking) { if (self::STATUS_STARTED !== $this->status) { return; } $this->processInformation = \proc_get_status($this->process); $running = $this->processInformation['running']; $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); if ($this->fallbackStatus && $this->isSigchildEnabled()) { $this->processInformation = $this->fallbackStatus + $this->processInformation; } if (!$running) { $this->close(); } } /** * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. * * @return bool */ protected function isSigchildEnabled() { if (null !== self::$sigchild) { return self::$sigchild; } if (!\function_exists('phpinfo')) { return self::$sigchild = \false; } \ob_start(); \phpinfo(\INFO_GENERAL); return self::$sigchild = \str_contains(\ob_get_clean(), '--enable-sigchild'); } /** * Reads pipes for the freshest output. * * @param string $caller The name of the method that needs fresh outputs * @param bool $blocking Whether to use blocking calls or not * * @throws LogicException in case output has been disabled or process is not started */ private function readPipesForOutput(string $caller, bool $blocking = \false) { if ($this->outputDisabled) { throw new LogicException('Output has been disabled.'); } $this->requireProcessIsStarted($caller); $this->updateStatus($blocking); } /** * Validates and returns the filtered timeout. * * @throws InvalidArgumentException if the given timeout is a negative number */ private function validateTimeout(?float $timeout) : ?float { $timeout = (float) $timeout; if (0.0 === $timeout) { $timeout = null; } elseif ($timeout < 0) { throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); } return $timeout; } /** * Reads pipes, executes callback. * * @param bool $blocking Whether to use blocking calls or not * @param bool $close Whether to close file handles or not */ private function readPipes(bool $blocking, bool $close) { $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 !== $type) { $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } } /** * Closes process resource, closes file handles, sets the exitcode. * * @return int The exitcode */ private function close() : int { $this->processPipes->close(); if (\is_resource($this->process)) { \proc_close($this->process); } $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; if (-1 === $this->exitcode) { if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { // if process has been signaled, no exitcode but a valid termsig, apply Unix convention $this->exitcode = 128 + $this->processInformation['termsig']; } elseif ($this->isSigchildEnabled()) { $this->processInformation['signaled'] = \true; $this->processInformation['termsig'] = -1; } } // Free memory from self-reference callback created by buildCallback // Doing so in other contexts like __destruct or by garbage collector is ineffective // Now pipes are closed, so the callback is no longer necessary $this->callback = null; return $this->exitcode; } /** * Resets data related to the latest run of the process. */ private function resetProcessData() { $this->starttime = null; $this->callback = null; $this->exitcode = null; $this->fallbackStatus = []; $this->processInformation = null; $this->stdout = \fopen('php://temp/maxmemory:' . 1024 * 1024, 'w+'); $this->stderr = \fopen('php://temp/maxmemory:' . 1024 * 1024, 'w+'); $this->process = null; $this->latestSignal = null; $this->status = self::STATUS_READY; $this->incrementalOutputOffset = 0; $this->incrementalErrorOutputOffset = 0; } /** * Sends a POSIX signal to the process. * * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) * @param bool $throwException Whether to throw exception in case signal failed * * @throws LogicException In case the process is not running * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ private function doSignal(int $signal, bool $throwException) : bool { if (null === ($pid = $this->getPid())) { if ($throwException) { throw new LogicException('Cannot send signal on a non running process.'); } return \false; } if ('\\' === \DIRECTORY_SEPARATOR) { \exec(\sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); if ($exitCode && $this->isRunning()) { if ($throwException) { throw new RuntimeException(\sprintf('Unable to kill the process (%s).', \implode(' ', $output))); } return \false; } } else { if (!$this->isSigchildEnabled()) { $ok = @\proc_terminate($this->process, $signal); } elseif (\function_exists('posix_kill')) { $ok = @\posix_kill($pid, $signal); } elseif ($ok = \proc_open(\sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { $ok = \false === \fgets($pipes[2]); } if (!$ok) { if ($throwException) { throw new RuntimeException(\sprintf('Error while sending signal "%s".', $signal)); } return \false; } } $this->latestSignal = $signal; $this->fallbackStatus['signaled'] = \true; $this->fallbackStatus['exitcode'] = -1; $this->fallbackStatus['termsig'] = $this->latestSignal; return \true; } private function prepareWindowsCommandLine(string $cmd, array &$env) : string { $uid = \uniqid('', \true); $varCount = 0; $varCache = []; $cmd = \preg_replace_callback('/"(?:( [^"%!^]*+ (?: (?: !LF! | "(?:\\^[%!^])?+" ) [^"%!^]*+ )++ ) | [^"]*+ )"/x', function ($m) use(&$env, &$varCache, &$varCount, $uid) { if (!isset($m[1])) { return $m[0]; } if (isset($varCache[$m[0]])) { return $varCache[$m[0]]; } if (\str_contains($value = $m[1], "\x00")) { $value = \str_replace("\x00", '?', $value); } if (\false === \strpbrk($value, "\"%!\n")) { return '"' . $value . '"'; } $value = \str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); $value = '"' . \preg_replace('/(\\\\*)"/', '$1$1\\"', $value) . '"'; $var = $uid . ++$varCount; $env[$var] = $value; return $varCache[$m[0]] = '!' . $var . '!'; }, $cmd); $cmd = 'cmd /V:ON /E:ON /D /C (' . \str_replace("\n", ' ', $cmd) . ')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $cmd .= ' ' . $offset . '>"' . $filename . '"'; } return $cmd; } /** * Ensures the process is running or terminated, throws a LogicException if the process has a not started. * * @throws LogicException if the process has not run */ private function requireProcessIsStarted(string $functionName) { if (!$this->isStarted()) { throw new LogicException(\sprintf('Process must be started before calling "%s()".', $functionName)); } } /** * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". * * @throws LogicException if the process is not yet terminated */ private function requireProcessIsTerminated(string $functionName) { if (!$this->isTerminated()) { throw new LogicException(\sprintf('Process must be terminated before calling "%s()".', $functionName)); } } /** * Escapes a string to be used as a shell argument. */ private function escapeArgument(?string $argument) : string { if ('' === $argument || null === $argument) { return '""'; } if ('\\' !== \DIRECTORY_SEPARATOR) { return "'" . \str_replace("'", "'\\''", $argument) . "'"; } if (\str_contains($argument, "\x00")) { $argument = \str_replace("\x00", '?', $argument); } if (!\preg_match('/[\\/()%!^"<>&|\\s]/', $argument)) { return $argument; } $argument = \preg_replace('/(\\\\+)$/', '$1$1', $argument); return '"' . \str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument) . '"'; } private function replacePlaceholders(string $commandline, array $env) { return \preg_replace_callback('/"\\$\\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\\}"/', function ($matches) use($commandline, $env) { if (!isset($env[$matches[1]]) || \false === $env[$matches[1]]) { throw new InvalidArgumentException(\sprintf('Command line is missing a value for parameter "%s": ', $matches[1]) . $commandline); } return $this->escapeArgument($env[$matches[1]]); }, $commandline); } private function getDefaultEnv() : array { $env = \getenv(); $env = ('\\' === \DIRECTORY_SEPARATOR ? \array_intersect_ukey($env, $_SERVER, 'strcasecmp') : \array_intersect_key($env, $_SERVER)) ?: $env; return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? \array_diff_ukey($env, $_ENV, 'strcasecmp') : $env); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console; use _ContaoManager\Symfony\Component\Console\Event\ConsoleCommandEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleErrorEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleSignalEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleTerminateEvent; /** * Contains all events dispatched by an Application. * * @author Francesco Levorato */ final class ConsoleEvents { /** * The COMMAND event allows you to attach listeners before any command is * executed by the console. It also allows you to modify the command, input and output * before they are handed to the command. * * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") */ public const COMMAND = 'console.command'; /** * The SIGNAL event allows you to perform some actions * after the command execution was interrupted. * * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent") */ public const SIGNAL = 'console.signal'; /** * The TERMINATE event allows you to attach listeners after a command is * executed by the console. * * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") */ public const TERMINATE = 'console.terminate'; /** * The ERROR event occurs when an uncaught exception or error appears. * * This event allows you to deal with the exception/error or * to modify the thrown exception. * * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") */ public const ERROR = 'console.error'; /** * Event aliases. * * These aliases can be consumed by RegisterListenersPass. */ public const ALIASES = [ConsoleCommandEvent::class => self::COMMAND, ConsoleErrorEvent::class => self::ERROR, ConsoleSignalEvent::class => self::SIGNAL, ConsoleTerminateEvent::class => self::TERMINATE]; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Pierre du Plessis */ final class Cursor { private $output; private $input; /** * @param resource|null $input */ public function __construct(OutputInterface $output, $input = null) { $this->output = $output; $this->input = $input ?? (\defined('STDIN') ? \STDIN : \fopen('php://input', 'r+')); } /** * @return $this */ public function moveUp(int $lines = 1) : self { $this->output->write(\sprintf("\x1b[%dA", $lines)); return $this; } /** * @return $this */ public function moveDown(int $lines = 1) : self { $this->output->write(\sprintf("\x1b[%dB", $lines)); return $this; } /** * @return $this */ public function moveRight(int $columns = 1) : self { $this->output->write(\sprintf("\x1b[%dC", $columns)); return $this; } /** * @return $this */ public function moveLeft(int $columns = 1) : self { $this->output->write(\sprintf("\x1b[%dD", $columns)); return $this; } /** * @return $this */ public function moveToColumn(int $column) : self { $this->output->write(\sprintf("\x1b[%dG", $column)); return $this; } /** * @return $this */ public function moveToPosition(int $column, int $row) : self { $this->output->write(\sprintf("\x1b[%d;%dH", $row + 1, $column)); return $this; } /** * @return $this */ public function savePosition() : self { $this->output->write("\x1b7"); return $this; } /** * @return $this */ public function restorePosition() : self { $this->output->write("\x1b8"); return $this; } /** * @return $this */ public function hide() : self { $this->output->write("\x1b[?25l"); return $this; } /** * @return $this */ public function show() : self { $this->output->write("\x1b[?25h\x1b[?0c"); return $this; } /** * Clears all the output from the current line. * * @return $this */ public function clearLine() : self { $this->output->write("\x1b[2K"); return $this; } /** * Clears all the output from the current line after the current position. */ public function clearLineAfter() : self { $this->output->write("\x1b[K"); return $this; } /** * Clears all the output from the cursors' current position to the end of the screen. * * @return $this */ public function clearOutput() : self { $this->output->write("\x1b[0J"); return $this; } /** * Clears the entire screen. * * @return $this */ public function clearScreen() : self { $this->output->write("\x1b[2J"); return $this; } /** * Returns the current cursor position as x,y coordinates. */ public function getCurrentPosition() : array { static $isTtySupported; if (null === $isTtySupported && \function_exists('proc_open')) { $isTtySupported = (bool) @\proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); } if (!$isTtySupported) { return [1, 1]; } $sttyMode = \shell_exec('stty -g'); \shell_exec('stty -icanon -echo'); @\fwrite($this->input, "\x1b[6n"); $code = \trim(\fread($this->input, 1024)); \shell_exec(\sprintf('stty %s', $sttyMode)); \sscanf($code, "\x1b[%d;%dR", $row, $col); return [$col, $row]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Command\CompleteCommand; use _ContaoManager\Symfony\Component\Console\Command\DumpCompletionCommand; use _ContaoManager\Symfony\Component\Console\Command\HelpCommand; use _ContaoManager\Symfony\Component\Console\Command\LazyCommand; use _ContaoManager\Symfony\Component\Console\Command\ListCommand; use _ContaoManager\Symfony\Component\Console\Command\SignalableCommandInterface; use _ContaoManager\Symfony\Component\Console\CommandLoader\CommandLoaderInterface; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Event\ConsoleCommandEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleErrorEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleSignalEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleTerminateEvent; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; use _ContaoManager\Symfony\Component\Console\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Exception\NamespaceNotFoundException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Helper\DebugFormatterHelper; use _ContaoManager\Symfony\Component\Console\Helper\FormatterHelper; use _ContaoManager\Symfony\Component\Console\Helper\Helper; use _ContaoManager\Symfony\Component\Console\Helper\HelperSet; use _ContaoManager\Symfony\Component\Console\Helper\ProcessHelper; use _ContaoManager\Symfony\Component\Console\Helper\QuestionHelper; use _ContaoManager\Symfony\Component\Console\Input\ArgvInput; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputAwareInterface; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutput; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\SignalRegistry\SignalRegistry; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; use _ContaoManager\Symfony\Component\ErrorHandler\ErrorHandler; use _ContaoManager\Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * An Application is the container for a collection of commands. * * It is the main entry point of a Console application. * * This class is optimized for a standard CLI environment. * * Usage: * * $app = new Application('myapp', '1.0 (stable)'); * $app->add(new SimpleCommand()); * $app->run(); * * @author Fabien Potencier */ class Application implements ResetInterface { private $commands = []; private $wantHelps = \false; private $runningCommand; private $name; private $version; private $commandLoader; private $catchExceptions = \true; private $autoExit = \true; private $definition; private $helperSet; private $dispatcher; private $terminal; private $defaultCommand; private $singleCommand = \false; private $initialized; private $signalRegistry; private $signalsToDispatchEvent = []; public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') { $this->name = $name; $this->version = $version; $this->terminal = new Terminal(); $this->defaultCommand = 'list'; if (\defined('SIGINT') && SignalRegistry::isSupported()) { $this->signalRegistry = new SignalRegistry(); $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2]; } } /** * @final */ public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } public function setCommandLoader(CommandLoaderInterface $commandLoader) { $this->commandLoader = $commandLoader; } public function getSignalRegistry() : SignalRegistry { if (!$this->signalRegistry) { throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); } return $this->signalRegistry; } public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) { $this->signalsToDispatchEvent = $signalsToDispatchEvent; } /** * Runs the current application. * * @return int 0 if everything went fine, or an error code * * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. */ public function run(?InputInterface $input = null, ?OutputInterface $output = null) { if (\function_exists('putenv')) { @\putenv('LINES=' . $this->terminal->getHeight()); @\putenv('COLUMNS=' . $this->terminal->getWidth()); } if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } $renderException = function (\Throwable $e) use($output) { if ($output instanceof ConsoleOutputInterface) { $this->renderThrowable($e, $output->getErrorOutput()); } else { $this->renderThrowable($e, $output); } }; if ($phpHandler = \set_exception_handler($renderException)) { \restore_exception_handler(); if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { $errorHandler = \true; } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { $phpHandler[0]->setExceptionHandler($errorHandler); } } $this->configureIO($input, $output); try { $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { if (!$this->catchExceptions) { throw $e; } $renderException($e); $exitCode = $e->getCode(); if (\is_numeric($exitCode)) { $exitCode = (int) $exitCode; if ($exitCode <= 0) { $exitCode = 1; } } else { $exitCode = 1; } } finally { // if the exception handler changed, keep it // otherwise, unregister $renderException if (!$phpHandler) { if (\set_exception_handler($renderException) === $renderException) { \restore_exception_handler(); } \restore_exception_handler(); } elseif (!$errorHandler) { $finalHandler = $phpHandler[0]->setExceptionHandler(null); if ($finalHandler !== $renderException) { $phpHandler[0]->setExceptionHandler($finalHandler); } } } if ($this->autoExit) { if ($exitCode > 255) { $exitCode = 255; } exit($exitCode); } return $exitCode; } /** * Runs the current application. * * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { if (\true === $input->hasParameterOption(['--version', '-V'], \true)) { $output->writeln($this->getLongVersion()); return 0; } try { // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { // Errors must be ignored, full binding/validation happens later when the command is known. } $name = $this->getCommandName($input); if (\true === $input->hasParameterOption(['--help', '-h'], \true)) { if (!$name) { $name = 'help'; $input = new ArrayInput(['command_name' => $this->defaultCommand]); } else { $this->wantHelps = \true; } } if (!$name) { $name = $this->defaultCommand; $definition = $this->getDefinition(); $definition->setArguments(\array_merge($definition->getArguments(), ['command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name)])); } try { $this->runningCommand = null; // the command name MUST be the first element of the input $command = $this->find($name); } catch (\Throwable $e) { if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); if (0 === $event->getExitCode()) { return 0; } $e = $event->getError(); } throw $e; } $alternative = $alternatives[0]; $style = new SymfonyStyle($input, $output); $output->writeln(''); $formattedBlock = (new FormatterHelper())->formatBlock(\sprintf('Command "%s" is not defined.', $name), 'error', \true); $output->writeln($formattedBlock); if (!$style->confirm(\sprintf('Do you want to run "%s" instead? ', $alternative), \false)) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); return $event->getExitCode(); } return 1; } $command = $this->find($alternative); } if ($command instanceof LazyCommand) { $command = $command->getCommand(); } $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } /** * {@inheritdoc} */ public function reset() { } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Get the helper set associated with the command. * * @return HelperSet */ public function getHelperSet() { if (!$this->helperSet) { $this->helperSet = $this->getDefaultHelperSet(); } return $this->helperSet; } public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } /** * Gets the InputDefinition related to this Application. * * @return InputDefinition */ public function getDefinition() { if (!$this->definition) { $this->definition = $this->getDefaultInputDefinition(); } if ($this->singleCommand) { $inputDefinition = $this->definition; $inputDefinition->setArguments(); return $inputDefinition; } return $this->definition; } /** * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && 'command' === $input->getCompletionName()) { $commandNames = []; foreach ($this->all() as $name => $command) { // skip hidden commands and aliased commands as they already get added below if ($command->isHidden() || $command->getName() !== $name) { continue; } $commandNames[] = $command->getName(); foreach ($command->getAliases() as $name) { $commandNames[] = $name; } } $suggestions->suggestValues(\array_filter($commandNames)); return; } if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) { $suggestions->suggestOptions($this->getDefinition()->getOptions()); return; } } /** * Gets the help message. * * @return string */ public function getHelp() { return $this->getLongVersion(); } /** * Gets whether to catch exceptions or not during commands execution. * * @return bool */ public function areExceptionsCaught() { return $this->catchExceptions; } /** * Sets whether to catch exceptions or not during commands execution. */ public function setCatchExceptions(bool $boolean) { $this->catchExceptions = $boolean; } /** * Gets whether to automatically exit after a command execution or not. * * @return bool */ public function isAutoExitEnabled() { return $this->autoExit; } /** * Sets whether to automatically exit after a command execution or not. */ public function setAutoExit(bool $boolean) { $this->autoExit = $boolean; } /** * Gets the name of the application. * * @return string */ public function getName() { return $this->name; } /** * Sets the application name. **/ public function setName(string $name) { $this->name = $name; } /** * Gets the application version. * * @return string */ public function getVersion() { return $this->version; } /** * Sets the application version. */ public function setVersion(string $version) { $this->version = $version; } /** * Returns the long version of the application. * * @return string */ public function getLongVersion() { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { return \sprintf('%s %s', $this->getName(), $this->getVersion()); } return $this->getName(); } return 'Console Tool'; } /** * Registers a new command. * * @return Command */ public function register(string $name) { return $this->add(new Command($name)); } /** * Adds an array of command objects. * * If a Command is not enabled it will not be added. * * @param Command[] $commands An array of commands */ public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } /** * Adds a command object. * * If a command with the same name already exists, it will be overridden. * If the command is not enabled it will not be added. * * @return Command|null */ public function add(Command $command) { $this->init(); $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return null; } if (!$command instanceof LazyCommand) { // Will throw if the command is not correctly initialized. $command->getDefinition(); } if (!$command->getName()) { throw new LogicException(\sprintf('The command defined in "%s" cannot have an empty name.', \get_debug_type($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } /** * Returns a registered command by name or alias. * * @return Command * * @throws CommandNotFoundException When given command name does not exist */ public function get(string $name) { $this->init(); if (!$this->has($name)) { throw new CommandNotFoundException(\sprintf('The command "%s" does not exist.', $name)); } // When the command has a different name than the one used at the command loader level if (!isset($this->commands[$name])) { throw new CommandNotFoundException(\sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name)); } $command = $this->commands[$name]; if ($this->wantHelps) { $this->wantHelps = \false; $helpCommand = $this->get('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } /** * Returns true if the command exists, false otherwise. * * @return bool */ public function has(string $name) { $this->init(); return isset($this->commands[$name]) || $this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)); } /** * Returns an array of all unique namespaces used by currently registered commands. * * It does not return the global namespace which always exists. * * @return string[] */ public function getNamespaces() { $namespaces = []; foreach ($this->all() as $command) { if ($command->isHidden()) { continue; } $namespaces[] = $this->extractAllNamespaces($command->getName()); foreach ($command->getAliases() as $alias) { $namespaces[] = $this->extractAllNamespaces($alias); } } return \array_values(\array_unique(\array_filter(\array_merge([], ...$namespaces)))); } /** * Finds a registered namespace by a name or an abbreviation. * * @return string * * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous */ public function findNamespace(string $namespace) { $allNamespaces = $this->getNamespaces(); $expr = \implode('[^:]*:', \array_map('preg_quote', \explode(':', $namespace))) . '[^:]*'; $namespaces = \preg_grep('{^' . $expr . '}', $allNamespaces); if (empty($namespaces)) { $message = \sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= \implode("\n ", $alternatives); } throw new NamespaceNotFoundException($message, $alternatives); } $exact = \in_array($namespace, $namespaces, \true); if (\count($namespaces) > 1 && !$exact) { throw new NamespaceNotFoundException(\sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(\array_values($namespaces))), \array_values($namespaces)); } return $exact ? $namespace : \reset($namespaces); } /** * Finds a command by name or alias. * * Contrary to get, this command tries to find the best * match if you give it an abbreviation of a name or alias. * * @return Command * * @throws CommandNotFoundException When command name is incorrect or ambiguous */ public function find(string $name) { $this->init(); $aliases = []; foreach ($this->commands as $command) { foreach ($command->getAliases() as $alias) { if (!$this->has($alias)) { $this->commands[$alias] = $command; } } } if ($this->has($name)) { return $this->get($name); } $allCommands = $this->commandLoader ? \array_merge($this->commandLoader->getNames(), \array_keys($this->commands)) : \array_keys($this->commands); $expr = \implode('[^:]*:', \array_map('preg_quote', \explode(':', $name))) . '[^:]*'; $commands = \preg_grep('{^' . $expr . '}', $allCommands); if (empty($commands)) { $commands = \preg_grep('{^' . $expr . '}i', $allCommands); } // if no commands matched or we just matched namespaces if (empty($commands) || \count(\preg_grep('{^' . $expr . '$}i', $commands)) < 1) { if (\false !== ($pos = \strrpos($name, ':'))) { // check if a namespace exists and contains commands $this->findNamespace(\substr($name, 0, $pos)); } $message = \sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands)) { // remove hidden commands $alternatives = \array_filter($alternatives, function ($name) { return !$this->get($name)->isHidden(); }); if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= \implode("\n ", $alternatives); } throw new CommandNotFoundException($message, \array_values($alternatives)); } // filter out aliases for commands which are already on the list if (\count($commands) > 1) { $commandList = $this->commandLoader ? \array_merge(\array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; $commands = \array_unique(\array_filter($commands, function ($nameOrAlias) use(&$commandList, $commands, &$aliases) { if (!$commandList[$nameOrAlias] instanceof Command) { $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); } $commandName = $commandList[$nameOrAlias]->getName(); $aliases[$nameOrAlias] = $commandName; return $commandName === $nameOrAlias || !\in_array($commandName, $commands); })); } if (\count($commands) > 1) { $usableWidth = $this->terminal->getWidth() - 10; $abbrevs = \array_values($commands); $maxLen = 0; foreach ($abbrevs as $abbrev) { $maxLen = \max(Helper::width($abbrev), $maxLen); } $abbrevs = \array_map(function ($cmd) use($commandList, $usableWidth, $maxLen, &$commands) { if ($commandList[$cmd]->isHidden()) { unset($commands[\array_search($cmd, $commands)]); return \false; } $abbrev = \str_pad($cmd, $maxLen, ' ') . ' ' . $commandList[$cmd]->getDescription(); return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3) . '...' : $abbrev; }, \array_values($commands)); if (\count($commands) > 1) { $suggestions = $this->getAbbreviationSuggestions(\array_filter($abbrevs)); throw new CommandNotFoundException(\sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), \array_values($commands)); } } $command = $this->get(\reset($commands)); if ($command->isHidden()) { throw new CommandNotFoundException(\sprintf('The command "%s" does not exist.', $name)); } return $command; } /** * Gets the commands (registered in the given namespace if provided). * * The array keys are the full names and the values the command instances. * * @return Command[] */ public function all(?string $namespace = null) { $this->init(); if (null === $namespace) { if (!$this->commandLoader) { return $this->commands; } $commands = $this->commands; foreach ($this->commandLoader->getNames() as $name) { if (!isset($commands[$name]) && $this->has($name)) { $commands[$name] = $this->get($name); } } return $commands; } $commands = []; foreach ($this->commands as $name => $command) { if ($namespace === $this->extractNamespace($name, \substr_count($namespace, ':') + 1)) { $commands[$name] = $command; } } if ($this->commandLoader) { foreach ($this->commandLoader->getNames() as $name) { if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, \substr_count($namespace, ':') + 1) && $this->has($name)) { $commands[$name] = $this->get($name); } } } return $commands; } /** * Returns an array of possible abbreviations given a set of names. * * @return string[][] */ public static function getAbbreviations(array $names) { $abbrevs = []; foreach ($names as $name) { for ($len = \strlen($name); $len > 0; --$len) { $abbrev = \substr($name, 0, $len); $abbrevs[$abbrev][] = $name; } } return $abbrevs; } public function renderThrowable(\Throwable $e, OutputInterface $output) : void { $output->writeln('', OutputInterface::VERBOSITY_QUIET); $this->doRenderThrowable($e, $output); if (null !== $this->runningCommand) { $output->writeln(\sprintf('%s', OutputFormatter::escape(\sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } protected function doRenderThrowable(\Throwable $e, OutputInterface $output) : void { do { $message = \trim($e->getMessage()); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $class = \get_debug_type($e); $title = \sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' (' . $code . ')' : ''); $len = Helper::width($title); } else { $len = 0; } if (\str_contains($message, "@anonymous\x00")) { $message = \preg_replace_callback('/[a-zA-Z_\\x7f-\\xff][\\\\a-zA-Z0-9_\\x7f-\\xff]*+@anonymous\\x00.*?\\.php(?:0x?|:[0-9]++\\$)[0-9a-fA-F]++/', function ($m) { return \class_exists($m[0], \false) ? ((\get_parent_class($m[0]) ?: \key(\class_implements($m[0]))) ?: 'class') . '@anonymous' : $m[0]; }, $message); } $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; $lines = []; foreach ('' !== $message ? \preg_split('/\\r?\\n/', $message) : [] as $line) { foreach ($this->splitStringByWidth($line, $width - 4) as $line) { // pre-format lines to get the right string length $lineLength = Helper::width($line) + 4; $lines[] = [$line, $lineLength]; $len = \max($lineLength, $len); } } $messages = []; if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = \sprintf('%s', OutputFormatter::escape(\sprintf('In %s line %s:', \basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); } $messages[] = $emptyLine = \sprintf('%s', \str_repeat(' ', $len)); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = \sprintf('%s%s', $title, \str_repeat(' ', \max(0, $len - Helper::width($title)))); } foreach ($lines as $line) { $messages[] = \sprintf(' %s %s', OutputFormatter::escape($line[0]), \str_repeat(' ', $len - $line[1])); } $messages[] = $emptyLine; $messages[] = ''; $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); // exception related properties $trace = $e->getTrace(); \array_unshift($trace, ['function' => '', 'file' => $e->getFile() ?: 'n/a', 'line' => $e->getLine() ?: 'n/a', 'args' => []]); for ($i = 0, $count = \count($trace); $i < $count; ++$i) { $class = $trace[$i]['class'] ?? ''; $type = $trace[$i]['type'] ?? ''; $function = $trace[$i]['function'] ?? ''; $file = $trace[$i]['file'] ?? 'n/a'; $line = $trace[$i]['line'] ?? 'n/a'; $output->writeln(\sprintf(' %s%s at %s:%s', $class, $function ? $type . $function . '()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); } $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } while ($e = $e->getPrevious()); } /** * Configures the input and output instances based on the user arguments and options. */ protected function configureIO(InputInterface $input, OutputInterface $output) { if (\true === $input->hasParameterOption(['--ansi'], \true)) { $output->setDecorated(\true); } elseif (\true === $input->hasParameterOption(['--no-ansi'], \true)) { $output->setDecorated(\false); } if (\true === $input->hasParameterOption(['--no-interaction', '-n'], \true)) { $input->setInteractive(\false); } switch ($shellVerbosity = (int) \getenv('SHELL_VERBOSITY')) { case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break; case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break; case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break; case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break; default: $shellVerbosity = 0; break; } if (\true === $input->hasParameterOption(['--quiet', '-q'], \true)) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); $shellVerbosity = -1; } else { if ($input->hasParameterOption('-vvv', \true) || $input->hasParameterOption('--verbose=3', \true) || 3 === $input->getParameterOption('--verbose', \false, \true)) { $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); $shellVerbosity = 3; } elseif ($input->hasParameterOption('-vv', \true) || $input->hasParameterOption('--verbose=2', \true) || 2 === $input->getParameterOption('--verbose', \false, \true)) { $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); $shellVerbosity = 2; } elseif ($input->hasParameterOption('-v', \true) || $input->hasParameterOption('--verbose=1', \true) || $input->hasParameterOption('--verbose', \true) || $input->getParameterOption('--verbose', \false, \true)) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); $shellVerbosity = 1; } } if (-1 === $shellVerbosity) { $input->setInteractive(\false); } if (\function_exists('putenv')) { @\putenv('SHELL_VERBOSITY=' . $shellVerbosity); } $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; } /** * Runs the current command. * * If an event dispatcher has been attached to the application, * events are also dispatched during the life-cycle of the command. * * @return int 0 if everything went fine, or an error code */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if ($this->signalsToDispatchEvent) { $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; if ($commandSignals || null !== $this->dispatcher) { if (!$this->signalRegistry) { throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); } if (Terminal::hasSttyAvailable()) { $sttyMode = \shell_exec('stty -g'); foreach ([\SIGINT, \SIGTERM] as $signal) { $this->signalRegistry->register($signal, static function () use($sttyMode) { \shell_exec('stty ' . $sttyMode); }); } } } if (null !== $this->dispatcher) { foreach ($this->signalsToDispatchEvent as $signal) { $event = new ConsoleSignalEvent($command, $input, $output, $signal); $this->signalRegistry->register($signal, function ($signal, $hasNext) use($event) { $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); // No more handlers, we try to simulate PHP default behavior if (!$hasNext) { if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], \true)) { exit(0); } } }); } } foreach ($commandSignals as $signal) { $this->signalRegistry->register($signal, [$command, 'handleSignal']); } } if (null === $this->dispatcher) { return $command->run($input, $output); } // bind before the console.command event, so the listeners have access to input options/arguments try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); } catch (ExceptionInterface $e) { // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition } $event = new ConsoleCommandEvent($command, $input, $output); $e = null; try { $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); if ($event->commandShouldRun()) { $exitCode = $command->run($input, $output); } else { $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; } } catch (\Throwable $e) { $event = new ConsoleErrorEvent($input, $output, $e, $command); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); $e = $event->getError(); if (0 === ($exitCode = $event->getExitCode())) { $e = null; } } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); if (null !== $e) { throw $e; } return $event->getExitCode(); } /** * Gets the name of the command based on input. * * @return string|null */ protected function getCommandName(InputInterface $input) { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); } /** * Gets the default input definition. * * @return InputDefinition */ protected function getDefaultInputDefinition() { return new InputDefinition([new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the ' . $this->defaultCommand . ' command'), new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question')]); } /** * Gets the default commands that should always be available. * * @return Command[] */ protected function getDefaultCommands() { return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()]; } /** * Gets the default helper set with the helpers that should always be available. * * @return HelperSet */ protected function getDefaultHelperSet() { return new HelperSet([new FormatterHelper(), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper()]); } /** * Returns abbreviated suggestions in string format. */ private function getAbbreviationSuggestions(array $abbrevs) : string { return ' ' . \implode("\n ", $abbrevs); } /** * Returns the namespace part of the command name. * * This method is not part of public API and should not be used directly. * * @return string */ public function extractNamespace(string $name, ?int $limit = null) { $parts = \explode(':', $name, -1); return \implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); } /** * Finds alternative of $name among $collection, * if nothing is found in $collection, try in $abbrevs. * * @return string[] */ private function findAlternatives(string $name, iterable $collection) : array { $threshold = 1000.0; $alternatives = []; $collectionParts = []; foreach ($collection as $item) { $collectionParts[$item] = \explode(':', $item); } foreach (\explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; } elseif (!isset($parts[$i])) { continue; } $lev = \levenshtein($subname, $parts[$i]); if ($lev <= \strlen($subname) / 3 || '' !== $subname && \str_contains($parts[$i], $subname)) { $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } } } foreach ($collection as $item) { $lev = \levenshtein($name, $item); if ($lev <= \strlen($name) / 3 || \str_contains($item, $name)) { $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = \array_filter($alternatives, function ($lev) use($threshold) { return $lev < 2 * $threshold; }); \ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return \array_keys($alternatives); } /** * Sets the default Command name. * * @return $this */ public function setDefaultCommand(string $commandName, bool $isSingleCommand = \false) { $this->defaultCommand = \explode('|', \ltrim($commandName, '|'))[0]; if ($isSingleCommand) { // Ensure the command exist $this->find($commandName); $this->singleCommand = \true; } return $this; } /** * @internal */ public function isSingleCommand() : bool { return $this->singleCommand; } private function splitStringByWidth(string $string, int $width) : array { // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. // additionally, array_slice() is not enough as some character has doubled width. // we need a function to split string not by character count but by string width if (\false === ($encoding = \mb_detect_encoding($string, null, \true))) { return \str_split($string, $width); } $utf8String = \mb_convert_encoding($string, 'utf8', $encoding); $lines = []; $line = ''; $offset = 0; while (\preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) { $offset += \strlen($m[0]); foreach (\preg_split('//u', $m[0]) as $char) { // test if $char could be appended to current line if (\mb_strwidth($line . $char, 'utf8') <= $width) { $line .= $char; continue; } // if not, push current line to array and make new line $lines[] = \str_pad($line, $width); $line = $char; } } $lines[] = \count($lines) ? \str_pad($line, $width) : $line; \mb_convert_variables($encoding, 'utf8', $lines); return $lines; } /** * Returns all namespaces of the command name. * * @return string[] */ private function extractAllNamespaces(string $name) : array { // -1 as third argument is needed to skip the command short name when exploding $parts = \explode(':', $name, -1); $namespaces = []; foreach ($parts as $part) { if (\count($namespaces)) { $namespaces[] = \end($namespaces) . ':' . $part; } else { $namespaces[] = $part; } } return $namespaces; } private function init() { if ($this->initialized) { return; } $this->initialized = \true; foreach ($this->getDefaultCommands() as $command) { $this->add($command); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Logger; use _ContaoManager\Psr\Log\AbstractLogger; use _ContaoManager\Psr\Log\InvalidArgumentException; use _ContaoManager\Psr\Log\LogLevel; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * PSR-3 compliant console logger. * * @author Kévin Dunglas * * @see https://www.php-fig.org/psr/psr-3/ */ class ConsoleLogger extends AbstractLogger { public const INFO = 'info'; public const ERROR = 'error'; private $output; private $verbosityLevelMap = [LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG]; private $formatLevelMap = [LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, LogLevel::ERROR => self::ERROR, LogLevel::WARNING => self::INFO, LogLevel::NOTICE => self::INFO, LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO]; private $errored = \false; public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } /** * {@inheritdoc} * * @return void */ public function log($level, $message, array $context = []) { if (!isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $level)); } $output = $this->output; // Write to the error output if necessary and available if (self::ERROR === $this->formatLevelMap[$level]) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->errored = \true; } // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. // We only do it for efficiency here as the message formatting is relatively expensive. if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(\sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } } /** * Returns true when any messages have been logged at error levels. * * @return bool */ public function hasErrored() { return $this->errored; } /** * Interpolates context values into the message placeholders. * * @author PHP Framework Interoperability Group */ private function interpolate(string $message, array $context) : string { if (!\str_contains($message, '{')) { return $message; } $replacements = []; foreach ($context as $key => $val) { if (null === $val || \is_scalar($val) || \is_object($val) && \method_exists($val, '__toString')) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); } elseif (\is_object($val)) { $replacements["{{$key}}"] = '[object ' . \get_class($val) . ']'; } else { $replacements["{{$key}}"] = '[' . \gettype($val) . ']'; } } return \strtr($message, $replacements); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Attribute; /** * Service tag to autoconfigure commands. */ #[\Attribute(\Attribute::TARGET_CLASS)] class AsCommand { public function __construct(public string $name, public ?string $description = null, array $aliases = [], bool $hidden = \false) { if (!$hidden && !$aliases) { return; } $name = \explode('|', $name); $name = \array_merge($name, $aliases); if ($hidden && '' !== $name[0]) { \array_unshift($name, ''); } $this->name = \implode('|', $name); } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHANGELOG ========= 5.4 --- * Add `TesterTrait::assertCommandIsSuccessful()` to test command * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement 5.3 --- * Add `GithubActionReporter` to render annotations in a Github Action * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options * Add the `Command::$defaultDescription` static property and the `description` attribute on the `console.command` tag to allow the `list` command to instantiate commands lazily * Add option `--short` to the `list` command * Add support for bright colors * Add `#[AsCommand]` attribute for declaring commands on PHP 8 * Add `Helper::width()` and `Helper::length()` * The `--ansi` and `--no-ansi` options now default to `null`. 5.2.0 ----- * Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester` * added support for multiline responses to questions through `Question::setMultiline()` and `Question::isMultiline()` * Added `SignalRegistry` class to stack signals handlers * Added support for signals: * Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods * Added `SignalableCommandInterface` interface * Added `TableCellStyle` class to customize table cell * Removed `php ` prefix invocation from help messages. 5.1.0 ----- * `Command::setHidden()` is final since Symfony 5.1 * Add `SingleCommandApplication` * Add `Cursor` class 5.0.0 ----- * removed support for finding hidden commands using an abbreviation, use the full name instead * removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()` * removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()` * removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()` * removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()` * removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()` * removed support for returning `null` from `Command::execute()`, return `0` instead * `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument * `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` for its `dispatcher` argument * renamed `Application::renderException()` and `Application::doRenderException()` to `renderThrowable()` and `doRenderThrowable()` respectively. 4.4.0 ----- * deprecated finding hidden commands using an abbreviation, use the full name instead * added `Question::setTrimmable` default to true to allow the answer to be trimmed * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar` * `Application` implements `ResetInterface` * marked all dispatched event classes as `@final` * added support for displaying table horizontally * deprecated returning `null` from `Command::execute()`, return `0` instead * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods, use `renderThrowable()` and `doRenderThrowable()` instead. * added support for the `NO_COLOR` env var (https://no-color.org/) 4.3.0 ----- * added support for hyperlinks * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating * added `Question::setAutocompleterCallback()` to provide a callback function that dynamically generates suggestions as the user types 4.2.0 ----- * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to `ProcessHelper::run()` to pass environment variables * deprecated passing a command as a string to `ProcessHelper::run()`, pass it the command as an array of its arguments instead * made the `ProcessHelper` class final * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`) * added `capture_stderr_separately` option to `CommandTester::execute()` 4.1.0 ----- * added option to run suggested command if command is not found and only 1 alternative is available * added option to modify console output and print multiple modifiable sections * added support for iterable messages in output `write` and `writeln` methods 4.0.0 ----- * `OutputFormatter` throws an exception when unknown options are used * removed `QuestionHelper::setInputStream()/getInputStream()` * removed `Application::getTerminalWidth()/getTerminalHeight()` and `Application::setTerminalDimensions()/getTerminalDimensions()` * removed `ConsoleExceptionEvent` * removed `ConsoleEvents::EXCEPTION` 3.4.0 ----- * added `SHELL_VERBOSITY` env var to control verbosity * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11 `ContainerCommandLoader` for commands lazy-loading * added a case-insensitive command name matching fallback * added static `Command::$defaultName/getDefaultName()`, allowing for commands to be registered at compile time in the application command loader. Setting the `$defaultName` property avoids the need for filling the `command` attribute on the `console.command` tag when using `AddConsoleCommandPass`. 3.3.0 ----- * added `ExceptionListener` * added `AddConsoleCommandPass` (originally in FrameworkBundle) * [BC BREAK] `Input::getOption()` no longer returns the default value for options with value optional explicitly passed empty * added console.error event to catch exceptions thrown by other listeners * deprecated console.exception event in favor of console.error * added ability to handle `CommandNotFoundException` through the `console.error` event * deprecated default validation in `SymfonyQuestionHelper::ask` 3.2.0 ------ * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) * added StreamableInputInterface * added LockableTrait 3.1.0 ----- * added truncate method to FormatterHelper * added setColumnWidth(s) method to Table 2.8.3 ----- * remove readline support from the question helper as it caused issues 2.8.0 ----- * use readline for user input in the question helper when available to allow the use of arrow keys 2.6.0 ----- * added a Process helper * added a DebugFormatter helper 2.5.0 ----- * deprecated the dialog helper (use the question helper instead) * deprecated TableHelper in favor of Table * deprecated ProgressHelper in favor of ProgressBar * added ConsoleLogger * added a question helper * added a way to set the process name of a command * added a way to set a default command instead of `ListCommand` 2.4.0 ----- * added a way to force terminal dimensions * added a convenient method to detect verbosity level * [BC BREAK] made descriptors use output instead of returning a string 2.3.0 ----- * added multiselect support to the select dialog helper * added Table Helper for tabular data rendering * added support for events in `Application` * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` * added a way to set the progress bar progress via the `setCurrent` method * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG 2.2.0 ----- * added support for colorization on Windows via ConEmu * add a method to Dialog Helper to ask for a question and hide the response * added support for interactive selections in console (DialogHelper::select()) * added support for autocompletion as you type in Dialog Helper 2.1.0 ----- * added ConsoleOutputInterface * added the possibility to disable a command (Command::isEnabled()) * added suggestions when a command does not exist * added a --raw option to the list command * added support for STDERR in the console output class (errors are now sent to STDERR) * made the defaults (helper set, commands, input definition) in Application more easily customizable * added support for the shell even if readline is not available * added support for process isolation in Symfony shell via `--process-isolation` switch * added support for `--`, which disables options parsing after that point (tokens will be parsed as arguments) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; /** * StreamableInputInterface is the interface implemented by all input classes * that have an input stream. * * @author Robin Chalas */ interface StreamableInputInterface extends InputInterface { /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream */ public function setStream($stream); /** * Returns the input stream. * * @return resource|null */ public function getStream(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\InvalidOptionException; /** * ArrayInput represents an input provided as an array. * * Usage: * * $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); * * @author Fabien Potencier */ class ArrayInput extends Input { private $parameters; public function __construct(array $parameters, ?InputDefinition $definition = null) { $this->parameters = $parameters; parent::__construct($definition); } /** * {@inheritdoc} */ public function getFirstArgument() { foreach ($this->parameters as $param => $value) { if ($param && \is_string($param) && '-' === $param[0]) { continue; } return $value; } return null; } /** * {@inheritdoc} */ public function hasParameterOption($values, bool $onlyParams = \false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!\is_int($k)) { $v = $k; } if ($onlyParams && '--' === $v) { return \false; } if (\in_array($v, $values)) { return \true; } } return \false; } /** * {@inheritdoc} */ public function getParameterOption($values, $default = \false, bool $onlyParams = \false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if ($onlyParams && ('--' === $k || \is_int($k) && '--' === $v)) { return $default; } if (\is_int($k)) { if (\in_array($v, $values)) { return \true; } } elseif (\in_array($k, $values)) { return $v; } } return $default; } /** * Returns a stringified representation of the args passed to the command. * * @return string */ public function __toString() { $params = []; foreach ($this->parameters as $param => $val) { if ($param && \is_string($param) && '-' === $param[0]) { $glue = '-' === $param[1] ? '=' : ' '; if (\is_array($val)) { foreach ($val as $v) { $params[] = $param . ('' != $v ? $glue . $this->escapeToken($v) : ''); } } else { $params[] = $param . ('' != $val ? $glue . $this->escapeToken($val) : ''); } } else { $params[] = \is_array($val) ? \implode(' ', \array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); } } return \implode(' ', $params); } /** * {@inheritdoc} */ protected function parse() { foreach ($this->parameters as $key => $value) { if ('--' === $key) { return; } if (\str_starts_with($key, '--')) { $this->addLongOption(\substr($key, 2), $value); } elseif (\str_starts_with($key, '-')) { $this->addShortOption(\substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } /** * Adds a short option value. * * @throws InvalidOptionException When option given doesn't exist */ private function addShortOption(string $shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new InvalidOptionException(\sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @throws InvalidOptionException When option given doesn't exist * @throws InvalidOptionException When a required value is missing */ private function addLongOption(string $name, $value) { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { throw new InvalidOptionException(\sprintf('The "--%s" option does not exist.', $name)); } $optionName = $this->definition->negationToName($name); $this->options[$optionName] = \false; return; } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new InvalidOptionException(\sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isValueOptional()) { $value = \true; } } $this->options[$name] = $value; } /** * Adds an argument value. * * @param string|int $name The argument name * @param mixed $value The value for the argument * * @throws InvalidArgumentException When argument given doesn't exist */ private function addArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; /** * ArgvInput represents an input coming from the CLI arguments. * * Usage: * * $input = new ArgvInput(); * * By default, the `$_SERVER['argv']` array is used for the input values. * * This can be overridden by explicitly passing the input values in the constructor: * * $input = new ArgvInput($_SERVER['argv']); * * If you pass it yourself, don't forget that the first element of the array * is the name of the running application. * * When passing an argument to the constructor, be sure that it respects * the same rules as the argv one. It's almost always better to use the * `StringInput` when you want to provide your own input. * * @author Fabien Potencier * * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 */ class ArgvInput extends Input { private $tokens; private $parsed; public function __construct(?array $argv = null, ?InputDefinition $definition = null) { $argv = $argv ?? $_SERVER['argv'] ?? []; // strip the application name \array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } /** * {@inheritdoc} */ protected function parse() { $parseOptions = \true; $this->parsed = $this->tokens; while (null !== ($token = \array_shift($this->parsed))) { $parseOptions = $this->parseToken($token, $parseOptions); } } protected function parseToken(string $token, bool $parseOptions) : bool { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { return \false; } elseif ($parseOptions && \str_starts_with($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } return $parseOptions; } /** * Parses a short option. */ private function parseShortOption(string $token) { $name = \substr($token, 1); if (\strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { // an option with a value (with no space) $this->addShortOption($name[0], \substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } /** * Parses a short option set. * * @throws RuntimeException When option given doesn't exist */ private function parseShortOptionSet(string $name) { $len = \strlen($name); for ($i = 0; $i < $len; ++$i) { if (!$this->definition->hasShortcut($name[$i])) { $encoding = \mb_detect_encoding($name, null, \true); throw new RuntimeException(\sprintf('The "-%s" option does not exist.', \false === $encoding ? $name[$i] : \mb_substr($name, $i, 1, $encoding))); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptValue()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : \substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), null); } } } /** * Parses a long option. */ private function parseLongOption(string $token) { $name = \substr($token, 2); if (\false !== ($pos = \strpos($name, '='))) { if ('' === ($value = \substr($name, $pos + 1))) { \array_unshift($this->parsed, $value); } $this->addLongOption(\substr($name, 0, $pos), $value); } else { $this->addLongOption($name, null); } } /** * Parses an argument. * * @throws RuntimeException When too many arguments are given */ private function parseArgument(string $token) { $c = \count($this->arguments); // if input is expecting another argument, add it if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; // if last argument isArray(), append token to last argument } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; // unexpected argument } else { $all = $this->definition->getArguments(); $symfonyCommandName = null; if (($inputArgument = $all[$key = \array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) { $symfonyCommandName = $this->arguments['command'] ?? null; unset($all[$key]); } if (\count($all)) { if ($symfonyCommandName) { $message = \sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, \implode('" "', \array_keys($all))); } else { $message = \sprintf('Too many arguments, expected arguments "%s".', \implode('" "', \array_keys($all))); } } elseif ($symfonyCommandName) { $message = \sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token); } else { $message = \sprintf('No arguments expected, got "%s".', $token); } throw new RuntimeException($message); } } /** * Adds a short option value. * * @throws RuntimeException When option given doesn't exist */ private function addShortOption(string $shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new RuntimeException(\sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @throws RuntimeException When option given doesn't exist */ private function addLongOption(string $name, $value) { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { throw new RuntimeException(\sprintf('The "--%s" option does not exist.', $name)); } $optionName = $this->definition->negationToName($name); if (null !== $value) { throw new RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name)); } $this->options[$optionName] = \false; return; } $option = $this->definition->getOption($name); if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name)); } if (\in_array($value, ['', null], \true) && $option->acceptValue() && \count($this->parsed)) { // if option accepts an optional or mandatory argument // let's see if there is one provided $next = \array_shift($this->parsed); if (isset($next[0]) && '-' !== $next[0] || \in_array($next, ['', null], \true)) { $value = $next; } else { \array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new RuntimeException(\sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray() && !$option->isValueOptional()) { $value = \true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } /** * {@inheritdoc} */ public function getFirstArgument() { $isOption = \false; foreach ($this->tokens as $i => $token) { if ($token && '-' === $token[0]) { if (\str_contains($token, '=') || !isset($this->tokens[$i + 1])) { continue; } // If it's a long option, consider that everything after "--" is the option name. // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) $name = '-' === $token[1] ? \substr($token, 2) : \substr($token, -1); if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { // noop } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { $isOption = \true; } continue; } if ($isOption) { $isOption = \false; continue; } return $token; } return null; } /** * {@inheritdoc} */ public function hasParameterOption($values, bool $onlyParams = \false) { $values = (array) $values; foreach ($this->tokens as $token) { if ($onlyParams && '--' === $token) { return \false; } foreach ($values as $value) { // Options with values: // For long options, test for '--option=' at beginning // For short options, test for '-o' at beginning $leading = \str_starts_with($value, '--') ? $value . '=' : $value; if ($token === $value || '' !== $leading && \str_starts_with($token, $leading)) { return \true; } } } return \false; } /** * {@inheritdoc} */ public function getParameterOption($values, $default = \false, bool $onlyParams = \false) { $values = (array) $values; $tokens = $this->tokens; while (0 < \count($tokens)) { $token = \array_shift($tokens); if ($onlyParams && '--' === $token) { return $default; } foreach ($values as $value) { if ($token === $value) { return \array_shift($tokens); } // Options with values: // For long options, test for '--option=' at beginning // For short options, test for '-o' at beginning $leading = \str_starts_with($value, '--') ? $value . '=' : $value; if ('' !== $leading && \str_starts_with($token, $leading)) { return \substr($token, \strlen($leading)); } } } return $default; } /** * Returns a stringified representation of the args passed to the command. * * @return string */ public function __toString() { $tokens = \array_map(function ($token) { if (\preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1] . $this->escapeToken($match[2]); } if ($token && '-' !== $token[0]) { return $this->escapeToken($token); } return $token; }, $this->tokens); return \implode(' ', $tokens); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; /** * Represents a command line argument. * * @author Fabien Potencier */ class InputArgument { public const REQUIRED = 1; public const OPTIONAL = 2; public const IS_ARRAY = 4; private $name; private $mode; private $default; private $description; /** * @param string $name The argument name * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY * @param string $description A description text * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) * * @throws InvalidArgumentException When argument mode is not valid */ public function __construct(string $name, ?int $mode = null, string $description = '', $default = null) { if (null === $mode) { $mode = self::OPTIONAL; } elseif ($mode > 7 || $mode < 1) { throw new InvalidArgumentException(\sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } /** * Returns the argument name. * * @return string */ public function getName() { return $this->name; } /** * Returns true if the argument is required. * * @return bool true if parameter mode is self::REQUIRED, false otherwise */ public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } /** * Returns true if the argument can take multiple values. * * @return bool true if mode is self::IS_ARRAY, false otherwise */ public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param string|bool|int|float|array|null $default * * @throws LogicException When incorrect default value is given */ public function setDefault($default = null) { if ($this->isRequired() && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } /** * Returns the default value. * * @return string|bool|int|float|array|null */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string */ public function getDescription() { return $this->description; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; /** * InputAwareInterface should be implemented by classes that depends on the * Console Input. * * @author Wouter J */ interface InputAwareInterface { /** * Sets the Console Input. */ public function setInput(InputInterface $input); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; /** * Represents a command line option. * * @author Fabien Potencier */ class InputOption { /** * Do not accept input for the option (e.g. --yell). This is the default behavior of options. */ public const VALUE_NONE = 1; /** * A value must be passed when the option is used (e.g. --iterations=5 or -i5). */ public const VALUE_REQUIRED = 2; /** * The option may or may not have a value (e.g. --yell or --yell=loud). */ public const VALUE_OPTIONAL = 4; /** * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). */ public const VALUE_IS_ARRAY = 8; /** * The option may have either positive or negative value (e.g. --ansi or --no-ansi). */ public const VALUE_NEGATABLE = 16; private $name; private $shortcut; private $mode; private $default; private $description; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int|null $mode The option mode: One of the VALUE_* constants * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible */ public function __construct(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null) { if (\str_starts_with($name, '--')) { $name = \substr($name, 2); } if (empty($name)) { throw new InvalidArgumentException('An option name cannot be empty.'); } if ('' === $shortcut || [] === $shortcut) { $shortcut = null; } if (null !== $shortcut) { if (\is_array($shortcut)) { $shortcut = \implode('|', $shortcut); } $shortcuts = \preg_split('{(\\|)-?}', \ltrim($shortcut, '-')); $shortcuts = \array_filter($shortcuts, 'strlen'); $shortcut = \implode('|', $shortcuts); if ('' === $shortcut) { throw new InvalidArgumentException('An option shortcut cannot be empty.'); } } if (null === $mode) { $mode = self::VALUE_NONE; } elseif ($mode >= self::VALUE_NEGATABLE << 1 || $mode < 1) { throw new InvalidArgumentException(\sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } if ($this->isNegatable() && $this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); } $this->setDefault($default); } /** * Returns the option shortcut. * * @return string|null */ public function getShortcut() { return $this->shortcut; } /** * Returns the option name. * * @return string */ public function getName() { return $this->name; } /** * Returns true if the option accepts a value. * * @return bool true if value mode is not self::VALUE_NONE, false otherwise */ public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } /** * Returns true if the option requires a value. * * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise */ public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } /** * Returns true if the option takes an optional value. * * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise */ public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } /** * Returns true if the option can take multiple values. * * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise */ public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } public function isNegatable() : bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } /** * @param string|bool|int|float|array|null $default */ public function setDefault($default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() || $this->isNegatable() ? $default : \false; } /** * Returns the default value. * * @return string|bool|int|float|array|null */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string */ public function getDescription() { return $this->description; } /** * Checks whether the given option equals this one. * * @return bool */ public function equals(self $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isNegatable() === $this->isNegatable() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; /** * A InputDefinition represents a set of valid command line arguments and options. * * Usage: * * $definition = new InputDefinition([ * new InputArgument('name', InputArgument::REQUIRED), * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), * ]); * * @author Fabien Potencier */ class InputDefinition { private $arguments; private $requiredCount; private $lastArrayArgument; private $lastOptionalArgument; private $options; private $negations; private $shortcuts; /** * @param array $definition An array of InputArgument and InputOption instance */ public function __construct(array $definition = []) { $this->setDefinition($definition); } /** * Sets the definition of the input. */ public function setDefinition(array $definition) { $arguments = []; $options = []; foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } /** * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function setArguments(array $arguments = []) { $this->arguments = []; $this->requiredCount = 0; $this->lastOptionalArgument = null; $this->lastArrayArgument = null; $this->addArguments($arguments); } /** * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function addArguments(?array $arguments = []) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } /** * @throws LogicException When incorrect argument is given */ public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new LogicException(\sprintf('An argument with name "%s" already exists.', $argument->getName())); } if (null !== $this->lastArrayArgument) { throw new LogicException(\sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName())); } if ($argument->isRequired() && null !== $this->lastOptionalArgument) { throw new LogicException(\sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName())); } if ($argument->isArray()) { $this->lastArrayArgument = $argument; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->lastOptionalArgument = $argument; } $this->arguments[$argument->getName()] = $argument; } /** * Returns an InputArgument by name or by position. * * @param string|int $name The InputArgument name or position * * @return InputArgument * * @throws InvalidArgumentException When argument given doesn't exist */ public function getArgument($name) { if (!$this->hasArgument($name)) { throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name)); } $arguments = \is_int($name) ? \array_values($this->arguments) : $this->arguments; return $arguments[$name]; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool */ public function hasArgument($name) { $arguments = \is_int($name) ? \array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } /** * Gets the array of InputArgument objects. * * @return InputArgument[] */ public function getArguments() { return $this->arguments; } /** * Returns the number of InputArguments. * * @return int */ public function getArgumentCount() { return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); } /** * Returns the number of required InputArguments. * * @return int */ public function getArgumentRequiredCount() { return $this->requiredCount; } /** * @return array */ public function getArgumentDefaults() { $values = []; foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } /** * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function setOptions(array $options = []) { $this->options = []; $this->shortcuts = []; $this->negations = []; $this->addOptions($options); } /** * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function addOptions(array $options = []) { foreach ($options as $option) { $this->addOption($option); } } /** * @throws LogicException When option given already exist */ public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName())); } if (isset($this->negations[$option->getName()])) { throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (\explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new LogicException(\sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (\explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } if ($option->isNegatable()) { $negatedName = 'no-' . $option->getName(); if (isset($this->options[$negatedName])) { throw new LogicException(\sprintf('An option named "%s" already exists.', $negatedName)); } $this->negations[$negatedName] = $option->getName(); } } /** * Returns an InputOption by name. * * @return InputOption * * @throws InvalidArgumentException When option given doesn't exist */ public function getOption(string $name) { if (!$this->hasOption($name)) { throw new InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } /** * Returns true if an InputOption object exists by name. * * This method can't be used to check if the user included the option when * executing the command (use getOption() instead). * * @return bool */ public function hasOption(string $name) { return isset($this->options[$name]); } /** * Gets the array of InputOption objects. * * @return InputOption[] */ public function getOptions() { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. * * @return bool */ public function hasShortcut(string $name) { return isset($this->shortcuts[$name]); } /** * Returns true if an InputOption object exists by negated name. */ public function hasNegation(string $name) : bool { return isset($this->negations[$name]); } /** * Gets an InputOption by shortcut. * * @return InputOption */ public function getOptionForShortcut(string $shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } /** * @return array */ public function getOptionDefaults() { $values = []; foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } /** * Returns the InputOption name given a shortcut. * * @throws InvalidArgumentException When option given does not exist * * @internal */ public function shortcutToName(string $shortcut) : string { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(\sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } /** * Returns the InputOption name given a negation. * * @throws InvalidArgumentException When option given does not exist * * @internal */ public function negationToName(string $negation) : string { if (!isset($this->negations[$negation])) { throw new InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $negation)); } return $this->negations[$negation]; } /** * Gets the synopsis. * * @return string */ public function getSynopsis(bool $short = \false) { $elements = []; if ($short && $this->getOptions()) { $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { $value = ''; if ($option->acceptValue()) { $value = \sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', \strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); } $shortcut = $option->getShortcut() ? \sprintf('-%s|', $option->getShortcut()) : ''; $negation = $option->isNegatable() ? \sprintf('|--no-%s', $option->getName()) : ''; $elements[] = \sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); } } if (\count($elements) && $this->getArguments()) { $elements[] = '[--]'; } $tail = ''; foreach ($this->getArguments() as $argument) { $element = '<' . $argument->getName() . '>'; if ($argument->isArray()) { $element .= '...'; } if (!$argument->isRequired()) { $element = '[' . $element; $tail .= ']'; } $elements[] = $element; } return \implode(' ', $elements) . $tail; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; /** * StringInput represents an input provided as a string. * * Usage: * * $input = new StringInput('foo --bar="foobar"'); * * @author Fabien Potencier */ class StringInput extends ArgvInput { public const REGEX_STRING = '([^\\s]+?)(?:\\s|(?setTokens($this->tokenize($input)); } /** * Tokenizes a string. * * @throws InvalidArgumentException When unable to parse input (should never happen) */ private function tokenize(string $input) : array { $tokens = []; $length = \strlen($input); $cursor = 0; $token = null; while ($cursor < $length) { if ('\\' === $input[$cursor]) { $token .= $input[++$cursor] ?? ''; ++$cursor; continue; } if (\preg_match('/\\s+/A', $input, $match, 0, $cursor)) { if (null !== $token) { $tokens[] = $token; $token = null; } } elseif (\preg_match('/([^="\'\\s]+?)(=?)(' . self::REGEX_QUOTED_STRING . '+)/A', $input, $match, 0, $cursor)) { $token .= $match[1] . $match[2] . \stripcslashes(\str_replace(['"\'', '\'"', '\'\'', '""'], '', \substr($match[3], 1, -1))); } elseif (\preg_match('/' . self::REGEX_QUOTED_STRING . '/A', $input, $match, 0, $cursor)) { $token .= \stripcslashes(\substr($match[0], 1, -1)); } elseif (\preg_match('/' . self::REGEX_UNQUOTED_STRING . '/A', $input, $match, 0, $cursor)) { $token .= $match[1]; } else { // should never happen throw new InvalidArgumentException(\sprintf('Unable to parse input near "... %s ...".', \substr($input, $cursor, 10))); } $cursor += \strlen($match[0]); } if (null !== $token) { $tokens[] = $token; } return $tokens; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; /** * Input is the base class for all concrete Input classes. * * Three concrete classes are provided by default: * * * `ArgvInput`: The input comes from the CLI arguments (argv) * * `StringInput`: The input is provided as a string * * `ArrayInput`: The input is provided as an array * * @author Fabien Potencier */ abstract class Input implements InputInterface, StreamableInputInterface { protected $definition; protected $stream; protected $options = []; protected $arguments = []; protected $interactive = \true; public function __construct(?InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } /** * {@inheritdoc} */ public function bind(InputDefinition $definition) { $this->arguments = []; $this->options = []; $this->definition = $definition; $this->parse(); } /** * Processes command line arguments. */ protected abstract function parse(); /** * {@inheritdoc} */ public function validate() { $definition = $this->definition; $givenArguments = $this->arguments; $missingArguments = \array_filter(\array_keys($definition->getArguments()), function ($argument) use($definition, $givenArguments) { return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); }); if (\count($missingArguments) > 0) { throw new RuntimeException(\sprintf('Not enough arguments (missing: "%s").', \implode(', ', $missingArguments))); } } /** * {@inheritdoc} */ public function isInteractive() { return $this->interactive; } /** * {@inheritdoc} */ public function setInteractive(bool $interactive) { $this->interactive = $interactive; } /** * {@inheritdoc} */ public function getArguments() { return \array_merge($this->definition->getArgumentDefaults(), $this->arguments); } /** * {@inheritdoc} */ public function getArgument(string $name) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name)); } return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); } /** * {@inheritdoc} */ public function setArgument(string $name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } /** * {@inheritdoc} */ public function hasArgument(string $name) { return $this->definition->hasArgument($name); } /** * {@inheritdoc} */ public function getOptions() { return \array_merge($this->definition->getOptionDefaults(), $this->options); } /** * {@inheritdoc} */ public function getOption(string $name) { if ($this->definition->hasNegation($name)) { if (null === ($value = $this->getOption($this->definition->negationToName($name)))) { return $value; } return !$value; } if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(\sprintf('The "%s" option does not exist.', $name)); } return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } /** * {@inheritdoc} */ public function setOption(string $name, $value) { if ($this->definition->hasNegation($name)) { $this->options[$this->definition->negationToName($name)] = !$value; return; } elseif (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(\sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } /** * {@inheritdoc} */ public function hasOption(string $name) { return $this->definition->hasOption($name) || $this->definition->hasNegation($name); } /** * Escapes a token through escapeshellarg if it contains unsafe chars. * * @return string */ public function escapeToken(string $token) { return \preg_match('{^[\\w-]+$}', $token) ? $token : \escapeshellarg($token); } /** * {@inheritdoc} */ public function setStream($stream) { $this->stream = $stream; } /** * {@inheritdoc} */ public function getStream() { return $this->stream; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Input; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; /** * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). * * @return string|null */ public function getFirstArgument(); /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * Does not necessarily return the correct result for short options * when multiple flags are combined in the same option. * * @param string|array $values The values to look for in the raw parameters (can be an array) * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal * * @return bool */ public function hasParameterOption($values, bool $onlyParams = \false); /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * Does not necessarily return the correct result for short options * when multiple flags are combined in the same option. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param string|bool|int|float|array|null $default The default value to return if no result is found * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal * * @return mixed */ public function getParameterOption($values, $default = \false, bool $onlyParams = \false); /** * Binds the current Input instance with the given arguments and options. * * @throws RuntimeException */ public function bind(InputDefinition $definition); /** * Validates the input. * * @throws RuntimeException When not enough arguments are given */ public function validate(); /** * Returns all the given arguments merged with the default values. * * @return array */ public function getArguments(); /** * Returns the argument value for a given argument name. * * @return mixed * * @throws InvalidArgumentException When argument given doesn't exist */ public function getArgument(string $name); /** * Sets an argument value by name. * * @param mixed $value The argument value * * @throws InvalidArgumentException When argument given doesn't exist */ public function setArgument(string $name, $value); /** * Returns true if an InputArgument object exists by name or position. * * @return bool */ public function hasArgument(string $name); /** * Returns all the given options merged with the default values. * * @return array */ public function getOptions(); /** * Returns the option value for a given option name. * * @return mixed * * @throws InvalidArgumentException When option given doesn't exist */ public function getOption(string $name); /** * Sets an option value by name. * * @param mixed $value The option value * * @throws InvalidArgumentException When option given doesn't exist */ public function setOption(string $name, $value); /** * Returns true if an InputOption object exists by name. * * @return bool */ public function hasOption(string $name); /** * Is this input means interactive? * * @return bool */ public function isInteractive(); /** * Sets the input interactivity. */ public function setInteractive(bool $interactive); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console; class Terminal { private static $width; private static $height; private static $stty; /** * Gets the terminal width. * * @return int */ public function getWidth() { $width = \getenv('COLUMNS'); if (\false !== $width) { return (int) \trim($width); } if (null === self::$width) { self::initDimensions(); } return self::$width ?: 80; } /** * Gets the terminal height. * * @return int */ public function getHeight() { $height = \getenv('LINES'); if (\false !== $height) { return (int) \trim($height); } if (null === self::$height) { self::initDimensions(); } return self::$height ?: 50; } /** * @internal */ public static function hasSttyAvailable() : bool { if (null !== self::$stty) { return self::$stty; } // skip check if shell_exec function is disabled if (!\function_exists('shell_exec')) { return \false; } return self::$stty = (bool) \shell_exec('stty 2> ' . ('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); } private static function initDimensions() { if ('\\' === \DIRECTORY_SEPARATOR) { $ansicon = \getenv('ANSICON'); if (\false !== $ansicon && \preg_match('/^(\\d+)x(\\d+)(?: \\((\\d+)x(\\d+)\\))?$/', \trim($ansicon), $matches)) { // extract [w, H] from "wxh (WxH)" // or [w, h] from "wxh" self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash) // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT self::initDimensionsUsingStty(); } elseif (null !== ($dimensions = self::getConsoleMode())) { // extract [w, h] from "wxh" self::$width = (int) $dimensions[0]; self::$height = (int) $dimensions[1]; } } else { self::initDimensionsUsingStty(); } } /** * Returns whether STDOUT has vt100 support (some Windows 10+ configurations). */ private static function hasVt100Support() : bool { return \function_exists('sapi_windows_vt100_support') && \sapi_windows_vt100_support(\fopen('php://stdout', 'w')); } /** * Initializes dimensions using the output of an stty columns line. */ private static function initDimensionsUsingStty() { if ($sttyString = self::getSttyColumns()) { if (\preg_match('/rows.(\\d+);.columns.(\\d+);/i', $sttyString, $matches)) { // extract [w, h] from "rows h; columns w;" self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } elseif (\preg_match('/;.(\\d+).rows;.(\\d+).columns/i', $sttyString, $matches)) { // extract [w, h] from "; h rows; w columns" self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } } } /** * Runs and parses mode CON if it's available, suppressing any error output. * * @return int[]|null An array composed of the width and the height or null if it could not be parsed */ private static function getConsoleMode() : ?array { $info = self::readFromProcess('mode CON'); if (null === $info || !\preg_match('/--------+\\r?\\n.+?(\\d+)\\r?\\n.+?(\\d+)\\r?\\n/', $info, $matches)) { return null; } return [(int) $matches[2], (int) $matches[1]]; } /** * Runs and parses stty -a if it's available, suppressing any error output. */ private static function getSttyColumns() : ?string { return self::readFromProcess('stty -a | grep columns'); } private static function readFromProcess(string $command) : ?string { if (!\function_exists('proc_open')) { return null; } $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $cp = \function_exists('sapi_windows_cp_set') ? \sapi_windows_cp_get() : 0; $process = \proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => \true]); if (!\is_resource($process)) { return null; } $info = \stream_get_contents($pipes[1]); \fclose($pipes[1]); \fclose($pipes[2]); \proc_close($process); if ($cp) { \sapi_windows_cp_set($cp); } return $info; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\CI; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Utility class for Github actions. * * @author Maxime Steinhausser */ class GithubActionReporter { private $output; /** * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 */ private const ESCAPED_DATA = ['%' => '%25', "\r" => '%0D', "\n" => '%0A']; /** * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94 */ private const ESCAPED_PROPERTIES = ['%' => '%25', "\r" => '%0D', "\n" => '%0A', ':' => '%3A', ',' => '%2C']; public function __construct(OutputInterface $output) { $this->output = $output; } public static function isGithubActionEnvironment() : bool { return \false !== \getenv('GITHUB_ACTIONS'); } /** * Output an error using the Github annotations format. * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message */ public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null) : void { $this->log('error', $message, $file, $line, $col); } /** * Output a warning using the Github annotations format. * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message */ public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null) : void { $this->log('warning', $message, $file, $line, $col); } /** * Output a debug log using the Github annotations format. * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message */ public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null) : void { $this->log('debug', $message, $file, $line, $col); } private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null) : void { // Some values must be encoded. $message = \strtr($message, self::ESCAPED_DATA); if (!$file) { // No file provided, output the message solely: $this->output->writeln(\sprintf('::%s::%s', $type, $message)); return; } $this->output->writeln(\sprintf('::%s file=%s,line=%s,col=%s::%s', $type, \strtr($file, self::ESCAPED_PROPERTIES), \strtr($line ?? 1, self::ESCAPED_PROPERTIES), \strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); } } # This file is part of the Symfony package. # # (c) Fabien Potencier # # For the full copyright and license information, please view # https://symfony.com/doc/current/contributing/code/license.html _sf_{{ COMMAND_NAME }}() { # Use newline as only separator to allow space in completion values IFS=$'\n' local sf_cmd="${COMP_WORDS[0]}" # for an alias, get the real script behind it sf_cmd_type=$(type -t $sf_cmd) if [[ $sf_cmd_type == "alias" ]]; then sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/") elif [[ $sf_cmd_type == "file" ]]; then sf_cmd=$(type -p $sf_cmd) fi if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then return 1 fi local cur prev words cword _get_comp_words_by_ref -n := cur prev words cword local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S{{ VERSION }}") for w in ${words[@]}; do w=$(printf -- '%b' "$w") # remove quotes from typed values quote="${w:0:1}" if [ "$quote" == \' ]; then w="${w%\'}" w="${w#\'}" elif [ "$quote" == \" ]; then w="${w%\"}" w="${w#\"}" fi # empty values are ignored if [ ! -z "$w" ]; then completecmd+=("-i$w") fi done local sfcomplete if sfcomplete=$(${completecmd[@]} 2>&1); then local quote suggestions quote=${cur:0:1} # Use single quotes by default if suggestions contains backslash (FQCN) if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then quote=\' fi if [ "$quote" == \' ]; then # single quotes: no additional escaping (does not accept ' in values) suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) elif [ "$quote" == \" ]; then # double quotes: double escaping for \ $ ` " suggestions=$(for s in $sfcomplete; do s=${s//\\/\\\\} s=${s//\$/\\\$} s=${s//\`/\\\`} s=${s//\"/\\\"} printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) else # no quotes: double escaping suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done) fi COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur"))) __ltrim_colon_completions "$cur" else if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then >&2 echo >&2 echo $sfcomplete fi return 1 fi } complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} MZ@ !L!This program cannot be run in DOS mode. $,;B;B;B2מ:B2-B2ƞ9B2ў?Ba98B;CB2Ȟ:B2֞:B2Ӟ:BRich;BPELMoO  8 @`?@"P@ Pp!8!@ .text   `.rdata @@.data0@.rsrc @@@.relocP"@Bj$@xj @eEPV @EЃPV @MX @eEP5H @L @YY5\ @EP5` @D @YYP @MMT @3H; 0@uh@l3@$40@5h3@40@h$0@h(0@h 0@ @00@}jYjh"@3ۉ]dp]俀3@SVW0 @;t;u3Fuh4 @3F|3@;u j\Y;|3@u,5|3@h @h @YYtE5<0@|3@;uh @h @lYY|3@9]uSW8 @93@th3@Yt SjS3@$0@ @5$0@5(0@5 0@ 80@9,0@u7P @E MPQYYËeE80@39,0@uPh @9<0@u @E80@øMZf9@t3M<@@8PEuH t uՃv39xtv39j,0@p @jl @YY3@3@ @ t3@ @ p3@ @x3@V=0@u h@ @Yg=0@u j @Y3{U(H1@ D1@@1@<1@581@=41@f`1@f T1@f01@f,1@f%(1@f-$1@X1@EL1@EP1@E\1@0@P1@L0@@0@ D0@0@0@ @0@j?Yj @h!@$ @=0@ujYh ( @P, @ËUE8csmu*xu$@= t=!t="t=@u3]hH@ @3% @jh("@b53@5 @YEu u @YgjYe53@։E53@YYEEPEPu5l @YPUEu֣3@uփ3@E EjYËUuNYH]ËV!@!@W;stЃ;r_^ËV"@"@W;stЃ;r_^% @̋UMMZf9t3]ËA<8PEu3ҹ f9H‹]̋UEH<ASVq3WDv} H ;r X;r B(;r3_^[]̋UjhH"@he@dPSVW0@1E3PEdeEh@*tUE-@Ph@Pt;@$ЃEMd Y_^[]ËE3=‹ËeE3Md Y_^[]% @% @he@d5D$l$l$+SVW0@1E3PeuEEEEdËMd Y__^[]QËUuuu uh@h0@]ËVhh3V t VVVVV^3ËU0@eeSWN@;t t У0@`VEP< @u3u @3 @3 @3EP @E3E3;uO@ u 50@։50@^_[%t @%x @%| @% @% @% @% @% @% @Pd5D$ +d$ SVW(0@3PEuEEdËMd Y__^[]QËM3M%T @T$B J3J3l"@s###)r)b)H)4))(((((()#$%%&d&&$('''''(((6('H(Z(t(('''''l'^'R'F'>'>(0'')@W@@MoOl!@0@0@bad allocationH0@!@RSDSьJ!LZc:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdbe@@:@@@@"d"@"# $#&D H#(h ###)r)b)H)4))(((((()#$%%&d&&$('''''(((6('H(Z(t(('''''l'^'R'F'>'>(0'')GetConsoleModeSetConsoleMode;GetStdHandleKERNEL32.dll??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@AJ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z_??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ{??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@ZMSVCP90.dll_amsg_exit__getmainargs,_cexit|_exitf_XcptFilterexit__initenv_initterm_initterm_e<_configthreadlocale__setusermatherr _adjust_fdiv__p__commode__p__fmodej_encode_pointer__set_app_typeK_crt_debugger_hookC?terminate@@YAXXZMSVCR90.dll_unlock__dllonexitv_lock_onexit`_decode_pointers_except_handler4_common _invoke_watson?_controlfp_sInterlockedExchange!SleepInterlockedCompareExchange-TerminateProcessGetCurrentProcess>UnhandledExceptionFilterSetUnhandledExceptionFilterIsDebuggerPresentTQueryPerformanceCounterfGetTickCountGetCurrentThreadIdGetCurrentProcessIdOGetSystemTimeAsFileTimes__CxxFrameHandler3N@D$!@ 8Ph  @(CV(4VS_VERSION_INFOStringFileInfob040904b0QFileDescriptionReads from stdin without leaking info to the terminal and outputs back to stdout6 FileVersion1, 0, 0, 08 InternalNamehiddeninputPLegalCopyrightJordi Boggiano - 2012HOriginalFilenamehiddeninput.exe: ProductNameHidden Input: ProductVersion1, 0, 0, 0DVarFileInfo$Translation  PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING@00!0/080F0L0T0^0d0n0{000000000000001#1-1@1J1O1T1v1{1111111111111112"2*23292A2M2_2j2p222222222222 333%303N3T3Z3`3f3l3s3z333333333333333334444%4;4B444444444445!5^5c5555H6M6_6}66677 7*7w7|777778 88=8E8P8V8\8b8h8n8t8z88889 $0001 1t1x12 2@2\2`2h2t20 0 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * A BufferedOutput that keeps only the last N chars. * * @author Jérémy Derussé */ class TrimmedBufferOutput extends Output { private $maxLength; private $buffer = ''; public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = \false, ?OutputFormatterInterface $formatter = null) { if ($maxLength <= 0) { throw new InvalidArgumentException(\sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); } parent::__construct($verbosity, $decorated, $formatter); $this->maxLength = $maxLength; } /** * Empties buffer and returns its content. * * @return string */ public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } $this->buffer = \substr($this->buffer, 0 - $this->maxLength); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; /** * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. * This adds information about stderr and section output stream. * * @author Dariusz Górecki */ interface ConsoleOutputInterface extends OutputInterface { /** * Gets the OutputInterface for errors. * * @return OutputInterface */ public function getErrorOutput(); public function setErrorOutput(OutputInterface $error); public function section() : ConsoleSectionOutput; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; /** * @author Jean-François Simon */ class BufferedOutput extends Output { private $buffer = ''; /** * Empties buffer and returns its content. * * @return string */ public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier */ interface OutputInterface { public const VERBOSITY_QUIET = 16; public const VERBOSITY_NORMAL = 32; public const VERBOSITY_VERBOSE = 64; public const VERBOSITY_VERY_VERBOSE = 128; public const VERBOSITY_DEBUG = 256; public const OUTPUT_NORMAL = 1; public const OUTPUT_RAW = 2; public const OUTPUT_PLAIN = 4; /** * Writes a message to the output. * * @param string|iterable $messages The message as an iterable of strings or a single string * @param bool $newline Whether to add a newline * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL */ public function write($messages, bool $newline = \false, int $options = 0); /** * Writes a message to the output and adds a newline at the end. * * @param string|iterable $messages The message as an iterable of strings or a single string * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL */ public function writeln($messages, int $options = 0); /** * Sets the verbosity of the output. */ public function setVerbosity(int $level); /** * Gets the current verbosity of the output. * * @return int */ public function getVerbosity(); /** * Returns whether verbosity is quiet (-q). * * @return bool */ public function isQuiet(); /** * Returns whether verbosity is verbose (-v). * * @return bool */ public function isVerbose(); /** * Returns whether verbosity is very verbose (-vv). * * @return bool */ public function isVeryVerbose(); /** * Returns whether verbosity is debug (-vvv). * * @return bool */ public function isDebug(); /** * Sets the decorated flag. */ public function setDecorated(bool $decorated); /** * Gets the decorated flag. * * @return bool */ public function isDecorated(); public function setFormatter(OutputFormatterInterface $formatter); /** * Returns current output formatter instance. * * @return OutputFormatterInterface */ public function getFormatter(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * Base class for output classes. * * There are five levels of verbosity: * * * normal: no option passed (normal output) * * verbose: -v (more output) * * very verbose: -vv (highly extended output) * * debug: -vvv (all debug output) * * quiet: -q (no output) * * @author Fabien Potencier */ abstract class Output implements OutputInterface { private $verbosity; private $formatter; /** * @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool $decorated Whether to decorate messages * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = \false, ?OutputFormatterInterface $formatter = null) { $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL; $this->formatter = $formatter ?? new OutputFormatter(); $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } /** * {@inheritdoc} */ public function getFormatter() { return $this->formatter; } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->formatter->isDecorated(); } /** * {@inheritdoc} */ public function setVerbosity(int $level) { $this->verbosity = $level; } /** * {@inheritdoc} */ public function getVerbosity() { return $this->verbosity; } /** * {@inheritdoc} */ public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } /** * {@inheritdoc} */ public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } /** * {@inheritdoc} */ public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } /** * {@inheritdoc} */ public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } /** * {@inheritdoc} */ public function writeln($messages, int $options = self::OUTPUT_NORMAL) { $this->write($messages, \true, $options); } /** * {@inheritdoc} */ public function write($messages, bool $newline = \false, int $options = self::OUTPUT_NORMAL) { if (!\is_iterable($messages)) { $messages = [$messages]; } $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; $type = $types & $options ?: self::OUTPUT_NORMAL; $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; if ($verbosity > $this->getVerbosity()) { return; } foreach ($messages as $message) { switch ($type) { case OutputInterface::OUTPUT_NORMAL: $message = $this->formatter->format($message); break; case OutputInterface::OUTPUT_RAW: break; case OutputInterface::OUTPUT_PLAIN: $message = \strip_tags($this->formatter->format($message)); break; } $this->doWrite($message ?? '', $newline); } } /** * Writes a message to the output. */ protected abstract function doWrite(string $message, bool $newline); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; use _ContaoManager\Symfony\Component\Console\Helper\Helper; use _ContaoManager\Symfony\Component\Console\Terminal; /** * @author Pierre du Plessis * @author Gabriel Ostrolucký */ class ConsoleSectionOutput extends StreamOutput { private $content = []; private $lines = 0; private $sections; private $terminal; /** * @param resource $stream * @param ConsoleSectionOutput[] $sections */ public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) { parent::__construct($stream, $verbosity, $decorated, $formatter); \array_unshift($sections, $this); $this->sections =& $sections; $this->terminal = new Terminal(); } /** * Clears previous output for this section. * * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared */ public function clear(?int $lines = null) { if (empty($this->content) || !$this->isDecorated()) { return; } if ($lines) { \array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content } else { $lines = $this->lines; $this->content = []; } $this->lines -= $lines; parent::doWrite($this->popStreamContentUntilCurrentSection($lines), \false); } /** * Overwrites the previous output with a new message. * * @param array|string $message */ public function overwrite($message) { $this->clear(); $this->writeln($message); } public function getContent() : string { return \implode('', $this->content); } /** * @internal */ public function addContent(string $input) { foreach (\explode(\PHP_EOL, $input) as $lineContent) { $this->lines += \ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; $this->content[] = $lineContent; $this->content[] = \PHP_EOL; } } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { if (!$this->isDecorated()) { parent::doWrite($message, $newline); return; } $erasedContent = $this->popStreamContentUntilCurrentSection(); $this->addContent($message); parent::doWrite($message, \true); parent::doWrite($erasedContent, \false); } /** * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. */ private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0) : string { $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; $erasedContent = []; foreach ($this->sections as $section) { if ($section === $this) { break; } $numberOfLinesToClear += $section->lines; $erasedContent[] = $section->getContent(); } if ($numberOfLinesToClear > 0) { // move cursor up n lines parent::doWrite(\sprintf("\x1b[%dA", $numberOfLinesToClear), \false); // erase to end of screen parent::doWrite("\x1b[0J", \false); } return \implode('', \array_reverse($erasedContent)); } private function getDisplayLength(string $text) : int { return Helper::width(Helper::removeDecoration($this->getFormatter(), \str_replace("\t", ' ', $text))); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. * * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. * * $output = new ConsoleOutput(); * * This is equivalent to: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); * * @author Fabien Potencier */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { private $stderr; private $consoleSectionOutputs = []; /** * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) { parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); if (null === $formatter) { // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); return; } $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); if (null === $decorated) { $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); } } /** * Creates a new output section. */ public function section() : ConsoleSectionOutput { return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } /** * {@inheritdoc} */ public function setVerbosity(int $level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } /** * {@inheritdoc} */ public function getErrorOutput() { return $this->stderr; } /** * {@inheritdoc} */ public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } /** * Returns true if current environment supports writing console output to * STDOUT. * * @return bool */ protected function hasStdoutSupport() { return \false === $this->isRunningOS400(); } /** * Returns true if current environment supports writing console output to * STDERR. * * @return bool */ protected function hasStderrSupport() { return \false === $this->isRunningOS400(); } /** * Checks if current executing environment is IBM iSeries (OS400), which * doesn't properly convert character-encodings between ASCII to EBCDIC. */ private function isRunningOS400() : bool { $checks = [\function_exists('php_uname') ? \php_uname('s') : '', \getenv('OSTYPE'), \PHP_OS]; return \false !== \stripos(\implode(';', $checks), 'OS400'); } /** * @return resource */ private function openOutputStream() { if (!$this->hasStdoutSupport()) { return \fopen('php://output', 'w'); } // Use STDOUT when possible to prevent from opening too many file descriptors return \defined('STDOUT') ? \STDOUT : (@\fopen('php://stdout', 'w') ?: \fopen('php://output', 'w')); } /** * @return resource */ private function openErrorStream() { if (!$this->hasStderrSupport()) { return \fopen('php://output', 'w'); } // Use STDERR when possible to prevent from opening too many file descriptors return \defined('STDERR') ? \STDERR : (@\fopen('php://stderr', 'w') ?: \fopen('php://output', 'w')); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Formatter\NullOutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * NullOutput suppresses all output. * * $output = new NullOutput(); * * @author Fabien Potencier * @author Tobias Schultze */ class NullOutput implements OutputInterface { private $formatter; /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { // do nothing } /** * {@inheritdoc} */ public function getFormatter() { if ($this->formatter) { return $this->formatter; } // to comply with the interface we must return a OutputFormatterInterface return $this->formatter = new NullOutputFormatter(); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { // do nothing } /** * {@inheritdoc} */ public function isDecorated() { return \false; } /** * {@inheritdoc} */ public function setVerbosity(int $level) { // do nothing } /** * {@inheritdoc} */ public function getVerbosity() { return self::VERBOSITY_QUIET; } /** * {@inheritdoc} */ public function isQuiet() { return \true; } /** * {@inheritdoc} */ public function isVerbose() { return \false; } /** * {@inheritdoc} */ public function isVeryVerbose() { return \false; } /** * {@inheritdoc} */ public function isDebug() { return \false; } /** * {@inheritdoc} */ public function writeln($messages, int $options = self::OUTPUT_NORMAL) { // do nothing } /** * {@inheritdoc} */ public function write($messages, bool $newline = \false, int $options = self::OUTPUT_NORMAL) { // do nothing } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Output; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * StreamOutput writes the output to a given stream. * * Usage: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * As `StreamOutput` can use any stream, you can also use a file: * * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * * @author Fabien Potencier */ class StreamOutput extends Output { private $stream; /** * @param resource $stream A stream resource * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @throws InvalidArgumentException When first argument is not a real stream */ public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null) { if (!\is_resource($stream) || 'stream' !== \get_resource_type($stream)) { throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); } $this->stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } /** * Gets the stream attached to this StreamOutput instance. * * @return resource */ public function getStream() { return $this->stream; } protected function doWrite(string $message, bool $newline) { if ($newline) { $message .= \PHP_EOL; } @\fwrite($this->stream, $message); \fflush($this->stream); } /** * Returns true if the stream supports colorization. * * Colorization is disabled if not supported by the stream: * * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo * terminals via named pipes, so we can only check the environment. * * Reference: Composer\XdebugHandler\Process::supportsColor * https://github.com/composer/xdebug-handler * * @return bool true if the stream supports colorization, false otherwise */ protected function hasColorSupport() { // Follow https://no-color.org/ if (isset($_SERVER['NO_COLOR']) || \false !== \getenv('NO_COLOR')) { return \false; } if (!$this->isTty()) { return \false; } if (\DIRECTORY_SEPARATOR === '\\' && \function_exists('sapi_windows_vt100_support') && @\sapi_windows_vt100_support($this->stream)) { return \true; } return 'Hyper' === \getenv('TERM_PROGRAM') || \false !== \getenv('ANSICON') || 'ON' === \getenv('ConEmuANSI') || \str_starts_with((string) \getenv('TERM'), 'xterm'); } /** * Checks if the stream is a TTY, i.e; whether the output stream is connected to a terminal. * * Reference: Composer\Util\Platform::isTty * https://github.com/composer/composer */ private function isTty() : bool { // Detect msysgit/mingw and assume this is a tty because detection // does not work correctly, see https://github.com/composer/composer/issues/9690 if (\in_array(\strtoupper((string) \getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], \true)) { return \true; } // Modern cross-platform function, includes the fstat fallback so if it is present we trust it if (\function_exists('stream_isatty')) { return \stream_isatty($this->stream); } // Only trusting this if it is positive, otherwise prefer fstat fallback. if (\function_exists('posix_isatty') && \posix_isatty($this->stream)) { return \true; } $stat = @\fstat($this->stream); // Check if formatted mode is S_IFCHR return $stat ? 020000 === ($stat['mode'] & 0170000) : \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Completion\Output; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Wouter de Jong */ class BashCompletionOutput implements CompletionOutputInterface { public function write(CompletionSuggestions $suggestions, OutputInterface $output) : void { $values = $suggestions->getValueSuggestions(); foreach ($suggestions->getOptionSuggestions() as $option) { $values[] = '--' . $option->getName(); if ($option->isNegatable()) { $values[] = '--no-' . $option->getName(); } } $output->writeln(\implode("\n", $values)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Completion\Output; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion. * * @author Wouter de Jong */ interface CompletionOutputInterface { public function write(CompletionSuggestions $suggestions, OutputInterface $output) : void; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Completion; /** * Represents a single suggested value. * * @author Wouter de Jong */ class Suggestion { private $value; public function __construct(string $value) { $this->value = $value; } public function getValue() : string { return $this->value; } public function __toString() : string { return $this->getValue(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Completion; use _ContaoManager\Symfony\Component\Console\Input\InputOption; /** * Stores all completion suggestions for the current input. * * @author Wouter de Jong */ final class CompletionSuggestions { private $valueSuggestions = []; private $optionSuggestions = []; /** * Add a suggested value for an input option or argument. * * @param string|Suggestion $value * * @return $this */ public function suggestValue($value) : self { $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; return $this; } /** * Add multiple suggested values at once for an input option or argument. * * @param list $values * * @return $this */ public function suggestValues(array $values) : self { foreach ($values as $value) { $this->suggestValue($value); } return $this; } /** * Add a suggestion for an input option name. * * @return $this */ public function suggestOption(InputOption $option) : self { $this->optionSuggestions[] = $option; return $this; } /** * Add multiple suggestions for input option names at once. * * @param InputOption[] $options * * @return $this */ public function suggestOptions(array $options) : self { foreach ($options as $option) { $this->suggestOption($option); } return $this; } /** * @return InputOption[] */ public function getOptionSuggestions() : array { return $this->optionSuggestions; } /** * @return Suggestion[] */ public function getValueSuggestions() : array { return $this->valueSuggestions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Completion; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Input\ArgvInput; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputOption; /** * An input specialized for shell completion. * * This input allows unfinished option names or values and exposes what kind of * completion is expected. * * @author Wouter de Jong */ final class CompletionInput extends ArgvInput { public const TYPE_ARGUMENT_VALUE = 'argument_value'; public const TYPE_OPTION_VALUE = 'option_value'; public const TYPE_OPTION_NAME = 'option_name'; public const TYPE_NONE = 'none'; private $tokens; private $currentIndex; private $completionType; private $completionName = null; private $completionValue = ''; /** * Converts a terminal string into tokens. * * This is required for shell completions without COMP_WORDS support. */ public static function fromString(string $inputStr, int $currentIndex) : self { \preg_match_all('/(?<=^|\\s)([\'"]?)(.+?)(?tokens = $tokens; $input->currentIndex = $currentIndex; return $input; } /** * {@inheritdoc} */ public function bind(InputDefinition $definition) : void { parent::bind($definition); $relevantToken = $this->getRelevantToken(); if ('-' === $relevantToken[0]) { // the current token is an input option: complete either option name or option value [$optionToken, $optionValue] = \explode('=', $relevantToken, 2) + ['', '']; $option = $this->getOptionFromToken($optionToken); if (null === $option && !$this->isCursorFree()) { $this->completionType = self::TYPE_OPTION_NAME; $this->completionValue = $relevantToken; return; } if (null !== $option && $option->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $option->getName(); $this->completionValue = $optionValue ?: (!\str_starts_with($optionToken, '--') ? \substr($optionToken, 2) : ''); return; } } $previousToken = $this->tokens[$this->currentIndex - 1]; if ('-' === $previousToken[0] && '' !== \trim($previousToken, '-')) { // check if previous option accepted a value $previousOption = $this->getOptionFromToken($previousToken); if (null !== $previousOption && $previousOption->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $previousOption->getName(); $this->completionValue = $relevantToken; return; } } // complete argument value $this->completionType = self::TYPE_ARGUMENT_VALUE; foreach ($this->definition->getArguments() as $argumentName => $argument) { if (!isset($this->arguments[$argumentName])) { break; } $argumentValue = $this->arguments[$argumentName]; $this->completionName = $argumentName; if (\is_array($argumentValue)) { $this->completionValue = $argumentValue ? $argumentValue[\array_key_last($argumentValue)] : null; } else { $this->completionValue = $argumentValue; } } if ($this->currentIndex >= \count($this->tokens)) { if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { $this->completionName = $argumentName; $this->completionValue = ''; } else { // we've reached the end $this->completionType = self::TYPE_NONE; $this->completionName = null; $this->completionValue = ''; } } } /** * Returns the type of completion required. * * TYPE_ARGUMENT_VALUE when completing the value of an input argument * TYPE_OPTION_VALUE when completing the value of an input option * TYPE_OPTION_NAME when completing the name of an input option * TYPE_NONE when nothing should be completed * * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component */ public function getCompletionType() : string { return $this->completionType; } /** * The name of the input option or argument when completing a value. * * @return string|null returns null when completing an option name */ public function getCompletionName() : ?string { return $this->completionName; } /** * The value already typed by the user (or empty string). */ public function getCompletionValue() : string { return $this->completionValue; } public function mustSuggestOptionValuesFor(string $optionName) : bool { return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); } public function mustSuggestArgumentValuesFor(string $argumentName) : bool { return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); } protected function parseToken(string $token, bool $parseOptions) : bool { try { return parent::parseToken($token, $parseOptions); } catch (RuntimeException $e) { // suppress errors, completed input is almost never valid } return $parseOptions; } private function getOptionFromToken(string $optionToken) : ?InputOption { $optionName = \ltrim($optionToken, '-'); if (!$optionName) { return null; } if ('-' === ($optionToken[1] ?? ' ')) { // long option name return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; } // short option name return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; } /** * The token of the cursor, or the last token if the cursor is at the end of the input. */ private function getRelevantToken() : string { return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; } /** * Whether the cursor is "free" (i.e. at the end of the input preceded by a space). */ private function isCursorFree() : bool { $nrOfTokens = \count($this->tokens); if ($this->currentIndex > $nrOfTokens) { throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); } return $this->currentIndex >= $nrOfTokens; } public function __toString() { $str = ''; foreach ($this->tokens as $i => $token) { $str .= $token; if ($this->currentIndex === $i) { $str .= '|'; } $str .= ' '; } if ($this->currentIndex > $i) { $str .= '|'; } return \rtrim($str); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Style; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; use _ContaoManager\Symfony\Component\Console\Helper\ProgressBar; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Decorates output to add console style guide helpers. * * @author Kevin Bond */ abstract class OutputStyle implements OutputInterface, StyleInterface { private $output; public function __construct(OutputInterface $output) { $this->output = $output; } /** * {@inheritdoc} */ public function newLine(int $count = 1) { $this->output->write(\str_repeat(\PHP_EOL, $count)); } /** * @return ProgressBar */ public function createProgressBar(int $max = 0) { return new ProgressBar($this->output, $max); } /** * {@inheritdoc} */ public function write($messages, bool $newline = \false, int $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } /** * {@inheritdoc} */ public function writeln($messages, int $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } /** * {@inheritdoc} */ public function setVerbosity(int $level) { $this->output->setVerbosity($level); } /** * {@inheritdoc} */ public function getVerbosity() { return $this->output->getVerbosity(); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { $this->output->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->output->isDecorated(); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } /** * {@inheritdoc} */ public function getFormatter() { return $this->output->getFormatter(); } /** * {@inheritdoc} */ public function isQuiet() { return $this->output->isQuiet(); } /** * {@inheritdoc} */ public function isVerbose() { return $this->output->isVerbose(); } /** * {@inheritdoc} */ public function isVeryVerbose() { return $this->output->isVeryVerbose(); } /** * {@inheritdoc} */ public function isDebug() { return $this->output->isDebug(); } protected function getErrorOutput() { if (!$this->output instanceof ConsoleOutputInterface) { return $this->output; } return $this->output->getErrorOutput(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Style; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Helper\Helper; use _ContaoManager\Symfony\Component\Console\Helper\ProgressBar; use _ContaoManager\Symfony\Component\Console\Helper\SymfonyQuestionHelper; use _ContaoManager\Symfony\Component\Console\Helper\Table; use _ContaoManager\Symfony\Component\Console\Helper\TableCell; use _ContaoManager\Symfony\Component\Console\Helper\TableSeparator; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Output\TrimmedBufferOutput; use _ContaoManager\Symfony\Component\Console\Question\ChoiceQuestion; use _ContaoManager\Symfony\Component\Console\Question\ConfirmationQuestion; use _ContaoManager\Symfony\Component\Console\Question\Question; use _ContaoManager\Symfony\Component\Console\Terminal; /** * Output decorator helpers for the Symfony Style Guide. * * @author Kevin Bond */ class SymfonyStyle extends OutputStyle { public const MAX_LINE_LENGTH = 120; private $input; private $output; private $questionHelper; private $progressBar; private $lineLength; private $bufferedOutput; public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), \false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = \min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($this->output = $output); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block */ public function block($messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = \false, bool $escape = \true) { $messages = \is_array($messages) ? \array_values($messages) : [$messages]; $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); $this->newLine(); } /** * {@inheritdoc} */ public function title(string $message) { $this->autoPrependBlock(); $this->writeln([\sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), \sprintf('%s', \str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message))))]); $this->newLine(); } /** * {@inheritdoc} */ public function section(string $message) { $this->autoPrependBlock(); $this->writeln([\sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), \sprintf('%s', \str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message))))]); $this->newLine(); } /** * {@inheritdoc} */ public function listing(array $elements) { $this->autoPrependText(); $elements = \array_map(function ($element) { return \sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } /** * {@inheritdoc} */ public function text($message) { $this->autoPrependText(); $messages = \is_array($message) ? \array_values($message) : [$message]; foreach ($messages as $message) { $this->writeln(\sprintf(' %s', $message)); } } /** * Formats a command comment. * * @param string|array $message */ public function comment($message) { $this->block($message, null, null, ' // ', \false, \false); } /** * {@inheritdoc} */ public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', \true); } /** * {@inheritdoc} */ public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', \true); } /** * {@inheritdoc} */ public function warning($message) { $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', \true); } /** * {@inheritdoc} */ public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } /** * Formats an info message. * * @param string|array $message */ public function info($message) { $this->block($message, 'INFO', 'fg=green', ' ', \true); } /** * {@inheritdoc} */ public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', \true); } /** * {@inheritdoc} */ public function table(array $headers, array $rows) { $this->createTable()->setHeaders($headers)->setRows($rows)->render(); $this->newLine(); } /** * Formats a horizontal table. */ public function horizontalTable(array $headers, array $rows) { $this->createTable()->setHorizontal(\true)->setHeaders($headers)->setRows($rows)->render(); $this->newLine(); } /** * Formats a list of key/value horizontally. * * Each row can be one of: * * 'A title' * * ['key' => 'value'] * * new TableSeparator() * * @param string|array|TableSeparator ...$list */ public function definitionList(...$list) { $headers = []; $row = []; foreach ($list as $value) { if ($value instanceof TableSeparator) { $headers[] = $value; $row[] = $value; continue; } if (\is_string($value)) { $headers[] = new TableCell($value, ['colspan' => 2]); $row[] = null; continue; } if (!\is_array($value)) { throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); } $headers[] = \key($value); $row[] = \current($value); } $this->horizontalTable($headers, [$row]); } /** * {@inheritdoc} */ public function ask(string $question, ?string $default = null, ?callable $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function askHidden(string $question, ?callable $validator = null) { $question = new Question($question); $question->setHidden(\true); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function confirm(string $question, bool $default = \true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } /** * {@inheritdoc} */ public function choice(string $question, array $choices, $default = null) { if (null !== $default) { $values = \array_flip($choices); $default = $values[$default] ?? $default; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } /** * {@inheritdoc} */ public function progressStart(int $max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } /** * {@inheritdoc} */ public function progressAdvance(int $step = 1) { $this->getProgressBar()->advance($step); } /** * {@inheritdoc} */ public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } /** * {@inheritdoc} */ public function createProgressBar(int $max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === \getenv('TERM_PROGRAM')) { $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); // dark shade character \u2593 } return $progressBar; } /** * @see ProgressBar::iterate() */ public function progressIterate(iterable $iterable, ?int $max = null) : iterable { yield from $this->createProgressBar()->iterate($iterable, $max); $this->newLine(2); } /** * @return mixed */ public function askQuestion(Question $question) { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } if (!$this->questionHelper) { $this->questionHelper = new SymfonyQuestionHelper(); } $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { $this->newLine(); $this->bufferedOutput->write("\n"); } return $answer; } /** * {@inheritdoc} */ public function writeln($messages, int $type = self::OUTPUT_NORMAL) { if (!\is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::writeln($message, $type); $this->writeBuffer($message, \true, $type); } } /** * {@inheritdoc} */ public function write($messages, bool $newline = \false, int $type = self::OUTPUT_NORMAL) { if (!\is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::write($message, $newline, $type); $this->writeBuffer($message, $newline, $type); } } /** * {@inheritdoc} */ public function newLine(int $count = 1) { parent::newLine($count); $this->bufferedOutput->write(\str_repeat("\n", $count)); } /** * Returns a new instance which makes use of stderr if available. * * @return self */ public function getErrorStyle() { return new self($this->input, $this->getErrorOutput()); } public function createTable() : Table { $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); return (new Table($output))->setStyle($style); } private function getProgressBar() : ProgressBar { if (!$this->progressBar) { throw new RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function autoPrependBlock() : void { $chars = \substr(\str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { $this->newLine(); // empty history, so we should start with a new line. return; } // Prepend new line for each non LF chars (This means no blank line was output before) $this->newLine(2 - \substr_count($chars, "\n")); } private function autoPrependText() : void { $fetched = $this->bufferedOutput->fetch(); // Prepend new line if last char isn't EOL: if (!\str_ends_with($fetched, "\n")) { $this->newLine(); } } private function writeBuffer(string $message, bool $newLine, int $type) : void { // We need to know if the last chars are PHP_EOL $this->bufferedOutput->write($message, $newLine, $type); } private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = \false, bool $escape = \false) : array { $indentLength = 0; $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); $lines = []; if (null !== $type) { $type = \sprintf('[%s] ', $type); $indentLength = \strlen($type); $lineIndentation = \str_repeat(' ', $indentLength); } // wrap and add newlines for each element foreach ($messages as $key => $message) { if ($escape) { $message = OutputFormatter::escape($message); } $decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message)); $messageLineLength = \min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength); $messageLines = \explode(\PHP_EOL, \wordwrap($message, $messageLineLength, \PHP_EOL, \true)); foreach ($messageLines as $messageLine) { $lines[] = $messageLine; } if (\count($messages) > 1 && $key < \count($messages) - 1) { $lines[] = ''; } } $firstLineIndex = 0; if ($padding && $this->isDecorated()) { $firstLineIndex = 1; \array_unshift($lines, ''); $lines[] = ''; } foreach ($lines as $i => &$line) { if (null !== $type) { $line = $firstLineIndex === $i ? $type . $line : $lineIndentation . $line; } $line = $prefix . $line; $line .= \str_repeat(' ', \max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0)); if ($style) { $line = \sprintf('<%s>%s', $style, $line); } } return $lines; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Style; /** * Output style helpers. * * @author Kevin Bond */ interface StyleInterface { /** * Formats a command title. */ public function title(string $message); /** * Formats a section title. */ public function section(string $message); /** * Formats a list. */ public function listing(array $elements); /** * Formats informational text. * * @param string|array $message */ public function text($message); /** * Formats a success result bar. * * @param string|array $message */ public function success($message); /** * Formats an error result bar. * * @param string|array $message */ public function error($message); /** * Formats an warning result bar. * * @param string|array $message */ public function warning($message); /** * Formats a note admonition. * * @param string|array $message */ public function note($message); /** * Formats a caution admonition. * * @param string|array $message */ public function caution($message); /** * Formats a table. */ public function table(array $headers, array $rows); /** * Asks a question. * * @return mixed */ public function ask(string $question, ?string $default = null, ?callable $validator = null); /** * Asks a question with the user input hidden. * * @return mixed */ public function askHidden(string $question, ?callable $validator = null); /** * Asks for confirmation. * * @return bool */ public function confirm(string $question, bool $default = \true); /** * Asks a choice question. * * @param string|int|null $default * * @return mixed */ public function choice(string $question, array $choices, $default = null); /** * Add newline(s). */ public function newLine(int $count = 1); /** * Starts the progress output. */ public function progressStart(int $max = 0); /** * Advances the progress output X steps. */ public function progressAdvance(int $step = 1); /** * Finishes the progress output. */ public function progressFinish(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Question; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; /** * Represents a choice question. * * @author Fabien Potencier */ class ChoiceQuestion extends Question { private $choices; private $multiselect = \false; private $prompt = ' > '; private $errorMessage = 'Value "%s" is invalid'; /** * @param string $question The question to ask to the user * @param array $choices The list of available choices * @param mixed $default The default answer to return */ public function __construct(string $question, array $choices, $default = null) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); } parent::__construct($question, $default); $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } /** * Returns available choices. * * @return array */ public function getChoices() { return $this->choices; } /** * Sets multiselect option. * * When multiselect is set to true, multiple choices can be answered. * * @return $this */ public function setMultiselect(bool $multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } /** * Returns whether the choices are multiselect. * * @return bool */ public function isMultiselect() { return $this->multiselect; } /** * Gets the prompt for choices. * * @return string */ public function getPrompt() { return $this->prompt; } /** * Sets the prompt for choices. * * @return $this */ public function setPrompt(string $prompt) { $this->prompt = $prompt; return $this; } /** * Sets the error message for invalid values. * * The error message has a string placeholder (%s) for the invalid value. * * @return $this */ public function setErrorMessage(string $errorMessage) { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } private function getDefaultValidator() : callable { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use($choices, $errorMessage, $multiselect, $isAssoc) { if ($multiselect) { // Check for a separated comma values if (!\preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { throw new InvalidArgumentException(\sprintf($errorMessage, $selected)); } $selectedChoices = \explode(',', (string) $selected); } else { $selectedChoices = [$selected]; } if ($this->isTrimmable()) { foreach ($selectedChoices as $k => $v) { $selectedChoices[$k] = \trim((string) $v); } } $multiselectChoices = []; foreach ($selectedChoices as $value) { $results = []; foreach ($choices as $key => $choice) { if ($choice === $value) { $results[] = $key; } } if (\count($results) > 1) { throw new InvalidArgumentException(\sprintf('The provided answer is ambiguous. Value should be one of "%s".', \implode('" or "', $results))); } $result = \array_search($value, $choices); if (!$isAssoc) { if (\false !== $result) { $result = $choices[$result]; } elseif (isset($choices[$value])) { $result = $choices[$value]; } } elseif (\false === $result && isset($choices[$value])) { $result = $value; } if (\false === $result) { throw new InvalidArgumentException(\sprintf($errorMessage, $value)); } // For associative choices, consistently return the key as string: $multiselectChoices[] = $isAssoc ? (string) $result : $result; } if ($multiselect) { return $multiselectChoices; } return \current($multiselectChoices); }; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Question; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; /** * Represents a Question. * * @author Fabien Potencier */ class Question { private $question; private $attempts; private $hidden = \false; private $hiddenFallback = \true; private $autocompleterCallback; private $validator; private $default; private $normalizer; private $trimmable = \true; private $multiline = \false; /** * @param string $question The question to ask to the user * @param string|bool|int|float|null $default The default answer to return if the user enters nothing */ public function __construct(string $question, $default = null) { $this->question = $question; $this->default = $default; } /** * Returns the question. * * @return string */ public function getQuestion() { return $this->question; } /** * Returns the default answer. * * @return string|bool|int|float|null */ public function getDefault() { return $this->default; } /** * Returns whether the user response accepts newline characters. */ public function isMultiline() : bool { return $this->multiline; } /** * Sets whether the user response should accept newline characters. * * @return $this */ public function setMultiline(bool $multiline) : self { $this->multiline = $multiline; return $this; } /** * Returns whether the user response must be hidden. * * @return bool */ public function isHidden() { return $this->hidden; } /** * Sets whether the user response must be hidden or not. * * @return $this * * @throws LogicException In case the autocompleter is also used */ public function setHidden(bool $hidden) { if ($this->autocompleterCallback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = $hidden; return $this; } /** * In case the response cannot be hidden, whether to fallback on non-hidden question or not. * * @return bool */ public function isHiddenFallback() { return $this->hiddenFallback; } /** * Sets whether to fallback on non-hidden question if the response cannot be hidden. * * @return $this */ public function setHiddenFallback(bool $fallback) { $this->hiddenFallback = $fallback; return $this; } /** * Gets values for the autocompleter. * * @return iterable|null */ public function getAutocompleterValues() { $callback = $this->getAutocompleterCallback(); return $callback ? $callback('') : null; } /** * Sets values for the autocompleter. * * @return $this * * @throws LogicException */ public function setAutocompleterValues(?iterable $values) { if (\is_array($values)) { $values = $this->isAssoc($values) ? \array_merge(\array_keys($values), \array_values($values)) : \array_values($values); $callback = static function () use($values) { return $values; }; } elseif ($values instanceof \Traversable) { $valueCache = null; $callback = static function () use($values, &$valueCache) { return $valueCache ?? ($valueCache = \iterator_to_array($values, \false)); }; } else { $callback = null; } return $this->setAutocompleterCallback($callback); } /** * Gets the callback function used for the autocompleter. */ public function getAutocompleterCallback() : ?callable { return $this->autocompleterCallback; } /** * Sets the callback function used for the autocompleter. * * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. * * @return $this */ public function setAutocompleterCallback(?callable $callback = null) : self { if ($this->hidden && null !== $callback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->autocompleterCallback = $callback; return $this; } /** * Sets a validator for the question. * * @return $this */ public function setValidator(?callable $validator = null) { $this->validator = $validator; return $this; } /** * Gets the validator for the question. * * @return callable|null */ public function getValidator() { return $this->validator; } /** * Sets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @return $this * * @throws InvalidArgumentException in case the number of attempts is invalid */ public function setMaxAttempts(?int $attempts) { if (null !== $attempts && $attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); } $this->attempts = $attempts; return $this; } /** * Gets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @return int|null */ public function getMaxAttempts() { return $this->attempts; } /** * Sets a normalizer for the response. * * The normalizer can be a callable (a string), a closure or a class implementing __invoke. * * @return $this */ public function setNormalizer(callable $normalizer) { $this->normalizer = $normalizer; return $this; } /** * Gets the normalizer for the response. * * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. * * @return callable|null */ public function getNormalizer() { return $this->normalizer; } protected function isAssoc(array $array) { return (bool) \count(\array_filter(\array_keys($array), 'is_string')); } public function isTrimmable() : bool { return $this->trimmable; } /** * @return $this */ public function setTrimmable(bool $trimmable) : self { $this->trimmable = $trimmable; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Question; /** * Represents a yes/no question. * * @author Fabien Potencier */ class ConfirmationQuestion extends Question { private $trueAnswerRegex; /** * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer */ public function __construct(string $question, bool $default = \true, string $trueAnswerRegex = '/^y/i') { parent::__construct($question, $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } /** * Returns the default answer normalizer. */ private function getDefaultNormalizer() : callable { $default = $this->getDefault(); $regex = $this->trueAnswerRegex; return function ($answer) use($default, $regex) { if (\is_bool($answer)) { return $answer; } $answerIsTrue = (bool) \preg_match($regex, $answer); if (\false === $default) { return $answer && $answerIsTrue; } return '' === $answer || $answerIsTrue; }; } } Console Component ================= The Console component eases the creation of beautiful and testable command line interfaces. Sponsor ------- The Console component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and fix your projects. We provide a wide range of professional services including development, consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. We are a worker cooperative! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/console.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) Credits ------- `Resources/bin/hiddeninput.exe` is a third party binary provided within this component. Find sources and license at https://github.com/Seldaek/hidden-input. [1]: https://symfony.com/backers [2]: https://les-tilleuls.coop [3]: https://symfony.com/sponsor * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; use _ContaoManager\Symfony\Component\Console\Color; /** * Formatter style class for defining styles. * * @author Konstantin Kudryashov */ class OutputFormatterStyle implements OutputFormatterStyleInterface { private $color; private $foreground; private $background; private $options; private $href; private $handlesHrefGracefully; /** * Initializes output formatter style. * * @param string|null $foreground The style foreground color name * @param string|null $background The style background color name */ public function __construct(?string $foreground = null, ?string $background = null, array $options = []) { $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); } /** * {@inheritdoc} */ public function setForeground(?string $color = null) { $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); } /** * {@inheritdoc} */ public function setBackground(?string $color = null) { $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); } public function setHref(string $url) : void { $this->href = $url; } /** * {@inheritdoc} */ public function setOption(string $option) { $this->options[] = $option; $this->color = new Color($this->foreground, $this->background, $this->options); } /** * {@inheritdoc} */ public function unsetOption(string $option) { $pos = \array_search($option, $this->options); if (\false !== $pos) { unset($this->options[$pos]); } $this->color = new Color($this->foreground, $this->background, $this->options); } /** * {@inheritdoc} */ public function setOptions(array $options) { $this->color = new Color($this->foreground, $this->background, $this->options = $options); } /** * {@inheritdoc} */ public function apply(string $text) { if (null === $this->handlesHrefGracefully) { $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== \getenv('TERMINAL_EMULATOR') && (!\getenv('KONSOLE_VERSION') || (int) \getenv('KONSOLE_VERSION') > 201100) && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); } if (null !== $this->href && $this->handlesHrefGracefully) { $text = "\x1b]8;;{$this->href}\x1b\\{$text}\x1b]8;;\x1b\\"; } return $this->color->apply($text); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; /** * Formatter style interface for defining styles. * * @author Konstantin Kudryashov */ interface OutputFormatterStyleInterface { /** * Sets style foreground color. */ public function setForeground(?string $color = null); /** * Sets style background color. */ public function setBackground(?string $color = null); /** * Sets some specific style option. */ public function setOption(string $option); /** * Unsets some specific style option. */ public function unsetOption(string $option); /** * Sets multiple style options at once. */ public function setOptions(array $options); /** * Applies the style to a given text. * * @return string */ public function apply(string $text); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; /** * @author Tien Xuan Vo */ final class NullOutputFormatterStyle implements OutputFormatterStyleInterface { /** * {@inheritdoc} */ public function apply(string $text) : string { return $text; } /** * {@inheritdoc} */ public function setBackground(?string $color = null) : void { // do nothing } /** * {@inheritdoc} */ public function setForeground(?string $color = null) : void { // do nothing } /** * {@inheritdoc} */ public function setOption(string $option) : void { // do nothing } /** * {@inheritdoc} */ public function setOptions(array $options) : void { // do nothing } /** * {@inheritdoc} */ public function unsetOption(string $option) : void { // do nothing } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; /** * Formatter interface for console output. * * @author Konstantin Kudryashov */ interface OutputFormatterInterface { /** * Sets the decorated flag. */ public function setDecorated(bool $decorated); /** * Whether the output will decorate messages. * * @return bool */ public function isDecorated(); /** * Sets a new style. */ public function setStyle(string $name, OutputFormatterStyleInterface $style); /** * Checks if output formatter has style with specified name. * * @return bool */ public function hasStyle(string $name); /** * Gets style options from style with specified name. * * @return OutputFormatterStyleInterface * * @throws \InvalidArgumentException When style isn't defined */ public function getStyle(string $name); /** * Formats a message according to the given styles. * * @return string|null */ public function format(?string $message); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use function _ContaoManager\Symfony\Component\String\b; /** * Formatter class for console output. * * @author Konstantin Kudryashov * @author Roland Franssen */ class OutputFormatter implements WrappableOutputFormatterInterface { private $decorated; private $styles = []; private $styleStack; public function __clone() { $this->styleStack = clone $this->styleStack; foreach ($this->styles as $key => $value) { $this->styles[$key] = clone $value; } } /** * Escapes "<" and ">" special chars in given text. * * @return string */ public static function escape(string $text) { $text = \preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); return self::escapeTrailingBackslash($text); } /** * Escapes trailing "\" in given text. * * @internal */ public static function escapeTrailingBackslash(string $text) : string { if (\str_ends_with($text, '\\')) { $len = \strlen($text); $text = \rtrim($text, '\\'); $text = \str_replace("\x00", '', $text); $text .= \str_repeat("\x00", $len - \strlen($text)); } return $text; } /** * Initializes console output formatter. * * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances */ public function __construct(bool $decorated = \false, array $styles = []) { $this->decorated = $decorated; $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); foreach ($styles as $name => $style) { $this->setStyle($name, $style); } $this->styleStack = new OutputFormatterStyleStack(); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { $this->decorated = $decorated; } /** * {@inheritdoc} */ public function isDecorated() { return $this->decorated; } /** * {@inheritdoc} */ public function setStyle(string $name, OutputFormatterStyleInterface $style) { $this->styles[\strtolower($name)] = $style; } /** * {@inheritdoc} */ public function hasStyle(string $name) { return isset($this->styles[\strtolower($name)]); } /** * {@inheritdoc} */ public function getStyle(string $name) { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(\sprintf('Undefined style: "%s".', $name)); } return $this->styles[\strtolower($name)]; } /** * {@inheritdoc} */ public function format(?string $message) { return $this->formatAndWrap($message, 0); } /** * {@inheritdoc} */ public function formatAndWrap(?string $message, int $width) { if (null === $message) { return ''; } $offset = 0; $output = ''; $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; $closeTagRegex = '[a-z][^<>]*+'; $currentLineLength = 0; \preg_match_all("#<(({$openTagRegex}) | /({$closeTagRegex})?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; if (0 != $pos && '\\' == $message[$pos - 1]) { continue; } // add the text up to the next tag $output .= $this->applyCurrentStyle(\substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); $offset = $pos + \strlen($text); // opening tag? if ($open = '/' != $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = $matches[3][$i][0] ?? ''; } if (!$open && !$tag) { // $this->styleStack->pop(); } elseif (null === ($style = $this->createStyleFromString($tag))) { $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(\substr($message, $offset), $output, $width, $currentLineLength); return \strtr($output, ["\x00" => '\\', '\\<' => '<', '\\>' => '>']); } /** * @return OutputFormatterStyleStack */ public function getStyleStack() { return $this->styleStack; } /** * Tries to create new style instance from string. */ private function createStyleFromString(string $string) : ?OutputFormatterStyleInterface { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!\preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { return null; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { \array_shift($match); $match[0] = \strtolower($match[0]); if ('fg' == $match[0]) { $style->setForeground(\strtolower($match[1])); } elseif ('bg' == $match[0]) { $style->setBackground(\strtolower($match[1])); } elseif ('href' === $match[0]) { $url = \preg_replace('{\\\\([<>])}', '$1', $match[1]); $style->setHref($url); } elseif ('options' === $match[0]) { \preg_match_all('([^,;]+)', \strtolower($match[1]), $options); $options = \array_shift($options); foreach ($options as $option) { $style->setOption($option); } } else { return null; } } return $style; } /** * Applies current style from stack to text, if must be applied. */ private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength) : string { if ('' === $text) { return ''; } if (!$width) { return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; } if (!$currentLineLength && '' !== $current) { $text = \ltrim($text); } if ($currentLineLength) { $prefix = \substr($text, 0, $i = $width - $currentLineLength) . "\n"; $text = \substr($text, $i); } else { $prefix = ''; } \preg_match('~(\\n)$~', $text, $matches); $text = $prefix . $this->addLineBreaks($text, $width); $text = \rtrim($text, "\n") . ($matches[1] ?? ''); if (!$currentLineLength && '' !== $current && "\n" !== \substr($current, -1)) { $text = "\n" . $text; } $lines = \explode("\n", $text); foreach ($lines as $line) { $currentLineLength += \strlen($line); if ($width <= $currentLineLength) { $currentLineLength = 0; } } if ($this->isDecorated()) { foreach ($lines as $i => $line) { $lines[$i] = $this->styleStack->getCurrent()->apply($line); } } return \implode("\n", $lines); } private function addLineBreaks(string $text, int $width) : string { $encoding = \mb_detect_encoding($text, null, \true) ?: 'UTF-8'; return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", \true)->toByteString($encoding); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; /** * @author Tien Xuan Vo */ final class NullOutputFormatter implements OutputFormatterInterface { private $style; /** * {@inheritdoc} */ public function format(?string $message) : ?string { return null; } /** * {@inheritdoc} */ public function getStyle(string $name) : OutputFormatterStyleInterface { // to comply with the interface we must return a OutputFormatterStyleInterface return $this->style ?? ($this->style = new NullOutputFormatterStyle()); } /** * {@inheritdoc} */ public function hasStyle(string $name) : bool { return \false; } /** * {@inheritdoc} */ public function isDecorated() : bool { return \false; } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) : void { // do nothing } /** * {@inheritdoc} */ public function setStyle(string $name, OutputFormatterStyleInterface $style) : void { // do nothing } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; /** * Formatter interface for console output that supports word wrapping. * * @author Roland Franssen */ interface WrappableOutputFormatterInterface extends OutputFormatterInterface { /** * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). */ public function formatAndWrap(?string $message, int $width); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Formatter; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Contracts\Service\ResetInterface; /** * @author Jean-François Simon */ class OutputFormatterStyleStack implements ResetInterface { /** * @var OutputFormatterStyleInterface[] */ private $styles; private $emptyStyle; public function __construct(?OutputFormatterStyleInterface $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); $this->reset(); } /** * Resets stack (ie. empty internal arrays). */ public function reset() { $this->styles = []; } /** * Pushes a style in the stack. */ public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } /** * Pops a style from the stack. * * @return OutputFormatterStyleInterface * * @throws InvalidArgumentException When style tags incorrectly nested */ public function pop(?OutputFormatterStyleInterface $style = null) { if (empty($this->styles)) { return $this->emptyStyle; } if (null === $style) { return \array_pop($this->styles); } foreach (\array_reverse($this->styles, \true) as $index => $stackedStyle) { if ($style->apply('') === $stackedStyle->apply('')) { $this->styles = \array_slice($this->styles, 0, $index); return $stackedStyle; } } throw new InvalidArgumentException('Incorrectly nested style tag found.'); } /** * Computes current style with stacks top codes. * * @return OutputFormatterStyle */ public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[\count($this->styles) - 1]; } /** * @return $this */ public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } /** * @return OutputFormatterStyleInterface */ public function getEmptyStyle() { return $this->emptyStyle; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Fabien Potencier */ final class Color { private const COLORS = ['black' => 0, 'red' => 1, 'green' => 2, 'yellow' => 3, 'blue' => 4, 'magenta' => 5, 'cyan' => 6, 'white' => 7, 'default' => 9]; private const BRIGHT_COLORS = ['gray' => 0, 'bright-red' => 1, 'bright-green' => 2, 'bright-yellow' => 3, 'bright-blue' => 4, 'bright-magenta' => 5, 'bright-cyan' => 6, 'bright-white' => 7]; private const AVAILABLE_OPTIONS = ['bold' => ['set' => 1, 'unset' => 22], 'underscore' => ['set' => 4, 'unset' => 24], 'blink' => ['set' => 5, 'unset' => 25], 'reverse' => ['set' => 7, 'unset' => 27], 'conceal' => ['set' => 8, 'unset' => 28]]; private $foreground; private $background; private $options = []; public function __construct(string $foreground = '', string $background = '', array $options = []) { $this->foreground = $this->parseColor($foreground); $this->background = $this->parseColor($background, \true); foreach ($options as $option) { if (!isset(self::AVAILABLE_OPTIONS[$option])) { throw new InvalidArgumentException(\sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, \implode(', ', \array_keys(self::AVAILABLE_OPTIONS)))); } $this->options[$option] = self::AVAILABLE_OPTIONS[$option]; } } public function apply(string $text) : string { return $this->set() . $text . $this->unset(); } public function set() : string { $setCodes = []; if ('' !== $this->foreground) { $setCodes[] = $this->foreground; } if ('' !== $this->background) { $setCodes[] = $this->background; } foreach ($this->options as $option) { $setCodes[] = $option['set']; } if (0 === \count($setCodes)) { return ''; } return \sprintf("\x1b[%sm", \implode(';', $setCodes)); } public function unset() : string { $unsetCodes = []; if ('' !== $this->foreground) { $unsetCodes[] = 39; } if ('' !== $this->background) { $unsetCodes[] = 49; } foreach ($this->options as $option) { $unsetCodes[] = $option['unset']; } if (0 === \count($unsetCodes)) { return ''; } return \sprintf("\x1b[%sm", \implode(';', $unsetCodes)); } private function parseColor(string $color, bool $background = \false) : string { if ('' === $color) { return ''; } if ('#' === $color[0]) { $color = \substr($color, 1); if (3 === \strlen($color)) { $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2]; } if (6 !== \strlen($color)) { throw new InvalidArgumentException(\sprintf('Invalid "%s" color.', $color)); } return ($background ? '4' : '3') . $this->convertHexColorToAnsi(\hexdec($color)); } if (isset(self::COLORS[$color])) { return ($background ? '4' : '3') . self::COLORS[$color]; } if (isset(self::BRIGHT_COLORS[$color])) { return ($background ? '10' : '9') . self::BRIGHT_COLORS[$color]; } throw new InvalidArgumentException(\sprintf('Invalid "%s" color; expected one of (%s).', $color, \implode(', ', \array_merge(\array_keys(self::COLORS), \array_keys(self::BRIGHT_COLORS))))); } private function convertHexColorToAnsi(int $color) : string { $r = $color >> 16 & 255; $g = $color >> 8 & 255; $b = $color & 255; // see https://github.com/termstandard/colors/ for more information about true color support if ('truecolor' !== \getenv('COLORTERM')) { return (string) $this->degradeHexColorToAnsi($r, $g, $b); } return \sprintf('8;2;%d;%d;%d', $r, $g, $b); } private function degradeHexColorToAnsi(int $r, int $g, int $b) : int { if (0 === \round($this->getSaturation($r, $g, $b) / 50)) { return 0; } return \round($b / 255) << 2 | \round($g / 255) << 1 | \round($r / 255); } private function getSaturation(int $r, int $g, int $b) : int { $r = $r / 255; $g = $g / 255; $b = $b / 255; $v = \max($r, $g, $b); if (0 === ($diff = $v - \min($r, $g, $b))) { return 0; } return (int) $diff * 100 / $v; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\SignalRegistry; final class SignalRegistry { private $signalHandlers = []; public function __construct() { if (\function_exists('pcntl_async_signals')) { \pcntl_async_signals(\true); } } public function register(int $signal, callable $signalHandler) : void { if (!isset($this->signalHandlers[$signal])) { $previousCallback = \pcntl_signal_get_handler($signal); if (\is_callable($previousCallback)) { $this->signalHandlers[$signal][] = $previousCallback; } } $this->signalHandlers[$signal][] = $signalHandler; \pcntl_signal($signal, [$this, 'handle']); } public static function isSupported() : bool { if (!\function_exists('pcntl_signal')) { return \false; } if (\in_array('pcntl_signal', \explode(',', \ini_get('disable_functions')))) { return \false; } return \true; } /** * @internal */ public function handle(int $signal) : void { $count = \count($this->signalHandlers[$signal]); foreach ($this->signalHandlers[$signal] as $i => $signalHandler) { $hasNext = $i !== $count - 1; $signalHandler($signal, $hasNext); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Grégoire Pineau */ class SingleCommandApplication extends Command { private $version = 'UNKNOWN'; private $autoExit = \true; private $running = \false; /** * @return $this */ public function setVersion(string $version) : self { $this->version = $version; return $this; } /** * @final * * @return $this */ public function setAutoExit(bool $autoExit) : self { $this->autoExit = $autoExit; return $this; } public function run(?InputInterface $input = null, ?OutputInterface $output = null) : int { if ($this->running) { return parent::run($input, $output); } // We use the command name as the application name $application = new Application($this->getName() ?: 'UNKNOWN', $this->version); $application->setAutoExit($this->autoExit); // Fix the usage of the command displayed with "--help" $this->setName($_SERVER['argv'][0]); $application->add($this); $application->setDefaultCommand($this->getName(), \true); $this->running = \true; try { $ret = $application->run($input, $output); } finally { $this->running = \false; } return $ret ?? 1; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\CommandLoader; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; /** * @author Robin Chalas */ interface CommandLoaderInterface { /** * Loads a command. * * @return Command * * @throws CommandNotFoundException */ public function get(string $name); /** * Checks if a command exists. * * @return bool */ public function has(string $name); /** * @return string[] */ public function getNames(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\CommandLoader; use _ContaoManager\Psr\Container\ContainerInterface; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; /** * Loads commands from a PSR-11 container. * * @author Robin Chalas */ class ContainerCommandLoader implements CommandLoaderInterface { private $container; private $commandMap; /** * @param array $commandMap An array with command names as keys and service ids as values */ public function __construct(ContainerInterface $container, array $commandMap) { $this->container = $container; $this->commandMap = $commandMap; } /** * {@inheritdoc} */ public function get(string $name) { if (!$this->has($name)) { throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name)); } return $this->container->get($this->commandMap[$name]); } /** * {@inheritdoc} */ public function has(string $name) { return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); } /** * {@inheritdoc} */ public function getNames() { return \array_keys($this->commandMap); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\CommandLoader; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; /** * A simple command loader using factories to instantiate commands lazily. * * @author Maxime Steinhausser */ class FactoryCommandLoader implements CommandLoaderInterface { private $factories; /** * @param callable[] $factories Indexed by command names */ public function __construct(array $factories) { $this->factories = $factories; } /** * {@inheritdoc} */ public function has(string $name) { return isset($this->factories[$name]); } /** * {@inheritdoc} */ public function get(string $name) { if (!isset($this->factories[$name])) { throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name)); } $factory = $this->factories[$name]; return $factory(); } /** * {@inheritdoc} */ public function getNames() { return \array_keys($this->factories); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Helper\Helper; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputOption; /** * Text descriptor. * * @author Jean-François Simon * * @internal */ class TextDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { $default = \sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $totalWidth = $options['total_width'] ?? Helper::width($argument->getName()); $spacingWidth = $totalWidth - \strlen($argument->getName()); $this->writeText(\sprintf( ' %s %s%s%s', $argument->getName(), \str_repeat(' ', $spacingWidth), // + 4 = 2 spaces before , 2 spaces after \preg_replace('/\\s*[\\r\\n]\\s*/', "\n" . \str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = \sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); } else { $default = ''; } $value = ''; if ($option->acceptValue()) { $value = '=' . \strtoupper($option->getName()); if ($option->isValueOptional()) { $value = '[' . $value . ']'; } } $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); $synopsis = \sprintf('%s%s', $option->getShortcut() ? \sprintf('-%s, ', $option->getShortcut()) : ' ', \sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)); $spacingWidth = $totalWidth - Helper::width($synopsis); $this->writeText(\sprintf( ' %s %s%s%s%s', $synopsis, \str_repeat(' ', $spacingWidth), // + 4 = 2 spaces before , 2 spaces after \preg_replace('/\\s*[\\r\\n]\\s*/', "\n" . \str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { $totalWidth = \max($totalWidth, Helper::width($argument->getName())); } if ($definition->getArguments()) { $this->writeText('Arguments:', $options); $this->writeText("\n"); foreach ($definition->getArguments() as $argument) { $this->describeInputArgument($argument, \array_merge($options, ['total_width' => $totalWidth])); $this->writeText("\n"); } } if ($definition->getArguments() && $definition->getOptions()) { $this->writeText("\n"); } if ($definition->getOptions()) { $laterOptions = []; $this->writeText('Options:', $options); foreach ($definition->getOptions() as $option) { if (\strlen($option->getShortcut() ?? '') > 1) { $laterOptions[] = $option; continue; } $this->writeText("\n"); $this->describeInputOption($option, \array_merge($options, ['total_width' => $totalWidth])); } foreach ($laterOptions as $option) { $this->writeText("\n"); $this->describeInputOption($option, \array_merge($options, ['total_width' => $totalWidth])); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { $command->mergeApplicationDefinition(\false); if ($description = $command->getDescription()) { $this->writeText('Description:', $options); $this->writeText("\n"); $this->writeText(' ' . $description); $this->writeText("\n\n"); } $this->writeText('Usage:', $options); foreach (\array_merge([$command->getSynopsis(\true)], $command->getAliases(), $command->getUsages()) as $usage) { $this->writeText("\n"); $this->writeText(' ' . OutputFormatter::escape($usage), $options); } $this->writeText("\n"); $definition = $command->getDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } $help = $command->getProcessedHelp(); if ($help && $help !== $description) { $this->writeText("\n"); $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' ' . \str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { $width = $this->getColumnWidth($description->getCommands()); foreach ($description->getCommands() as $command) { $this->writeText(\sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); $this->writeText("\n"); } } else { if ('' != ($help = $application->getHelp())) { $this->writeText("{$help}\n\n", $options); } $this->writeText("Usage:\n", $options); $this->writeText(" command [options] [arguments]\n\n", $options); $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); $this->writeText("\n"); $this->writeText("\n"); $commands = $description->getCommands(); $namespaces = $description->getNamespaces(); if ($describedNamespace && $namespaces) { // make sure all alias commands are included when describing a specific namespace $describedNamespaceInfo = \reset($namespaces); foreach ($describedNamespaceInfo['commands'] as $name) { $commands[$name] = $description->getCommand($name); } } // calculate max. width based on available commands per namespace $width = $this->getColumnWidth(\array_merge(...\array_values(\array_map(function ($namespace) use($commands) { return \array_intersect($namespace['commands'], \array_keys($commands)); }, \array_values($namespaces))))); if ($describedNamespace) { $this->writeText(\sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); } else { $this->writeText('Available commands:', $options); } foreach ($namespaces as $namespace) { $namespace['commands'] = \array_filter($namespace['commands'], function ($name) use($commands) { return isset($commands[$name]); }); if (!$namespace['commands']) { continue; } if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(' ' . $namespace['id'] . '', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - Helper::width($name); $command = $commands[$name]; $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; $this->writeText(\sprintf(' %s%s%s', $name, \str_repeat(' ', $spacingWidth), $commandAliases . $command->getDescription()), $options); } } $this->writeText("\n"); } } /** * {@inheritdoc} */ private function writeText(string $content, array $options = []) { $this->write(isset($options['raw_text']) && $options['raw_text'] ? \strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : \true); } /** * Formats command aliases to show them in the command description. */ private function getCommandAliasesText(Command $command) : string { $text = ''; $aliases = $command->getAliases(); if ($aliases) { $text = '[' . \implode('|', $aliases) . '] '; } return $text; } /** * Formats input option/argument default value. * * @param mixed $default */ private function formatDefaultValue($default) : string { if (\INF === $default) { return 'INF'; } if (\is_string($default)) { $default = OutputFormatter::escape($default); } elseif (\is_array($default)) { foreach ($default as $key => $value) { if (\is_string($value)) { $default[$key] = OutputFormatter::escape($value); } } } return \str_replace('\\\\', '\\', \json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); } /** * @param array $commands */ private function getColumnWidth(array $commands) : int { $widths = []; foreach ($commands as $command) { if ($command instanceof Command) { $widths[] = Helper::width($command->getName()); foreach ($command->getAliases() as $alias) { $widths[] = Helper::width($alias); } } else { $widths[] = Helper::width($command); } } return $widths ? \max($widths) + 2 : 0; } /** * @param InputOption[] $options */ private function calculateTotalWidthForOptions(array $options) : int { $totalWidth = 0; foreach ($options as $option) { // "-" + shortcut + ", --" + name $nameLength = 1 + \max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName()); if ($option->isNegatable()) { $nameLength += 6 + Helper::width($option->getName()); // |--no- + name } elseif ($option->acceptValue()) { $valueLength = 1 + Helper::width($option->getName()); // = + value $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] $nameLength += $valueLength; } $totalWidth = \max($totalWidth, $nameLength); } return $totalWidth; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; /** * @author Jean-François Simon * * @internal */ class ApplicationDescription { public const GLOBAL_NAMESPACE = '_global'; private $application; private $namespace; private $showHidden; /** * @var array */ private $namespaces; /** * @var array */ private $commands; /** * @var array */ private $aliases; public function __construct(Application $application, ?string $namespace = null, bool $showHidden = \false) { $this->application = $application; $this->namespace = $namespace; $this->showHidden = $showHidden; } public function getNamespaces() : array { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } /** * @return Command[] */ public function getCommands() : array { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } /** * @throws CommandNotFoundException */ public function getCommand(string $name) : Command { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name)); } return $this->commands[$name] ?? $this->aliases[$name]; } private function inspectApplication() { $this->commands = []; $this->namespaces = []; $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); foreach ($this->sortCommands($all) as $namespace => $commands) { $names = []; /** @var Command $command */ foreach ($commands as $name => $command) { if (!$command->getName() || !$this->showHidden && $command->isHidden()) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; } } private function sortCommands(array $commands) : array { $namespacedCommands = []; $globalCommands = []; $sortedCommands = []; foreach ($commands as $name => $command) { $key = $this->application->extractNamespace($name, 1); if (\in_array($key, ['', self::GLOBAL_NAMESPACE], \true)) { $globalCommands[$name] = $command; } else { $namespacedCommands[$key][$name] = $command; } } if ($globalCommands) { \ksort($globalCommands); $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; } if ($namespacedCommands) { \ksort($namespacedCommands, \SORT_STRING); foreach ($namespacedCommands as $key => $commandsSet) { \ksort($commandsSet); $sortedCommands[$key] = $commandsSet; } } return $sortedCommands; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputOption; /** * XML descriptor. * * @author Jean-François Simon * * @internal */ class XmlDescriptor extends Descriptor { public function getInputDefinitionDocument(InputDefinition $definition) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($definition->getArguments() as $argument) { $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; } public function getCommandDocument(Command $command, bool $short = \false) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); $commandXML->appendChild($usagesXML = $dom->createElement('usages')); $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(\str_replace("\n", "\n ", $command->getDescription()))); if ($short) { foreach ($command->getAliases() as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } } else { $command->mergeApplicationDefinition(\false); foreach (\array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(\str_replace("\n", "\n ", $command->getProcessedHelp()))); $definitionXML = $this->getInputDefinitionDocument($command->getDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); } return $dom; } public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = \false) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ('UNKNOWN' !== $application->getName()) { $rootXml->setAttribute('name', $application->getName()); if ('UNKNOWN' !== $application->getVersion()) { $rootXml->setAttribute('version', $application->getVersion()); } } $rootXml->appendChild($commandsXML = $dom->createElement('commands')); $description = new ApplicationDescription($application, $namespace, \true); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } foreach ($description->getCommands() as $command) { $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeDocument($this->getInputArgumentDocument($argument)); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { $this->writeDocument($this->getInputOptionDocument($option)); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? \false)); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? \false)); } /** * Appends document children to parent node. */ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, \true)); } } /** * Writes DOM document. */ private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = \true; $this->write($dom->saveXML()); } private function getInputArgumentDocument(InputArgument $argument) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('argument')); $objectXML->setAttribute('name', $argument->getName()); $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [\var_export($argument->getDefault(), \true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } return $dom; } private function getInputOptionDocument(InputOption $option) : \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--' . $option->getName()); $pos = \strpos($option->getShortcut() ?? '', '|'); if (\false !== $pos) { $objectXML->setAttribute('shortcut', '-' . \substr($option->getShortcut(), 0, $pos)); $objectXML->setAttribute('shortcuts', '-' . \str_replace('|', '|-', $option->getShortcut())); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-' . $option->getShortcut() : ''); } $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptValue()) { $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [\var_export($option->getDefault(), \true)] : ($option->getDefault() ? [$option->getDefault()] : [])); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); if (!empty($defaults)) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } if ($option->isNegatable()) { $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--no-' . $option->getName()); $objectXML->setAttribute('shortcut', ''); $objectXML->setAttribute('accept_value', 0); $objectXML->setAttribute('is_value_required', 0); $objectXML->setAttribute('is_multiple', 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode('Negate the "--' . $option->getName() . '" option')); } return $dom; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Descriptor interface. * * @author Jean-François Simon */ interface DescriptorInterface { public function describe(OutputInterface $output, object $object, array $options = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Jean-François Simon * * @internal */ abstract class Descriptor implements DescriptorInterface { /** * @var OutputInterface */ protected $output; /** * {@inheritdoc} */ public function describe(OutputInterface $output, object $object, array $options = []) { $this->output = $output; switch (\true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', \get_debug_type($object))); } } /** * Writes content to output. */ protected function write(string $content, bool $decorated = \false) { $this->output->write($content, \false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } /** * Describes an InputArgument instance. */ protected abstract function describeInputArgument(InputArgument $argument, array $options = []); /** * Describes an InputOption instance. */ protected abstract function describeInputOption(InputOption $option, array $options = []); /** * Describes an InputDefinition instance. */ protected abstract function describeInputDefinition(InputDefinition $definition, array $options = []); /** * Describes a Command instance. */ protected abstract function describeCommand(Command $command, array $options = []); /** * Describes an Application instance. */ protected abstract function describeApplication(Application $application, array $options = []); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputOption; /** * JSON descriptor. * * @author Jean-François Simon * * @internal */ class JsonDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeData($this->getInputArgumentData($argument), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { $this->writeData($this->getInputOptionData($option), $options); if ($option->isNegatable()) { $this->writeData($this->getInputOptionData($option, \true), $options); } } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeData($this->getInputDefinitionData($definition), $options); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { $this->writeData($this->getCommandData($command, $options['short'] ?? \false), $options); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace, \true); $commands = []; foreach ($description->getCommands() as $command) { $commands[] = $this->getCommandData($command, $options['short'] ?? \false); } $data = []; if ('UNKNOWN' !== $application->getName()) { $data['application']['name'] = $application->getName(); if ('UNKNOWN' !== $application->getVersion()) { $data['application']['version'] = $application->getVersion(); } } $data['commands'] = $commands; if ($describedNamespace) { $data['namespace'] = $describedNamespace; } else { $data['namespaces'] = \array_values($description->getNamespaces()); } $this->writeData($data, $options); } /** * Writes data as json. */ private function writeData(array $data, array $options) { $flags = $options['json_encoding'] ?? 0; $this->write(\json_encode($data, $flags)); } private function getInputArgumentData(InputArgument $argument) : array { return ['name' => $argument->getName(), 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => \preg_replace('/\\s*[\\r\\n]\\s*/', ' ', $argument->getDescription()), 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault()]; } private function getInputOptionData(InputOption $option, bool $negated = \false) : array { return $negated ? ['name' => '--no-' . $option->getName(), 'shortcut' => '', 'accept_value' => \false, 'is_value_required' => \false, 'is_multiple' => \false, 'description' => 'Negate the "--' . $option->getName() . '" option', 'default' => \false] : ['name' => '--' . $option->getName(), 'shortcut' => $option->getShortcut() ? '-' . \str_replace('|', '|-', $option->getShortcut()) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => \preg_replace('/\\s*[\\r\\n]\\s*/', ' ', $option->getDescription()), 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault()]; } private function getInputDefinitionData(InputDefinition $definition) : array { $inputArguments = []; foreach ($definition->getArguments() as $name => $argument) { $inputArguments[$name] = $this->getInputArgumentData($argument); } $inputOptions = []; foreach ($definition->getOptions() as $name => $option) { $inputOptions[$name] = $this->getInputOptionData($option); if ($option->isNegatable()) { $inputOptions['no-' . $name] = $this->getInputOptionData($option, \true); } } return ['arguments' => $inputArguments, 'options' => $inputOptions]; } private function getCommandData(Command $command, bool $short = \false) : array { $data = ['name' => $command->getName(), 'description' => $command->getDescription()]; if ($short) { $data += ['usage' => $command->getAliases()]; } else { $command->mergeApplicationDefinition(\false); $data += ['usage' => \array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), 'help' => $command->getProcessedHelp(), 'definition' => $this->getInputDefinitionData($command->getDefinition())]; } $data['hidden'] = $command->isHidden(); return $data; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Descriptor; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Helper\Helper; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Markdown descriptor. * * @author Jean-François Simon * * @internal */ class MarkdownDescriptor extends Descriptor { /** * {@inheritdoc} */ public function describe(OutputInterface $output, object $object, array $options = []) { $decorated = $output->isDecorated(); $output->setDecorated(\false); parent::describe($output, $object, $options); $output->setDecorated($decorated); } /** * {@inheritdoc} */ protected function write(string $content, bool $decorated = \true) { parent::write($content, $decorated); } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->write('#### `' . ($argument->getName() ?: '') . "`\n\n" . ($argument->getDescription() ? \preg_replace('/\\s*[\\r\\n]\\s*/', "\n", $argument->getDescription()) . "\n\n" : '') . '* Is required: ' . ($argument->isRequired() ? 'yes' : 'no') . "\n" . '* Is array: ' . ($argument->isArray() ? 'yes' : 'no') . "\n" . '* Default: `' . \str_replace("\n", '', \var_export($argument->getDefault(), \true)) . '`'); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { $name = '--' . $option->getName(); if ($option->isNegatable()) { $name .= '|--no-' . $option->getName(); } if ($option->getShortcut()) { $name .= '|-' . \str_replace('|', '|-', $option->getShortcut()) . ''; } $this->write('#### `' . $name . '`' . "\n\n" . ($option->getDescription() ? \preg_replace('/\\s*[\\r\\n]\\s*/', "\n", $option->getDescription()) . "\n\n" : '') . '* Accept value: ' . ($option->acceptValue() ? 'yes' : 'no') . "\n" . '* Is value required: ' . ($option->isValueRequired() ? 'yes' : 'no') . "\n" . '* Is multiple: ' . ($option->isArray() ? 'yes' : 'no') . "\n" . '* Is negatable: ' . ($option->isNegatable() ? 'yes' : 'no') . "\n" . '* Default: `' . \str_replace("\n", '', \var_export($option->getDefault(), \true)) . '`'); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { if ($showArguments = \count($definition->getArguments()) > 0) { $this->write('### Arguments'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); if (null !== ($describeInputArgument = $this->describeInputArgument($argument))) { $this->write($describeInputArgument); } } } if (\count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); if (null !== ($describeInputOption = $this->describeInputOption($option))) { $this->write($describeInputOption); } } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { if ($options['short'] ?? \false) { $this->write('`' . $command->getName() . "`\n" . \str_repeat('-', Helper::width($command->getName()) + 2) . "\n\n" . ($command->getDescription() ? $command->getDescription() . "\n\n" : '') . '### Usage' . "\n\n" . \array_reduce($command->getAliases(), function ($carry, $usage) { return $carry . '* `' . $usage . '`' . "\n"; })); return; } $command->mergeApplicationDefinition(\false); $this->write('`' . $command->getName() . "`\n" . \str_repeat('-', Helper::width($command->getName()) + 2) . "\n\n" . ($command->getDescription() ? $command->getDescription() . "\n\n" : '') . '### Usage' . "\n\n" . \array_reduce(\array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { return $carry . '* `' . $usage . '`' . "\n"; })); if ($help = $command->getProcessedHelp()) { $this->write("\n"); $this->write($help); } $definition = $command->getDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->write("\n\n"); $this->describeInputDefinition($definition); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); $title = $this->getApplicationTitle($application); $this->write($title . "\n" . \str_repeat('=', Helper::width($title))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**' . $namespace['id'] . ':**'); } $this->write("\n\n"); $this->write(\implode("\n", \array_map(function ($commandName) use($description) { return \sprintf('* [`%s`](#%s)', $commandName, \str_replace(':', '', $description->getCommand($commandName)->getName())); }, $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); if (null !== ($describeCommand = $this->describeCommand($command, $options))) { $this->write($describeCommand); } } } private function getApplicationTitle(Application $application) : string { if ('UNKNOWN' !== $application->getName()) { if ('UNKNOWN' !== $application->getVersion()) { return \sprintf('%s %s', $application->getName(), $application->getVersion()); } return $application->getName(); } return 'Console Tool'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Tester; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; /** * Eases the testing of console commands. * * @author Fabien Potencier * @author Robin Chalas */ class CommandTester { use TesterTrait; private $command; public function __construct(Command $command) { $this->command = $command; } /** * Executes the command. * * Available execution options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * capture_stderr_separately: Make output of stdOut and stdErr separately available * * @param array $input An array of command arguments and options * @param array $options An array of execution options * * @return int The command exit code */ public function execute(array $input, array $options = []) { // set the command name automatically if the application requires // this argument and no command name was passed if (!isset($input['command']) && null !== ($application = $this->command->getApplication()) && $application->getDefinition()->hasArgument('command')) { $input = \array_merge(['command' => $this->command->getName()], $input); } $this->input = new ArrayInput($input); // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN. $this->input->setStream(self::createStream($this->inputs)); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if (!isset($options['decorated'])) { $options['decorated'] = \false; } $this->initOutput($options); return $this->statusCode = $this->command->run($this->input, $this->output); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Tester; use _ContaoManager\PHPUnit\Framework\Assert; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutput; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Output\StreamOutput; use _ContaoManager\Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; /** * @author Amrouche Hamza */ trait TesterTrait { /** @var StreamOutput */ private $output; private $inputs = []; private $captureStreamsIndependently = \false; /** @var InputInterface */ private $input; /** @var int */ private $statusCode; /** * Gets the display returned by the last execution of the command or application. * * @return string * * @throws \RuntimeException If it's called before the execute method */ public function getDisplay(bool $normalize = \false) { if (null === $this->output) { throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); } \rewind($this->output->getStream()); $display = \stream_get_contents($this->output->getStream()); if ($normalize) { $display = \str_replace(\PHP_EOL, "\n", $display); } return $display; } /** * Gets the output written to STDERR by the application. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string */ public function getErrorOutput(bool $normalize = \false) { if (!$this->captureStreamsIndependently) { throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); } \rewind($this->output->getErrorOutput()->getStream()); $display = \stream_get_contents($this->output->getErrorOutput()->getStream()); if ($normalize) { $display = \str_replace(\PHP_EOL, "\n", $display); } return $display; } /** * Gets the input instance used by the last execution of the command or application. * * @return InputInterface */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the command or application. * * @return OutputInterface */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the command or application. * * @return int * * @throws \RuntimeException If it's called before the execute method */ public function getStatusCode() { if (null === $this->statusCode) { throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); } return $this->statusCode; } public function assertCommandIsSuccessful(string $message = '') : void { Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message); } /** * Sets the user inputs. * * @param array $inputs An array of strings representing each input * passed to the command input stream * * @return $this */ public function setInputs(array $inputs) { $this->inputs = $inputs; return $this; } /** * Initializes the output property. * * Available options: * * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * capture_stderr_separately: Make output of stdOut and stdErr separately available */ private function initOutput(array $options) { $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; if (!$this->captureStreamsIndependently) { $this->output = new StreamOutput(\fopen('php://memory', 'w', \false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } } else { $this->output = new ConsoleOutput($options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, $options['decorated'] ?? null); $errorOutput = new StreamOutput(\fopen('php://memory', 'w', \false)); $errorOutput->setFormatter($this->output->getFormatter()); $errorOutput->setVerbosity($this->output->getVerbosity()); $errorOutput->setDecorated($this->output->isDecorated()); $reflectedOutput = new \ReflectionObject($this->output); $strErrProperty = $reflectedOutput->getProperty('stderr'); $strErrProperty->setAccessible(\true); $strErrProperty->setValue($this->output, $errorOutput); $reflectedParent = $reflectedOutput->getParentClass(); $streamProperty = $reflectedParent->getProperty('stream'); $streamProperty->setAccessible(\true); $streamProperty->setValue($this->output, \fopen('php://memory', 'w', \false)); } } /** * @return resource */ private static function createStream(array $inputs) { $stream = \fopen('php://memory', 'r+', \false); foreach ($inputs as $input) { \fwrite($stream, $input . \PHP_EOL); } \rewind($stream); return $stream; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Tester; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Input\ArrayInput; /** * Eases the testing of console applications. * * When testing an application, don't forget to disable the auto exit flag: * * $application = new Application(); * $application->setAutoExit(false); * * @author Fabien Potencier */ class ApplicationTester { use TesterTrait; private $application; public function __construct(Application $application) { $this->application = $application; } /** * Executes the application. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * capture_stderr_separately: Make output of stdOut and stdErr separately available * * @return int The command exit code */ public function run(array $input, array $options = []) { $prevShellVerbosity = \getenv('SHELL_VERBOSITY'); try { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if ($this->inputs) { $this->input->setStream(self::createStream($this->inputs)); } $this->initOutput($options); return $this->statusCode = $this->application->run($this->input, $this->output); } finally { // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it // to its previous value to avoid one test's verbosity to spread to the following tests if (\false === $prevShellVerbosity) { if (\function_exists('putenv')) { @\putenv('SHELL_VERBOSITY'); } unset($_ENV['SHELL_VERBOSITY']); unset($_SERVER['SHELL_VERBOSITY']); } else { if (\function_exists('putenv')) { @\putenv('SHELL_VERBOSITY=' . $prevShellVerbosity); } $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Tester; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; /** * Eases the testing of command completion. * * @author Jérôme Tamarelle */ class CommandCompletionTester { private $command; public function __construct(Command $command) { $this->command = $command; } /** * Create completion suggestions from input tokens. */ public function complete(array $input) : array { $currentIndex = \count($input); if ('' === \end($input)) { \array_pop($input); } \array_unshift($input, $this->command->getName()); $completionInput = CompletionInput::fromTokens($input, $currentIndex); $completionInput->bind($this->command->getDefinition()); $suggestions = new CompletionSuggestions(); $this->command->complete($completionInput, $suggestions); $options = []; foreach ($suggestions->getOptionSuggestions() as $option) { $options[] = '--' . $option->getName(); } return \array_map('strval', \array_merge($options, $suggestions->getValueSuggestions())); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Tester\Constraint; use _ContaoManager\PHPUnit\Framework\Constraint\Constraint; use _ContaoManager\Symfony\Component\Console\Command\Command; final class CommandIsSuccessful extends Constraint { /** * {@inheritdoc} */ public function toString() : string { return 'is successful'; } /** * {@inheritdoc} */ protected function matches($other) : bool { return Command::SUCCESS === $other; } /** * {@inheritdoc} */ protected function failureDescription($other) : string { return 'the command ' . $this->toString(); } /** * {@inheritdoc} */ protected function additionalFailureDescription($other) : string { $mapping = [Command::FAILURE => 'Command failed.', Command::INVALID => 'Command was invalid.']; return $mapping[$other] ?? \sprintf('Command returned exit status %d.', $other); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Helper\HelperSet; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Nicolas Grekas */ final class LazyCommand extends Command { private $command; private $isEnabled; public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = \true) { $this->setName($name)->setAliases($aliases)->setHidden($isHidden)->setDescription($description); $this->command = $commandFactory; $this->isEnabled = $isEnabled; } public function ignoreValidationErrors() : void { $this->getCommand()->ignoreValidationErrors(); } public function setApplication(?Application $application = null) : void { if ($this->command instanceof parent) { $this->command->setApplication($application); } parent::setApplication($application); } public function setHelperSet(HelperSet $helperSet) : void { if ($this->command instanceof parent) { $this->command->setHelperSet($helperSet); } parent::setHelperSet($helperSet); } public function isEnabled() : bool { return $this->isEnabled ?? $this->getCommand()->isEnabled(); } public function run(InputInterface $input, OutputInterface $output) : int { return $this->getCommand()->run($input, $output); } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { $this->getCommand()->complete($input, $suggestions); } /** * @return $this */ public function setCode(callable $code) : self { $this->getCommand()->setCode($code); return $this; } /** * @internal */ public function mergeApplicationDefinition(bool $mergeArgs = \true) : void { $this->getCommand()->mergeApplicationDefinition($mergeArgs); } /** * @return $this */ public function setDefinition($definition) : self { $this->getCommand()->setDefinition($definition); return $this; } public function getDefinition() : InputDefinition { return $this->getCommand()->getDefinition(); } public function getNativeDefinition() : InputDefinition { return $this->getCommand()->getNativeDefinition(); } /** * @return $this */ public function addArgument(string $name, ?int $mode = null, string $description = '', $default = null) : self { $this->getCommand()->addArgument($name, $mode, $description, $default); return $this; } /** * @return $this */ public function addOption(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null) : self { $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default); return $this; } /** * @return $this */ public function setProcessTitle(string $title) : self { $this->getCommand()->setProcessTitle($title); return $this; } /** * @return $this */ public function setHelp(string $help) : self { $this->getCommand()->setHelp($help); return $this; } public function getHelp() : string { return $this->getCommand()->getHelp(); } public function getProcessedHelp() : string { return $this->getCommand()->getProcessedHelp(); } public function getSynopsis(bool $short = \false) : string { return $this->getCommand()->getSynopsis($short); } /** * @return $this */ public function addUsage(string $usage) : self { $this->getCommand()->addUsage($usage); return $this; } public function getUsages() : array { return $this->getCommand()->getUsages(); } /** * @return mixed */ public function getHelper(string $name) { return $this->getCommand()->getHelper($name); } public function getCommand() : parent { if (!$this->command instanceof \Closure) { return $this->command; } $command = $this->command = ($this->command)(); $command->setApplication($this->getApplication()); if (null !== $this->getHelperSet()) { $command->setHelperSet($this->getHelperSet()); } $command->setName($this->getName())->setAliases($this->getAliases())->setHidden($this->isHidden())->setDescription($this->getDescription()); // Will throw if the command is not correctly initialized. $command->getDefinition(); return $command; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Lock\LockFactory; use _ContaoManager\Symfony\Component\Lock\LockInterface; use _ContaoManager\Symfony\Component\Lock\Store\FlockStore; use _ContaoManager\Symfony\Component\Lock\Store\SemaphoreStore; /** * Basic lock feature for commands. * * @author Geoffrey Brier */ trait LockableTrait { /** @var LockInterface|null */ private $lock; /** * Locks a command. */ private function lock(?string $name = null, bool $blocking = \false) : bool { if (!\class_exists(SemaphoreStore::class)) { throw new LogicException('To enable the locking feature you must install the symfony/lock component.'); } if (null !== $this->lock) { throw new LogicException('A lock is already in place.'); } if (SemaphoreStore::isSupported()) { $store = new SemaphoreStore(); } else { $store = new FlockStore(); } $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); if (!$this->lock->acquire($blocking)) { $this->lock = null; return \false; } return \true; } /** * Releases the command lock if there is one. */ private function release() { if ($this->lock) { $this->lock->release(); $this->lock = null; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Descriptor\ApplicationDescription; use _ContaoManager\Symfony\Component\Console\Helper\DescriptorHelper; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * HelpCommand displays the help for a given command. * * @author Fabien Potencier */ class HelpCommand extends Command { private $command; /** * {@inheritdoc} */ protected function configure() { $this->ignoreValidationErrors(); $this->setName('help')->setDefinition([new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help')])->setDescription('Display help for a command')->setHelp(<<<'EOF' The %command.name% command displays help for a given command: %command.full_name% list You can also output the help in other formats by using the --format option: %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ); } public function setCommand(Command $command) { $this->command = $command; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->getApplication()->find($input->getArgument('command_name')); } $helper = new DescriptorHelper(); $helper->describe($output, $this->command, ['format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw')]); $this->command = null; return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('command_name')) { $descriptor = new ApplicationDescription($this->getApplication()); $suggestions->suggestValues(\array_keys($descriptor->getCommands())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Completion\Output\BashCompletionOutput; use _ContaoManager\Symfony\Component\Console\Completion\Output\CompletionOutputInterface; use _ContaoManager\Symfony\Component\Console\Exception\CommandNotFoundException; use _ContaoManager\Symfony\Component\Console\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Responsible for providing the values to the shell completion. * * @author Wouter de Jong */ final class CompleteCommand extends Command { protected static $defaultName = '|_complete'; protected static $defaultDescription = 'Internal command to provide shell completion suggestions'; private $completionOutputs; private $isDebug = \false; /** * @param array> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value */ public function __construct(array $completionOutputs = []) { // must be set before the parent constructor, as the property value is used in configure() $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class]; parent::__construct(); } protected function configure() : void { $this->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("' . \implode('", "', \array_keys($this->completionOutputs)) . '")')->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script'); } protected function initialize(InputInterface $input, OutputInterface $output) { $this->isDebug = \filter_var(\getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN); } protected function execute(InputInterface $input, OutputInterface $output) : int { try { // uncomment when a bugfix or BC break has been introduced in the shell completion scripts // $version = $input->getOption('symfony'); // if ($version && version_compare($version, 'x.y', '>=')) { // $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version); // $this->log($message); // $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); // return 126; // } $shell = $input->getOption('shell'); if (!$shell) { throw new \RuntimeException('The "--shell" option must be set.'); } if (!($completionOutput = $this->completionOutputs[$shell] ?? \false)) { throw new \RuntimeException(\sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, \implode('", "', \array_keys($this->completionOutputs)))); } $completionInput = $this->createCompletionInput($input); $suggestions = new CompletionSuggestions(); $this->log(['', '' . \date('Y-m-d H:i:s') . '', 'Input: ("|" indicates the cursor position)', ' ' . (string) $completionInput, 'Command:', ' ' . (string) \implode(' ', $_SERVER['argv']), 'Messages:']); $command = $this->findCommand($completionInput, $output); if (null === $command) { $this->log(' No command found, completing using the Application class.'); $this->getApplication()->complete($completionInput, $suggestions); } elseif ($completionInput->mustSuggestArgumentValuesFor('command') && $command->getName() !== $completionInput->getCompletionValue() && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), \true)) { $this->log(' No command found, completing using the Application class.'); // expand shortcut names ("cache:cl") into their full name ("cache:clear") $suggestions->suggestValues(\array_filter(\array_merge([$command->getName()], $command->getAliases()))); } else { $command->mergeApplicationDefinition(); $completionInput->bind($command->getDefinition()); if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { $this->log(' Completing option names for the ' . \get_class($command instanceof LazyCommand ? $command->getCommand() : $command) . ' command.'); $suggestions->suggestOptions($command->getDefinition()->getOptions()); } else { $this->log([' Completing using the ' . \get_class($command instanceof LazyCommand ? $command->getCommand() : $command) . ' class.', ' Completing ' . $completionInput->getCompletionType() . ' for ' . $completionInput->getCompletionName() . '']); if (null !== ($compval = $completionInput->getCompletionValue())) { $this->log(' Current value: ' . $compval . ''); } $command->complete($completionInput, $suggestions); } } /** @var CompletionOutputInterface $completionOutput */ $completionOutput = new $completionOutput(); $this->log('Suggestions:'); if ($options = $suggestions->getOptionSuggestions()) { $this->log(' --' . \implode(' --', \array_map(function ($o) { return $o->getName(); }, $options))); } elseif ($values = $suggestions->getValueSuggestions()) { $this->log(' ' . \implode(' ', $values)); } else { $this->log(' No suggestions were provided'); } $completionOutput->write($suggestions, $output); } catch (\Throwable $e) { $this->log(['Error!', (string) $e]); if ($output->isDebug()) { throw $e; } return 2; } return 0; } private function createCompletionInput(InputInterface $input) : CompletionInput { $currentIndex = $input->getOption('current'); if (!$currentIndex || !\ctype_digit($currentIndex)) { throw new \RuntimeException('The "--current" option must be set and it must be an integer.'); } $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex); try { $completionInput->bind($this->getApplication()->getDefinition()); } catch (ExceptionInterface $e) { } return $completionInput; } private function findCommand(CompletionInput $completionInput, OutputInterface $output) : ?Command { try { $inputName = $completionInput->getFirstArgument(); if (null === $inputName) { return null; } return $this->getApplication()->find($inputName); } catch (CommandNotFoundException $e) { } return null; } private function log($messages) : void { if (!$this->isDebug) { return; } $commandName = \basename($_SERVER['argv'][0]); \file_put_contents(\sys_get_temp_dir() . '/sf_' . $commandName . '.log', \implode(\PHP_EOL, (array) $messages) . \PHP_EOL, \FILE_APPEND); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Descriptor\ApplicationDescription; use _ContaoManager\Symfony\Component\Console\Helper\DescriptorHelper; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier */ class ListCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this->setName('list')->setDefinition([new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments')])->setDescription('List commands')->setHelp(<<<'EOF' The %command.name% command lists all commands: %command.full_name% You can also display the commands for a specific namespace: %command.full_name% test You can also output the information in other formats by using the --format option: %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): %command.full_name% --raw EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), ['format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), 'short' => $input->getOption('short')]); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('namespace')) { $descriptor = new ApplicationDescription($this->getApplication()); $suggestions->suggestValues(\array_keys($descriptor->getNamespaces())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Process\Process; /** * Dumps the completion script for the current shell. * * @author Wouter de Jong */ final class DumpCompletionCommand extends Command { protected static $defaultName = 'completion'; protected static $defaultDescription = 'Dump the shell completion script'; public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { if ($input->mustSuggestArgumentValuesFor('shell')) { $suggestions->suggestValues($this->getSupportedShells()); } } protected function configure() { $fullCommand = $_SERVER['PHP_SELF']; $commandName = \basename($fullCommand); $fullCommand = @\realpath($fullCommand) ?: $fullCommand; $this->setHelp(<<%command.name% command dumps the shell completion script required to use shell autocompletion (currently only bash completion is supported). Static installation ------------------- Dump the script to a global completion file and restart your shell: %command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName} Or dump the script to a local file and source it: %command.full_name% bash > completion.sh # source the file whenever you use the project source completion.sh # or add this line at the end of your "~/.bashrc" file: source /path/to/completion.sh Dynamic installation -------------------- Add this to the end of your shell configuration file (e.g. "~/.bashrc"): eval "\$({$fullCommand} completion bash)" EOH )->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given')->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log'); } protected function execute(InputInterface $input, OutputInterface $output) : int { $commandName = \basename($_SERVER['argv'][0]); if ($input->getOption('debug')) { $this->tailDebugLog($commandName, $output); return 0; } $shell = $input->getArgument('shell') ?? self::guessShell(); $completionFile = __DIR__ . '/../Resources/completion.' . $shell; if (!\file_exists($completionFile)) { $supportedShells = $this->getSupportedShells(); if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if ($shell) { $output->writeln(\sprintf('Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").', $shell, \implode('", "', $supportedShells))); } else { $output->writeln(\sprintf('Shell not detected, Symfony shell completion only supports "%s").', \implode('", "', $supportedShells))); } return 2; } $output->write(\str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], \file_get_contents($completionFile))); return 0; } private static function guessShell() : string { return \basename($_SERVER['SHELL'] ?? ''); } private function tailDebugLog(string $commandName, OutputInterface $output) : void { $debugFile = \sys_get_temp_dir() . '/sf_' . $commandName . '.log'; if (!\file_exists($debugFile)) { \touch($debugFile); } $process = new Process(['tail', '-f', $debugFile], null, null, null, 0); $process->run(function (string $type, string $line) use($output) : void { $output->write($line); }); } /** * @return string[] */ private function getSupportedShells() : array { $shells = []; foreach (new \DirectoryIterator(__DIR__ . '/../Resources/') as $file) { if (\str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) { $shells[] = $file->getExtension(); } } return $shells; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; /** * Interface for command reacting to signal. * * @author Grégoire Pineau */ interface SignalableCommandInterface { /** * Returns the list of signals to subscribe. */ public function getSubscribedSignals() : array; /** * The method will be called when the application is signaled. */ public function handleSignal(int $signal) : void; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Command; use _ContaoManager\Symfony\Component\Console\Application; use _ContaoManager\Symfony\Component\Console\Attribute\AsCommand; use _ContaoManager\Symfony\Component\Console\Completion\CompletionInput; use _ContaoManager\Symfony\Component\Console\Completion\CompletionSuggestions; use _ContaoManager\Symfony\Component\Console\Exception\ExceptionInterface; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Helper\HelperSet; use _ContaoManager\Symfony\Component\Console\Input\InputArgument; use _ContaoManager\Symfony\Component\Console\Input\InputDefinition; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\InputOption; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Base class for all commands. * * @author Fabien Potencier */ class Command { // see https://tldp.org/LDP/abs/html/exitcodes.html public const SUCCESS = 0; public const FAILURE = 1; public const INVALID = 2; /** * @var string|null The default command name */ protected static $defaultName; /** * @var string|null The default command description */ protected static $defaultDescription; private $application; private $name; private $processTitle; private $aliases = []; private $definition; private $hidden = \false; private $help = ''; private $description = ''; private $fullDefinition; private $ignoreValidationErrors = \false; private $code; private $synopsis = []; private $usages = []; private $helperSet; /** * @return string|null */ public static function getDefaultName() { $class = static::class; if (\PHP_VERSION_ID >= 80000 && ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class))) { return $attribute[0]->newInstance()->name; } $r = new \ReflectionProperty($class, 'defaultName'); return $class === $r->class ? static::$defaultName : null; } public static function getDefaultDescription() : ?string { $class = static::class; if (\PHP_VERSION_ID >= 80000 && ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class))) { return $attribute[0]->newInstance()->description; } $r = new \ReflectionProperty($class, 'defaultDescription'); return $class === $r->class ? static::$defaultDescription : null; } /** * @param string|null $name The name of the command; passing null means it must be set in configure() * * @throws LogicException When the command name is empty */ public function __construct(?string $name = null) { $this->definition = new InputDefinition(); if (null === $name && null !== ($name = static::getDefaultName())) { $aliases = \explode('|', $name); if ('' === ($name = \array_shift($aliases))) { $this->setHidden(\true); $name = \array_shift($aliases); } $this->setAliases($aliases); } if (null !== $name) { $this->setName($name); } if ('' === $this->description) { $this->setDescription(static::getDefaultDescription() ?? ''); } $this->configure(); } /** * Ignores validation errors. * * This is mainly useful for the help command. */ public function ignoreValidationErrors() { $this->ignoreValidationErrors = \true; } public function setApplication(?Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } $this->fullDefinition = null; } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Gets the helper set. * * @return HelperSet|null */ public function getHelperSet() { return $this->helperSet; } /** * Gets the application instance for this command. * * @return Application|null */ public function getApplication() { return $this->application; } /** * Checks whether the command is enabled or not in the current environment. * * Override this to check for x or y and return false if the command cannot * run properly under the current conditions. * * @return bool */ public function isEnabled() { return \true; } /** * Configures the current command. */ protected function configure() { } /** * Executes the current command. * * This method is not abstract because you can use this class * as a concrete class. In this case, instead of defining the * execute() method, you set the code to execute by passing * a Closure to the setCode() method. * * @return int 0 if everything went fine, or an exit code * * @throws LogicException When this abstract method is not implemented * * @see setCode() */ protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } /** * Interacts with the user. * * This method is executed before the InputDefinition is validated. * This means that this is the only place where the command can * interactively ask for values of missing required arguments. */ protected function interact(InputInterface $input, OutputInterface $output) { } /** * Initializes the command after the input has been bound and before the input * is validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialized based on the input arguments and options. * * @see InputInterface::bind() * @see InputInterface::validate() */ protected function initialize(InputInterface $input, OutputInterface $output) { } /** * Runs the command. * * The code to execute is either defined directly with the * setCode() method or by overriding the execute() method * in a sub-class. * * @return int The command exit code * * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}. * * @see setCode() * @see execute() */ public function run(InputInterface $input, OutputInterface $output) { // add the application arguments and options $this->mergeApplicationDefinition(); // bind the input against the command specific arguments/options try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if (null !== $this->processTitle) { if (\function_exists('cli_set_process_title')) { if (!@\cli_set_process_title($this->processTitle)) { if ('Darwin' === \PHP_OS) { $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); } else { \cli_set_process_title($this->processTitle); } } } elseif (\function_exists('_ContaoManager\\setproctitle')) { setproctitle($this->processTitle); } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { $output->writeln('Install the proctitle PECL to be able to change the process title.'); } } if ($input->isInteractive()) { $this->interact($input, $output); } // The command name argument is often omitted when a command is executed directly with its run() method. // It would fail the validation if we didn't make sure the command argument is present, // since it's required by the application. if ($input->hasArgument('command') && null === $input->getArgument('command')) { $input->setArgument('command', $this->getName()); } $input->validate(); if ($this->code) { $statusCode = ($this->code)($input, $output); } else { $statusCode = $this->execute($input, $output); if (!\is_int($statusCode)) { throw new \TypeError(\sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, \get_debug_type($statusCode))); } } return \is_numeric($statusCode) ? (int) $statusCode : 0; } /** * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void { } /** * Sets the code to execute when running this command. * * If this method is used, it overrides the code defined * in the execute() method. * * @param callable $code A callable(InputInterface $input, OutputInterface $output) * * @return $this * * @throws InvalidArgumentException * * @see execute() */ public function setCode(callable $code) { if ($code instanceof \Closure) { $r = new \ReflectionFunction($code); if (null === $r->getClosureThis()) { \set_error_handler(static function () { }); try { if ($c = \Closure::bind($code, $this)) { $code = $c; } } finally { \restore_error_handler(); } } } $this->code = $code; return $this; } /** * Merges the application definition with the command definition. * * This method is not part of public API and should not be used directly. * * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments * * @internal */ public function mergeApplicationDefinition(bool $mergeArgs = \true) { if (null === $this->application) { return; } $this->fullDefinition = new InputDefinition(); $this->fullDefinition->setOptions($this->definition->getOptions()); $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions()); if ($mergeArgs) { $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments()); $this->fullDefinition->addArguments($this->definition->getArguments()); } else { $this->fullDefinition->setArguments($this->definition->getArguments()); } } /** * Sets an array of argument and option instances. * * @param array|InputDefinition $definition An array of argument and option instances or a definition instance * * @return $this */ public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->fullDefinition = null; return $this; } /** * Gets the InputDefinition attached to this Command. * * @return InputDefinition */ public function getDefinition() { return $this->fullDefinition ?? $this->getNativeDefinition(); } /** * Gets the InputDefinition to be used to create representations of this Command. * * Can be overridden to provide the original command representation when it would otherwise * be changed by merging with the application InputDefinition. * * This method is not part of public API and should not be used directly. * * @return InputDefinition */ public function getNativeDefinition() { if (null === $this->definition) { throw new LogicException(\sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); } return $this->definition; } /** * Adds an argument. * * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * * @return $this * * @throws InvalidArgumentException When argument mode is not valid */ public function addArgument(string $name, ?int $mode = null, string $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); if (null !== $this->fullDefinition) { $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default)); } return $this; } /** * Adds an option. * * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) * * @return $this * * @throws InvalidArgumentException If option mode is invalid or incompatible */ public function addOption(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); if (null !== $this->fullDefinition) { $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); } return $this; } /** * Sets the name of the command. * * This method can set both the namespace and the name if * you separate them by a colon (:) * * $command->setName('foo:bar'); * * @return $this * * @throws InvalidArgumentException When the name is invalid */ public function setName(string $name) { $this->validateName($name); $this->name = $name; return $this; } /** * Sets the process title of the command. * * This feature should be used only when creating a long process command, * like a daemon. * * @return $this */ public function setProcessTitle(string $title) { $this->processTitle = $title; return $this; } /** * Returns the command name. * * @return string|null */ public function getName() { return $this->name; } /** * @param bool $hidden Whether or not the command should be hidden from the list of commands * The default value will be true in Symfony 6.0 * * @return $this * * @final since Symfony 5.1 */ public function setHidden(bool $hidden) { $this->hidden = $hidden; return $this; } /** * @return bool whether the command should be publicly shown or not */ public function isHidden() { return $this->hidden; } /** * Sets the description for the command. * * @return $this */ public function setDescription(string $description) { $this->description = $description; return $this; } /** * Returns the description for the command. * * @return string */ public function getDescription() { return $this->description; } /** * Sets the help for the command. * * @return $this */ public function setHelp(string $help) { $this->help = $help; return $this; } /** * Returns the help for the command. * * @return string */ public function getHelp() { return $this->help; } /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. * * @return string */ public function getProcessedHelp() { $name = $this->name; $isSingleCommand = $this->application && $this->application->isSingleCommand(); $placeholders = ['%command.name%', '%command.full_name%']; $replacements = [$name, $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'] . ' ' . $name]; return \str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); } /** * Sets the aliases for the command. * * @param string[] $aliases An array of aliases for the command * * @return $this * * @throws InvalidArgumentException When an alias is invalid */ public function setAliases(iterable $aliases) { $list = []; foreach ($aliases as $alias) { $this->validateName($alias); $list[] = $alias; } $this->aliases = \is_array($aliases) ? $aliases : $list; return $this; } /** * Returns the aliases for the command. * * @return array */ public function getAliases() { return $this->aliases; } /** * Returns the synopsis for the command. * * @param bool $short Whether to show the short version of the synopsis (with options folded) or not * * @return string */ public function getSynopsis(bool $short = \false) { $key = $short ? 'short' : 'long'; if (!isset($this->synopsis[$key])) { $this->synopsis[$key] = \trim(\sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); } return $this->synopsis[$key]; } /** * Add a command usage example, it'll be prefixed with the command name. * * @return $this */ public function addUsage(string $usage) { if (!\str_starts_with($usage, $this->name)) { $usage = \sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } /** * Returns alternative usages of the command. * * @return array */ public function getUsages() { return $this->usages; } /** * Gets a helper instance by name. * * @return mixed * * @throws LogicException if no HelperSet is defined * @throws InvalidArgumentException if the helper is not defined */ public function getHelper(string $name) { if (null === $this->helperSet) { throw new LogicException(\sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); } return $this->helperSet->get($name); } /** * Validates a command name. * * It must be non-empty and parts can optionally be separated by ":". * * @throws InvalidArgumentException When the name is invalid */ private function validateName(string $name) { if (!\preg_match('/^[^\\:]++(\\:[^\\:]++)*$/', $name)) { throw new InvalidArgumentException(\sprintf('Command name "%s" is invalid.', $name)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; /** * Defines the styles for a Table. * * @author Fabien Potencier * @author Саша Стаменковић * @author Dany Maillard */ class TableStyle { private $paddingChar = ' '; private $horizontalOutsideBorderChar = '-'; private $horizontalInsideBorderChar = '-'; private $verticalOutsideBorderChar = '|'; private $verticalInsideBorderChar = '|'; private $crossingChar = '+'; private $crossingTopRightChar = '+'; private $crossingTopMidChar = '+'; private $crossingTopLeftChar = '+'; private $crossingMidRightChar = '+'; private $crossingBottomRightChar = '+'; private $crossingBottomMidChar = '+'; private $crossingBottomLeftChar = '+'; private $crossingMidLeftChar = '+'; private $crossingTopLeftBottomChar = '+'; private $crossingTopMidBottomChar = '+'; private $crossingTopRightBottomChar = '+'; private $headerTitleFormat = ' %s '; private $footerTitleFormat = ' %s '; private $cellHeaderFormat = '%s'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = \STR_PAD_RIGHT; /** * Sets padding character, used for cell padding. * * @return $this */ public function setPaddingChar(string $paddingChar) { if (!$paddingChar) { throw new LogicException('The padding char must not be empty.'); } $this->paddingChar = $paddingChar; return $this; } /** * Gets padding character, used for cell padding. * * @return string */ public function getPaddingChar() { return $this->paddingChar; } /** * Sets horizontal border characters. * * * ╔═══════════════╤══════════════════════════╤══════════════════╗ * 1 ISBN 2 Title │ Author ║ * ╠═══════════════╪══════════════════════════╪══════════════════╣ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ * ╚═══════════════╧══════════════════════════╧══════════════════╝ * * * @return $this */ public function setHorizontalBorderChars(string $outside, ?string $inside = null) : self { $this->horizontalOutsideBorderChar = $outside; $this->horizontalInsideBorderChar = $inside ?? $outside; return $this; } /** * Sets vertical border characters. * * * ╔═══════════════╤══════════════════════════╤══════════════════╗ * ║ ISBN │ Title │ Author ║ * ╠═══════1═══════╪══════════════════════════╪══════════════════╣ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ * ╟───────2───────┼──────────────────────────┼──────────────────╢ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ * ╚═══════════════╧══════════════════════════╧══════════════════╝ * * * @return $this */ public function setVerticalBorderChars(string $outside, ?string $inside = null) : self { $this->verticalOutsideBorderChar = $outside; $this->verticalInsideBorderChar = $inside ?? $outside; return $this; } /** * Gets border characters. * * @internal */ public function getBorderChars() : array { return [$this->horizontalOutsideBorderChar, $this->verticalOutsideBorderChar, $this->horizontalInsideBorderChar, $this->verticalInsideBorderChar]; } /** * Sets crossing characters. * * Example: * * 1═══════════════2══════════════════════════2══════════════════3 * ║ ISBN │ Title │ Author ║ * 8'══════════════0'═════════════════════════0'═════════════════4' * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ * 8───────────────0──────────────────────────0──────────────────4 * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ * 7═══════════════6══════════════════════════6══════════════════5 * * * @param string $cross Crossing char (see #0 of example) * @param string $topLeft Top left char (see #1 of example) * @param string $topMid Top mid char (see #2 of example) * @param string $topRight Top right char (see #3 of example) * @param string $midRight Mid right char (see #4 of example) * @param string $bottomRight Bottom right char (see #5 of example) * @param string $bottomMid Bottom mid char (see #6 of example) * @param string $bottomLeft Bottom left char (see #7 of example) * @param string $midLeft Mid left char (see #8 of example) * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null * * @return $this */ public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null) : self { $this->crossingChar = $cross; $this->crossingTopLeftChar = $topLeft; $this->crossingTopMidChar = $topMid; $this->crossingTopRightChar = $topRight; $this->crossingMidRightChar = $midRight; $this->crossingBottomRightChar = $bottomRight; $this->crossingBottomMidChar = $bottomMid; $this->crossingBottomLeftChar = $bottomLeft; $this->crossingMidLeftChar = $midLeft; $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft; $this->crossingTopMidBottomChar = $topMidBottom ?? $cross; $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight; return $this; } /** * Sets default crossing character used for each cross. * * @see {@link setCrossingChars()} for setting each crossing individually. */ public function setDefaultCrossingChar(string $char) : self { return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); } /** * Gets crossing character. * * @return string */ public function getCrossingChar() { return $this->crossingChar; } /** * Gets crossing characters. * * @internal */ public function getCrossingChars() : array { return [$this->crossingChar, $this->crossingTopLeftChar, $this->crossingTopMidChar, $this->crossingTopRightChar, $this->crossingMidRightChar, $this->crossingBottomRightChar, $this->crossingBottomMidChar, $this->crossingBottomLeftChar, $this->crossingMidLeftChar, $this->crossingTopLeftBottomChar, $this->crossingTopMidBottomChar, $this->crossingTopRightBottomChar]; } /** * Sets header cell format. * * @return $this */ public function setCellHeaderFormat(string $cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } /** * Gets header cell format. * * @return string */ public function getCellHeaderFormat() { return $this->cellHeaderFormat; } /** * Sets row cell format. * * @return $this */ public function setCellRowFormat(string $cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } /** * Gets row cell format. * * @return string */ public function getCellRowFormat() { return $this->cellRowFormat; } /** * Sets row cell content format. * * @return $this */ public function setCellRowContentFormat(string $cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } /** * Gets row cell content format. * * @return string */ public function getCellRowContentFormat() { return $this->cellRowContentFormat; } /** * Sets table border format. * * @return $this */ public function setBorderFormat(string $borderFormat) { $this->borderFormat = $borderFormat; return $this; } /** * Gets table border format. * * @return string */ public function getBorderFormat() { return $this->borderFormat; } /** * Sets cell padding type. * * @return $this */ public function setPadType(int $padType) { if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], \true)) { throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); } $this->padType = $padType; return $this; } /** * Gets cell padding type. * * @return int */ public function getPadType() { return $this->padType; } public function getHeaderTitleFormat() : string { return $this->headerTitleFormat; } /** * @return $this */ public function setHeaderTitleFormat(string $format) : self { $this->headerTitleFormat = $format; return $this; } public function getFooterTitleFormat() : string { return $this->footerTitleFormat; } /** * @return $this */ public function setFooterTitleFormat(string $format) : self { $this->footerTitleFormat = $format; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; /** * @internal */ class TableRows implements \IteratorAggregate { private $generator; public function __construct(\Closure $generator) { $this->generator = $generator; } public function getIterator() : \Traversable { return ($this->generator)(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Question\ChoiceQuestion; use _ContaoManager\Symfony\Component\Console\Question\ConfirmationQuestion; use _ContaoManager\Symfony\Component\Console\Question\Question; use _ContaoManager\Symfony\Component\Console\Style\SymfonyStyle; /** * Symfony Style Guide compliant question helper. * * @author Kevin Bond */ class SymfonyQuestionHelper extends QuestionHelper { /** * {@inheritdoc} */ protected function writePrompt(OutputInterface $output, Question $question) { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); $default = $question->getDefault(); if ($question->isMultiline()) { $text .= \sprintf(' (press %s to continue)', $this->getEofShortcut()); } switch (\true) { case null === $default: $text = \sprintf(' %s:', $text); break; case $question instanceof ConfirmationQuestion: $text = \sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); break; case $question instanceof ChoiceQuestion && $question->isMultiselect(): $choices = $question->getChoices(); $default = \explode(',', $default); foreach ($default as $key => $value) { $default[$key] = $choices[\trim($value)]; } $text = \sprintf(' %s [%s]:', $text, OutputFormatter::escape(\implode(', ', $default))); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = \sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); break; default: $text = \sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); } $output->writeln($text); $prompt = ' > '; if ($question instanceof ChoiceQuestion) { $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); $prompt = $question->getPrompt(); } $output->write($prompt); } /** * {@inheritdoc} */ protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } private function getEofShortcut() : string { if ('Windows' === \PHP_OS_FAMILY) { return 'Ctrl+Z then Enter'; } return 'Ctrl+D'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; /** * HelperInterface is the interface all helpers must implement. * * @author Fabien Potencier */ interface HelperInterface { /** * Sets the helper set associated with this helper. */ public function setHelperSet(?HelperSet $helperSet = null); /** * Gets the helper set associated with this helper. * * @return HelperSet|null */ public function getHelperSet(); /** * Returns the canonical name of this helper. * * @return string */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Abdellatif Ait boudad */ class TableCell { private $value; private $options = ['rowspan' => 1, 'colspan' => 1, 'style' => null]; public function __construct(string $value = '', array $options = []) { $this->value = $value; // check option names if ($diff = \array_diff(\array_keys($options), \array_keys($this->options))) { throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', \implode('\', \'', $diff))); } if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) { throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".'); } $this->options = \array_merge($this->options, $options); } /** * Returns the cell value. * * @return string */ public function __toString() { return $this->value; } /** * Gets number of colspan. * * @return int */ public function getColspan() { return (int) $this->options['colspan']; } /** * Gets number of rowspan. * * @return int */ public function getRowspan() { return (int) $this->options['rowspan']; } public function getStyle() : ?TableCellStyle { return $this->options['style']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Process\Exception\ProcessFailedException; use _ContaoManager\Symfony\Component\Process\Process; /** * The ProcessHelper class provides helpers to run external processes. * * @author Fabien Potencier * * @final */ class ProcessHelper extends Helper { /** * Runs an external process. * * @param array|Process $cmd An instance of Process or an array of the command and arguments * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR */ public function run(OutputInterface $output, $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) : Process { if (!\class_exists(Process::class)) { throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); } if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); if ($cmd instanceof Process) { $cmd = [$cmd]; } if (!\is_array($cmd)) { throw new \TypeError(\sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, \get_debug_type($cmd))); } if (\is_string($cmd[0] ?? null)) { $process = new Process($cmd); $cmd = []; } elseif (($cmd[0] ?? null) instanceof Process) { $process = $cmd[0]; unset($cmd[0]); } else { throw new \InvalidArgumentException(\sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__)); } if ($verbosity <= $output->getVerbosity()) { $output->write($formatter->start(\spl_object_hash($process), $this->escapeString($process->getCommandLine()))); } if ($output->isDebug()) { $callback = $this->wrapCallback($output, $process, $callback); } $process->run($callback, $cmd); if ($verbosity <= $output->getVerbosity()) { $message = $process->isSuccessful() ? 'Command ran successfully' : \sprintf('%s Command did not run successfully', $process->getExitCode()); $output->write($formatter->stop(\spl_object_hash($process), $message, $process->isSuccessful())); } if (!$process->isSuccessful() && null !== $error) { $output->writeln(\sprintf('%s', $this->escapeString($error))); } return $process; } /** * Runs the process. * * This is identical to run() except that an exception is thrown if the process * exits with a non-zero exit code. * * @param array|Process $cmd An instance of Process or a command to run * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @throws ProcessFailedException * * @see run() */ public function mustRun(OutputInterface $output, $cmd, ?string $error = null, ?callable $callback = null) : Process { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } /** * Wraps a Process callback to add debugging output. */ public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null) : callable { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); return function ($type, $buffer) use($output, $process, $callback, $formatter) { $output->write($formatter->progress(\spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); if (null !== $callback) { $callback($type, $buffer); } }; } private function escapeString(string $str) : string { return \str_replace('<', '\\<', $str); } /** * {@inheritdoc} */ public function getName() : string { return 'process'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; /** * Marks a row as being a separator. * * @author Fabien Potencier */ class TableSeparator extends TableCell { public function __construct(array $options = []) { parent::__construct('', $options); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; /** * The Formatter class provides helpers to format messages. * * @author Fabien Potencier */ class FormatterHelper extends Helper { /** * Formats a message within a section. * * @return string */ public function formatSection(string $section, string $message, string $style = 'info') { return \sprintf('<%s>[%s] %s', $style, $section, $style, $message); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * * @return string */ public function formatBlock($messages, string $style, bool $large = \false) { if (!\is_array($messages)) { $messages = [$messages]; } $len = 0; $lines = []; foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = \sprintf($large ? ' %s ' : ' %s ', $message); $len = \max(self::width($message) + ($large ? 4 : 2), $len); } $messages = $large ? [\str_repeat(' ', $len)] : []; for ($i = 0; isset($lines[$i]); ++$i) { $messages[] = $lines[$i] . \str_repeat(' ', $len - self::width($lines[$i])); } if ($large) { $messages[] = \str_repeat(' ', $len); } for ($i = 0; isset($messages[$i]); ++$i) { $messages[$i] = \sprintf('<%s>%s', $style, $messages[$i], $style); } return \implode("\n", $messages); } /** * Truncates a message to the given length. * * @return string */ public function truncate(string $message, int $length, string $suffix = '...') { $computedLength = $length - self::width($suffix); if ($computedLength > self::width($message)) { return $message; } return self::substr($message, 0, $length) . $suffix; } /** * {@inheritdoc} */ public function getName() { return 'formatter'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Yewhen Khoptynskyi */ class TableCellStyle { public const DEFAULT_ALIGN = 'left'; private const TAG_OPTIONS = ['fg', 'bg', 'options']; private const ALIGN_MAP = ['left' => \STR_PAD_RIGHT, 'center' => \STR_PAD_BOTH, 'right' => \STR_PAD_LEFT]; private $options = ['fg' => 'default', 'bg' => 'default', 'options' => null, 'align' => self::DEFAULT_ALIGN, 'cellFormat' => null]; public function __construct(array $options = []) { if ($diff = \array_diff(\array_keys($options), \array_keys($this->options))) { throw new InvalidArgumentException(\sprintf('The TableCellStyle does not support the following options: \'%s\'.', \implode('\', \'', $diff))); } if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) { throw new InvalidArgumentException(\sprintf('Wrong align value. Value must be following: \'%s\'.', \implode('\', \'', \array_keys(self::ALIGN_MAP)))); } $this->options = \array_merge($this->options, $options); } public function getOptions() : array { return $this->options; } /** * Gets options we need for tag for example fg, bg. * * @return string[] */ public function getTagOptions() { return \array_filter($this->getOptions(), function ($key) { return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]); }, \ARRAY_FILTER_USE_KEY); } /** * @return int */ public function getPadByAlign() { return self::ALIGN_MAP[$this->getOptions()['align']]; } public function getCellFormat() : ?string { return $this->getOptions()['cellFormat']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Cursor; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleSectionOutput; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Terminal; /** * The ProgressBar provides helpers to display progress output. * * @author Fabien Potencier * @author Chris Jones */ final class ProgressBar { public const FORMAT_VERBOSE = 'verbose'; public const FORMAT_VERY_VERBOSE = 'very_verbose'; public const FORMAT_DEBUG = 'debug'; public const FORMAT_NORMAL = 'normal'; private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax'; private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax'; private const FORMAT_DEBUG_NOMAX = 'debug_nomax'; private const FORMAT_NORMAL_NOMAX = 'normal_nomax'; private $barWidth = 28; private $barChar; private $emptyBarChar = '-'; private $progressChar = '>'; private $format; private $internalFormat; private $redrawFreq = 1; private $writeCount; private $lastWriteTime; private $minSecondsBetweenRedraws = 0; private $maxSecondsBetweenRedraws = 1; private $output; private $step = 0; private $max; private $startTime; private $stepWidth; private $percent = 0.0; private $messages = []; private $overwrite = \true; private $terminal; private $previousMessage; private $cursor; private static $formatters; private static $formats; /** * @param int $max Maximum steps (0 if unknown) */ public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->output = $output; $this->setMaxSteps($max); $this->terminal = new Terminal(); if (0 < $minSecondsBetweenRedraws) { $this->redrawFreq = null; $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws; } if (!$this->output->isDecorated()) { // disable overwrite when output does not support ANSI codes. $this->overwrite = \false; // set a reasonable redraw frequency so output isn't flooded $this->redrawFreq = null; } $this->startTime = \time(); $this->cursor = new Cursor($output); } /** * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. * * @param string $name The placeholder name (including the delimiter char like %) * @param callable $callable A PHP callable */ public static function setPlaceholderFormatterDefinition(string $name, callable $callable) : void { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } /** * Gets the placeholder formatter for a given name. * * @param string $name The placeholder name (including the delimiter char like %) */ public static function getPlaceholderFormatterDefinition(string $name) : ?callable { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return self::$formatters[$name] ?? null; } /** * Sets a format for a given name. * * This method also allow you to override an existing format. * * @param string $name The format name * @param string $format A format string */ public static function setFormatDefinition(string $name, string $format) : void { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } /** * Gets the format for a given name. * * @param string $name The format name */ public static function getFormatDefinition(string $name) : ?string { if (!self::$formats) { self::$formats = self::initFormats(); } return self::$formats[$name] ?? null; } /** * Associates a text with a named placeholder. * * The text is displayed when the progress bar is rendered but only * when the corresponding placeholder is part of the custom format line * (by wrapping the name with %). * * @param string $message The text to associate with the placeholder * @param string $name The name of the placeholder */ public function setMessage(string $message, string $name = 'message') { $this->messages[$name] = $message; } public function getMessage(string $name = 'message') { return $this->messages[$name]; } public function getStartTime() : int { return $this->startTime; } public function getMaxSteps() : int { return $this->max; } public function getProgress() : int { return $this->step; } private function getStepWidth() : int { return $this->stepWidth; } public function getProgressPercent() : float { return $this->percent; } public function getBarOffset() : float { return \floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (\min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); } public function getEstimated() : float { if (!$this->step) { return 0; } return \round((\time() - $this->startTime) / $this->step * $this->max); } public function getRemaining() : float { if (!$this->step) { return 0; } return \round((\time() - $this->startTime) / $this->step * ($this->max - $this->step)); } public function setBarWidth(int $size) { $this->barWidth = \max(1, $size); } public function getBarWidth() : int { return $this->barWidth; } public function setBarCharacter(string $char) { $this->barChar = $char; } public function getBarCharacter() : string { return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); } public function setEmptyBarCharacter(string $char) { $this->emptyBarChar = $char; } public function getEmptyBarCharacter() : string { return $this->emptyBarChar; } public function setProgressCharacter(string $char) { $this->progressChar = $char; } public function getProgressCharacter() : string { return $this->progressChar; } public function setFormat(string $format) { $this->format = null; $this->internalFormat = $format; } /** * Sets the redraw frequency. * * @param int|null $freq The frequency in steps */ public function setRedrawFrequency(?int $freq) { $this->redrawFreq = null !== $freq ? \max(1, $freq) : null; } public function minSecondsBetweenRedraws(float $seconds) : void { $this->minSecondsBetweenRedraws = $seconds; } public function maxSecondsBetweenRedraws(float $seconds) : void { $this->maxSecondsBetweenRedraws = $seconds; } /** * Returns an iterator that will automatically update the progress bar when iterated. * * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable */ public function iterate(iterable $iterable, ?int $max = null) : iterable { $this->start($max ?? (\is_countable($iterable) ? \count($iterable) : 0)); foreach ($iterable as $key => $value) { (yield $key => $value); $this->advance(); } $this->finish(); } /** * Starts the progress output. * * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged */ public function start(?int $max = null) { $this->startTime = \time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } /** * Advances the progress output X steps. * * @param int $step Number of steps to advance */ public function advance(int $step = 1) { $this->setProgress($this->step + $step); } /** * Sets whether to overwrite the progressbar, false for new line. */ public function setOverwrite(bool $overwrite) { $this->overwrite = $overwrite; } public function setProgress(int $step) { if ($this->max && $step > $this->max) { $this->max = $step; } elseif ($step < 0) { $step = 0; } $redrawFreq = $this->redrawFreq ?? ($this->max ?: 10) / 10; $prevPeriod = (int) ($this->step / $redrawFreq); $currPeriod = (int) ($step / $redrawFreq); $this->step = $step; $this->percent = $this->max ? (float) $this->step / $this->max : 0; $timeInterval = \microtime(\true) - $this->lastWriteTime; // Draw regardless of other limits if ($this->max === $step) { $this->display(); return; } // Throttling if ($timeInterval < $this->minSecondsBetweenRedraws) { return; } // Draw each step period, but not too late if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) { $this->display(); } } public function setMaxSteps(int $max) { $this->format = null; $this->max = \max(0, $max); $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; } /** * Finishes the progress output. */ public function finish() : void { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { // prevent double 100% output return; } $this->setProgress($this->max); } /** * Outputs the current progress string. */ public function display() : void { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite($this->buildLine()); } /** * Removes the progress bar from the current line. * * This is useful if you wish to write some output * while a progress bar is running. * Call display() to show the progress bar again. */ public function clear() : void { if (!$this->overwrite) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite(''); } private function setRealFormat(string $format) { // try to use the _nomax variant if available if (!$this->max && null !== self::getFormatDefinition($format . '_nomax')) { $this->format = self::getFormatDefinition($format . '_nomax'); } elseif (null !== self::getFormatDefinition($format)) { $this->format = self::getFormatDefinition($format); } else { $this->format = $format; } } /** * Overwrites a previous message to the output. */ private function overwrite(string $message) : void { if ($this->previousMessage === $message) { return; } $originalMessage = $message; if ($this->overwrite) { if (null !== $this->previousMessage) { if ($this->output instanceof ConsoleSectionOutput) { $messageLines = \explode("\n", $this->previousMessage); $lineCount = \count($messageLines); foreach ($messageLines as $messageLine) { $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine)); if ($messageLineLength > $this->terminal->getWidth()) { $lineCount += \floor($messageLineLength / $this->terminal->getWidth()); } } $this->output->clear($lineCount); } else { $lineCount = \substr_count($this->previousMessage, "\n"); for ($i = 0; $i < $lineCount; ++$i) { $this->cursor->moveToColumn(1); $this->cursor->clearLine(); $this->cursor->moveUp(); } $this->cursor->moveToColumn(1); $this->cursor->clearLine(); } } } elseif ($this->step > 0) { $message = \PHP_EOL . $message; } $this->previousMessage = $originalMessage; $this->lastWriteTime = \microtime(\true); $this->output->write($message); ++$this->writeCount; } private function determineBestFormat() : string { switch ($this->output->getVerbosity()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway case OutputInterface::VERBOSITY_VERBOSE: return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX; case OutputInterface::VERBOSITY_VERY_VERBOSE: return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX; case OutputInterface::VERBOSITY_DEBUG: return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX; default: return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX; } } private static function initPlaceholderFormatters() : array { return ['bar' => function (self $bar, OutputInterface $output) { $completeBars = $bar->getBarOffset(); $display = \str_repeat($bar->getBarCharacter(), $completeBars); if ($completeBars < $bar->getBarWidth()) { $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter())); $display .= $bar->getProgressCharacter() . \str_repeat($bar->getEmptyBarCharacter(), $emptyBars); } return $display; }, 'elapsed' => function (self $bar) { return Helper::formatTime(\time() - $bar->getStartTime()); }, 'remaining' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getRemaining()); }, 'estimated' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getEstimated()); }, 'memory' => function (self $bar) { return Helper::formatMemory(\memory_get_usage(\true)); }, 'current' => function (self $bar) { return \str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); }, 'max' => function (self $bar) { return $bar->getMaxSteps(); }, 'percent' => function (self $bar) { return \floor($bar->getProgressPercent() * 100); }]; } private static function initFormats() : array { return [self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%', self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]', self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%']; } private function buildLine() : string { $regex = "{%([a-z\\-_]+)(?:\\:([^%]+))?%}i"; $callback = function ($matches) { if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { $text = $formatter($this, $this->output); } elseif (isset($this->messages[$matches[1]])) { $text = $this->messages[$matches[1]]; } else { return $matches[0]; } if (isset($matches[2])) { $text = \sprintf('%' . $matches[2], $text); } return $text; }; $line = \preg_replace_callback($regex, $callback, $this->format); // gets string length for each sub line with multiline format $linesLength = \array_map(function ($subLine) { return Helper::width(Helper::removeDecoration($this->output->getFormatter(), \rtrim($subLine, "\r"))); }, \explode("\n", $line)); $linesWidth = \max($linesLength); $terminalWidth = $this->terminal->getWidth(); if ($linesWidth <= $terminalWidth) { return $line; } $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); return \preg_replace_callback($regex, $callback, $this->format); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; /** * Helps outputting debug information when running an external program from a command. * * An external program can be a Process, an HTTP request, or anything else. * * @author Fabien Potencier */ class DebugFormatterHelper extends Helper { private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default']; private $started = []; private $count = -1; /** * Starts a debug formatting session. * * @return string */ public function start(string $id, string $message, string $prefix = 'RUN') { $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; return \sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } /** * Adds progress to a formatting session. * * @return string */ public function progress(string $id, string $buffer, bool $error = \false, string $prefix = 'OUT', string $errorPrefix = 'ERR') { $message = ''; if ($error) { if (isset($this->started[$id]['out'])) { $message .= "\n"; unset($this->started[$id]['out']); } if (!isset($this->started[$id]['err'])) { $message .= \sprintf('%s %s ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = \true; } $message .= \str_replace("\n", \sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); } else { if (isset($this->started[$id]['err'])) { $message .= "\n"; unset($this->started[$id]['err']); } if (!isset($this->started[$id]['out'])) { $message .= \sprintf('%s %s ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = \true; } $message .= \str_replace("\n", \sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); } return $message; } /** * Stops a formatting session. * * @return string */ public function stop(string $id, string $message, bool $successful, string $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; if ($successful) { return \sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = \sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); unset($this->started[$id]['out'], $this->started[$id]['err']); return $message; } private function getBorder(string $id) : string { return \sprintf(' ', self::COLORS[$this->started[$id]['border']]); } /** * {@inheritdoc} */ public function getName() { return 'debug_formatter'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; /** * HelperSet represents a set of helpers to be used with a command. * * @author Fabien Potencier * * @implements \IteratorAggregate */ class HelperSet implements \IteratorAggregate { /** @var array */ private $helpers = []; private $command; /** * @param Helper[] $helpers An array of helper */ public function __construct(array $helpers = []) { foreach ($helpers as $alias => $helper) { $this->set($helper, \is_int($alias) ? null : $alias); } } public function set(HelperInterface $helper, ?string $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } /** * Returns true if the helper if defined. * * @return bool */ public function has(string $name) { return isset($this->helpers[$name]); } /** * Gets a helper value. * * @return HelperInterface * * @throws InvalidArgumentException if the helper is not defined */ public function get(string $name) { if (!$this->has($name)) { throw new InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name)); } return $this->helpers[$name]; } /** * @deprecated since Symfony 5.4 */ public function setCommand(?Command $command = null) { \trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); $this->command = $command; } /** * Gets the command associated with this helper set. * * @return Command * * @deprecated since Symfony 5.4 */ public function getCommand() { \trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); return $this->command; } /** * @return \Traversable */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->helpers); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\ClonerInterface; use _ContaoManager\Symfony\Component\VarDumper\Cloner\VarCloner; use _ContaoManager\Symfony\Component\VarDumper\Dumper\CliDumper; /** * @author Roland Franssen */ final class Dumper { private $output; private $dumper; private $cloner; private $handler; public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null) { $this->output = $output; $this->dumper = $dumper; $this->cloner = $cloner; if (\class_exists(CliDumper::class)) { $this->handler = function ($var) : string { $dumper = $this->dumper ?? ($this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR)); $dumper->setColors($this->output->isDecorated()); return \rtrim($dumper->dump(($this->cloner ?? ($this->cloner = new VarCloner()))->cloneVar($var)->withRefHandles(\false), \true)); }; } else { $this->handler = function ($var) : string { switch (\true) { case null === $var: return 'null'; case \true === $var: return 'true'; case \false === $var: return 'false'; case \is_string($var): return '"' . $var . '"'; default: return \rtrim(\print_r($var, \true)); } }; } } public function __invoke($var) : string { return ($this->handler)($var); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Input\InputAwareInterface; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; /** * An implementation of InputAwareInterface for Helpers. * * @author Wouter J */ abstract class InputAwareHelper extends Helper implements InputAwareInterface { protected $input; /** * {@inheritdoc} */ public function setInput(InputInterface $input) { $this->input = $input; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Cursor; use _ContaoManager\Symfony\Component\Console\Exception\MissingInputException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterStyle; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Input\StreamableInputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleOutputInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleSectionOutput; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Component\Console\Question\ChoiceQuestion; use _ContaoManager\Symfony\Component\Console\Question\Question; use _ContaoManager\Symfony\Component\Console\Terminal; use function _ContaoManager\Symfony\Component\String\s; /** * The QuestionHelper class provides helpers to interact with the user. * * @author Fabien Potencier */ class QuestionHelper extends Helper { /** * @var resource|null */ private $inputStream; private static $stty = \true; private static $stdinIsInteractive; /** * Asks a question to the user. * * @return mixed The user answer * * @throws RuntimeException If there is no data to read in the input stream */ public function ask(InputInterface $input, OutputInterface $output, Question $question) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if (!$input->isInteractive()) { return $this->getDefaultAnswer($question); } if ($input instanceof StreamableInputInterface && ($stream = $input->getStream())) { $this->inputStream = $stream; } try { if (!$question->getValidator()) { return $this->doAsk($output, $question); } $interviewer = function () use($output, $question) { return $this->doAsk($output, $question); }; return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { $input->setInteractive(\false); if (null === ($fallbackOutput = $this->getDefaultAnswer($question))) { throw $exception; } return $fallbackOutput; } } /** * {@inheritdoc} */ public function getName() { return 'question'; } /** * Prevents usage of stty. */ public static function disableStty() { self::$stty = \false; } /** * Asks the question to the user. * * @return mixed * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); $inputStream = $this->inputStream ?: \STDIN; $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { $ret = \false; if ($question->isHidden()) { try { $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); $ret = $question->isTrimmable() ? \trim($hiddenResponse) : $hiddenResponse; } catch (RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; } } } if (\false === $ret) { $isBlocked = \stream_get_meta_data($inputStream)['blocked'] ?? \true; if (!$isBlocked) { \stream_set_blocking($inputStream, \true); } $ret = $this->readInput($inputStream, $question); if (!$isBlocked) { \stream_set_blocking($inputStream, \false); } if (\false === $ret) { throw new MissingInputException('Aborted.'); } if ($question->isTrimmable()) { $ret = \trim($ret); } } } else { $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); $ret = $question->isTrimmable() ? \trim($autocomplete) : $autocomplete; } if ($output instanceof ConsoleSectionOutput) { $output->addContent($ret); } $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); if ($normalizer = $question->getNormalizer()) { return $normalizer($ret); } return $ret; } /** * @return mixed */ private function getDefaultAnswer(Question $question) { $default = $question->getDefault(); if (null === $default) { return $default; } if ($validator = $question->getValidator()) { return \call_user_func($question->getValidator(), $default); } elseif ($question instanceof ChoiceQuestion) { $choices = $question->getChoices(); if (!$question->isMultiselect()) { return $choices[$default] ?? $default; } $default = \explode(',', $default); foreach ($default as $k => $v) { $v = $question->isTrimmable() ? \trim($v) : $v; $default[$k] = $choices[$v] ?? $v; } } return $default; } /** * Outputs the question prompt. */ protected function writePrompt(OutputInterface $output, Question $question) { $message = $question->getQuestion(); if ($question instanceof ChoiceQuestion) { $output->writeln(\array_merge([$question->getQuestion()], $this->formatChoiceQuestionChoices($question, 'info'))); $message = $question->getPrompt(); } $output->write($message); } /** * @return string[] */ protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag) { $messages = []; $maxWidth = \max(\array_map([__CLASS__, 'width'], \array_keys($choices = $question->getChoices()))); foreach ($choices as $key => $value) { $padding = \str_repeat(' ', $maxWidth - self::width($key)); $messages[] = \sprintf(" [<{$tag}>%s{$padding}] %s", $key, $value); } return $messages; } /** * Outputs an error message. */ protected function writeError(OutputInterface $output, \Exception $error) { if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); } else { $message = '' . $error->getMessage() . ''; } $output->writeln($message); } /** * Autocompletes a question. * * @param resource $inputStream */ private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete) : string { $cursor = new Cursor($output, $inputStream); $fullChoice = ''; $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete($ret); $numMatches = \count($matches); $sttyMode = \shell_exec('stty -g'); $isStdin = 'php://stdin' === (\stream_get_meta_data($inputStream)['uri'] ?? null); $r = [$inputStream]; $w = []; // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) \shell_exec('stty -icanon -echo'); // Add highlighted text style $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); // Read a keypress while (!\feof($inputStream)) { while ($isStdin && 0 === @\stream_select($r, $w, $w, 0, 100)) { // Give signal handlers a chance to run $r = [$inputStream]; } $c = \fread($inputStream, 1); // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. if (\false === $c || '' === $ret && '' === $c && null === $question->getDefault()) { \shell_exec('stty ' . $sttyMode); throw new MissingInputException('Aborted.'); } elseif ("" === $c) { // Backspace Character if (0 === $numMatches && 0 !== $i) { --$i; $cursor->moveLeft(s($fullChoice)->slice(-1)->width(\false)); $fullChoice = self::substr($fullChoice, 0, $i); } if (0 === $i) { $ofs = -1; $matches = $autocomplete($ret); $numMatches = \count($matches); } else { $numMatches = 0; } // Pop the last character off the end of our string $ret = self::substr($ret, 0, $i); } elseif ("\x1b" === $c) { // Did we read an escape sequence? $c .= \fread($inputStream, 2); // A = Up Arrow. B = Down Arrow if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { if ('A' === $c[2] && -1 === $ofs) { $ofs = 0; } if (0 === $numMatches) { continue; } $ofs += 'A' === $c[2] ? -1 : 1; $ofs = ($numMatches + $ofs) % $numMatches; } } elseif (\ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { $ret = (string) $matches[$ofs]; // Echo out remaining chars for current match $remainingCharacters = \substr($ret, \strlen(\trim($this->mostRecentlyEnteredValue($fullChoice)))); $output->write($remainingCharacters); $fullChoice .= $remainingCharacters; $i = \false === ($encoding = \mb_detect_encoding($fullChoice, null, \true)) ? \strlen($fullChoice) : \mb_strlen($fullChoice, $encoding); $matches = \array_filter($autocomplete($ret), function ($match) use($ret) { return '' === $ret || \str_starts_with($match, $ret); }); $numMatches = \count($matches); $ofs = -1; } if ("\n" === $c) { $output->write($c); break; } $numMatches = 0; } continue; } else { if ("\x80" <= $c) { $c .= \fread($inputStream, ["\xc0" => 1, "\xd0" => 1, "\xe0" => 2, "\xf0" => 3][$c & "\xf0"]); } $output->write($c); $ret .= $c; $fullChoice .= $c; ++$i; $tempRet = $ret; if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { $tempRet = $this->mostRecentlyEnteredValue($fullChoice); } $numMatches = 0; $ofs = 0; foreach ($autocomplete($ret) as $value) { // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if (\str_starts_with($value, $tempRet)) { $matches[$numMatches++] = $value; } } } $cursor->clearLineAfter(); if ($numMatches > 0 && -1 !== $ofs) { $cursor->savePosition(); // Write highlighted text, complete the partially entered response $charactersEntered = \strlen(\trim($this->mostRecentlyEnteredValue($fullChoice))); $output->write('' . OutputFormatter::escapeTrailingBackslash(\substr($matches[$ofs], $charactersEntered)) . ''); $cursor->restorePosition(); } } // Reset stty so it behaves normally again \shell_exec('stty ' . $sttyMode); return $fullChoice; } private function mostRecentlyEnteredValue(string $entered) : string { // Determine the most recent value that the user entered if (!\str_contains($entered, ',')) { return $entered; } $choices = \explode(',', $entered); if ('' !== ($lastChoice = \trim($choices[\count($choices) - 1]))) { return $lastChoice; } return $entered; } /** * Gets a hidden response from user. * * @param resource $inputStream The handler resource * @param bool $trimmable Is the answer trimmable * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = \true) : string { if ('\\' === \DIRECTORY_SEPARATOR) { $exe = __DIR__ . '/../Resources/bin/hiddeninput.exe'; // handle code running from a phar if ('phar:' === \substr(__FILE__, 0, 5)) { $tmpExe = \sys_get_temp_dir() . '/hiddeninput.exe'; \copy($exe, $tmpExe); $exe = $tmpExe; } $sExec = \shell_exec('"' . $exe . '"'); $value = $trimmable ? \rtrim($sExec) : $sExec; $output->writeln(''); if (isset($tmpExe)) { \unlink($tmpExe); } return $value; } if (self::$stty && Terminal::hasSttyAvailable()) { $sttyMode = \shell_exec('stty -g'); \shell_exec('stty -echo'); } elseif ($this->isInteractiveInput($inputStream)) { throw new RuntimeException('Unable to hide the response.'); } $value = \fgets($inputStream, 4096); if (self::$stty && Terminal::hasSttyAvailable()) { \shell_exec('stty ' . $sttyMode); } if (\false === $value) { throw new MissingInputException('Aborted.'); } if ($trimmable) { $value = \trim($value); } $output->writeln(''); return $value; } /** * Validates an attempt. * * @param callable $interviewer A callable that will ask for a question and return the result * * @return mixed The validated response * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) { $error = null; $attempts = $question->getMaxAttempts(); while (null === $attempts || $attempts--) { if (null !== $error) { $this->writeError($output, $error); } try { return $question->getValidator()($interviewer()); } catch (RuntimeException $e) { throw $e; } catch (\Exception $error) { } } throw $error; } private function isInteractiveInput($inputStream) : bool { if ('php://stdin' !== (\stream_get_meta_data($inputStream)['uri'] ?? null)) { return \false; } if (null !== self::$stdinIsInteractive) { return self::$stdinIsInteractive; } if (\function_exists('stream_isatty')) { return self::$stdinIsInteractive = @\stream_isatty(\fopen('php://stdin', 'r')); } if (\function_exists('posix_isatty')) { return self::$stdinIsInteractive = @\posix_isatty(\fopen('php://stdin', 'r')); } if (!\function_exists('shell_exec')) { return self::$stdinIsInteractive = \true; } return self::$stdinIsInteractive = (bool) \shell_exec('stty 2> ' . ('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); } /** * Reads one or more lines of input and returns what is read. * * @param resource $inputStream The handler resource * @param Question $question The question being asked * * @return string|false The input received, false in case input could not be read */ private function readInput($inputStream, Question $question) { if (!$question->isMultiline()) { $cp = $this->setIOCodepage(); $ret = \fgets($inputStream, 4096); return $this->resetIOCodepage($cp, $ret); } $multiLineStreamReader = $this->cloneInputStream($inputStream); if (null === $multiLineStreamReader) { return \false; } $ret = ''; $cp = $this->setIOCodepage(); while (\false !== ($char = \fgetc($multiLineStreamReader))) { if (\PHP_EOL === "{$ret}{$char}") { break; } $ret .= $char; } return $this->resetIOCodepage($cp, $ret); } /** * Sets console I/O to the host code page. * * @return int Previous code page in IBM/EBCDIC format */ private function setIOCodepage() : int { if (\function_exists('sapi_windows_cp_set')) { $cp = \sapi_windows_cp_get(); \sapi_windows_cp_set(\sapi_windows_cp_get('oem')); return $cp; } return 0; } /** * Sets console I/O to the specified code page and converts the user input. * * @param string|false $input * * @return string|false */ private function resetIOCodepage(int $cp, $input) { if (0 !== $cp) { \sapi_windows_cp_set($cp); if (\false !== $input && '' !== $input) { $input = \sapi_windows_cp_conv(\sapi_windows_cp_get('oem'), $cp, $input); } } return $input; } /** * Clones an input stream in order to act on one instance of the same * stream without affecting the other instance. * * @param resource $inputStream The handler resource * * @return resource|null The cloned resource, null in case it could not be cloned */ private function cloneInputStream($inputStream) { $streamMetaData = \stream_get_meta_data($inputStream); $seekable = $streamMetaData['seekable'] ?? \false; $mode = $streamMetaData['mode'] ?? 'rb'; $uri = $streamMetaData['uri'] ?? null; if (null === $uri) { return null; } $cloneStream = \fopen($uri, $mode); // For seekable and writable streams, add all the same data to the // cloned stream and then seek to the same offset. if (\true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) { $offset = \ftell($inputStream); \rewind($inputStream); \stream_copy_to_stream($inputStream, $cloneStream); \fseek($inputStream, $offset); \fseek($cloneStream, $offset); } return $cloneStream; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatterInterface; use _ContaoManager\Symfony\Component\String\UnicodeString; /** * Helper is the base class for all helper classes. * * @author Fabien Potencier */ abstract class Helper implements HelperInterface { protected $helperSet = null; /** * {@inheritdoc} */ public function setHelperSet(?HelperSet $helperSet = null) { $this->helperSet = $helperSet; } /** * {@inheritdoc} */ public function getHelperSet() { return $this->helperSet; } /** * Returns the length of a string, using mb_strwidth if it is available. * * @deprecated since Symfony 5.3 * * @return int */ public static function strlen(?string $string) { \trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.', __METHOD__); return self::width($string); } /** * Returns the width of a string, using mb_strwidth if it is available. * The width is how many characters positions the string will use. */ public static function width(?string $string) : int { $string ?? ($string = ''); if (\preg_match('//u', $string)) { return (new UnicodeString($string))->width(\false); } if (\false === ($encoding = \mb_detect_encoding($string, null, \true))) { return \strlen($string); } return \mb_strwidth($string, $encoding); } /** * Returns the length of a string, using mb_strlen if it is available. * The length is related to how many bytes the string will use. */ public static function length(?string $string) : int { $string ?? ($string = ''); if (\preg_match('//u', $string)) { return (new UnicodeString($string))->length(); } if (\false === ($encoding = \mb_detect_encoding($string, null, \true))) { return \strlen($string); } return \mb_strlen($string, $encoding); } /** * Returns the subset of a string, using mb_substr if it is available. * * @return string */ public static function substr(?string $string, int $from, ?int $length = null) { $string ?? ($string = ''); if (\false === ($encoding = \mb_detect_encoding($string, null, \true))) { return \substr($string, $from, $length); } return \mb_substr($string, $from, $length, $encoding); } public static function formatTime($secs) { static $timeFormats = [[0, '< 1 sec'], [1, '1 sec'], [2, 'secs', 1], [60, '1 min'], [120, 'mins', 60], [3600, '1 hr'], [7200, 'hrs', 3600], [86400, '1 day'], [172800, 'days', 86400]]; foreach ($timeFormats as $index => $format) { if ($secs >= $format[0]) { if (isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0] || $index == \count($timeFormats) - 1) { if (2 == \count($format)) { return $format[1]; } return \floor($secs / $format[2]) . ' ' . $format[1]; } } } } public static function formatMemory(int $memory) { if ($memory >= 1024 * 1024 * 1024) { return \sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { return \sprintf('%.1f MiB', $memory / 1024 / 1024); } if ($memory >= 1024) { return \sprintf('%d KiB', $memory / 1024); } return \sprintf('%d B', $memory); } /** * @deprecated since Symfony 5.3 */ public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string) { \trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.', __METHOD__); return self::width(self::removeDecoration($formatter, $string)); } public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) { $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(\false); // remove <...> formatting $string = $formatter->format($string ?? ''); // remove already formatted characters $string = \preg_replace("/\x1b\\[[^m]*m/", '', $string ?? ''); // remove terminal hyperlinks $string = \preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? ''); $formatter->setDecorated($isDecorated); return $string; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\RuntimeException; use _ContaoManager\Symfony\Component\Console\Formatter\OutputFormatter; use _ContaoManager\Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; use _ContaoManager\Symfony\Component\Console\Output\ConsoleSectionOutput; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Provides helpers to display a table. * * @author Fabien Potencier * @author Саша Стаменковић * @author Abdellatif Ait boudad * @author Max Grigorian * @author Dany Maillard */ class Table { private const SEPARATOR_TOP = 0; private const SEPARATOR_TOP_BOTTOM = 1; private const SEPARATOR_MID = 2; private const SEPARATOR_BOTTOM = 3; private const BORDER_OUTSIDE = 0; private const BORDER_INSIDE = 1; private $headerTitle; private $footerTitle; /** * Table headers. */ private $headers = []; /** * Table rows. */ private $rows = []; private $horizontal = \false; /** * Column widths cache. */ private $effectiveColumnWidths = []; /** * Number of columns cache. * * @var int */ private $numberOfColumns; /** * @var OutputInterface */ private $output; /** * @var TableStyle */ private $style; /** * @var array */ private $columnStyles = []; /** * User set column widths. * * @var array */ private $columnWidths = []; private $columnMaxWidths = []; /** * @var array|null */ private static $styles; private $rendered = \false; public function __construct(OutputInterface $output) { $this->output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } /** * Sets a style definition. */ public static function setStyleDefinition(string $name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } /** * Gets a style definition by name. * * @return TableStyle */ public static function getStyleDefinition(string $name) { if (!self::$styles) { self::$styles = self::initStyles(); } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name)); } /** * Sets table style. * * @param TableStyle|string $name The style name or a TableStyle instance * * @return $this */ public function setStyle($name) { $this->style = $this->resolveStyle($name); return $this; } /** * Gets the current table style. * * @return TableStyle */ public function getStyle() { return $this->style; } /** * Sets table column style. * * @param TableStyle|string $name The style name or a TableStyle instance * * @return $this */ public function setColumnStyle(int $columnIndex, $name) { $this->columnStyles[$columnIndex] = $this->resolveStyle($name); return $this; } /** * Gets the current style for a column. * * If style was not set, it returns the global table style. * * @return TableStyle */ public function getColumnStyle(int $columnIndex) { return $this->columnStyles[$columnIndex] ?? $this->getStyle(); } /** * Sets the minimum width of a column. * * @return $this */ public function setColumnWidth(int $columnIndex, int $width) { $this->columnWidths[$columnIndex] = $width; return $this; } /** * Sets the minimum width of all columns. * * @return $this */ public function setColumnWidths(array $widths) { $this->columnWidths = []; foreach ($widths as $index => $width) { $this->setColumnWidth($index, $width); } return $this; } /** * Sets the maximum width of a column. * * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while * formatted strings are preserved. * * @return $this */ public function setColumnMaxWidth(int $columnIndex, int $width) : self { if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_debug_type($this->output->getFormatter()))); } $this->columnMaxWidths[$columnIndex] = $width; return $this; } /** * @return $this */ public function setHeaders(array $headers) { $headers = \array_values($headers); if (!empty($headers) && !\is_array($headers[0])) { $headers = [$headers]; } $this->headers = $headers; return $this; } public function setRows(array $rows) { $this->rows = []; return $this->addRows($rows); } /** * @return $this */ public function addRows(array $rows) { foreach ($rows as $row) { $this->addRow($row); } return $this; } /** * @return $this */ public function addRow($row) { if ($row instanceof TableSeparator) { $this->rows[] = $row; return $this; } if (!\is_array($row)) { throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); } $this->rows[] = \array_values($row); return $this; } /** * Adds a row to the table, and re-renders the table. * * @return $this */ public function appendRow($row) : self { if (!$this->output instanceof ConsoleSectionOutput) { throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); } if ($this->rendered) { $this->output->clear($this->calculateRowCount()); } $this->addRow($row); $this->render(); return $this; } /** * @return $this */ public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } /** * @return $this */ public function setHeaderTitle(?string $title) : self { $this->headerTitle = $title; return $this; } /** * @return $this */ public function setFooterTitle(?string $title) : self { $this->footerTitle = $title; return $this; } /** * @return $this */ public function setHorizontal(bool $horizontal = \true) : self { $this->horizontal = $horizontal; return $this; } /** * Renders table to output. * * Example: * * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ */ public function render() { $divider = new TableSeparator(); if ($this->horizontal) { $rows = []; foreach ($this->headers[0] ?? [] as $i => $header) { $rows[$i] = [$header]; foreach ($this->rows as $row) { if ($row instanceof TableSeparator) { continue; } if (isset($row[$i])) { $rows[$i][] = $row[$i]; } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { // Noop, there is a "title" } else { $rows[$i][] = null; } } } } else { $rows = \array_merge($this->headers, [$divider], $this->rows); } $this->calculateNumberOfColumns($rows); $rowGroups = $this->buildTableRows($rows); $this->calculateColumnsWidth($rowGroups); $isHeader = !$this->horizontal; $isFirstRow = $this->horizontal; $hasTitle = (bool) $this->headerTitle; foreach ($rowGroups as $rowGroup) { $isHeaderSeparatorRendered = \false; foreach ($rowGroup as $row) { if ($divider === $row) { $isHeader = \false; $isFirstRow = \true; continue; } if ($row instanceof TableSeparator) { $this->renderRowSeparator(); continue; } if (!$row) { continue; } if ($isHeader && !$isHeaderSeparatorRendered) { $this->renderRowSeparator($isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null); $hasTitle = \false; $isHeaderSeparatorRendered = \true; } if ($isFirstRow) { $this->renderRowSeparator($isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null); $isFirstRow = \false; $hasTitle = \false; } if ($this->horizontal) { $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); } else { $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); } } } $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); $this->cleanup(); $this->rendered = \true; } /** * Renders horizontal header separator. * * Example: * * +-----+-----------+-------+ */ private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null) { if (0 === ($count = $this->numberOfColumns)) { return; } $borders = $this->style->getBorderChars(); if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { return; } $crossings = $this->style->getCrossingChars(); if (self::SEPARATOR_MID === $type) { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; } elseif (self::SEPARATOR_TOP === $type) { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; } else { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; } $markup = $leftChar; for ($column = 0; $column < $count; ++$column) { $markup .= \str_repeat($horizontal, $this->effectiveColumnWidths[$column]); $markup .= $column === $count - 1 ? $rightChar : $midChar; } if (null !== $title) { $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title))); $markupLength = Helper::width($markup); if ($titleLength > ($limit = $markupLength - 4)) { $titleLength = $limit; $formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, ''))); $formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3) . '...'); } $titleStart = \intdiv($markupLength - $titleLength, 2); if (\false === \mb_detect_encoding($markup, null, \true)) { $markup = \substr_replace($markup, $formattedTitle, $titleStart, $titleLength); } else { $markup = \mb_substr($markup, 0, $titleStart) . $formattedTitle . \mb_substr($markup, $titleStart + $titleLength); } } $this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup)); } /** * Renders vertical column separator. */ private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE) : string { $borders = $this->style->getBorderChars(); return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); } /** * Renders table row. * * Example: * * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | */ private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null) { $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); $columns = $this->getRowColumns($row); $last = \count($columns) - 1; foreach ($columns as $i => $column) { if ($firstCellFormat && 0 === $i) { $rowContent .= $this->renderCell($row, $column, $firstCellFormat); } else { $rowContent .= $this->renderCell($row, $column, $cellFormat); } $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); } $this->output->writeln($rowContent); } /** * Renders table cell with padding. */ private function renderCell(array $row, int $column, string $cellFormat) : string { $cell = $row[$column] ?? ''; $width = $this->effectiveColumnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { // add the width of the following columns(numbers of colspan). foreach (\range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; } } // str_pad won't work properly with multi-byte strings, we need to fix the padding if (\false !== ($encoding = \mb_detect_encoding($cell, null, \true))) { $width += \strlen($cell) - \mb_strwidth($cell, $encoding); } $style = $this->getColumnStyle($column); if ($cell instanceof TableSeparator) { return \sprintf($style->getBorderFormat(), \str_repeat($style->getBorderChars()[2], $width)); } $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell)); $content = \sprintf($style->getCellRowContentFormat(), $cell); $padType = $style->getPadType(); if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) { $isNotStyledByTag = !\preg_match('/^<(\\w+|(\\w+=[\\w,]+;?)*)>.+<\\/(\\w+|(\\w+=\\w+;?)*)?>$/', $cell); if ($isNotStyledByTag) { $cellFormat = $cell->getStyle()->getCellFormat(); if (!\is_string($cellFormat)) { $tag = \http_build_query($cell->getStyle()->getTagOptions(), '', ';'); $cellFormat = '<' . $tag . '>%s'; } if (\strstr($content, '')) { $content = \str_replace('', '', $content); $width -= 3; } if (\strstr($content, '')) { $content = \str_replace('', '', $content); $width -= \strlen(''); } } $padType = $cell->getStyle()->getPadByAlign(); } return \sprintf($cellFormat, \str_pad($content, $width, $style->getPaddingChar(), $padType)); } /** * Calculate number of columns for this table. */ private function calculateNumberOfColumns(array $rows) { $columns = [0]; foreach ($rows as $row) { if ($row instanceof TableSeparator) { continue; } $columns[] = $this->getNumberOfColumns($row); } $this->numberOfColumns = \max($columns); } private function buildTableRows(array $rows) : TableRows { /** @var WrappableOutputFormatterInterface $formatter */ $formatter = $this->output->getFormatter(); $unmergedRows = []; for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); // Remove any new line breaks and replace it with a new line foreach ($rows[$rowKey] as $column => $cell) { $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); } if (!\strstr($cell ?? '', "\n")) { continue; } $escaped = \implode("\n", \array_map([OutputFormatter::class, 'escapeTrailingBackslash'], \explode("\n", $cell))); $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; $lines = \explode("\n", \str_replace("\n", "\n", $cell)); foreach ($lines as $lineKey => $line) { if ($colspan > 1) { $line = new TableCell($line, ['colspan' => $colspan]); } if (0 === $lineKey) { $rows[$rowKey][$column] = $line; } else { if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); } $unmergedRows[$rowKey][$lineKey][$column] = $line; } } } } return new TableRows(function () use($rows, $unmergedRows) : \Traversable { foreach ($rows as $rowKey => $row) { $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; if (isset($unmergedRows[$rowKey])) { foreach ($unmergedRows[$rowKey] as $row) { $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); } } (yield $rowGroup); } }); } private function calculateRowCount() : int { $numberOfRows = \count(\iterator_to_array($this->buildTableRows(\array_merge($this->headers, [new TableSeparator()], $this->rows)))); if ($this->headers) { ++$numberOfRows; // Add row for header separator } if (\count($this->rows) > 0) { ++$numberOfRows; // Add row for footer separator } return $numberOfRows; } /** * fill rows that contains rowspan > 1. * * @throws InvalidArgumentException */ private function fillNextRows(array $rows, int $line) : array { $unmergedRows = []; foreach ($rows[$line] as $column => $cell) { if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && \method_exists($cell, '__toString'))) { throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \get_debug_type($cell))); } if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = [$cell]; if (\strstr($cell, "\n")) { $lines = \explode("\n", \str_replace("\n", "\n", $cell)); $nbLines = \count($lines) > $nbLines ? \substr_count($cell, "\n") : $nbLines; $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); unset($lines[0]); } // create a two dimensional array (rowspan x colspan) $unmergedRows = \array_replace_recursive(\array_fill($line + 1, $nbLines, []), $unmergedRows); foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { $value = $lines[$unmergedRowKey - $line] ?? ''; $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); if ($nbLines === $unmergedRowKey - $line) { break; } } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { // we need to know if $unmergedRow will be merged or inserted into $rows if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && $this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns) { foreach ($unmergedRow as $cellKey => $cell) { // insert cell into row at cellKey position \array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); } } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { if (!empty($cell)) { $row[$column] = $unmergedRow[$column]; } } \array_splice($rows, $unmergedRowKey, 0, [$row]); } } return $rows; } /** * fill cells for a row that contains colspan > 1. */ private function fillCells(iterable $row) { $newRow = []; foreach ($row as $column => $cell) { $newRow[] = $cell; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (\range($column + 1, $column + $cell->getColspan() - 1) as $position) { // insert empty value at column position $newRow[] = ''; } } } return $newRow ?: $row; } private function copyRow(array $rows, int $line) : array { $row = $rows[$line]; foreach ($row as $cellKey => $cellValue) { $row[$cellKey] = ''; if ($cellValue instanceof TableCell) { $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); } } return $row; } /** * Gets number of columns by row. */ private function getNumberOfColumns(array $row) : int { $columns = \count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? $column->getColspan() - 1 : 0; } return $columns; } /** * Gets list of columns for the given row. */ private function getRowColumns(array $row) : array { $columns = \range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { // exclude grouped columns. $columns = \array_diff($columns, \range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } /** * Calculates columns widths. */ private function calculateColumnsWidth(iterable $groups) { for ($column = 0; $column < $this->numberOfColumns; ++$column) { $lengths = []; foreach ($groups as $group) { foreach ($group as $row) { if ($row instanceof TableSeparator) { continue; } foreach ($row as $i => $cell) { if ($cell instanceof TableCell) { $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); $textLength = Helper::width($textContent); if ($textLength > 0) { $contentColumns = \mb_str_split($textContent, \ceil($textLength / $cell->getColspan())); foreach ($contentColumns as $position => $content) { $row[$i + $position] = $content; } } } } $lengths[] = $this->getCellWidth($row, $column); } } $this->effectiveColumnWidths[$column] = \max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2; } } private function getColumnSeparatorWidth() : int { return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); } private function getCellWidth(array $row, int $column) : int { $cellWidth = 0; if (isset($row[$column])) { $cell = $row[$column]; $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell)); } $columnWidth = $this->columnWidths[$column] ?? 0; $cellWidth = \max($cellWidth, $columnWidth); return isset($this->columnMaxWidths[$column]) ? \min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; } /** * Called after rendering to cleanup cache data. */ private function cleanup() { $this->effectiveColumnWidths = []; $this->numberOfColumns = null; } /** * @return array */ private static function initStyles() : array { $borderless = new TableStyle(); $borderless->setHorizontalBorderChars('=')->setVerticalBorderChars(' ')->setDefaultCrossingChar(' '); $compact = new TableStyle(); $compact->setHorizontalBorderChars('')->setVerticalBorderChars('')->setDefaultCrossingChar('')->setCellRowContentFormat('%s '); $styleGuide = new TableStyle(); $styleGuide->setHorizontalBorderChars('-')->setVerticalBorderChars(' ')->setDefaultCrossingChar(' ')->setCellHeaderFormat('%s'); $box = (new TableStyle())->setHorizontalBorderChars('─')->setVerticalBorderChars('│')->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├'); $boxDouble = (new TableStyle())->setHorizontalBorderChars('═', '─')->setVerticalBorderChars('║', '│')->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣'); return ['default' => new TableStyle(), 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, 'box' => $box, 'box-double' => $boxDouble]; } private function resolveStyle($name) : TableStyle { if ($name instanceof TableStyle) { return $name; } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Exception\LogicException; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author Kevin Bond */ class ProgressIndicator { private const FORMATS = ['normal' => ' %indicator% %message%', 'normal_no_ansi' => ' %message%', 'verbose' => ' %indicator% %message% (%elapsed:6s%)', 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)']; private $output; private $startTime; private $format; private $message; private $indicatorValues; private $indicatorCurrent; private $indicatorChangeInterval; private $indicatorUpdateTime; private $started = \false; /** * @var array */ private static $formatters; /** * @param int $indicatorChangeInterval Change interval in milliseconds * @param array|null $indicatorValues Animated indicator characters */ public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null) { $this->output = $output; if (null === $format) { $format = $this->determineBestFormat(); } if (null === $indicatorValues) { $indicatorValues = ['-', '\\', '|', '/']; } $indicatorValues = \array_values($indicatorValues); if (2 > \count($indicatorValues)) { throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); } $this->format = self::getFormatDefinition($format); $this->indicatorChangeInterval = $indicatorChangeInterval; $this->indicatorValues = $indicatorValues; $this->startTime = \time(); } /** * Sets the current indicator message. */ public function setMessage(?string $message) { $this->message = $message; $this->display(); } /** * Starts the indicator output. */ public function start(string $message) { if ($this->started) { throw new LogicException('Progress indicator already started.'); } $this->message = $message; $this->started = \true; $this->startTime = \time(); $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; $this->indicatorCurrent = 0; $this->display(); } /** * Advances the indicator. */ public function advance() { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } if (!$this->output->isDecorated()) { return; } $currentTime = $this->getCurrentTimeInMilliseconds(); if ($currentTime < $this->indicatorUpdateTime) { return; } $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; ++$this->indicatorCurrent; $this->display(); } /** * Finish the indicator with message. */ public function finish(string $message) { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } $this->message = $message; $this->display(); $this->output->writeln(''); $this->started = \false; } /** * Gets the format for a given name. * * @return string|null */ public static function getFormatDefinition(string $name) { return self::FORMATS[$name] ?? null; } /** * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. */ public static function setPlaceholderFormatterDefinition(string $name, callable $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } /** * Gets the placeholder formatter for a given name (including the delimiter char like %). * * @return callable|null */ public static function getPlaceholderFormatterDefinition(string $name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return self::$formatters[$name] ?? null; } private function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } $this->overwrite(\preg_replace_callback("{%([a-z\\-_]+)(?:\\:([^%]+))?%}i", function ($matches) { if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { return $formatter($this); } return $matches[0]; }, $this->format ?? '')); } private function determineBestFormat() : string { switch ($this->output->getVerbosity()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway case OutputInterface::VERBOSITY_VERBOSE: return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; case OutputInterface::VERBOSITY_VERY_VERBOSE: case OutputInterface::VERBOSITY_DEBUG: return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; default: return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; } } /** * Overwrites a previous message to the output. */ private function overwrite(string $message) { if ($this->output->isDecorated()) { $this->output->write("\r\x1b[2K"); $this->output->write($message); } else { $this->output->writeln($message); } } private function getCurrentTimeInMilliseconds() : float { return \round(\microtime(\true) * 1000); } private static function initPlaceholderFormatters() : array { return ['indicator' => function (self $indicator) { return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; }, 'message' => function (self $indicator) { return $indicator->message; }, 'elapsed' => function (self $indicator) { return Helper::formatTime(\time() - $indicator->startTime); }, 'memory' => function () { return Helper::formatMemory(\memory_get_usage(\true)); }]; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Helper; use _ContaoManager\Symfony\Component\Console\Descriptor\DescriptorInterface; use _ContaoManager\Symfony\Component\Console\Descriptor\JsonDescriptor; use _ContaoManager\Symfony\Component\Console\Descriptor\MarkdownDescriptor; use _ContaoManager\Symfony\Component\Console\Descriptor\TextDescriptor; use _ContaoManager\Symfony\Component\Console\Descriptor\XmlDescriptor; use _ContaoManager\Symfony\Component\Console\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * This class adds helper method to describe objects in various formats. * * @author Jean-François Simon */ class DescriptorHelper extends Helper { /** * @var DescriptorInterface[] */ private $descriptors = []; public function __construct() { $this->register('txt', new TextDescriptor())->register('xml', new XmlDescriptor())->register('json', new JsonDescriptor())->register('md', new MarkdownDescriptor()); } /** * Describes an object if supported. * * Available options are: * * format: string, the output format name * * raw_text: boolean, sets output type as raw * * @throws InvalidArgumentException when the given format is not supported */ public function describe(OutputInterface $output, ?object $object, array $options = []) { $options = \array_merge(['raw_text' => \false, 'format' => 'txt'], $options); if (!isset($this->descriptors[$options['format']])) { throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $options['format'])); } $descriptor = $this->descriptors[$options['format']]; $descriptor->describe($output, $object, $options); } /** * Registers a descriptor. * * @return $this */ public function register(string $format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } /** * {@inheritdoc} */ public function getName() { return 'descriptor'; } public function getFormats() : array { return \array_keys($this->descriptors); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\DependencyInjection; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Command\LazyCommand; use _ContaoManager\Symfony\Component\Console\CommandLoader\ContainerCommandLoader; use _ContaoManager\Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use _ContaoManager\Symfony\Component\DependencyInjection\Reference; use _ContaoManager\Symfony\Component\DependencyInjection\TypedReference; /** * Registers console commands. * * @author Grégoire Pineau */ class AddConsoleCommandPass implements CompilerPassInterface { private $commandLoaderServiceId; private $commandTag; private $noPreloadTag; private $privateTagName; public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private') { if (0 < \func_num_args()) { \trigger_deprecation('symfony/console', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); } $this->commandLoaderServiceId = $commandLoaderServiceId; $this->commandTag = $commandTag; $this->noPreloadTag = $noPreloadTag; $this->privateTagName = $privateTagName; } public function process(ContainerBuilder $container) { $commandServices = $container->findTaggedServiceIds($this->commandTag, \true); $lazyCommandMap = []; $lazyCommandRefs = []; $serviceIds = []; foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); $definition->addTag($this->noPreloadTag); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (isset($tags[0]['command'])) { $aliases = $tags[0]['command']; } else { if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } $aliases = \str_replace('%', '%%', $class::getDefaultName() ?? ''); } $aliases = \explode('|', $aliases ?? ''); $commandName = \array_shift($aliases); if ($isHidden = '' === $commandName) { $commandName = \array_shift($aliases); } if (null === $commandName) { if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) { $commandId = 'console.command.public_alias.' . $id; $container->setAlias($commandId, $id)->setPublic(\true); $id = $commandId; } $serviceIds[] = $id; continue; } $description = $tags[0]['description'] ?? null; unset($tags[0]); $lazyCommandMap[$commandName] = $id; $lazyCommandRefs[$id] = new TypedReference($id, $class); foreach ($aliases as $alias) { $lazyCommandMap[$alias] = $id; } foreach ($tags as $tag) { if (isset($tag['command'])) { $aliases[] = $tag['command']; $lazyCommandMap[$tag['command']] = $id; } $description = $description ?? $tag['description'] ?? null; } $definition->addMethodCall('setName', [$commandName]); if ($aliases) { $definition->addMethodCall('setAliases', [$aliases]); } if ($isHidden) { $definition->addMethodCall('setHidden', [\true]); } if (!$description) { if (!($r = $container->getReflectionClass($class))) { throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } $description = \str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } if ($description) { $definition->addMethodCall('setDescription', [$description]); $container->register('.' . $id . '.lazy', LazyCommand::class)->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); $lazyCommandRefs[$id] = new Reference('.' . $id . '.lazy'); } } $container->register($this->commandLoaderServiceId, ContainerCommandLoader::class)->setPublic(\true)->addTag($this->noPreloadTag)->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); $container->setParameter('console.command.ids', $serviceIds); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle */ class LogicException extends \LogicException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * Represents an incorrect option name or value typed in the console. * * @author Jérôme Tamarelle */ class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * ExceptionInterface. * * @author Jérôme Tamarelle */ interface ExceptionInterface extends \Throwable { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * Represents an incorrect namespace typed in the console. * * @author Pierre du Plessis */ class NamespaceNotFoundException extends CommandNotFoundException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * Represents failure to read input from stdin. * * @author Gabriel Ostrolucký */ class MissingInputException extends RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Exception; /** * Represents an incorrect command name typed in the console. * * @author Jérôme Tamarelle */ class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface { private $alternatives; /** * @param string $message Exception message to throw * @param string[] $alternatives List of similar defined names * @param int $code Exception code * @param \Throwable|null $previous Previous exception used for the exception chaining */ public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->alternatives = $alternatives; } /** * @return string[] */ public function getAlternatives() { return $this->alternatives; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Event; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Allows to manipulate the exit code of a command after its execution. * * @author Francesco Levorato */ final class ConsoleTerminateEvent extends ConsoleEvent { private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode) { parent::__construct($command, $input, $output); $this->setExitCode($exitCode); } public function setExitCode(int $exitCode) : void { $this->exitCode = $exitCode; } public function getExitCode() : int { return $this->exitCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Event; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * Allows to handle throwables thrown while running a command. * * @author Wouter de Jong */ final class ConsoleErrorEvent extends ConsoleEvent { private $error; private $exitCode; public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null) { parent::__construct($command, $input, $output); $this->error = $error; } public function getError() : \Throwable { return $this->error; } public function setError(\Throwable $error) : void { $this->error = $error; } public function setExitCode(int $exitCode) : void { $this->exitCode = $exitCode; $r = new \ReflectionProperty($this->error, 'code'); $r->setAccessible(\true); $r->setValue($this->error, $this->exitCode); } public function getExitCode() : int { return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Event; /** * Allows to do things before the command is executed, like skipping the command or executing code before the command is * going to be executed. * * Changing the input arguments will have no effect. * * @author Fabien Potencier */ final class ConsoleCommandEvent extends ConsoleEvent { /** * The return code for skipped commands, this will also be passed into the terminate event. */ public const RETURN_CODE_DISABLED = 113; /** * Indicates if the command should be run or skipped. */ private $commandShouldRun = \true; /** * Disables the command, so it won't be run. */ public function disableCommand() : bool { return $this->commandShouldRun = \false; } public function enableCommand() : bool { return $this->commandShouldRun = \true; } /** * Returns true if the command is runnable, false otherwise. */ public function commandShouldRun() : bool { return $this->commandShouldRun; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Event; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; use _ContaoManager\Symfony\Contracts\EventDispatcher\Event; /** * Allows to inspect input and output of a command. * * @author Francesco Levorato */ class ConsoleEvent extends Event { protected $command; private $input; private $output; public function __construct(?Command $command, InputInterface $input, OutputInterface $output) { $this->command = $command; $this->input = $input; $this->output = $output; } /** * Gets the command that is executed. * * @return Command|null */ public function getCommand() { return $this->command; } /** * Gets the input instance. * * @return InputInterface */ public function getInput() { return $this->input; } /** * Gets the output instance. * * @return OutputInterface */ public function getOutput() { return $this->output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\Event; use _ContaoManager\Symfony\Component\Console\Command\Command; use _ContaoManager\Symfony\Component\Console\Input\InputInterface; use _ContaoManager\Symfony\Component\Console\Output\OutputInterface; /** * @author marie */ final class ConsoleSignalEvent extends ConsoleEvent { private $handlingSignal; public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal) { parent::__construct($command, $input, $output); $this->handlingSignal = $handlingSignal; } public function getHandlingSignal() : int { return $this->handlingSignal; } } { "name": "symfony\/console", "type": "library", "description": "Eases the creation of beautiful and testable command line interfaces", "keywords": [ "console", "cli", "command-line", "terminal" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.2.5", "symfony\/deprecation-contracts": "^2.1|^3", "symfony\/polyfill-mbstring": "~1.0", "symfony\/polyfill-php73": "^1.9", "symfony\/polyfill-php80": "^1.16", "symfony\/service-contracts": "^1.1|^2|^3", "symfony\/string": "^5.1|^6.0" }, "require-dev": { "symfony\/config": "^4.4|^5.0|^6.0", "symfony\/event-dispatcher": "^4.4|^5.0|^6.0", "symfony\/dependency-injection": "^4.4|^5.0|^6.0", "symfony\/lock": "^4.4|^5.0|^6.0", "symfony\/process": "^4.4|^5.0|^6.0", "symfony\/var-dumper": "^4.4|^5.0|^6.0", "psr\/log": "^1|^2" }, "provide": { "psr\/log-implementation": "1.0|2.0" }, "suggest": { "symfony\/event-dispatcher": "", "symfony\/lock": "", "symfony\/process": "", "psr\/log": "For using the console logger" }, "conflict": { "psr\/log": ">=3", "symfony\/dependency-injection": "<4.4", "symfony\/dotenv": "<5.1", "symfony\/event-dispatcher": "<4.4", "symfony\/lock": "<4.4", "symfony\/process": "<4.4" }, "autoload": { "psr-4": { "_ContaoManager\\Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "\/Tests\/" ] }, "minimum-stability": "dev" } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace _ContaoManager\Symfony\Component\Console\EventListener; use _ContaoManager\Psr\Log\LoggerInterface; use _ContaoManager\Symfony\Component\Console\ConsoleEvents; use _ContaoManager\Symfony\Component\Console\Event\ConsoleErrorEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleEvent; use _ContaoManager\Symfony\Component\Console\Event\ConsoleTerminateEvent; use _ContaoManager\Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * @author James Halsall * @author Robin Chalas */ class ErrorListener implements EventSubscriberInterface { private $logger; public function __construct(?LoggerInterface $logger = null) { $this->logger = $logger; } public function onConsoleError(ConsoleErrorEvent $event) { if (null === $this->logger) { return; } $error = $event->getError(); if (!($inputString = $this->getInputString($event))) { $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); return; } $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); } public function onConsoleTerminate(ConsoleTerminateEvent $event) { if (null === $this->logger) { return; } $exitCode = $event->getExitCode(); if (0 === $exitCode) { return; } if (!($inputString = $this->getInputString($event))) { $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); return; } $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); } public static function getSubscribedEvents() { return [ConsoleEvents::ERROR => ['onConsoleError', -128], ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128]]; } private static function getInputString(ConsoleEvent $event) : ?string { $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; $input = $event->getInput(); if (\method_exists($input, '__toString')) { if ($commandName) { return \str_replace(["'{$commandName}'", "\"{$commandName}\""], $commandName, (string) $input); } return (string) $input; } return $commandName; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Mbstring as p; if (!function_exists('mb_convert_encoding')) { function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } } if (!function_exists('mb_decode_mimeheader')) { function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } } if (!function_exists('mb_encode_mimeheader')) { function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } } if (!function_exists('mb_decode_numericentity')) { function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } } if (!function_exists('mb_encode_numericentity')) { function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } } if (!function_exists('mb_convert_case')) { function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } } if (!function_exists('mb_internal_encoding')) { function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } } if (!function_exists('mb_language')) { function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } } if (!function_exists('mb_list_encodings')) { function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } } if (!function_exists('mb_encoding_aliases')) { function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } } if (!function_exists('mb_check_encoding')) { function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } } if (!function_exists('mb_detect_encoding')) { function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } } if (!function_exists('mb_detect_order')) { function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } } if (!function_exists('mb_parse_str')) { function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } } if (!function_exists('mb_strlen')) { function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } } if (!function_exists('mb_strpos')) { function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } } if (!function_exists('mb_strtolower')) { function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } } if (!function_exists('mb_strtoupper')) { function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } } if (!function_exists('mb_substitute_character')) { function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } } if (!function_exists('mb_substr')) { function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } } if (!function_exists('mb_stripos')) { function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } } if (!function_exists('mb_stristr')) { function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } } if (!function_exists('mb_strrchr')) { function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } } if (!function_exists('mb_strrichr')) { function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } } if (!function_exists('mb_strripos')) { function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } } if (!function_exists('mb_strrpos')) { function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } } if (!function_exists('mb_strstr')) { function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } } if (!function_exists('mb_get_info')) { function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } } if (!function_exists('mb_http_output')) { function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } } if (!function_exists('mb_strwidth')) { function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } } if (!function_exists('mb_substr_count')) { function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } } if (!function_exists('mb_output_handler')) { function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } } if (!function_exists('mb_http_input')) { function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } } if (!function_exists('mb_convert_variables')) { function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } } if (!function_exists('mb_ord')) { function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } } if (!function_exists('mb_chr')) { function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } } if (!function_exists('mb_scrub')) { function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } } if (!function_exists('mb_str_split')) { function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } } if (!function_exists('mb_str_pad')) { function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } } if (extension_loaded('mbstring')) { return; } if (!defined('MB_CASE_UPPER')) { define('MB_CASE_UPPER', 0); } if (!defined('MB_CASE_LOWER')) { define('MB_CASE_LOWER', 1); } if (!defined('MB_CASE_TITLE')) { define('MB_CASE_TITLE', 2); } Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 'i̇', 'µ' => 'μ', 'ſ' => 's', 'ͅ' => 'ι', 'ς' => 'σ', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϵ' => 'ε', 'ẛ' => 'ṡ', 'ι' => 'ι', 'ß' => 'ss', 'ʼn' => 'ʼn', 'ǰ' => 'ǰ', 'ΐ' => 'ΐ', 'ΰ' => 'ΰ', 'և' => 'եւ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẚ' => 'aʾ', 'ẞ' => 'ss', 'ὐ' => 'ὐ', 'ὒ' => 'ὒ', 'ὔ' => 'ὔ', 'ὖ' => 'ὖ', 'ᾀ' => 'ἀι', 'ᾁ' => 'ἁι', 'ᾂ' => 'ἂι', 'ᾃ' => 'ἃι', 'ᾄ' => 'ἄι', 'ᾅ' => 'ἅι', 'ᾆ' => 'ἆι', 'ᾇ' => 'ἇι', 'ᾈ' => 'ἀι', 'ᾉ' => 'ἁι', 'ᾊ' => 'ἂι', 'ᾋ' => 'ἃι', 'ᾌ' => 'ἄι', 'ᾍ' => 'ἅι', 'ᾎ' => 'ἆι', 'ᾏ' => 'ἇι', 'ᾐ' => 'ἠι', 'ᾑ' => 'ἡι', 'ᾒ' => 'ἢι', 'ᾓ' => 'ἣι', 'ᾔ' => 'ἤι', 'ᾕ' => 'ἥι', 'ᾖ' => 'ἦι', 'ᾗ' => 'ἧι', 'ᾘ' => 'ἠι', 'ᾙ' => 'ἡι', 'ᾚ' => 'ἢι', 'ᾛ' => 'ἣι', 'ᾜ' => 'ἤι', 'ᾝ' => 'ἥι', 'ᾞ' => 'ἦι', 'ᾟ' => 'ἧι', 'ᾠ' => 'ὠι', 'ᾡ' => 'ὡι', 'ᾢ' => 'ὢι', 'ᾣ' => 'ὣι', 'ᾤ' => 'ὤι', 'ᾥ' => 'ὥι', 'ᾦ' => 'ὦι', 'ᾧ' => 'ὧι', 'ᾨ' => 'ὠι', 'ᾩ' => 'ὡι', 'ᾪ' => 'ὢι', 'ᾫ' => 'ὣι', 'ᾬ' => 'ὤι', 'ᾭ' => 'ὥι', 'ᾮ' => 'ὦι', 'ᾯ' => 'ὧι', 'ᾲ' => 'ὰι', 'ᾳ' => 'αι', 'ᾴ' => 'άι', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾶι', 'ᾼ' => 'αι', 'ῂ' => 'ὴι', 'ῃ' => 'ηι', 'ῄ' => 'ήι', 'ῆ' => 'ῆ', 'ῇ' => 'ῆι', 'ῌ' => 'ηι', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'ῲ' => 'ὼι', 'ῳ' => 'ωι', 'ῴ' => 'ώι', 'ῶ' => 'ῶ', 'ῷ' => 'ῶι', 'ῼ' => 'ωι', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ']; 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', 'À' => 'à', 'Á' => 'á', 'Â' => 'â', 'Ã' => 'ã', 'Ä' => 'ä', 'Å' => 'å', 'Æ' => 'æ', 'Ç' => 'ç', 'È' => 'è', 'É' => 'é', 'Ê' => 'ê', 'Ë' => 'ë', 'Ì' => 'ì', 'Í' => 'í', 'Î' => 'î', 'Ï' => 'ï', 'Ð' => 'ð', 'Ñ' => 'ñ', 'Ò' => 'ò', 'Ó' => 'ó', 'Ô' => 'ô', 'Õ' => 'õ', 'Ö' => 'ö', 'Ø' => 'ø', 'Ù' => 'ù', 'Ú' => 'ú', 'Û' => 'û', 'Ü' => 'ü', 'Ý' => 'ý', 'Þ' => 'þ', 'Ā' => 'ā', 'Ă' => 'ă', 'Ą' => 'ą', 'Ć' => 'ć', 'Ĉ' => 'ĉ', 'Ċ' => 'ċ', 'Č' => 'č', 'Ď' => 'ď', 'Đ' => 'đ', 'Ē' => 'ē', 'Ĕ' => 'ĕ', 'Ė' => 'ė', 'Ę' => 'ę', 'Ě' => 'ě', 'Ĝ' => 'ĝ', 'Ğ' => 'ğ', 'Ġ' => 'ġ', 'Ģ' => 'ģ', 'Ĥ' => 'ĥ', 'Ħ' => 'ħ', 'Ĩ' => 'ĩ', 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', 'İ' => 'i̇', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', 'Ĺ' => 'ĺ', 'Ļ' => 'ļ', 'Ľ' => 'ľ', 'Ŀ' => 'ŀ', 'Ł' => 'ł', 'Ń' => 'ń', 'Ņ' => 'ņ', 'Ň' => 'ň', 'Ŋ' => 'ŋ', 'Ō' => 'ō', 'Ŏ' => 'ŏ', 'Ő' => 'ő', 'Œ' => 'œ', 'Ŕ' => 'ŕ', 'Ŗ' => 'ŗ', 'Ř' => 'ř', 'Ś' => 'ś', 'Ŝ' => 'ŝ', 'Ş' => 'ş', 'Š' => 'š', 'Ţ' => 'ţ', 'Ť' => 'ť', 'Ŧ' => 'ŧ', 'Ũ' => 'ũ', 'Ū' => 'ū', 'Ŭ' => 'ŭ', 'Ů' => 'ů', 'Ű' => 'ű', 'Ų' => 'ų', 'Ŵ' => 'ŵ', 'Ŷ' => 'ŷ', 'Ÿ' => 'ÿ', 'Ź' => 'ź', 'Ż' => 'ż', 'Ž' => 'ž', 'Ɓ' => 'ɓ', 'Ƃ' => 'ƃ', 'Ƅ' => 'ƅ', 'Ɔ' => 'ɔ', 'Ƈ' => 'ƈ', 'Ɖ' => 'ɖ', 'Ɗ' => 'ɗ', 'Ƌ' => 'ƌ', 'Ǝ' => 'ǝ', 'Ə' => 'ə', 'Ɛ' => 'ɛ', 'Ƒ' => 'ƒ', 'Ɠ' => 'ɠ', 'Ɣ' => 'ɣ', 'Ɩ' => 'ɩ', 'Ɨ' => 'ɨ', 'Ƙ' => 'ƙ', 'Ɯ' => 'ɯ', 'Ɲ' => 'ɲ', 'Ɵ' => 'ɵ', 'Ơ' => 'ơ', 'Ƣ' => 'ƣ', 'Ƥ' => 'ƥ', 'Ʀ' => 'ʀ', 'Ƨ' => 'ƨ', 'Ʃ' => 'ʃ', 'Ƭ' => 'ƭ', 'Ʈ' => 'ʈ', 'Ư' => 'ư', 'Ʊ' => 'ʊ', 'Ʋ' => 'ʋ', 'Ƴ' => 'ƴ', 'Ƶ' => 'ƶ', 'Ʒ' => 'ʒ', 'Ƹ' => 'ƹ', 'Ƽ' => 'ƽ', 'DŽ' => 'dž', 'Dž' => 'dž', 'LJ' => 'lj', 'Lj' => 'lj', 'NJ' => 'nj', 'Nj' => 'nj', 'Ǎ' => 'ǎ', 'Ǐ' => 'ǐ', 'Ǒ' => 'ǒ', 'Ǔ' => 'ǔ', 'Ǖ' => 'ǖ', 'Ǘ' => 'ǘ', 'Ǚ' => 'ǚ', 'Ǜ' => 'ǜ', 'Ǟ' => 'ǟ', 'Ǡ' => 'ǡ', 'Ǣ' => 'ǣ', 'Ǥ' => 'ǥ', 'Ǧ' => 'ǧ', 'Ǩ' => 'ǩ', 'Ǫ' => 'ǫ', 'Ǭ' => 'ǭ', 'Ǯ' => 'ǯ', 'DZ' => 'dz', 'Dz' => 'dz', 'Ǵ' => 'ǵ', 'Ƕ' => 'ƕ', 'Ƿ' => 'ƿ', 'Ǹ' => 'ǹ', 'Ǻ' => 'ǻ', 'Ǽ' => 'ǽ', 'Ǿ' => 'ǿ', 'Ȁ' => 'ȁ', 'Ȃ' => 'ȃ', 'Ȅ' => 'ȅ', 'Ȇ' => 'ȇ', 'Ȉ' => 'ȉ', 'Ȋ' => 'ȋ', 'Ȍ' => 'ȍ', 'Ȏ' => 'ȏ', 'Ȑ' => 'ȑ', 'Ȓ' => 'ȓ', 'Ȕ' => 'ȕ', 'Ȗ' => 'ȗ', 'Ș' => 'ș', 'Ț' => 'ț', 'Ȝ' => 'ȝ', 'Ȟ' => 'ȟ', 'Ƞ' => 'ƞ', 'Ȣ' => 'ȣ', 'Ȥ' => 'ȥ', 'Ȧ' => 'ȧ', 'Ȩ' => 'ȩ', 'Ȫ' => 'ȫ', 'Ȭ' => 'ȭ', 'Ȯ' => 'ȯ', 'Ȱ' => 'ȱ', 'Ȳ' => 'ȳ', 'Ⱥ' => 'ⱥ', 'Ȼ' => 'ȼ', 'Ƚ' => 'ƚ', 'Ⱦ' => 'ⱦ', 'Ɂ' => 'ɂ', 'Ƀ' => 'ƀ', 'Ʉ' => 'ʉ', 'Ʌ' => 'ʌ', 'Ɇ' => 'ɇ', 'Ɉ' => 'ɉ', 'Ɋ' => 'ɋ', 'Ɍ' => 'ɍ', 'Ɏ' => 'ɏ', 'Ͱ' => 'ͱ', 'Ͳ' => 'ͳ', 'Ͷ' => 'ͷ', 'Ϳ' => 'ϳ', 'Ά' => 'ά', 'Έ' => 'έ', 'Ή' => 'ή', 'Ί' => 'ί', 'Ό' => 'ό', 'Ύ' => 'ύ', 'Ώ' => 'ώ', 'Α' => 'α', 'Β' => 'β', 'Γ' => 'γ', 'Δ' => 'δ', 'Ε' => 'ε', 'Ζ' => 'ζ', 'Η' => 'η', 'Θ' => 'θ', 'Ι' => 'ι', 'Κ' => 'κ', 'Λ' => 'λ', 'Μ' => 'μ', 'Ν' => 'ν', 'Ξ' => 'ξ', 'Ο' => 'ο', 'Π' => 'π', 'Ρ' => 'ρ', 'Σ' => 'σ', 'Τ' => 'τ', 'Υ' => 'υ', 'Φ' => 'φ', 'Χ' => 'χ', 'Ψ' => 'ψ', 'Ω' => 'ω', 'Ϊ' => 'ϊ', 'Ϋ' => 'ϋ', 'Ϗ' => 'ϗ', 'Ϙ' => 'ϙ', 'Ϛ' => 'ϛ', 'Ϝ' => 'ϝ', 'Ϟ' => 'ϟ', 'Ϡ' => 'ϡ', 'Ϣ' => 'ϣ', 'Ϥ' => 'ϥ', 'Ϧ' => 'ϧ', 'Ϩ' => 'ϩ', 'Ϫ' => 'ϫ', 'Ϭ' => 'ϭ', 'Ϯ' => 'ϯ', 'ϴ' => 'θ', 'Ϸ' => 'ϸ', 'Ϲ' => 'ϲ', 'Ϻ' => 'ϻ', 'Ͻ' => 'ͻ', 'Ͼ' => 'ͼ', 'Ͽ' => 'ͽ', 'Ѐ' => 'ѐ', 'Ё' => 'ё', 'Ђ' => 'ђ', 'Ѓ' => 'ѓ', 'Є' => 'є', 'Ѕ' => 'ѕ', 'І' => 'і', 'Ї' => 'ї', 'Ј' => 'ј', 'Љ' => 'љ', 'Њ' => 'њ', 'Ћ' => 'ћ', 'Ќ' => 'ќ', 'Ѝ' => 'ѝ', 'Ў' => 'ў', 'Џ' => 'џ', 'А' => 'а', 'Б' => 'б', 'В' => 'в', 'Г' => 'г', 'Д' => 'д', 'Е' => 'е', 'Ж' => 'ж', 'З' => 'з', 'И' => 'и', 'Й' => 'й', 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н', 'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', 'Т' => 'т', 'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', 'Ц' => 'ц', 'Ч' => 'ч', 'Ш' => 'ш', 'Щ' => 'щ', 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь', 'Э' => 'э', 'Ю' => 'ю', 'Я' => 'я', 'Ѡ' => 'ѡ', 'Ѣ' => 'ѣ', 'Ѥ' => 'ѥ', 'Ѧ' => 'ѧ', 'Ѩ' => 'ѩ', 'Ѫ' => 'ѫ', 'Ѭ' => 'ѭ', 'Ѯ' => 'ѯ', 'Ѱ' => 'ѱ', 'Ѳ' => 'ѳ', 'Ѵ' => 'ѵ', 'Ѷ' => 'ѷ', 'Ѹ' => 'ѹ', 'Ѻ' => 'ѻ', 'Ѽ' => 'ѽ', 'Ѿ' => 'ѿ', 'Ҁ' => 'ҁ', 'Ҋ' => 'ҋ', 'Ҍ' => 'ҍ', 'Ҏ' => 'ҏ', 'Ґ' => 'ґ', 'Ғ' => 'ғ', 'Ҕ' => 'ҕ', 'Җ' => 'җ', 'Ҙ' => 'ҙ', 'Қ' => 'қ', 'Ҝ' => 'ҝ', 'Ҟ' => 'ҟ', 'Ҡ' => 'ҡ', 'Ң' => 'ң', 'Ҥ' => 'ҥ', 'Ҧ' => 'ҧ', 'Ҩ' => 'ҩ', 'Ҫ' => 'ҫ', 'Ҭ' => 'ҭ', 'Ү' => 'ү', 'Ұ' => 'ұ', 'Ҳ' => 'ҳ', 'Ҵ' => 'ҵ', 'Ҷ' => 'ҷ', 'Ҹ' => 'ҹ', 'Һ' => 'һ', 'Ҽ' => 'ҽ', 'Ҿ' => 'ҿ', 'Ӏ' => 'ӏ', 'Ӂ' => 'ӂ', 'Ӄ' => 'ӄ', 'Ӆ' => 'ӆ', 'Ӈ' => 'ӈ', 'Ӊ' => 'ӊ', 'Ӌ' => 'ӌ', 'Ӎ' => 'ӎ', 'Ӑ' => 'ӑ', 'Ӓ' => 'ӓ', 'Ӕ' => 'ӕ', 'Ӗ' => 'ӗ', 'Ә' => 'ә', 'Ӛ' => 'ӛ', 'Ӝ' => 'ӝ', 'Ӟ' => 'ӟ', 'Ӡ' => 'ӡ', 'Ӣ' => 'ӣ', 'Ӥ' => 'ӥ', 'Ӧ' => 'ӧ', 'Ө' => 'ө', 'Ӫ' => 'ӫ', 'Ӭ' => 'ӭ', 'Ӯ' => 'ӯ', 'Ӱ' => 'ӱ', 'Ӳ' => 'ӳ', 'Ӵ' => 'ӵ', 'Ӷ' => 'ӷ', 'Ӹ' => 'ӹ', 'Ӻ' => 'ӻ', 'Ӽ' => 'ӽ', 'Ӿ' => 'ӿ', 'Ԁ' => 'ԁ', 'Ԃ' => 'ԃ', 'Ԅ' => 'ԅ', 'Ԇ' => 'ԇ', 'Ԉ' => 'ԉ', 'Ԋ' => 'ԋ', 'Ԍ' => 'ԍ', 'Ԏ' => 'ԏ', 'Ԑ' => 'ԑ', 'Ԓ' => 'ԓ', 'Ԕ' => 'ԕ', 'Ԗ' => 'ԗ', 'Ԙ' => 'ԙ', 'Ԛ' => 'ԛ', 'Ԝ' => 'ԝ', 'Ԟ' => 'ԟ', 'Ԡ' => 'ԡ', 'Ԣ' => 'ԣ', 'Ԥ' => 'ԥ', 'Ԧ' => 'ԧ', 'Ԩ' => 'ԩ', 'Ԫ' => 'ԫ', 'Ԭ' => 'ԭ', 'Ԯ' => 'ԯ', 'Ա' => 'ա', 'Բ' => 'բ', 'Գ' => 'գ', 'Դ' => 'դ', 'Ե' => 'ե', 'Զ' => 'զ', 'Է' => 'է', 'Ը' => 'ը', 'Թ' => 'թ', 'Ժ' => 'ժ', 'Ի' => 'ի', 'Լ' => 'լ', 'Խ' => 'խ', 'Ծ' => 'ծ', 'Կ' => 'կ', 'Հ' => 'հ', 'Ձ' => 'ձ', 'Ղ' => 'ղ', 'Ճ' => 'ճ', 'Մ' => 'մ', 'Յ' => 'յ', 'Ն' => 'ն', 'Շ' => 'շ', 'Ո' => 'ո', 'Չ' => 'չ', 'Պ' => 'պ', 'Ջ' => 'ջ', 'Ռ' => 'ռ', 'Ս' => 'ս', 'Վ' => 'վ', 'Տ' => 'տ', 'Ր' => 'ր', 'Ց' => 'ց', 'Ւ' => 'ւ', 'Փ' => 'փ', 'Ք' => 'ք', 'Օ' => 'օ', 'Ֆ' => 'ֆ', 'Ⴀ' => 'ⴀ', 'Ⴁ' => 'ⴁ', 'Ⴂ' => 'ⴂ', 'Ⴃ' => 'ⴃ', 'Ⴄ' => 'ⴄ', 'Ⴅ' => 'ⴅ', 'Ⴆ' => 'ⴆ', 'Ⴇ' => 'ⴇ', 'Ⴈ' => 'ⴈ', 'Ⴉ' => 'ⴉ', 'Ⴊ' => 'ⴊ', 'Ⴋ' => 'ⴋ', 'Ⴌ' => 'ⴌ', 'Ⴍ' => 'ⴍ', 'Ⴎ' => 'ⴎ', 'Ⴏ' => 'ⴏ', 'Ⴐ' => 'ⴐ', 'Ⴑ' => 'ⴑ', 'Ⴒ' => 'ⴒ', 'Ⴓ' => 'ⴓ', 'Ⴔ' => 'ⴔ', 'Ⴕ' => 'ⴕ', 'Ⴖ' => 'ⴖ', 'Ⴗ' => 'ⴗ', 'Ⴘ' => 'ⴘ', 'Ⴙ' => 'ⴙ', 'Ⴚ' => 'ⴚ', 'Ⴛ' => 'ⴛ', 'Ⴜ' => 'ⴜ', 'Ⴝ' => 'ⴝ', 'Ⴞ' => 'ⴞ', 'Ⴟ' => 'ⴟ', 'Ⴠ' => 'ⴠ', 'Ⴡ' => 'ⴡ', 'Ⴢ' => 'ⴢ', 'Ⴣ' => 'ⴣ', 'Ⴤ' => 'ⴤ', 'Ⴥ' => 'ⴥ', 'Ⴧ' => 'ⴧ', 'Ⴭ' => 'ⴭ', 'Ꭰ' => 'ꭰ', 'Ꭱ' => 'ꭱ', 'Ꭲ' => 'ꭲ', 'Ꭳ' => 'ꭳ', 'Ꭴ' => 'ꭴ', 'Ꭵ' => 'ꭵ', 'Ꭶ' => 'ꭶ', 'Ꭷ' => 'ꭷ', 'Ꭸ' => 'ꭸ', 'Ꭹ' => 'ꭹ', 'Ꭺ' => 'ꭺ', 'Ꭻ' => 'ꭻ', 'Ꭼ' => 'ꭼ', 'Ꭽ' => 'ꭽ', 'Ꭾ' => 'ꭾ', 'Ꭿ' => 'ꭿ', 'Ꮀ' => 'ꮀ', 'Ꮁ' => 'ꮁ', 'Ꮂ' => 'ꮂ', 'Ꮃ' => 'ꮃ', 'Ꮄ' => 'ꮄ', 'Ꮅ' => 'ꮅ', 'Ꮆ' => 'ꮆ', 'Ꮇ' => 'ꮇ', 'Ꮈ' => 'ꮈ', 'Ꮉ' => 'ꮉ', 'Ꮊ' => 'ꮊ', 'Ꮋ' => 'ꮋ', 'Ꮌ' => 'ꮌ', 'Ꮍ' => 'ꮍ', 'Ꮎ' => 'ꮎ', 'Ꮏ' => 'ꮏ', 'Ꮐ' => 'ꮐ', 'Ꮑ' => 'ꮑ', 'Ꮒ' => 'ꮒ', 'Ꮓ' => 'ꮓ', 'Ꮔ' => 'ꮔ', 'Ꮕ' => 'ꮕ', 'Ꮖ' => 'ꮖ', 'Ꮗ' => 'ꮗ', 'Ꮘ' => 'ꮘ', 'Ꮙ' => 'ꮙ', 'Ꮚ' => 'ꮚ', 'Ꮛ' => 'ꮛ', 'Ꮜ' => 'ꮜ', 'Ꮝ' => 'ꮝ', 'Ꮞ' => 'ꮞ', 'Ꮟ' => 'ꮟ', 'Ꮠ' => 'ꮠ', 'Ꮡ' => 'ꮡ', 'Ꮢ' => 'ꮢ', 'Ꮣ' => 'ꮣ', 'Ꮤ' => 'ꮤ', 'Ꮥ' => 'ꮥ', 'Ꮦ' => 'ꮦ', 'Ꮧ' => 'ꮧ', 'Ꮨ' => 'ꮨ', 'Ꮩ' => 'ꮩ', 'Ꮪ' => 'ꮪ', 'Ꮫ' => 'ꮫ', 'Ꮬ' => 'ꮬ', 'Ꮭ' => 'ꮭ', 'Ꮮ' => 'ꮮ', 'Ꮯ' => 'ꮯ', 'Ꮰ' => 'ꮰ', 'Ꮱ' => 'ꮱ', 'Ꮲ' => 'ꮲ', 'Ꮳ' => 'ꮳ', 'Ꮴ' => 'ꮴ', 'Ꮵ' => 'ꮵ', 'Ꮶ' => 'ꮶ', 'Ꮷ' => 'ꮷ', 'Ꮸ' => 'ꮸ', 'Ꮹ' => 'ꮹ', 'Ꮺ' => 'ꮺ', 'Ꮻ' => 'ꮻ', 'Ꮼ' => 'ꮼ', 'Ꮽ' => 'ꮽ', 'Ꮾ' => 'ꮾ', 'Ꮿ' => 'ꮿ', 'Ᏸ' => 'ᏸ', 'Ᏹ' => 'ᏹ', 'Ᏺ' => 'ᏺ', 'Ᏻ' => 'ᏻ', 'Ᏼ' => 'ᏼ', 'Ᏽ' => 'ᏽ', 'Ა' => 'ა', 'Ბ' => 'ბ', 'Გ' => 'გ', 'Დ' => 'დ', 'Ე' => 'ე', 'Ვ' => 'ვ', 'Ზ' => 'ზ', 'Თ' => 'თ', 'Ი' => 'ი', 'Კ' => 'კ', 'Ლ' => 'ლ', 'Მ' => 'მ', 'Ნ' => 'ნ', 'Ო' => 'ო', 'Პ' => 'პ', 'Ჟ' => 'ჟ', 'Რ' => 'რ', 'Ს' => 'ს', 'Ტ' => 'ტ', 'Უ' => 'უ', 'Ფ' => 'ფ', 'Ქ' => 'ქ', 'Ღ' => 'ღ', 'Ყ' => 'ყ', 'Შ' => 'შ', 'Ჩ' => 'ჩ', 'Ც' => 'ც', 'Ძ' => 'ძ', 'Წ' => 'წ', 'Ჭ' => 'ჭ', 'Ხ' => 'ხ', 'Ჯ' => 'ჯ', 'Ჰ' => 'ჰ', 'Ჱ' => 'ჱ', 'Ჲ' => 'ჲ', 'Ჳ' => 'ჳ', 'Ჴ' => 'ჴ', 'Ჵ' => 'ჵ', 'Ჶ' => 'ჶ', 'Ჷ' => 'ჷ', 'Ჸ' => 'ჸ', 'Ჹ' => 'ჹ', 'Ჺ' => 'ჺ', 'Ჽ' => 'ჽ', 'Ჾ' => 'ჾ', 'Ჿ' => 'ჿ', 'Ḁ' => 'ḁ', 'Ḃ' => 'ḃ', 'Ḅ' => 'ḅ', 'Ḇ' => 'ḇ', 'Ḉ' => 'ḉ', 'Ḋ' => 'ḋ', 'Ḍ' => 'ḍ', 'Ḏ' => 'ḏ', 'Ḑ' => 'ḑ', 'Ḓ' => 'ḓ', 'Ḕ' => 'ḕ', 'Ḗ' => 'ḗ', 'Ḙ' => 'ḙ', 'Ḛ' => 'ḛ', 'Ḝ' => 'ḝ', 'Ḟ' => 'ḟ', 'Ḡ' => 'ḡ', 'Ḣ' => 'ḣ', 'Ḥ' => 'ḥ', 'Ḧ' => 'ḧ', 'Ḩ' => 'ḩ', 'Ḫ' => 'ḫ', 'Ḭ' => 'ḭ', 'Ḯ' => 'ḯ', 'Ḱ' => 'ḱ', 'Ḳ' => 'ḳ', 'Ḵ' => 'ḵ', 'Ḷ' => 'ḷ', 'Ḹ' => 'ḹ', 'Ḻ' => 'ḻ', 'Ḽ' => 'ḽ', 'Ḿ' => 'ḿ', 'Ṁ' => 'ṁ', 'Ṃ' => 'ṃ', 'Ṅ' => 'ṅ', 'Ṇ' => 'ṇ', 'Ṉ' => 'ṉ', 'Ṋ' => 'ṋ', 'Ṍ' => 'ṍ', 'Ṏ' => 'ṏ', 'Ṑ' => 'ṑ', 'Ṓ' => 'ṓ', 'Ṕ' => 'ṕ', 'Ṗ' => 'ṗ', 'Ṙ' => 'ṙ', 'Ṛ' => 'ṛ', 'Ṝ' => 'ṝ', 'Ṟ' => 'ṟ', 'Ṡ' => 'ṡ', 'Ṣ' => 'ṣ', 'Ṥ' => 'ṥ', 'Ṧ' => 'ṧ', 'Ṩ' => 'ṩ', 'Ṫ' => 'ṫ', 'Ṭ' => 'ṭ', 'Ṯ' => 'ṯ', 'Ṱ' => 'ṱ', 'Ṳ' => 'ṳ', 'Ṵ' => 'ṵ', 'Ṷ' => 'ṷ', 'Ṹ' => 'ṹ', 'Ṻ' => 'ṻ', 'Ṽ' => 'ṽ', 'Ṿ' => 'ṿ', 'Ẁ' => 'ẁ', 'Ẃ' => 'ẃ', 'Ẅ' => 'ẅ', 'Ẇ' => 'ẇ', 'Ẉ' => 'ẉ', 'Ẋ' => 'ẋ', 'Ẍ' => 'ẍ', 'Ẏ' => 'ẏ', 'Ẑ' => 'ẑ', 'Ẓ' => 'ẓ', 'Ẕ' => 'ẕ', 'ẞ' => 'ß', 'Ạ' => 'ạ', 'Ả' => 'ả', 'Ấ' => 'ấ', 'Ầ' => 'ầ', 'Ẩ' => 'ẩ', 'Ẫ' => 'ẫ', 'Ậ' => 'ậ', 'Ắ' => 'ắ', 'Ằ' => 'ằ', 'Ẳ' => 'ẳ', 'Ẵ' => 'ẵ', 'Ặ' => 'ặ', 'Ẹ' => 'ẹ', 'Ẻ' => 'ẻ', 'Ẽ' => 'ẽ', 'Ế' => 'ế', 'Ề' => 'ề', 'Ể' => 'ể', 'Ễ' => 'ễ', 'Ệ' => 'ệ', 'Ỉ' => 'ỉ', 'Ị' => 'ị', 'Ọ' => 'ọ', 'Ỏ' => 'ỏ', 'Ố' => 'ố', 'Ồ' => 'ồ', 'Ổ' => 'ổ', 'Ỗ' => 'ỗ', 'Ộ' => 'ộ', 'Ớ' => 'ớ', 'Ờ' => 'ờ', 'Ở' => 'ở', 'Ỡ' => 'ỡ', 'Ợ' => 'ợ', 'Ụ' => 'ụ', 'Ủ' => 'ủ', 'Ứ' => 'ứ', 'Ừ' => 'ừ', 'Ử' => 'ử', 'Ữ' => 'ữ', 'Ự' => 'ự', 'Ỳ' => 'ỳ', 'Ỵ' => 'ỵ', 'Ỷ' => 'ỷ', 'Ỹ' => 'ỹ', 'Ỻ' => 'ỻ', 'Ỽ' => 'ỽ', 'Ỿ' => 'ỿ', 'Ἀ' => 'ἀ', 'Ἁ' => 'ἁ', 'Ἂ' => 'ἂ', 'Ἃ' => 'ἃ', 'Ἄ' => 'ἄ', 'Ἅ' => 'ἅ', 'Ἆ' => 'ἆ', 'Ἇ' => 'ἇ', 'Ἐ' => 'ἐ', 'Ἑ' => 'ἑ', 'Ἒ' => 'ἒ', 'Ἓ' => 'ἓ', 'Ἔ' => 'ἔ', 'Ἕ' => 'ἕ', 'Ἠ' => 'ἠ', 'Ἡ' => 'ἡ', 'Ἢ' => 'ἢ', 'Ἣ' => 'ἣ', 'Ἤ' => 'ἤ', 'Ἥ' => 'ἥ', 'Ἦ' => 'ἦ', 'Ἧ' => 'ἧ', 'Ἰ' => 'ἰ', 'Ἱ' => 'ἱ', 'Ἲ' => 'ἲ', 'Ἳ' => 'ἳ', 'Ἴ' => 'ἴ', 'Ἵ' => 'ἵ', 'Ἶ' => 'ἶ', 'Ἷ' => 'ἷ', 'Ὀ' => 'ὀ', 'Ὁ' => 'ὁ', 'Ὂ' => 'ὂ', 'Ὃ' => 'ὃ', 'Ὄ' => 'ὄ', 'Ὅ' => 'ὅ', 'Ὑ' => 'ὑ', 'Ὓ' => 'ὓ', 'Ὕ' => 'ὕ', 'Ὗ' => 'ὗ', 'Ὠ' => 'ὠ', 'Ὡ' => 'ὡ', 'Ὢ' => 'ὢ', 'Ὣ' => 'ὣ', 'Ὤ' => 'ὤ', 'Ὥ' => 'ὥ', 'Ὦ' => 'ὦ', 'Ὧ' => 'ὧ', 'ᾈ' => 'ᾀ', 'ᾉ' => 'ᾁ', 'ᾊ' => 'ᾂ', 'ᾋ' => 'ᾃ', 'ᾌ' => 'ᾄ', 'ᾍ' => 'ᾅ', 'ᾎ' => 'ᾆ', 'ᾏ' => 'ᾇ', 'ᾘ' => 'ᾐ', 'ᾙ' => 'ᾑ', 'ᾚ' => 'ᾒ', 'ᾛ' => 'ᾓ', 'ᾜ' => 'ᾔ', 'ᾝ' => 'ᾕ', 'ᾞ' => 'ᾖ', 'ᾟ' => 'ᾗ', 'ᾨ' => 'ᾠ', 'ᾩ' => 'ᾡ', 'ᾪ' => 'ᾢ', 'ᾫ' => 'ᾣ', 'ᾬ' => 'ᾤ', 'ᾭ' => 'ᾥ', 'ᾮ' => 'ᾦ', 'ᾯ' => 'ᾧ', 'Ᾰ' => 'ᾰ', 'Ᾱ' => 'ᾱ', 'Ὰ' => 'ὰ', 'Ά' => 'ά', 'ᾼ' => 'ᾳ', 'Ὲ' => 'ὲ', 'Έ' => 'έ', 'Ὴ' => 'ὴ', 'Ή' => 'ή', 'ῌ' => 'ῃ', 'Ῐ' => 'ῐ', 'Ῑ' => 'ῑ', 'Ὶ' => 'ὶ', 'Ί' => 'ί', 'Ῠ' => 'ῠ', 'Ῡ' => 'ῡ', 'Ὺ' => 'ὺ', 'Ύ' => 'ύ', 'Ῥ' => 'ῥ', 'Ὸ' => 'ὸ', 'Ό' => 'ό', 'Ὼ' => 'ὼ', 'Ώ' => 'ώ', 'ῼ' => 'ῳ', 'Ω' => 'ω', 'K' => 'k', 'Å' => 'å', 'Ⅎ' => 'ⅎ', 'Ⅰ' => 'ⅰ', 'Ⅱ' => 'ⅱ', 'Ⅲ' => 'ⅲ', 'Ⅳ' => 'ⅳ', 'Ⅴ' => 'ⅴ', 'Ⅵ' => 'ⅵ', 'Ⅶ' => 'ⅶ', 'Ⅷ' => 'ⅷ', 'Ⅸ' => 'ⅸ', 'Ⅹ' => 'ⅹ', 'Ⅺ' => 'ⅺ', 'Ⅻ' => 'ⅻ', 'Ⅼ' => 'ⅼ', 'Ⅽ' => 'ⅽ', 'Ⅾ' => 'ⅾ', 'Ⅿ' => 'ⅿ', 'Ↄ' => 'ↄ', 'Ⓐ' => 'ⓐ', 'Ⓑ' => 'ⓑ', 'Ⓒ' => 'ⓒ', 'Ⓓ' => 'ⓓ', 'Ⓔ' => 'ⓔ', 'Ⓕ' => 'ⓕ', 'Ⓖ' => 'ⓖ', 'Ⓗ' => 'ⓗ', 'Ⓘ' => 'ⓘ', 'Ⓙ' => 'ⓙ', 'Ⓚ' => 'ⓚ', 'Ⓛ' => 'ⓛ', 'Ⓜ' => 'ⓜ', 'Ⓝ' => 'ⓝ', 'Ⓞ' => 'ⓞ', 'Ⓟ' => 'ⓟ', 'Ⓠ' => 'ⓠ', 'Ⓡ' => 'ⓡ', 'Ⓢ' => 'ⓢ', 'Ⓣ' => 'ⓣ', 'Ⓤ' => 'ⓤ', 'Ⓥ' => 'ⓥ', 'Ⓦ' => 'ⓦ', 'Ⓧ' => 'ⓧ', 'Ⓨ' => 'ⓨ', 'Ⓩ' => 'ⓩ', 'Ⰰ' => 'ⰰ', 'Ⰱ' => 'ⰱ', 'Ⰲ' => 'ⰲ', 'Ⰳ' => 'ⰳ', 'Ⰴ' => 'ⰴ', 'Ⰵ' => 'ⰵ', 'Ⰶ' => 'ⰶ', 'Ⰷ' => 'ⰷ', 'Ⰸ' => 'ⰸ', 'Ⰹ' => 'ⰹ', 'Ⰺ' => 'ⰺ', 'Ⰻ' => 'ⰻ', 'Ⰼ' => 'ⰼ', 'Ⰽ' => 'ⰽ', 'Ⰾ' => 'ⰾ', 'Ⰿ' => 'ⰿ', 'Ⱀ' => 'ⱀ', 'Ⱁ' => 'ⱁ', 'Ⱂ' => 'ⱂ', 'Ⱃ' => 'ⱃ', 'Ⱄ' => 'ⱄ', 'Ⱅ' => 'ⱅ', 'Ⱆ' => 'ⱆ', 'Ⱇ' => 'ⱇ', 'Ⱈ' => 'ⱈ', 'Ⱉ' => 'ⱉ', 'Ⱊ' => 'ⱊ', 'Ⱋ' => 'ⱋ', 'Ⱌ' => 'ⱌ', 'Ⱍ' => 'ⱍ', 'Ⱎ' => 'ⱎ', 'Ⱏ' => 'ⱏ', 'Ⱐ' => 'ⱐ', 'Ⱑ' => 'ⱑ', 'Ⱒ' => 'ⱒ', 'Ⱓ' => 'ⱓ', 'Ⱔ' => 'ⱔ', 'Ⱕ' => 'ⱕ', 'Ⱖ' => 'ⱖ', 'Ⱗ' => 'ⱗ', 'Ⱘ' => 'ⱘ', 'Ⱙ' => 'ⱙ', 'Ⱚ' => 'ⱚ', 'Ⱛ' => 'ⱛ', 'Ⱜ' => 'ⱜ', 'Ⱝ' => 'ⱝ', 'Ⱞ' => 'ⱞ', 'Ⱡ' => 'ⱡ', 'Ɫ' => 'ɫ', 'Ᵽ' => 'ᵽ', 'Ɽ' => 'ɽ', 'Ⱨ' => 'ⱨ', 'Ⱪ' => 'ⱪ', 'Ⱬ' => 'ⱬ', 'Ɑ' => 'ɑ', 'Ɱ' => 'ɱ', 'Ɐ' => 'ɐ', 'Ɒ' => 'ɒ', 'Ⱳ' => 'ⱳ', 'Ⱶ' => 'ⱶ', 'Ȿ' => 'ȿ', 'Ɀ' => 'ɀ', 'Ⲁ' => 'ⲁ', 'Ⲃ' => 'ⲃ', 'Ⲅ' => 'ⲅ', 'Ⲇ' => 'ⲇ', 'Ⲉ' => 'ⲉ', 'Ⲋ' => 'ⲋ', 'Ⲍ' => 'ⲍ', 'Ⲏ' => 'ⲏ', 'Ⲑ' => 'ⲑ', 'Ⲓ' => 'ⲓ', 'Ⲕ' => 'ⲕ', 'Ⲗ' => 'ⲗ', 'Ⲙ' => 'ⲙ', 'Ⲛ' => 'ⲛ', 'Ⲝ' => 'ⲝ', 'Ⲟ' => 'ⲟ', 'Ⲡ' => 'ⲡ', 'Ⲣ' => 'ⲣ', 'Ⲥ' => 'ⲥ', 'Ⲧ' => 'ⲧ', 'Ⲩ' => 'ⲩ', 'Ⲫ' => 'ⲫ', 'Ⲭ' => 'ⲭ', 'Ⲯ' => 'ⲯ', 'Ⲱ' => 'ⲱ', 'Ⲳ' => 'ⲳ', 'Ⲵ' => 'ⲵ', 'Ⲷ' => 'ⲷ', 'Ⲹ' => 'ⲹ', 'Ⲻ' => 'ⲻ', 'Ⲽ' => 'ⲽ', 'Ⲿ' => 'ⲿ', 'Ⳁ' => 'ⳁ', 'Ⳃ' => 'ⳃ', 'Ⳅ' => 'ⳅ', 'Ⳇ' => 'ⳇ', 'Ⳉ' => 'ⳉ', 'Ⳋ' => 'ⳋ', 'Ⳍ' => 'ⳍ', 'Ⳏ' => 'ⳏ', 'Ⳑ' => 'ⳑ', 'Ⳓ' => 'ⳓ', 'Ⳕ' => 'ⳕ', 'Ⳗ' => 'ⳗ', 'Ⳙ' => 'ⳙ', 'Ⳛ' => 'ⳛ', 'Ⳝ' => 'ⳝ', 'Ⳟ' => 'ⳟ', 'Ⳡ' => 'ⳡ', 'Ⳣ' => 'ⳣ', 'Ⳬ' => 'ⳬ', 'Ⳮ' => 'ⳮ', 'Ⳳ' => 'ⳳ', 'Ꙁ' => 'ꙁ', 'Ꙃ' => 'ꙃ', 'Ꙅ' => 'ꙅ', 'Ꙇ' => 'ꙇ', 'Ꙉ' => 'ꙉ', 'Ꙋ' => 'ꙋ', 'Ꙍ' => 'ꙍ', 'Ꙏ' => 'ꙏ', 'Ꙑ' => 'ꙑ', 'Ꙓ' => 'ꙓ', 'Ꙕ' => 'ꙕ', 'Ꙗ' => 'ꙗ', 'Ꙙ' => 'ꙙ', 'Ꙛ' => 'ꙛ', 'Ꙝ' => 'ꙝ', 'Ꙟ' => 'ꙟ', 'Ꙡ' => 'ꙡ', 'Ꙣ' => 'ꙣ', 'Ꙥ' => 'ꙥ', 'Ꙧ' => 'ꙧ', 'Ꙩ' => 'ꙩ', 'Ꙫ' => 'ꙫ', 'Ꙭ' => 'ꙭ', 'Ꚁ' => 'ꚁ', 'Ꚃ' => 'ꚃ', 'Ꚅ' => 'ꚅ', 'Ꚇ' => 'ꚇ', 'Ꚉ' => 'ꚉ', 'Ꚋ' => 'ꚋ', 'Ꚍ' => 'ꚍ', 'Ꚏ' => 'ꚏ', 'Ꚑ' => 'ꚑ', 'Ꚓ' => 'ꚓ', 'Ꚕ' => 'ꚕ', 'Ꚗ' => 'ꚗ', 'Ꚙ' => 'ꚙ', 'Ꚛ' => 'ꚛ', 'Ꜣ' => 'ꜣ', 'Ꜥ' => 'ꜥ', 'Ꜧ' => 'ꜧ', 'Ꜩ' => 'ꜩ', 'Ꜫ' => 'ꜫ', 'Ꜭ' => 'ꜭ', 'Ꜯ' => 'ꜯ', 'Ꜳ' => 'ꜳ', 'Ꜵ' => 'ꜵ', 'Ꜷ' => 'ꜷ', 'Ꜹ' => 'ꜹ', 'Ꜻ' => 'ꜻ', 'Ꜽ' => 'ꜽ', 'Ꜿ' => 'ꜿ', 'Ꝁ' => 'ꝁ', 'Ꝃ' => 'ꝃ', 'Ꝅ' => 'ꝅ', 'Ꝇ' => 'ꝇ', 'Ꝉ' => 'ꝉ', 'Ꝋ' => 'ꝋ', 'Ꝍ' => 'ꝍ', 'Ꝏ' => 'ꝏ', 'Ꝑ' => 'ꝑ', 'Ꝓ' => 'ꝓ', 'Ꝕ' => 'ꝕ', 'Ꝗ' => 'ꝗ', 'Ꝙ' => 'ꝙ', 'Ꝛ' => 'ꝛ', 'Ꝝ' => 'ꝝ', 'Ꝟ' => 'ꝟ', 'Ꝡ' => 'ꝡ', 'Ꝣ' => 'ꝣ', 'Ꝥ' => 'ꝥ', 'Ꝧ' => 'ꝧ', 'Ꝩ' => 'ꝩ', 'Ꝫ' => 'ꝫ', 'Ꝭ' => 'ꝭ', 'Ꝯ' => 'ꝯ', 'Ꝺ' => 'ꝺ', 'Ꝼ' => 'ꝼ', 'Ᵹ' => 'ᵹ', 'Ꝿ' => 'ꝿ', 'Ꞁ' => 'ꞁ', 'Ꞃ' => 'ꞃ', 'Ꞅ' => 'ꞅ', 'Ꞇ' => 'ꞇ', 'Ꞌ' => 'ꞌ', 'Ɥ' => 'ɥ', 'Ꞑ' => 'ꞑ', 'Ꞓ' => 'ꞓ', 'Ꞗ' => 'ꞗ', 'Ꞙ' => 'ꞙ', 'Ꞛ' => 'ꞛ', 'Ꞝ' => 'ꞝ', 'Ꞟ' => 'ꞟ', 'Ꞡ' => 'ꞡ', 'Ꞣ' => 'ꞣ', 'Ꞥ' => 'ꞥ', 'Ꞧ' => 'ꞧ', 'Ꞩ' => 'ꞩ', 'Ɦ' => 'ɦ', 'Ɜ' => 'ɜ', 'Ɡ' => 'ɡ', 'Ɬ' => 'ɬ', 'Ɪ' => 'ɪ', 'Ʞ' => 'ʞ', 'Ʇ' => 'ʇ', 'Ʝ' => 'ʝ', 'Ꭓ' => 'ꭓ', 'Ꞵ' => 'ꞵ', 'Ꞷ' => 'ꞷ', 'Ꞹ' => 'ꞹ', 'Ꞻ' => 'ꞻ', 'Ꞽ' => 'ꞽ', 'Ꞿ' => 'ꞿ', 'Ꟃ' => 'ꟃ', 'Ꞔ' => 'ꞔ', 'Ʂ' => 'ʂ', 'Ᶎ' => 'ᶎ', 'Ꟈ' => 'ꟈ', 'Ꟊ' => 'ꟊ', 'Ꟶ' => 'ꟶ', 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', '𐐀' => '𐐨', '𐐁' => '𐐩', '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', '𐐦' => '𐑎', '𐐧' => '𐑏', '𐒰' => '𐓘', '𐒱' => '𐓙', '𐒲' => '𐓚', '𐒳' => '𐓛', '𐒴' => '𐓜', '𐒵' => '𐓝', '𐒶' => '𐓞', '𐒷' => '𐓟', '𐒸' => '𐓠', '𐒹' => '𐓡', '𐒺' => '𐓢', '𐒻' => '𐓣', '𐒼' => '𐓤', '𐒽' => '𐓥', '𐒾' => '𐓦', '𐒿' => '𐓧', '𐓀' => '𐓨', '𐓁' => '𐓩', '𐓂' => '𐓪', '𐓃' => '𐓫', '𐓄' => '𐓬', '𐓅' => '𐓭', '𐓆' => '𐓮', '𐓇' => '𐓯', '𐓈' => '𐓰', '𐓉' => '𐓱', '𐓊' => '𐓲', '𐓋' => '𐓳', '𐓌' => '𐓴', '𐓍' => '𐓵', '𐓎' => '𐓶', '𐓏' => '𐓷', '𐓐' => '𐓸', '𐓑' => '𐓹', '𐓒' => '𐓺', '𐓓' => '𐓻', '𐲀' => '𐳀', '𐲁' => '𐳁', '𐲂' => '𐳂', '𐲃' => '𐳃', '𐲄' => '𐳄', '𐲅' => '𐳅', '𐲆' => '𐳆', '𐲇' => '𐳇', '𐲈' => '𐳈', '𐲉' => '𐳉', '𐲊' => '𐳊', '𐲋' => '𐳋', '𐲌' => '𐳌', '𐲍' => '𐳍', '𐲎' => '𐳎', '𐲏' => '𐳏', '𐲐' => '𐳐', '𐲑' => '𐳑', '𐲒' => '𐳒', '𐲓' => '𐳓', '𐲔' => '𐳔', '𐲕' => '𐳕', '𐲖' => '𐳖', '𐲗' => '𐳗', '𐲘' => '𐳘', '𐲙' => '𐳙', '𐲚' => '𐳚', '𐲛' => '𐳛', '𐲜' => '𐳜', '𐲝' => '𐳝', '𐲞' => '𐳞', '𐲟' => '𐳟', '𐲠' => '𐳠', '𐲡' => '𐳡', '𐲢' => '𐳢', '𐲣' => '𐳣', '𐲤' => '𐳤', '𐲥' => '𐳥', '𐲦' => '𐳦', '𐲧' => '𐳧', '𐲨' => '𐳨', '𐲩' => '𐳩', '𐲪' => '𐳪', '𐲫' => '𐳫', '𐲬' => '𐳬', '𐲭' => '𐳭', '𐲮' => '𐳮', '𐲯' => '𐳯', '𐲰' => '𐳰', '𐲱' => '𐳱', '𐲲' => '𐳲', '𑢠' => '𑣀', '𑢡' => '𑣁', '𑢢' => '𑣂', '𑢣' => '𑣃', '𑢤' => '𑣄', '𑢥' => '𑣅', '𑢦' => '𑣆', '𑢧' => '𑣇', '𑢨' => '𑣈', '𑢩' => '𑣉', '𑢪' => '𑣊', '𑢫' => '𑣋', '𑢬' => '𑣌', '𑢭' => '𑣍', '𑢮' => '𑣎', '𑢯' => '𑣏', '𑢰' => '𑣐', '𑢱' => '𑣑', '𑢲' => '𑣒', '𑢳' => '𑣓', '𑢴' => '𑣔', '𑢵' => '𑣕', '𑢶' => '𑣖', '𑢷' => '𑣗', '𑢸' => '𑣘', '𑢹' => '𑣙', '𑢺' => '𑣚', '𑢻' => '𑣛', '𑢼' => '𑣜', '𑢽' => '𑣝', '𑢾' => '𑣞', '𑢿' => '𑣟', '𖹀' => '𖹠', '𖹁' => '𖹡', '𖹂' => '𖹢', '𖹃' => '𖹣', '𖹄' => '𖹤', '𖹅' => '𖹥', '𖹆' => '𖹦', '𖹇' => '𖹧', '𖹈' => '𖹨', '𖹉' => '𖹩', '𖹊' => '𖹪', '𖹋' => '𖹫', '𖹌' => '𖹬', '𖹍' => '𖹭', '𖹎' => '𖹮', '𖹏' => '𖹯', '𖹐' => '𖹰', '𖹑' => '𖹱', '𖹒' => '𖹲', '𖹓' => '𖹳', '𖹔' => '𖹴', '𖹕' => '𖹵', '𖹖' => '𖹶', '𖹗' => '𖹷', '𖹘' => '𖹸', '𖹙' => '𖹹', '𖹚' => '𖹺', '𖹛' => '𖹻', '𖹜' => '𖹼', '𖹝' => '𖹽', '𖹞' => '𖹾', '𖹟' => '𖹿', '𞤀' => '𞤢', '𞤁' => '𞤣', '𞤂' => '𞤤', '𞤃' => '𞤥', '𞤄' => '𞤦', '𞤅' => '𞤧', '𞤆' => '𞤨', '𞤇' => '𞤩', '𞤈' => '𞤪', '𞤉' => '𞤫', '𞤊' => '𞤬', '𞤋' => '𞤭', '𞤌' => '𞤮', '𞤍' => '𞤯', '𞤎' => '𞤰', '𞤏' => '𞤱', '𞤐' => '𞤲', '𞤑' => '𞤳', '𞤒' => '𞤴', '𞤓' => '𞤵', '𞤔' => '𞤶', '𞤕' => '𞤷', '𞤖' => '𞤸', '𞤗' => '𞤹', '𞤘' => '𞤺', '𞤙' => '𞤻', '𞤚' => '𞤼', '𞤛' => '𞤽', '𞤜' => '𞤾', '𞤝' => '𞤿', '𞤞' => '𞥀', '𞤟' => '𞥁', '𞤠' => '𞥂', '𞤡' => '𞥃'); 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', 'µ' => 'Μ', 'à' => 'À', 'á' => 'Á', 'â' => 'Â', 'ã' => 'Ã', 'ä' => 'Ä', 'å' => 'Å', 'æ' => 'Æ', 'ç' => 'Ç', 'è' => 'È', 'é' => 'É', 'ê' => 'Ê', 'ë' => 'Ë', 'ì' => 'Ì', 'í' => 'Í', 'î' => 'Î', 'ï' => 'Ï', 'ð' => 'Ð', 'ñ' => 'Ñ', 'ò' => 'Ò', 'ó' => 'Ó', 'ô' => 'Ô', 'õ' => 'Õ', 'ö' => 'Ö', 'ø' => 'Ø', 'ù' => 'Ù', 'ú' => 'Ú', 'û' => 'Û', 'ü' => 'Ü', 'ý' => 'Ý', 'þ' => 'Þ', 'ÿ' => 'Ÿ', 'ā' => 'Ā', 'ă' => 'Ă', 'ą' => 'Ą', 'ć' => 'Ć', 'ĉ' => 'Ĉ', 'ċ' => 'Ċ', 'č' => 'Č', 'ď' => 'Ď', 'đ' => 'Đ', 'ē' => 'Ē', 'ĕ' => 'Ĕ', 'ė' => 'Ė', 'ę' => 'Ę', 'ě' => 'Ě', 'ĝ' => 'Ĝ', 'ğ' => 'Ğ', 'ġ' => 'Ġ', 'ģ' => 'Ģ', 'ĥ' => 'Ĥ', 'ħ' => 'Ħ', 'ĩ' => 'Ĩ', 'ī' => 'Ī', 'ĭ' => 'Ĭ', 'į' => 'Į', 'ı' => 'I', 'ij' => 'IJ', 'ĵ' => 'Ĵ', 'ķ' => 'Ķ', 'ĺ' => 'Ĺ', 'ļ' => 'Ļ', 'ľ' => 'Ľ', 'ŀ' => 'Ŀ', 'ł' => 'Ł', 'ń' => 'Ń', 'ņ' => 'Ņ', 'ň' => 'Ň', 'ŋ' => 'Ŋ', 'ō' => 'Ō', 'ŏ' => 'Ŏ', 'ő' => 'Ő', 'œ' => 'Œ', 'ŕ' => 'Ŕ', 'ŗ' => 'Ŗ', 'ř' => 'Ř', 'ś' => 'Ś', 'ŝ' => 'Ŝ', 'ş' => 'Ş', 'š' => 'Š', 'ţ' => 'Ţ', 'ť' => 'Ť', 'ŧ' => 'Ŧ', 'ũ' => 'Ũ', 'ū' => 'Ū', 'ŭ' => 'Ŭ', 'ů' => 'Ů', 'ű' => 'Ű', 'ų' => 'Ų', 'ŵ' => 'Ŵ', 'ŷ' => 'Ŷ', 'ź' => 'Ź', 'ż' => 'Ż', 'ž' => 'Ž', 'ſ' => 'S', 'ƀ' => 'Ƀ', 'ƃ' => 'Ƃ', 'ƅ' => 'Ƅ', 'ƈ' => 'Ƈ', 'ƌ' => 'Ƌ', 'ƒ' => 'Ƒ', 'ƕ' => 'Ƕ', 'ƙ' => 'Ƙ', 'ƚ' => 'Ƚ', 'ƞ' => 'Ƞ', 'ơ' => 'Ơ', 'ƣ' => 'Ƣ', 'ƥ' => 'Ƥ', 'ƨ' => 'Ƨ', 'ƭ' => 'Ƭ', 'ư' => 'Ư', 'ƴ' => 'Ƴ', 'ƶ' => 'Ƶ', 'ƹ' => 'Ƹ', 'ƽ' => 'Ƽ', 'ƿ' => 'Ƿ', 'Dž' => 'DŽ', 'dž' => 'DŽ', 'Lj' => 'LJ', 'lj' => 'LJ', 'Nj' => 'NJ', 'nj' => 'NJ', 'ǎ' => 'Ǎ', 'ǐ' => 'Ǐ', 'ǒ' => 'Ǒ', 'ǔ' => 'Ǔ', 'ǖ' => 'Ǖ', 'ǘ' => 'Ǘ', 'ǚ' => 'Ǚ', 'ǜ' => 'Ǜ', 'ǝ' => 'Ǝ', 'ǟ' => 'Ǟ', 'ǡ' => 'Ǡ', 'ǣ' => 'Ǣ', 'ǥ' => 'Ǥ', 'ǧ' => 'Ǧ', 'ǩ' => 'Ǩ', 'ǫ' => 'Ǫ', 'ǭ' => 'Ǭ', 'ǯ' => 'Ǯ', 'Dz' => 'DZ', 'dz' => 'DZ', 'ǵ' => 'Ǵ', 'ǹ' => 'Ǹ', 'ǻ' => 'Ǻ', 'ǽ' => 'Ǽ', 'ǿ' => 'Ǿ', 'ȁ' => 'Ȁ', 'ȃ' => 'Ȃ', 'ȅ' => 'Ȅ', 'ȇ' => 'Ȇ', 'ȉ' => 'Ȉ', 'ȋ' => 'Ȋ', 'ȍ' => 'Ȍ', 'ȏ' => 'Ȏ', 'ȑ' => 'Ȑ', 'ȓ' => 'Ȓ', 'ȕ' => 'Ȕ', 'ȗ' => 'Ȗ', 'ș' => 'Ș', 'ț' => 'Ț', 'ȝ' => 'Ȝ', 'ȟ' => 'Ȟ', 'ȣ' => 'Ȣ', 'ȥ' => 'Ȥ', 'ȧ' => 'Ȧ', 'ȩ' => 'Ȩ', 'ȫ' => 'Ȫ', 'ȭ' => 'Ȭ', 'ȯ' => 'Ȯ', 'ȱ' => 'Ȱ', 'ȳ' => 'Ȳ', 'ȼ' => 'Ȼ', 'ȿ' => 'Ȿ', 'ɀ' => 'Ɀ', 'ɂ' => 'Ɂ', 'ɇ' => 'Ɇ', 'ɉ' => 'Ɉ', 'ɋ' => 'Ɋ', 'ɍ' => 'Ɍ', 'ɏ' => 'Ɏ', 'ɐ' => 'Ɐ', 'ɑ' => 'Ɑ', 'ɒ' => 'Ɒ', 'ɓ' => 'Ɓ', 'ɔ' => 'Ɔ', 'ɖ' => 'Ɖ', 'ɗ' => 'Ɗ', 'ə' => 'Ə', 'ɛ' => 'Ɛ', 'ɜ' => 'Ɜ', 'ɠ' => 'Ɠ', 'ɡ' => 'Ɡ', 'ɣ' => 'Ɣ', 'ɥ' => 'Ɥ', 'ɦ' => 'Ɦ', 'ɨ' => 'Ɨ', 'ɩ' => 'Ɩ', 'ɪ' => 'Ɪ', 'ɫ' => 'Ɫ', 'ɬ' => 'Ɬ', 'ɯ' => 'Ɯ', 'ɱ' => 'Ɱ', 'ɲ' => 'Ɲ', 'ɵ' => 'Ɵ', 'ɽ' => 'Ɽ', 'ʀ' => 'Ʀ', 'ʂ' => 'Ʂ', 'ʃ' => 'Ʃ', 'ʇ' => 'Ʇ', 'ʈ' => 'Ʈ', 'ʉ' => 'Ʉ', 'ʊ' => 'Ʊ', 'ʋ' => 'Ʋ', 'ʌ' => 'Ʌ', 'ʒ' => 'Ʒ', 'ʝ' => 'Ʝ', 'ʞ' => 'Ʞ', 'ͅ' => 'Ι', 'ͱ' => 'Ͱ', 'ͳ' => 'Ͳ', 'ͷ' => 'Ͷ', 'ͻ' => 'Ͻ', 'ͼ' => 'Ͼ', 'ͽ' => 'Ͽ', 'ά' => 'Ά', 'έ' => 'Έ', 'ή' => 'Ή', 'ί' => 'Ί', 'α' => 'Α', 'β' => 'Β', 'γ' => 'Γ', 'δ' => 'Δ', 'ε' => 'Ε', 'ζ' => 'Ζ', 'η' => 'Η', 'θ' => 'Θ', 'ι' => 'Ι', 'κ' => 'Κ', 'λ' => 'Λ', 'μ' => 'Μ', 'ν' => 'Ν', 'ξ' => 'Ξ', 'ο' => 'Ο', 'π' => 'Π', 'ρ' => 'Ρ', 'ς' => 'Σ', 'σ' => 'Σ', 'τ' => 'Τ', 'υ' => 'Υ', 'φ' => 'Φ', 'χ' => 'Χ', 'ψ' => 'Ψ', 'ω' => 'Ω', 'ϊ' => 'Ϊ', 'ϋ' => 'Ϋ', 'ό' => 'Ό', 'ύ' => 'Ύ', 'ώ' => 'Ώ', 'ϐ' => 'Β', 'ϑ' => 'Θ', 'ϕ' => 'Φ', 'ϖ' => 'Π', 'ϗ' => 'Ϗ', 'ϙ' => 'Ϙ', 'ϛ' => 'Ϛ', 'ϝ' => 'Ϝ', 'ϟ' => 'Ϟ', 'ϡ' => 'Ϡ', 'ϣ' => 'Ϣ', 'ϥ' => 'Ϥ', 'ϧ' => 'Ϧ', 'ϩ' => 'Ϩ', 'ϫ' => 'Ϫ', 'ϭ' => 'Ϭ', 'ϯ' => 'Ϯ', 'ϰ' => 'Κ', 'ϱ' => 'Ρ', 'ϲ' => 'Ϲ', 'ϳ' => 'Ϳ', 'ϵ' => 'Ε', 'ϸ' => 'Ϸ', 'ϻ' => 'Ϻ', 'а' => 'А', 'б' => 'Б', 'в' => 'В', 'г' => 'Г', 'д' => 'Д', 'е' => 'Е', 'ж' => 'Ж', 'з' => 'З', 'и' => 'И', 'й' => 'Й', 'к' => 'К', 'л' => 'Л', 'м' => 'М', 'н' => 'Н', 'о' => 'О', 'п' => 'П', 'р' => 'Р', 'с' => 'С', 'т' => 'Т', 'у' => 'У', 'ф' => 'Ф', 'х' => 'Х', 'ц' => 'Ц', 'ч' => 'Ч', 'ш' => 'Ш', 'щ' => 'Щ', 'ъ' => 'Ъ', 'ы' => 'Ы', 'ь' => 'Ь', 'э' => 'Э', 'ю' => 'Ю', 'я' => 'Я', 'ѐ' => 'Ѐ', 'ё' => 'Ё', 'ђ' => 'Ђ', 'ѓ' => 'Ѓ', 'є' => 'Є', 'ѕ' => 'Ѕ', 'і' => 'І', 'ї' => 'Ї', 'ј' => 'Ј', 'љ' => 'Љ', 'њ' => 'Њ', 'ћ' => 'Ћ', 'ќ' => 'Ќ', 'ѝ' => 'Ѝ', 'ў' => 'Ў', 'џ' => 'Џ', 'ѡ' => 'Ѡ', 'ѣ' => 'Ѣ', 'ѥ' => 'Ѥ', 'ѧ' => 'Ѧ', 'ѩ' => 'Ѩ', 'ѫ' => 'Ѫ', 'ѭ' => 'Ѭ', 'ѯ' => 'Ѯ', 'ѱ' => 'Ѱ', 'ѳ' => 'Ѳ', 'ѵ' => 'Ѵ', 'ѷ' => 'Ѷ', 'ѹ' => 'Ѹ', 'ѻ' => 'Ѻ', 'ѽ' => 'Ѽ', 'ѿ' => 'Ѿ', 'ҁ' => 'Ҁ', 'ҋ' => 'Ҋ', 'ҍ' => 'Ҍ', 'ҏ' => 'Ҏ', 'ґ' => 'Ґ', 'ғ' => 'Ғ', 'ҕ' => 'Ҕ', 'җ' => 'Җ', 'ҙ' => 'Ҙ', 'қ' => 'Қ', 'ҝ' => 'Ҝ', 'ҟ' => 'Ҟ', 'ҡ' => 'Ҡ', 'ң' => 'Ң', 'ҥ' => 'Ҥ', 'ҧ' => 'Ҧ', 'ҩ' => 'Ҩ', 'ҫ' => 'Ҫ', 'ҭ' => 'Ҭ', 'ү' => 'Ү', 'ұ' => 'Ұ', 'ҳ' => 'Ҳ', 'ҵ' => 'Ҵ', 'ҷ' => 'Ҷ', 'ҹ' => 'Ҹ', 'һ' => 'Һ', 'ҽ' => 'Ҽ', 'ҿ' => 'Ҿ', 'ӂ' => 'Ӂ', 'ӄ' => 'Ӄ', 'ӆ' => 'Ӆ', 'ӈ' => 'Ӈ', 'ӊ' => 'Ӊ', 'ӌ' => 'Ӌ', 'ӎ' => 'Ӎ', 'ӏ' => 'Ӏ', 'ӑ' => 'Ӑ', 'ӓ' => 'Ӓ', 'ӕ' => 'Ӕ', 'ӗ' => 'Ӗ', 'ә' => 'Ә', 'ӛ' => 'Ӛ', 'ӝ' => 'Ӝ', 'ӟ' => 'Ӟ', 'ӡ' => 'Ӡ', 'ӣ' => 'Ӣ', 'ӥ' => 'Ӥ', 'ӧ' => 'Ӧ', 'ө' => 'Ө', 'ӫ' => 'Ӫ', 'ӭ' => 'Ӭ', 'ӯ' => 'Ӯ', 'ӱ' => 'Ӱ', 'ӳ' => 'Ӳ', 'ӵ' => 'Ӵ', 'ӷ' => 'Ӷ', 'ӹ' => 'Ӹ', 'ӻ' => 'Ӻ', 'ӽ' => 'Ӽ', 'ӿ' => 'Ӿ', 'ԁ' => 'Ԁ', 'ԃ' => 'Ԃ', 'ԅ' => 'Ԅ', 'ԇ' => 'Ԇ', 'ԉ' => 'Ԉ', 'ԋ' => 'Ԋ', 'ԍ' => 'Ԍ', 'ԏ' => 'Ԏ', 'ԑ' => 'Ԑ', 'ԓ' => 'Ԓ', 'ԕ' => 'Ԕ', 'ԗ' => 'Ԗ', 'ԙ' => 'Ԙ', 'ԛ' => 'Ԛ', 'ԝ' => 'Ԝ', 'ԟ' => 'Ԟ', 'ԡ' => 'Ԡ', 'ԣ' => 'Ԣ', 'ԥ' => 'Ԥ', 'ԧ' => 'Ԧ', 'ԩ' => 'Ԩ', 'ԫ' => 'Ԫ', 'ԭ' => 'Ԭ', 'ԯ' => 'Ԯ', 'ա' => 'Ա', 'բ' => 'Բ', 'գ' => 'Գ', 'դ' => 'Դ', 'ե' => 'Ե', 'զ' => 'Զ', 'է' => 'Է', 'ը' => 'Ը', 'թ' => 'Թ', 'ժ' => 'Ժ', 'ի' => 'Ի', 'լ' => 'Լ', 'խ' => 'Խ', 'ծ' => 'Ծ', 'կ' => 'Կ', 'հ' => 'Հ', 'ձ' => 'Ձ', 'ղ' => 'Ղ', 'ճ' => 'Ճ', 'մ' => 'Մ', 'յ' => 'Յ', 'ն' => 'Ն', 'շ' => 'Շ', 'ո' => 'Ո', 'չ' => 'Չ', 'պ' => 'Պ', 'ջ' => 'Ջ', 'ռ' => 'Ռ', 'ս' => 'Ս', 'վ' => 'Վ', 'տ' => 'Տ', 'ր' => 'Ր', 'ց' => 'Ց', 'ւ' => 'Ւ', 'փ' => 'Փ', 'ք' => 'Ք', 'օ' => 'Օ', 'ֆ' => 'Ֆ', 'ა' => 'Ა', 'ბ' => 'Ბ', 'გ' => 'Გ', 'დ' => 'Დ', 'ე' => 'Ე', 'ვ' => 'Ვ', 'ზ' => 'Ზ', 'თ' => 'Თ', 'ი' => 'Ი', 'კ' => 'Კ', 'ლ' => 'Ლ', 'მ' => 'Მ', 'ნ' => 'Ნ', 'ო' => 'Ო', 'პ' => 'Პ', 'ჟ' => 'Ჟ', 'რ' => 'Რ', 'ს' => 'Ს', 'ტ' => 'Ტ', 'უ' => 'Უ', 'ფ' => 'Ფ', 'ქ' => 'Ქ', 'ღ' => 'Ღ', 'ყ' => 'Ყ', 'შ' => 'Შ', 'ჩ' => 'Ჩ', 'ც' => 'Ც', 'ძ' => 'Ძ', 'წ' => 'Წ', 'ჭ' => 'Ჭ', 'ხ' => 'Ხ', 'ჯ' => 'Ჯ', 'ჰ' => 'Ჰ', 'ჱ' => 'Ჱ', 'ჲ' => 'Ჲ', 'ჳ' => 'Ჳ', 'ჴ' => 'Ჴ', 'ჵ' => 'Ჵ', 'ჶ' => 'Ჶ', 'ჷ' => 'Ჷ', 'ჸ' => 'Ჸ', 'ჹ' => 'Ჹ', 'ჺ' => 'Ჺ', 'ჽ' => 'Ჽ', 'ჾ' => 'Ჾ', 'ჿ' => 'Ჿ', 'ᏸ' => 'Ᏸ', 'ᏹ' => 'Ᏹ', 'ᏺ' => 'Ᏺ', 'ᏻ' => 'Ᏻ', 'ᏼ' => 'Ᏼ', 'ᏽ' => 'Ᏽ', 'ᲀ' => 'В', 'ᲁ' => 'Д', 'ᲂ' => 'О', 'ᲃ' => 'С', 'ᲄ' => 'Т', 'ᲅ' => 'Т', 'ᲆ' => 'Ъ', 'ᲇ' => 'Ѣ', 'ᲈ' => 'Ꙋ', 'ᵹ' => 'Ᵹ', 'ᵽ' => 'Ᵽ', 'ᶎ' => 'Ᶎ', 'ḁ' => 'Ḁ', 'ḃ' => 'Ḃ', 'ḅ' => 'Ḅ', 'ḇ' => 'Ḇ', 'ḉ' => 'Ḉ', 'ḋ' => 'Ḋ', 'ḍ' => 'Ḍ', 'ḏ' => 'Ḏ', 'ḑ' => 'Ḑ', 'ḓ' => 'Ḓ', 'ḕ' => 'Ḕ', 'ḗ' => 'Ḗ', 'ḙ' => 'Ḙ', 'ḛ' => 'Ḛ', 'ḝ' => 'Ḝ', 'ḟ' => 'Ḟ', 'ḡ' => 'Ḡ', 'ḣ' => 'Ḣ', 'ḥ' => 'Ḥ', 'ḧ' => 'Ḧ', 'ḩ' => 'Ḩ', 'ḫ' => 'Ḫ', 'ḭ' => 'Ḭ', 'ḯ' => 'Ḯ', 'ḱ' => 'Ḱ', 'ḳ' => 'Ḳ', 'ḵ' => 'Ḵ', 'ḷ' => 'Ḷ', 'ḹ' => 'Ḹ', 'ḻ' => 'Ḻ', 'ḽ' => 'Ḽ', 'ḿ' => 'Ḿ', 'ṁ' => 'Ṁ', 'ṃ' => 'Ṃ', 'ṅ' => 'Ṅ', 'ṇ' => 'Ṇ', 'ṉ' => 'Ṉ', 'ṋ' => 'Ṋ', 'ṍ' => 'Ṍ', 'ṏ' => 'Ṏ', 'ṑ' => 'Ṑ', 'ṓ' => 'Ṓ', 'ṕ' => 'Ṕ', 'ṗ' => 'Ṗ', 'ṙ' => 'Ṙ', 'ṛ' => 'Ṛ', 'ṝ' => 'Ṝ', 'ṟ' => 'Ṟ', 'ṡ' => 'Ṡ', 'ṣ' => 'Ṣ', 'ṥ' => 'Ṥ', 'ṧ' => 'Ṧ', 'ṩ' => 'Ṩ', 'ṫ' => 'Ṫ', 'ṭ' => 'Ṭ', 'ṯ' => 'Ṯ', 'ṱ' => 'Ṱ', 'ṳ' => 'Ṳ', 'ṵ' => 'Ṵ', 'ṷ' => 'Ṷ', 'ṹ' => 'Ṹ', 'ṻ' => 'Ṻ', 'ṽ' => 'Ṽ', 'ṿ' => 'Ṿ', 'ẁ' => 'Ẁ', 'ẃ' => 'Ẃ', 'ẅ' => 'Ẅ', 'ẇ' => 'Ẇ', 'ẉ' => 'Ẉ', 'ẋ' => 'Ẋ', 'ẍ' => 'Ẍ', 'ẏ' => 'Ẏ', 'ẑ' => 'Ẑ', 'ẓ' => 'Ẓ', 'ẕ' => 'Ẕ', 'ẛ' => 'Ṡ', 'ạ' => 'Ạ', 'ả' => 'Ả', 'ấ' => 'Ấ', 'ầ' => 'Ầ', 'ẩ' => 'Ẩ', 'ẫ' => 'Ẫ', 'ậ' => 'Ậ', 'ắ' => 'Ắ', 'ằ' => 'Ằ', 'ẳ' => 'Ẳ', 'ẵ' => 'Ẵ', 'ặ' => 'Ặ', 'ẹ' => 'Ẹ', 'ẻ' => 'Ẻ', 'ẽ' => 'Ẽ', 'ế' => 'Ế', 'ề' => 'Ề', 'ể' => 'Ể', 'ễ' => 'Ễ', 'ệ' => 'Ệ', 'ỉ' => 'Ỉ', 'ị' => 'Ị', 'ọ' => 'Ọ', 'ỏ' => 'Ỏ', 'ố' => 'Ố', 'ồ' => 'Ồ', 'ổ' => 'Ổ', 'ỗ' => 'Ỗ', 'ộ' => 'Ộ', 'ớ' => 'Ớ', 'ờ' => 'Ờ', 'ở' => 'Ở', 'ỡ' => 'Ỡ', 'ợ' => 'Ợ', 'ụ' => 'Ụ', 'ủ' => 'Ủ', 'ứ' => 'Ứ', 'ừ' => 'Ừ', 'ử' => 'Ử', 'ữ' => 'Ữ', 'ự' => 'Ự', 'ỳ' => 'Ỳ', 'ỵ' => 'Ỵ', 'ỷ' => 'Ỷ', 'ỹ' => 'Ỹ', 'ỻ' => 'Ỻ', 'ỽ' => 'Ỽ', 'ỿ' => 'Ỿ', 'ἀ' => 'Ἀ', 'ἁ' => 'Ἁ', 'ἂ' => 'Ἂ', 'ἃ' => 'Ἃ', 'ἄ' => 'Ἄ', 'ἅ' => 'Ἅ', 'ἆ' => 'Ἆ', 'ἇ' => 'Ἇ', 'ἐ' => 'Ἐ', 'ἑ' => 'Ἑ', 'ἒ' => 'Ἒ', 'ἓ' => 'Ἓ', 'ἔ' => 'Ἔ', 'ἕ' => 'Ἕ', 'ἠ' => 'Ἠ', 'ἡ' => 'Ἡ', 'ἢ' => 'Ἢ', 'ἣ' => 'Ἣ', 'ἤ' => 'Ἤ', 'ἥ' => 'Ἥ', 'ἦ' => 'Ἦ', 'ἧ' => 'Ἧ', 'ἰ' => 'Ἰ', 'ἱ' => 'Ἱ', 'ἲ' => 'Ἲ', 'ἳ' => 'Ἳ', 'ἴ' => 'Ἴ', 'ἵ' => 'Ἵ', 'ἶ' => 'Ἶ', 'ἷ' => 'Ἷ', 'ὀ' => 'Ὀ', 'ὁ' => 'Ὁ', 'ὂ' => 'Ὂ', 'ὃ' => 'Ὃ', 'ὄ' => 'Ὄ', 'ὅ' => 'Ὅ', 'ὑ' => 'Ὑ', 'ὓ' => 'Ὓ', 'ὕ' => 'Ὕ', 'ὗ' => 'Ὗ', 'ὠ' => 'Ὠ', 'ὡ' => 'Ὡ', 'ὢ' => 'Ὢ', 'ὣ' => 'Ὣ', 'ὤ' => 'Ὤ', 'ὥ' => 'Ὥ', 'ὦ' => 'Ὦ', 'ὧ' => 'Ὧ', 'ὰ' => 'Ὰ', 'ά' => 'Ά', 'ὲ' => 'Ὲ', 'έ' => 'Έ', 'ὴ' => 'Ὴ', 'ή' => 'Ή', 'ὶ' => 'Ὶ', 'ί' => 'Ί', 'ὸ' => 'Ὸ', 'ό' => 'Ό', 'ὺ' => 'Ὺ', 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', 'ᾀ' => 'ἈΙ', 'ᾁ' => 'ἉΙ', 'ᾂ' => 'ἊΙ', 'ᾃ' => 'ἋΙ', 'ᾄ' => 'ἌΙ', 'ᾅ' => 'ἍΙ', 'ᾆ' => 'ἎΙ', 'ᾇ' => 'ἏΙ', 'ᾐ' => 'ἨΙ', 'ᾑ' => 'ἩΙ', 'ᾒ' => 'ἪΙ', 'ᾓ' => 'ἫΙ', 'ᾔ' => 'ἬΙ', 'ᾕ' => 'ἭΙ', 'ᾖ' => 'ἮΙ', 'ᾗ' => 'ἯΙ', 'ᾠ' => 'ὨΙ', 'ᾡ' => 'ὩΙ', 'ᾢ' => 'ὪΙ', 'ᾣ' => 'ὫΙ', 'ᾤ' => 'ὬΙ', 'ᾥ' => 'ὭΙ', 'ᾦ' => 'ὮΙ', 'ᾧ' => 'ὯΙ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', 'ᾳ' => 'ΑΙ', 'ι' => 'Ι', 'ῃ' => 'ΗΙ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', 'ῳ' => 'ΩΙ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', 'ⅲ' => 'Ⅲ', 'ⅳ' => 'Ⅳ', 'ⅴ' => 'Ⅴ', 'ⅵ' => 'Ⅵ', 'ⅶ' => 'Ⅶ', 'ⅷ' => 'Ⅷ', 'ⅸ' => 'Ⅸ', 'ⅹ' => 'Ⅹ', 'ⅺ' => 'Ⅺ', 'ⅻ' => 'Ⅻ', 'ⅼ' => 'Ⅼ', 'ⅽ' => 'Ⅽ', 'ⅾ' => 'Ⅾ', 'ⅿ' => 'Ⅿ', 'ↄ' => 'Ↄ', 'ⓐ' => 'Ⓐ', 'ⓑ' => 'Ⓑ', 'ⓒ' => 'Ⓒ', 'ⓓ' => 'Ⓓ', 'ⓔ' => 'Ⓔ', 'ⓕ' => 'Ⓕ', 'ⓖ' => 'Ⓖ', 'ⓗ' => 'Ⓗ', 'ⓘ' => 'Ⓘ', 'ⓙ' => 'Ⓙ', 'ⓚ' => 'Ⓚ', 'ⓛ' => 'Ⓛ', 'ⓜ' => 'Ⓜ', 'ⓝ' => 'Ⓝ', 'ⓞ' => 'Ⓞ', 'ⓟ' => 'Ⓟ', 'ⓠ' => 'Ⓠ', 'ⓡ' => 'Ⓡ', 'ⓢ' => 'Ⓢ', 'ⓣ' => 'Ⓣ', 'ⓤ' => 'Ⓤ', 'ⓥ' => 'Ⓥ', 'ⓦ' => 'Ⓦ', 'ⓧ' => 'Ⓧ', 'ⓨ' => 'Ⓨ', 'ⓩ' => 'Ⓩ', 'ⰰ' => 'Ⰰ', 'ⰱ' => 'Ⰱ', 'ⰲ' => 'Ⰲ', 'ⰳ' => 'Ⰳ', 'ⰴ' => 'Ⰴ', 'ⰵ' => 'Ⰵ', 'ⰶ' => 'Ⰶ', 'ⰷ' => 'Ⰷ', 'ⰸ' => 'Ⰸ', 'ⰹ' => 'Ⰹ', 'ⰺ' => 'Ⰺ', 'ⰻ' => 'Ⰻ', 'ⰼ' => 'Ⰼ', 'ⰽ' => 'Ⰽ', 'ⰾ' => 'Ⰾ', 'ⰿ' => 'Ⰿ', 'ⱀ' => 'Ⱀ', 'ⱁ' => 'Ⱁ', 'ⱂ' => 'Ⱂ', 'ⱃ' => 'Ⱃ', 'ⱄ' => 'Ⱄ', 'ⱅ' => 'Ⱅ', 'ⱆ' => 'Ⱆ', 'ⱇ' => 'Ⱇ', 'ⱈ' => 'Ⱈ', 'ⱉ' => 'Ⱉ', 'ⱊ' => 'Ⱊ', 'ⱋ' => 'Ⱋ', 'ⱌ' => 'Ⱌ', 'ⱍ' => 'Ⱍ', 'ⱎ' => 'Ⱎ', 'ⱏ' => 'Ⱏ', 'ⱐ' => 'Ⱐ', 'ⱑ' => 'Ⱑ', 'ⱒ' => 'Ⱒ', 'ⱓ' => 'Ⱓ', 'ⱔ' => 'Ⱔ', 'ⱕ' => 'Ⱕ', 'ⱖ' => 'Ⱖ', 'ⱗ' => 'Ⱗ', 'ⱘ' => 'Ⱘ', 'ⱙ' => 'Ⱙ', 'ⱚ' => 'Ⱚ', 'ⱛ' => 'Ⱛ', 'ⱜ' => 'Ⱜ', 'ⱝ' => 'Ⱝ', 'ⱞ' => 'Ⱞ', 'ⱡ' => 'Ⱡ', 'ⱥ' => 'Ⱥ', 'ⱦ' => 'Ⱦ', 'ⱨ' => 'Ⱨ', 'ⱪ' => 'Ⱪ', 'ⱬ' => 'Ⱬ', 'ⱳ' => 'Ⱳ', 'ⱶ' => 'Ⱶ', 'ⲁ' => 'Ⲁ', 'ⲃ' => 'Ⲃ', 'ⲅ' => 'Ⲅ', 'ⲇ' => 'Ⲇ', 'ⲉ' => 'Ⲉ', 'ⲋ' => 'Ⲋ', 'ⲍ' => 'Ⲍ', 'ⲏ' => 'Ⲏ', 'ⲑ' => 'Ⲑ', 'ⲓ' => 'Ⲓ', 'ⲕ' => 'Ⲕ', 'ⲗ' => 'Ⲗ', 'ⲙ' => 'Ⲙ', 'ⲛ' => 'Ⲛ', 'ⲝ' => 'Ⲝ', 'ⲟ' => 'Ⲟ', 'ⲡ' => 'Ⲡ', 'ⲣ' => 'Ⲣ', 'ⲥ' => 'Ⲥ', 'ⲧ' => 'Ⲧ', 'ⲩ' => 'Ⲩ', 'ⲫ' => 'Ⲫ', 'ⲭ' => 'Ⲭ', 'ⲯ' => 'Ⲯ', 'ⲱ' => 'Ⲱ', 'ⲳ' => 'Ⲳ', 'ⲵ' => 'Ⲵ', 'ⲷ' => 'Ⲷ', 'ⲹ' => 'Ⲹ', 'ⲻ' => 'Ⲻ', 'ⲽ' => 'Ⲽ', 'ⲿ' => 'Ⲿ', 'ⳁ' => 'Ⳁ', 'ⳃ' => 'Ⳃ', 'ⳅ' => 'Ⳅ', 'ⳇ' => 'Ⳇ', 'ⳉ' => 'Ⳉ', 'ⳋ' => 'Ⳋ', 'ⳍ' => 'Ⳍ', 'ⳏ' => 'Ⳏ', 'ⳑ' => 'Ⳑ', 'ⳓ' => 'Ⳓ', 'ⳕ' => 'Ⳕ', 'ⳗ' => 'Ⳗ', 'ⳙ' => 'Ⳙ', 'ⳛ' => 'Ⳛ', 'ⳝ' => 'Ⳝ', 'ⳟ' => 'Ⳟ', 'ⳡ' => 'Ⳡ', 'ⳣ' => 'Ⳣ', 'ⳬ' => 'Ⳬ', 'ⳮ' => 'Ⳮ', 'ⳳ' => 'Ⳳ', 'ⴀ' => 'Ⴀ', 'ⴁ' => 'Ⴁ', 'ⴂ' => 'Ⴂ', 'ⴃ' => 'Ⴃ', 'ⴄ' => 'Ⴄ', 'ⴅ' => 'Ⴅ', 'ⴆ' => 'Ⴆ', 'ⴇ' => 'Ⴇ', 'ⴈ' => 'Ⴈ', 'ⴉ' => 'Ⴉ', 'ⴊ' => 'Ⴊ', 'ⴋ' => 'Ⴋ', 'ⴌ' => 'Ⴌ', 'ⴍ' => 'Ⴍ', 'ⴎ' => 'Ⴎ', 'ⴏ' => 'Ⴏ', 'ⴐ' => 'Ⴐ', 'ⴑ' => 'Ⴑ', 'ⴒ' => 'Ⴒ', 'ⴓ' => 'Ⴓ', 'ⴔ' => 'Ⴔ', 'ⴕ' => 'Ⴕ', 'ⴖ' => 'Ⴖ', 'ⴗ' => 'Ⴗ', 'ⴘ' => 'Ⴘ', 'ⴙ' => 'Ⴙ', 'ⴚ' => 'Ⴚ', 'ⴛ' => 'Ⴛ', 'ⴜ' => 'Ⴜ', 'ⴝ' => 'Ⴝ', 'ⴞ' => 'Ⴞ', 'ⴟ' => 'Ⴟ', 'ⴠ' => 'Ⴠ', 'ⴡ' => 'Ⴡ', 'ⴢ' => 'Ⴢ', 'ⴣ' => 'Ⴣ', 'ⴤ' => 'Ⴤ', 'ⴥ' => 'Ⴥ', 'ⴧ' => 'Ⴧ', 'ⴭ' => 'Ⴭ', 'ꙁ' => 'Ꙁ', 'ꙃ' => 'Ꙃ', 'ꙅ' => 'Ꙅ', 'ꙇ' => 'Ꙇ', 'ꙉ' => 'Ꙉ', 'ꙋ' => 'Ꙋ', 'ꙍ' => 'Ꙍ', 'ꙏ' => 'Ꙏ', 'ꙑ' => 'Ꙑ', 'ꙓ' => 'Ꙓ', 'ꙕ' => 'Ꙕ', 'ꙗ' => 'Ꙗ', 'ꙙ' => 'Ꙙ', 'ꙛ' => 'Ꙛ', 'ꙝ' => 'Ꙝ', 'ꙟ' => 'Ꙟ', 'ꙡ' => 'Ꙡ', 'ꙣ' => 'Ꙣ', 'ꙥ' => 'Ꙥ', 'ꙧ' => 'Ꙧ', 'ꙩ' => 'Ꙩ', 'ꙫ' => 'Ꙫ', 'ꙭ' => 'Ꙭ', 'ꚁ' => 'Ꚁ', 'ꚃ' => 'Ꚃ', 'ꚅ' => 'Ꚅ', 'ꚇ' => 'Ꚇ', 'ꚉ' => 'Ꚉ', 'ꚋ' => 'Ꚋ', 'ꚍ' => 'Ꚍ', 'ꚏ' => 'Ꚏ', 'ꚑ' => 'Ꚑ', 'ꚓ' => 'Ꚓ', 'ꚕ' => 'Ꚕ', 'ꚗ' => 'Ꚗ', 'ꚙ' => 'Ꚙ', 'ꚛ' => 'Ꚛ', 'ꜣ' => 'Ꜣ', 'ꜥ' => 'Ꜥ', 'ꜧ' => 'Ꜧ', 'ꜩ' => 'Ꜩ', 'ꜫ' => 'Ꜫ', 'ꜭ' => 'Ꜭ', 'ꜯ' => 'Ꜯ', 'ꜳ' => 'Ꜳ', 'ꜵ' => 'Ꜵ', 'ꜷ' => 'Ꜷ', 'ꜹ' => 'Ꜹ', 'ꜻ' => 'Ꜻ', 'ꜽ' => 'Ꜽ', 'ꜿ' => 'Ꜿ', 'ꝁ' => 'Ꝁ', 'ꝃ' => 'Ꝃ', 'ꝅ' => 'Ꝅ', 'ꝇ' => 'Ꝇ', 'ꝉ' => 'Ꝉ', 'ꝋ' => 'Ꝋ', 'ꝍ' => 'Ꝍ', 'ꝏ' => 'Ꝏ', 'ꝑ' => 'Ꝑ', 'ꝓ' => 'Ꝓ', 'ꝕ' => 'Ꝕ', 'ꝗ' => 'Ꝗ', 'ꝙ' => 'Ꝙ', 'ꝛ' => 'Ꝛ', 'ꝝ' => 'Ꝝ', 'ꝟ' => 'Ꝟ', 'ꝡ' => 'Ꝡ', 'ꝣ' => 'Ꝣ', 'ꝥ' => 'Ꝥ', 'ꝧ' => 'Ꝧ', 'ꝩ' => 'Ꝩ', 'ꝫ' => 'Ꝫ', 'ꝭ' => 'Ꝭ', 'ꝯ' => 'Ꝯ', 'ꝺ' => 'Ꝺ', 'ꝼ' => 'Ꝼ', 'ꝿ' => 'Ꝿ', 'ꞁ' => 'Ꞁ', 'ꞃ' => 'Ꞃ', 'ꞅ' => 'Ꞅ', 'ꞇ' => 'Ꞇ', 'ꞌ' => 'Ꞌ', 'ꞑ' => 'Ꞑ', 'ꞓ' => 'Ꞓ', 'ꞔ' => 'Ꞔ', 'ꞗ' => 'Ꞗ', 'ꞙ' => 'Ꞙ', 'ꞛ' => 'Ꞛ', 'ꞝ' => 'Ꞝ', 'ꞟ' => 'Ꞟ', 'ꞡ' => 'Ꞡ', 'ꞣ' => 'Ꞣ', 'ꞥ' => 'Ꞥ', 'ꞧ' => 'Ꞧ', 'ꞩ' => 'Ꞩ', 'ꞵ' => 'Ꞵ', 'ꞷ' => 'Ꞷ', 'ꞹ' => 'Ꞹ', 'ꞻ' => 'Ꞻ', 'ꞽ' => 'Ꞽ', 'ꞿ' => 'Ꞿ', 'ꟃ' => 'Ꟃ', 'ꟈ' => 'Ꟈ', 'ꟊ' => 'Ꟊ', 'ꟶ' => 'Ꟶ', 'ꭓ' => 'Ꭓ', 'ꭰ' => 'Ꭰ', 'ꭱ' => 'Ꭱ', 'ꭲ' => 'Ꭲ', 'ꭳ' => 'Ꭳ', 'ꭴ' => 'Ꭴ', 'ꭵ' => 'Ꭵ', 'ꭶ' => 'Ꭶ', 'ꭷ' => 'Ꭷ', 'ꭸ' => 'Ꭸ', 'ꭹ' => 'Ꭹ', 'ꭺ' => 'Ꭺ', 'ꭻ' => 'Ꭻ', 'ꭼ' => 'Ꭼ', 'ꭽ' => 'Ꭽ', 'ꭾ' => 'Ꭾ', 'ꭿ' => 'Ꭿ', 'ꮀ' => 'Ꮀ', 'ꮁ' => 'Ꮁ', 'ꮂ' => 'Ꮂ', 'ꮃ' => 'Ꮃ', 'ꮄ' => 'Ꮄ', 'ꮅ' => 'Ꮅ', 'ꮆ' => 'Ꮆ', 'ꮇ' => 'Ꮇ', 'ꮈ' => 'Ꮈ', 'ꮉ' => 'Ꮉ', 'ꮊ' => 'Ꮊ', 'ꮋ' => 'Ꮋ', 'ꮌ' => 'Ꮌ', 'ꮍ' => 'Ꮍ', 'ꮎ' => 'Ꮎ', 'ꮏ' => 'Ꮏ', 'ꮐ' => 'Ꮐ', 'ꮑ' => 'Ꮑ', 'ꮒ' => 'Ꮒ', 'ꮓ' => 'Ꮓ', 'ꮔ' => 'Ꮔ', 'ꮕ' => 'Ꮕ', 'ꮖ' => 'Ꮖ', 'ꮗ' => 'Ꮗ', 'ꮘ' => 'Ꮘ', 'ꮙ' => 'Ꮙ', 'ꮚ' => 'Ꮚ', 'ꮛ' => 'Ꮛ', 'ꮜ' => 'Ꮜ', 'ꮝ' => 'Ꮝ', 'ꮞ' => 'Ꮞ', 'ꮟ' => 'Ꮟ', 'ꮠ' => 'Ꮠ', 'ꮡ' => 'Ꮡ', 'ꮢ' => 'Ꮢ', 'ꮣ' => 'Ꮣ', 'ꮤ' => 'Ꮤ', 'ꮥ' => 'Ꮥ', 'ꮦ' => 'Ꮦ', 'ꮧ' => 'Ꮧ', 'ꮨ' => 'Ꮨ', 'ꮩ' => 'Ꮩ', 'ꮪ' => 'Ꮪ', 'ꮫ' => 'Ꮫ', 'ꮬ' => 'Ꮬ', 'ꮭ' => 'Ꮭ', 'ꮮ' => 'Ꮮ', 'ꮯ' => 'Ꮯ', 'ꮰ' => 'Ꮰ', 'ꮱ' => 'Ꮱ', 'ꮲ' => 'Ꮲ', 'ꮳ' => 'Ꮳ', 'ꮴ' => 'Ꮴ', 'ꮵ' => 'Ꮵ', 'ꮶ' => 'Ꮶ', 'ꮷ' => 'Ꮷ', 'ꮸ' => 'Ꮸ', 'ꮹ' => 'Ꮹ', 'ꮺ' => 'Ꮺ', 'ꮻ' => 'Ꮻ', 'ꮼ' => 'Ꮼ', 'ꮽ' => 'Ꮽ', 'ꮾ' => 'Ꮾ', 'ꮿ' => 'Ꮿ', 'a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', '𐐨' => '𐐀', '𐐩' => '𐐁', '𐐪' => '𐐂', '𐐫' => '𐐃', '𐐬' => '𐐄', '𐐭' => '𐐅', '𐐮' => '𐐆', '𐐯' => '𐐇', '𐐰' => '𐐈', '𐐱' => '𐐉', '𐐲' => '𐐊', '𐐳' => '𐐋', '𐐴' => '𐐌', '𐐵' => '𐐍', '𐐶' => '𐐎', '𐐷' => '𐐏', '𐐸' => '𐐐', '𐐹' => '𐐑', '𐐺' => '𐐒', '𐐻' => '𐐓', '𐐼' => '𐐔', '𐐽' => '𐐕', '𐐾' => '𐐖', '𐐿' => '𐐗', '𐑀' => '𐐘', '𐑁' => '𐐙', '𐑂' => '𐐚', '𐑃' => '𐐛', '𐑄' => '𐐜', '𐑅' => '𐐝', '𐑆' => '𐐞', '𐑇' => '𐐟', '𐑈' => '𐐠', '𐑉' => '𐐡', '𐑊' => '𐐢', '𐑋' => '𐐣', '𐑌' => '𐐤', '𐑍' => '𐐥', '𐑎' => '𐐦', '𐑏' => '𐐧', '𐓘' => '𐒰', '𐓙' => '𐒱', '𐓚' => '𐒲', '𐓛' => '𐒳', '𐓜' => '𐒴', '𐓝' => '𐒵', '𐓞' => '𐒶', '𐓟' => '𐒷', '𐓠' => '𐒸', '𐓡' => '𐒹', '𐓢' => '𐒺', '𐓣' => '𐒻', '𐓤' => '𐒼', '𐓥' => '𐒽', '𐓦' => '𐒾', '𐓧' => '𐒿', '𐓨' => '𐓀', '𐓩' => '𐓁', '𐓪' => '𐓂', '𐓫' => '𐓃', '𐓬' => '𐓄', '𐓭' => '𐓅', '𐓮' => '𐓆', '𐓯' => '𐓇', '𐓰' => '𐓈', '𐓱' => '𐓉', '𐓲' => '𐓊', '𐓳' => '𐓋', '𐓴' => '𐓌', '𐓵' => '𐓍', '𐓶' => '𐓎', '𐓷' => '𐓏', '𐓸' => '𐓐', '𐓹' => '𐓑', '𐓺' => '𐓒', '𐓻' => '𐓓', '𐳀' => '𐲀', '𐳁' => '𐲁', '𐳂' => '𐲂', '𐳃' => '𐲃', '𐳄' => '𐲄', '𐳅' => '𐲅', '𐳆' => '𐲆', '𐳇' => '𐲇', '𐳈' => '𐲈', '𐳉' => '𐲉', '𐳊' => '𐲊', '𐳋' => '𐲋', '𐳌' => '𐲌', '𐳍' => '𐲍', '𐳎' => '𐲎', '𐳏' => '𐲏', '𐳐' => '𐲐', '𐳑' => '𐲑', '𐳒' => '𐲒', '𐳓' => '𐲓', '𐳔' => '𐲔', '𐳕' => '𐲕', '𐳖' => '𐲖', '𐳗' => '𐲗', '𐳘' => '𐲘', '𐳙' => '𐲙', '𐳚' => '𐲚', '𐳛' => '𐲛', '𐳜' => '𐲜', '𐳝' => '𐲝', '𐳞' => '𐲞', '𐳟' => '𐲟', '𐳠' => '𐲠', '𐳡' => '𐲡', '𐳢' => '𐲢', '𐳣' => '𐲣', '𐳤' => '𐲤', '𐳥' => '𐲥', '𐳦' => '𐲦', '𐳧' => '𐲧', '𐳨' => '𐲨', '𐳩' => '𐲩', '𐳪' => '𐲪', '𐳫' => '𐲫', '𐳬' => '𐲬', '𐳭' => '𐲭', '𐳮' => '𐲮', '𐳯' => '𐲯', '𐳰' => '𐲰', '𐳱' => '𐲱', '𐳲' => '𐲲', '𑣀' => '𑢠', '𑣁' => '𑢡', '𑣂' => '𑢢', '𑣃' => '𑢣', '𑣄' => '𑢤', '𑣅' => '𑢥', '𑣆' => '𑢦', '𑣇' => '𑢧', '𑣈' => '𑢨', '𑣉' => '𑢩', '𑣊' => '𑢪', '𑣋' => '𑢫', '𑣌' => '𑢬', '𑣍' => '𑢭', '𑣎' => '𑢮', '𑣏' => '𑢯', '𑣐' => '𑢰', '𑣑' => '𑢱', '𑣒' => '𑢲', '𑣓' => '𑢳', '𑣔' => '𑢴', '𑣕' => '𑢵', '𑣖' => '𑢶', '𑣗' => '𑢷', '𑣘' => '𑢸', '𑣙' => '𑢹', '𑣚' => '𑢺', '𑣛' => '𑢻', '𑣜' => '𑢼', '𑣝' => '𑢽', '𑣞' => '𑢾', '𑣟' => '𑢿', '𖹠' => '𖹀', '𖹡' => '𖹁', '𖹢' => '𖹂', '𖹣' => '𖹃', '𖹤' => '𖹄', '𖹥' => '𖹅', '𖹦' => '𖹆', '𖹧' => '𖹇', '𖹨' => '𖹈', '𖹩' => '𖹉', '𖹪' => '𖹊', '𖹫' => '𖹋', '𖹬' => '𖹌', '𖹭' => '𖹍', '𖹮' => '𖹎', '𖹯' => '𖹏', '𖹰' => '𖹐', '𖹱' => '𖹑', '𖹲' => '𖹒', '𖹳' => '𖹓', '𖹴' => '𖹔', '𖹵' => '𖹕', '𖹶' => '𖹖', '𖹷' => '𖹗', '𖹸' => '𖹘', '𖹹' => '𖹙', '𖹺' => '𖹚', '𖹻' => '𖹛', '𖹼' => '𖹜', '𖹽' => '𖹝', '𖹾' => '𖹞', '𖹿' => '𖹟', '𞤢' => '𞤀', '𞤣' => '𞤁', '𞤤' => '𞤂', '𞤥' => '𞤃', '𞤦' => '𞤄', '𞤧' => '𞤅', '𞤨' => '𞤆', '𞤩' => '𞤇', '𞤪' => '𞤈', '𞤫' => '𞤉', '𞤬' => '𞤊', '𞤭' => '𞤋', '𞤮' => '𞤌', '𞤯' => '𞤍', '𞤰' => '𞤎', '𞤱' => '𞤏', '𞤲' => '𞤐', '𞤳' => '𞤑', '𞤴' => '𞤒', '𞤵' => '𞤓', '𞤶' => '𞤔', '𞤷' => '𞤕', '𞤸' => '𞤖', '𞤹' => '𞤗', '𞤺' => '𞤘', '𞤻' => '𞤙', '𞤼' => '𞤚', '𞤽' => '𞤛', '𞤾' => '𞤜', '𞤿' => '𞤝', '𞥀' => '𞤞', '𞥁' => '𞤟', '𞥂' => '𞤠', '𞥃' => '𞤡', 'ß' => 'SS', 'ff' => 'FF', 'fi' => 'FI', 'fl' => 'FL', 'ffi' => 'FFI', 'ffl' => 'FFL', 'ſt' => 'ST', 'st' => 'ST', 'և' => 'ԵՒ', 'ﬓ' => 'ՄՆ', 'ﬔ' => 'ՄԵ', 'ﬕ' => 'ՄԻ', 'ﬖ' => 'ՎՆ', 'ﬗ' => 'ՄԽ', 'ʼn' => 'ʼN', 'ΐ' => 'Ϊ́', 'ΰ' => 'Ϋ́', 'ǰ' => 'J̌', 'ẖ' => 'H̱', 'ẗ' => 'T̈', 'ẘ' => 'W̊', 'ẙ' => 'Y̊', 'ẚ' => 'Aʾ', 'ὐ' => 'Υ̓', 'ὒ' => 'Υ̓̀', 'ὔ' => 'Υ̓́', 'ὖ' => 'Υ̓͂', 'ᾶ' => 'Α͂', 'ῆ' => 'Η͂', 'ῒ' => 'Ϊ̀', 'ΐ' => 'Ϊ́', 'ῖ' => 'Ι͂', 'ῗ' => 'Ϊ͂', 'ῢ' => 'Ϋ̀', 'ΰ' => 'Ϋ́', 'ῤ' => 'Ρ̓', 'ῦ' => 'Υ͂', 'ῧ' => 'Ϋ͂', 'ῶ' => 'Ω͂', 'ᾈ' => 'ἈΙ', 'ᾉ' => 'ἉΙ', 'ᾊ' => 'ἊΙ', 'ᾋ' => 'ἋΙ', 'ᾌ' => 'ἌΙ', 'ᾍ' => 'ἍΙ', 'ᾎ' => 'ἎΙ', 'ᾏ' => 'ἏΙ', 'ᾘ' => 'ἨΙ', 'ᾙ' => 'ἩΙ', 'ᾚ' => 'ἪΙ', 'ᾛ' => 'ἫΙ', 'ᾜ' => 'ἬΙ', 'ᾝ' => 'ἭΙ', 'ᾞ' => 'ἮΙ', 'ᾟ' => 'ἯΙ', 'ᾨ' => 'ὨΙ', 'ᾩ' => 'ὩΙ', 'ᾪ' => 'ὪΙ', 'ᾫ' => 'ὫΙ', 'ᾬ' => 'ὬΙ', 'ᾭ' => 'ὭΙ', 'ᾮ' => 'ὮΙ', 'ᾯ' => 'ὯΙ', 'ᾼ' => 'ΑΙ', 'ῌ' => 'ΗΙ', 'ῼ' => 'ΩΙ', 'ᾲ' => 'ᾺΙ', 'ᾴ' => 'ΆΙ', 'ῂ' => 'ῊΙ', 'ῄ' => 'ΉΙ', 'ῲ' => 'ῺΙ', 'ῴ' => 'ΏΙ', 'ᾷ' => 'Α͂Ι', 'ῇ' => 'Η͂Ι', 'ῷ' => 'Ω͂Ι'); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Mbstring; /** * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. * * Implemented: * - mb_chr - Returns a specific character from its Unicode code point * - mb_convert_encoding - Convert character encoding * - mb_convert_variables - Convert character code in variable(s) * - mb_decode_mimeheader - Decode string in MIME header field * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED * - mb_decode_numericentity - Decode HTML numeric string reference to character * - mb_encode_numericentity - Encode character to HTML numeric string reference * - mb_convert_case - Perform case folding on a string * - mb_detect_encoding - Detect character encoding * - mb_get_info - Get internal settings of mbstring * - mb_http_input - Detect HTTP input character encoding * - mb_http_output - Set/Get HTTP output character encoding * - mb_internal_encoding - Set/Get internal character encoding * - mb_list_encodings - Returns an array of all supported encodings * - mb_ord - Returns the Unicode code point of a character * - mb_output_handler - Callback function converts character encoding in output buffer * - mb_scrub - Replaces ill-formed byte sequences with substitute characters * - mb_strlen - Get string length * - mb_strpos - Find position of first occurrence of string in a string * - mb_strrpos - Find position of last occurrence of a string in a string * - mb_str_split - Convert a string to an array * - mb_strtolower - Make a string lowercase * - mb_strtoupper - Make a string uppercase * - mb_substitute_character - Set/Get substitution character * - mb_substr - Get part of string * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive * - mb_stristr - Finds first occurrence of a string within another, case insensitive * - mb_strrchr - Finds the last occurrence of a character in a string within another * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive * - mb_strstr - Finds first occurrence of a string within another * - mb_strwidth - Return width of string * - mb_substr_count - Count the number of substring occurrences * * Not implemented: * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) * - mb_ereg_* - Regular expression with multibyte support * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable * - mb_preferred_mime_name - Get MIME charset string * - mb_regex_encoding - Returns current encoding for multibyte regex as string * - mb_regex_set_options - Set/Get the default options for mbregex functions * - mb_send_mail - Send encoded mail * - mb_split - Split multibyte string using regular expression * - mb_strcut - Get part of string * - mb_strimwidth - Get truncated string with specified width * * @author Nicolas Grekas * * @internal */ final class Mbstring { public const MB_CASE_FOLD = \PHP_INT_MAX; private const SIMPLE_CASE_FOLD = [['µ', 'ſ', "ͅ", 'ς', "ϐ", "ϑ", "ϕ", "ϖ", "ϰ", "ϱ", "ϵ", "ẛ", "ι"], ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "ṡ", 'ι']]; private static $encodingList = ['ASCII', 'UTF-8']; private static $language = 'neutral'; private static $internalEncoding = 'UTF-8'; public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { if (\is_array($fromEncoding) || null !== $fromEncoding && \false !== \strpos($fromEncoding, ',')) { $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); } else { $fromEncoding = self::getEncoding($fromEncoding); } $toEncoding = self::getEncoding($toEncoding); if ('BASE64' === $fromEncoding) { $s = \base64_decode($s); $fromEncoding = $toEncoding; } if ('BASE64' === $toEncoding) { return \base64_encode($s); } if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { $fromEncoding = 'Windows-1252'; } if ('UTF-8' !== $fromEncoding) { $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); } return \preg_replace_callback('/[\\x80-\\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); } if ('HTML-ENTITIES' === $fromEncoding) { $s = \html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); $fromEncoding = 'UTF-8'; } return \iconv($fromEncoding, $toEncoding . '//IGNORE', $s); } public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) { $ok = \true; \array_walk_recursive($vars, function (&$v) use(&$ok, $toEncoding, $fromEncoding) { if (\false === ($v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding))) { $ok = \false; } }); return $ok ? $fromEncoding : \false; } public static function mb_decode_mimeheader($s) { return \iconv_mime_decode($s, 2, self::$internalEncoding); } public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) { \trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); } public static function mb_decode_numericentity($s, $convmap, $encoding = null) { if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { \trigger_error('mb_decode_numericentity() expects parameter 1 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); return null; } if (!\is_array($convmap) || 80000 > \PHP_VERSION_ID && !$convmap) { return \false; } if (null !== $encoding && !\is_scalar($encoding)) { \trigger_error('mb_decode_numericentity() expects parameter 3 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); return ''; // Instead of null (cf. mb_encode_numericentity). } $s = (string) $s; if ('' === $s) { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!\preg_match('//u', $s)) { $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } $cnt = \floor(\count($convmap) / 4) * 4; for ($i = 0; $i < $cnt; $i += 4) { // collector_decode_htmlnumericentity ignores $convmap[$i + 3] $convmap[$i] += $convmap[$i + 2]; $convmap[$i + 1] += $convmap[$i + 2]; } $s = \preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use($cnt, $convmap) { $c = isset($m[2]) ? (int) \hexdec($m[2]) : $m[1]; for ($i = 0; $i < $cnt; $i += 4) { if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { return self::mb_chr($c - $convmap[$i + 2]); } } return $m[0]; }, $s); if (null === $encoding) { return $s; } return \iconv('UTF-8', $encoding . '//IGNORE', $s); } public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = \false) { if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { \trigger_error('mb_encode_numericentity() expects parameter 1 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); return null; } if (!\is_array($convmap) || 80000 > \PHP_VERSION_ID && !$convmap) { return \false; } if (null !== $encoding && !\is_scalar($encoding)) { \trigger_error('mb_encode_numericentity() expects parameter 3 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); return null; // Instead of '' (cf. mb_decode_numericentity). } if (null !== $is_hex && !\is_scalar($is_hex)) { \trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, ' . \gettype($s) . ' given', \E_USER_WARNING); return null; } $s = (string) $s; if ('' === $s) { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!\preg_match('//u', $s)) { $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } static $ulenMask = ["\xc0" => 2, "\xd0" => 2, "\xe0" => 3, "\xf0" => 4]; $cnt = \floor(\count($convmap) / 4) * 4; $i = 0; $len = \strlen($s); $result = ''; while ($i < $len) { $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xf0"]; $uchr = \substr($s, $i, $ulen); $i += $ulen; $c = self::mb_ord($uchr); for ($j = 0; $j < $cnt; $j += 4) { if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { $cOffset = $c + $convmap[$j + 2] & $convmap[$j + 3]; $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#' . $cOffset . ';'; continue 2; } } $result .= $uchr; } if (null === $encoding) { return $result; } return \iconv('UTF-8', $encoding . '//IGNORE', $result); } public static function mb_convert_case($s, $mode, $encoding = null) { $s = (string) $s; if ('' === $s) { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!\preg_match('//u', $s)) { $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } if (\MB_CASE_TITLE == $mode) { static $titleRegexp = null; if (null === $titleRegexp) { $titleRegexp = self::getData('titleCaseRegexp'); } $s = \preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); } else { if (\MB_CASE_UPPER == $mode) { static $upper = null; if (null === $upper) { $upper = self::getData('upperCase'); } $map = $upper; } else { if (self::MB_CASE_FOLD === $mode) { static $caseFolding = null; if (null === $caseFolding) { $caseFolding = self::getData('caseFolding'); } $s = \strtr($s, $caseFolding); } static $lower = null; if (null === $lower) { $lower = self::getData('lowerCase'); } $map = $lower; } static $ulenMask = ["\xc0" => 2, "\xd0" => 2, "\xe0" => 3, "\xf0" => 4]; $i = 0; $len = \strlen($s); while ($i < $len) { $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xf0"]; $uchr = \substr($s, $i, $ulen); $i += $ulen; if (isset($map[$uchr])) { $uchr = $map[$uchr]; $nlen = \strlen($uchr); if ($nlen == $ulen) { $nlen = $i; do { $s[--$nlen] = $uchr[--$ulen]; } while ($ulen); } else { $s = \substr_replace($s, $uchr, $i - $ulen, $ulen); $len += $nlen - $ulen; $i += $nlen - $ulen; } } } } if (null === $encoding) { return $s; } return \iconv('UTF-8', $encoding . '//IGNORE', $s); } public static function mb_internal_encoding($encoding = null) { if (null === $encoding) { return self::$internalEncoding; } $normalizedEncoding = self::getEncoding($encoding); if ('UTF-8' === $normalizedEncoding || \false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { self::$internalEncoding = $normalizedEncoding; return \true; } if (80000 > \PHP_VERSION_ID) { return \false; } throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); } public static function mb_language($lang = null) { if (null === $lang) { return self::$language; } switch ($normalizedLang = \strtolower($lang)) { case 'uni': case 'neutral': self::$language = $normalizedLang; return \true; } if (80000 > \PHP_VERSION_ID) { return \false; } throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); } public static function mb_list_encodings() { return ['UTF-8']; } public static function mb_encoding_aliases($encoding) { switch (\strtoupper($encoding)) { case 'UTF8': case 'UTF-8': return ['utf8']; } return \false; } public static function mb_check_encoding($var = null, $encoding = null) { if (\PHP_VERSION_ID < 70200 && \is_array($var)) { \trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); return null; } if (null === $encoding) { if (null === $var) { return \false; } $encoding = self::$internalEncoding; } if (!\is_array($var)) { return self::mb_detect_encoding($var, [$encoding]) || \false !== @\iconv($encoding, $encoding, $var); } foreach ($var as $key => $value) { if (!self::mb_check_encoding($key, $encoding)) { return \false; } if (!self::mb_check_encoding($value, $encoding)) { return \false; } } return \true; } public static function mb_detect_encoding($str, $encodingList = null, $strict = \false) { if (null === $encodingList) { $encodingList = self::$encodingList; } else { if (!\is_array($encodingList)) { $encodingList = \array_map('trim', \explode(',', $encodingList)); } $encodingList = \array_map('strtoupper', $encodingList); } foreach ($encodingList as $enc) { switch ($enc) { case 'ASCII': if (!\preg_match('/[\\x80-\\xFF]/', $str)) { return $enc; } break; case 'UTF8': case 'UTF-8': if (\preg_match('//u', $str)) { return 'UTF-8'; } break; default: if (0 === \strncmp($enc, 'ISO-8859-', 9)) { return $enc; } } } return \false; } public static function mb_detect_order($encodingList = null) { if (null === $encodingList) { return self::$encodingList; } if (!\is_array($encodingList)) { $encodingList = \array_map('trim', \explode(',', $encodingList)); } $encodingList = \array_map('strtoupper', $encodingList); foreach ($encodingList as $enc) { switch ($enc) { default: if (\strncmp($enc, 'ISO-8859-', 9)) { return \false; } // no break case 'ASCII': case 'UTF8': case 'UTF-8': } } self::$encodingList = $encodingList; return \true; } public static function mb_strlen($s, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return \strlen($s); } return @\iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return \strpos($haystack, $needle, $offset); } $needle = (string) $needle; if ('' === $needle) { if (80000 > \PHP_VERSION_ID) { \trigger_error(__METHOD__ . ': Empty delimiter', \E_USER_WARNING); return \false; } return 0; } return \iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return \strrpos($haystack, $needle, $offset); } if ($offset != (int) $offset) { $offset = 0; } elseif ($offset = (int) $offset) { if ($offset < 0) { if (0 > ($offset += self::mb_strlen($needle))) { $haystack = self::mb_substr($haystack, 0, $offset, $encoding); } $offset = 0; } else { $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); } } $pos = '' !== $needle || 80000 > \PHP_VERSION_ID ? \iconv_strrpos($haystack, $needle, $encoding) : self::mb_strlen($haystack, $encoding); return \false !== $pos ? $offset + $pos : \false; } public static function mb_str_split($string, $split_length = 1, $encoding = null) { if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { \trigger_error('mb_str_split() expects parameter 1 to be string, ' . \gettype($string) . ' given', \E_USER_WARNING); return null; } if (1 > ($split_length = (int) $split_length)) { if (80000 > \PHP_VERSION_ID) { \trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); return \false; } throw new \ValueError('Argument #2 ($length) must be greater than 0'); } if (null === $encoding) { $encoding = \mb_internal_encoding(); } if ('UTF-8' === ($encoding = self::getEncoding($encoding))) { $rx = '/('; while (65535 < $split_length) { $rx .= '.{65535}'; $split_length -= 65535; } $rx .= '.{' . $split_length . '})/us'; return \preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } $result = []; $length = \mb_strlen($string, $encoding); for ($i = 0; $i < $length; $i += $split_length) { $result[] = \mb_substr($string, $i, $split_length, $encoding); } return $result; } public static function mb_strtolower($s, $encoding = null) { return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); } public static function mb_strtoupper($s, $encoding = null) { return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); } public static function mb_substitute_character($c = null) { if (null === $c) { return 'none'; } if (0 === \strcasecmp($c, 'none')) { return \true; } if (80000 > \PHP_VERSION_ID) { return \false; } if (\is_int($c) || 'long' === $c || 'entity' === $c) { return \false; } throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); } public static function mb_substr($s, $start, $length = null, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return (string) \substr($s, $start, null === $length ? 2147483647 : $length); } if ($start < 0) { $start = \iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } } if (null === $length) { $length = 2147483647; } elseif ($length < 0) { $length = \iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } return (string) \iconv_substr($s, $start, $length, $encoding); } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { [$haystack, $needle] = \str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding)]); return self::mb_strpos($haystack, $needle, $offset, $encoding); } public static function mb_stristr($haystack, $needle, $part = \false, $encoding = null) { $pos = self::mb_stripos($haystack, $needle, 0, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strrchr($haystack, $needle, $part = \false, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { $pos = \strrpos($haystack, $needle); } else { $needle = self::mb_substr($needle, 0, 1, $encoding); $pos = \iconv_strrpos($haystack, $needle, $encoding); } return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strrichr($haystack, $needle, $part = \false, $encoding = null) { $needle = self::mb_substr($needle, 0, 1, $encoding); $pos = self::mb_strripos($haystack, $needle, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); $haystack = \str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); $needle = \str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); return self::mb_strrpos($haystack, $needle, $offset, $encoding); } public static function mb_strstr($haystack, $needle, $part = \false, $encoding = null) { $pos = \strpos($haystack, $needle); if (\false === $pos) { return \false; } if ($part) { return \substr($haystack, 0, $pos); } return \substr($haystack, $pos); } public static function mb_get_info($type = 'all') { $info = ['internal_encoding' => self::$internalEncoding, 'http_output' => 'pass', 'http_output_conv_mimetypes' => '^(text/|application/xhtml\\+xml)', 'func_overload' => 0, 'func_overload_list' => 'no overload', 'mail_charset' => 'UTF-8', 'mail_header_encoding' => 'BASE64', 'mail_body_encoding' => 'BASE64', 'illegal_chars' => 0, 'encoding_translation' => 'Off', 'language' => self::$language, 'detect_order' => self::$encodingList, 'substitute_character' => 'none', 'strict_detection' => 'Off']; if ('all' === $type) { return $info; } if (isset($info[$type])) { return $info[$type]; } return \false; } public static function mb_http_input($type = '') { return \false; } public static function mb_http_output($encoding = null) { return null !== $encoding ? 'pass' === $encoding : 'pass'; } public static function mb_strwidth($s, $encoding = null) { $encoding = self::getEncoding($encoding); if ('UTF-8' !== $encoding) { $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } $s = \preg_replace('/[\\x{1100}-\\x{115F}\\x{2329}\\x{232A}\\x{2E80}-\\x{303E}\\x{3040}-\\x{A4CF}\\x{AC00}-\\x{D7A3}\\x{F900}-\\x{FAFF}\\x{FE10}-\\x{FE19}\\x{FE30}-\\x{FE6F}\\x{FF00}-\\x{FF60}\\x{FFE0}-\\x{FFE6}\\x{20000}-\\x{2FFFD}\\x{30000}-\\x{3FFFD}]/u', '', $s, -1, $wide); return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) { return \substr_count($haystack, $needle); } public static function mb_output_handler($contents, $status) { return $contents; } public static function mb_chr($code, $encoding = null) { if (0x80 > ($code %= 0x200000)) { $s = \chr($code); } elseif (0x800 > $code) { $s = \chr(0xc0 | $code >> 6) . \chr(0x80 | $code & 0x3f); } elseif (0x10000 > $code) { $s = \chr(0xe0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f); } else { $s = \chr(0xf0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3f) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f); } if ('UTF-8' !== ($encoding = self::getEncoding($encoding))) { $s = \mb_convert_encoding($s, $encoding, 'UTF-8'); } return $s; } public static function mb_ord($s, $encoding = null) { if ('UTF-8' !== ($encoding = self::getEncoding($encoding))) { $s = \mb_convert_encoding($s, 'UTF-8', $encoding); } if (1 === \strlen($s)) { return \ord($s); } $code = ($s = \unpack('C*', \substr($s, 0, 4))) ? $s[1] : 0; if (0xf0 <= $code) { return ($code - 0xf0 << 18) + ($s[2] - 0x80 << 12) + ($s[3] - 0x80 << 6) + $s[4] - 0x80; } if (0xe0 <= $code) { return ($code - 0xe0 << 12) + ($s[2] - 0x80 << 6) + $s[3] - 0x80; } if (0xc0 <= $code) { return ($code - 0xc0 << 6) + $s[2] - 0x80; } return $code; } public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null) : string { if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], \true)) { throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); } if (null === $encoding) { $encoding = self::mb_internal_encoding(); } try { $validEncoding = @self::mb_check_encoding('', $encoding); } catch (\ValueError $e) { throw new \ValueError(\sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); } // BC for PHP 7.3 and lower if (!$validEncoding) { throw new \ValueError(\sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); } if (self::mb_strlen($pad_string, $encoding) <= 0) { throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); } $paddingRequired = $length - self::mb_strlen($string, $encoding); if ($paddingRequired < 1) { return $string; } switch ($pad_type) { case \STR_PAD_LEFT: return self::mb_substr(\str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding) . $string; case \STR_PAD_RIGHT: return $string . self::mb_substr(\str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); default: $leftPaddingLength = \floor($paddingRequired / 2); $rightPaddingLength = $paddingRequired - $leftPaddingLength; return self::mb_substr(\str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding) . $string . self::mb_substr(\str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); } } private static function getSubpart($pos, $part, $haystack, $encoding) { if (\false === $pos) { return \false; } if ($part) { return self::mb_substr($haystack, 0, $pos, $encoding); } return self::mb_substr($haystack, $pos, null, $encoding); } private static function html_encoding_callback(array $m) { $i = 1; $entities = ''; $m = \unpack('C*', \htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); while (isset($m[$i])) { if (0x80 > $m[$i]) { $entities .= \chr($m[$i++]); continue; } if (0xf0 <= $m[$i]) { $c = ($m[$i++] - 0xf0 << 18) + ($m[$i++] - 0x80 << 12) + ($m[$i++] - 0x80 << 6) + $m[$i++] - 0x80; } elseif (0xe0 <= $m[$i]) { $c = ($m[$i++] - 0xe0 << 12) + ($m[$i++] - 0x80 << 6) + $m[$i++] - 0x80; } else { $c = ($m[$i++] - 0xc0 << 6) + $m[$i++] - 0x80; } $entities .= '&#' . $c . ';'; } return $entities; } private static function title_case(array $s) { return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8') . self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); } private static function getData($file) { if (\file_exists($file = __DIR__ . '/Resources/unidata/' . $file . '.php')) { return require $file; } return \false; } private static function getEncoding($encoding) { if (null === $encoding) { return self::$internalEncoding; } if ('UTF-8' === $encoding) { return 'UTF-8'; } $encoding = \strtoupper($encoding); if ('8BIT' === $encoding || 'BINARY' === $encoding) { return 'CP850'; } if ('UTF8' === $encoding) { return 'UTF-8'; } return $encoding; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Mbstring as p; if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; } if (!function_exists('mb_convert_encoding')) { function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } } if (!function_exists('mb_decode_mimeheader')) { function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } } if (!function_exists('mb_encode_mimeheader')) { function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } } if (!function_exists('mb_decode_numericentity')) { function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } } if (!function_exists('mb_encode_numericentity')) { function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } } if (!function_exists('mb_convert_case')) { function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } } if (!function_exists('mb_internal_encoding')) { function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } } if (!function_exists('mb_language')) { function mb_language($language = null) { return p\Mbstring::mb_language($language); } } if (!function_exists('mb_list_encodings')) { function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } } if (!function_exists('mb_encoding_aliases')) { function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } } if (!function_exists('mb_check_encoding')) { function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } } if (!function_exists('mb_detect_encoding')) { function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } } if (!function_exists('mb_detect_order')) { function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } } if (!function_exists('mb_parse_str')) { function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } } if (!function_exists('mb_strlen')) { function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } } if (!function_exists('mb_strpos')) { function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } } if (!function_exists('mb_strtolower')) { function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } } if (!function_exists('mb_strtoupper')) { function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } } if (!function_exists('mb_substitute_character')) { function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } } if (!function_exists('mb_substr')) { function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } } if (!function_exists('mb_stripos')) { function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } } if (!function_exists('mb_stristr')) { function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } } if (!function_exists('mb_strrchr')) { function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } } if (!function_exists('mb_strrichr')) { function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } } if (!function_exists('mb_strripos')) { function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } } if (!function_exists('mb_strrpos')) { function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } } if (!function_exists('mb_strstr')) { function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } } if (!function_exists('mb_get_info')) { function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } } if (!function_exists('mb_http_output')) { function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } } if (!function_exists('mb_strwidth')) { function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } } if (!function_exists('mb_substr_count')) { function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } } if (!function_exists('mb_output_handler')) { function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } } if (!function_exists('mb_http_input')) { function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } } if (!function_exists('mb_convert_variables')) { function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } } if (!function_exists('mb_ord')) { function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } } if (!function_exists('mb_chr')) { function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } } if (!function_exists('mb_scrub')) { function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } } if (!function_exists('mb_str_split')) { function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } } if (!function_exists('mb_str_pad')) { function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } } if (extension_loaded('mbstring')) { return; } if (!defined('MB_CASE_UPPER')) { define('MB_CASE_UPPER', 0); } if (!defined('MB_CASE_LOWER')) { define('MB_CASE_LOWER', 1); } if (!defined('MB_CASE_TITLE')) { define('MB_CASE_TITLE', 2); } Symfony Polyfill / Mbstring =========================== This component provides a partial, native PHP implementation for the [Mbstring](https://php.net/mbstring) extension. More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). { "name": "symfony\/polyfill-mbstring", "type": "library", "description": "Symfony polyfill for the Mbstring extension", "keywords": [ "polyfill", "shim", "compatibility", "portable", "mbstring" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "provide": { "ext-mbstring": "*" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, "files": [ "bootstrap.php" ] }, "suggest": { "ext-mbstring": "For best performance" }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Intl\Grapheme as p; if (!defined('GRAPHEME_EXTR_COUNT')) { define('GRAPHEME_EXTR_COUNT', 0); } if (!defined('GRAPHEME_EXTR_MAXBYTES')) { define('GRAPHEME_EXTR_MAXBYTES', 1); } if (!defined('GRAPHEME_EXTR_MAXCHARS')) { define('GRAPHEME_EXTR_MAXCHARS', 2); } if (!function_exists('grapheme_extract')) { function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); } } if (!function_exists('grapheme_stripos')) { function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_stristr')) { function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } } if (!function_exists('grapheme_strlen')) { function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); } } if (!function_exists('grapheme_strpos')) { function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_strripos')) { function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_strrpos')) { function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_strstr')) { function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } } if (!function_exists('grapheme_substr')) { function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); } } Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Polyfill\Intl\Grapheme; \define('SYMFONY_GRAPHEME_CLUSTER_RX', ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39) ? '\\X' : \Symfony\Polyfill\Intl\Grapheme\Grapheme::GRAPHEME_CLUSTER_RX); /** * Partial intl implementation in pure PHP. * * Implemented: * - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8 * - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string * - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack * - grapheme_strlen - Get string length in grapheme units * - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack * - grapheme_substr - Return part of a string * * @author Nicolas Grekas * * @internal */ final class Grapheme { // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control]) // This regular expression is a work around for http://bugs.exim.org/1279 public const GRAPHEME_CLUSTER_RX = '(?:\\r\\n|(?:[ -~\\x{200C}\\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}])[\\p{Mn}\\p{Me}\\x{09BE}\\x{09D7}\\x{0B3E}\\x{0B57}\\x{0BBE}\\x{0BD7}\\x{0CC2}\\x{0CD5}\\x{0CD6}\\x{0D3E}\\x{0D57}\\x{0DCF}\\x{0DDF}\\x{200C}\\x{200D}\\x{1D165}\\x{1D16E}-\\x{1D172}]*|[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}])'; private const CASE_FOLD = [['µ', 'ſ', "ͅ", 'ς', "ϐ", "ϑ", "ϕ", "ϖ", "ϰ", "ϱ", "ϵ", "ẛ", "ι"], ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "ṡ", 'ι']]; public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) { if (0 > $start) { $start = \strlen($s) + $start; } if (!\is_scalar($s)) { $hasError = \false; \set_error_handler(function () use(&$hasError) { $hasError = \true; }); $next = \substr($s, $start); \restore_error_handler(); if ($hasError) { \substr($s, $start); $s = ''; } else { $s = $next; } } else { $s = \substr($s, $start); } $size = (int) $size; $type = (int) $type; $start = (int) $start; if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) { if (80000 > \PHP_VERSION_ID) { return \false; } throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS'); } if (!isset($s[0]) || 0 > $size || 0 > $start) { return \false; } if (0 === $size) { return ''; } $next = $start; $s = \preg_split('/(' . \SYMFONY_GRAPHEME_CLUSTER_RX . ')/u', "\r\n" . $s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); if (!isset($s[1])) { return \false; } $i = 1; $ret = ''; do { if (\GRAPHEME_EXTR_COUNT === $type) { --$size; } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) { $size -= \strlen($s[$i]); } else { $size -= \iconv_strlen($s[$i], 'UTF-8//IGNORE'); } if ($size >= 0) { $ret .= $s[$i]; } } while (isset($s[++$i]) && $size > 0); $next += \strlen($ret); return $ret; } public static function grapheme_strlen($s) { \preg_replace('/' . \SYMFONY_GRAPHEME_CLUSTER_RX . '/u', '', $s, -1, $len); return 0 === $len && '' !== $s ? null : $len; } public static function grapheme_substr($s, $start, $len = null) { if (null === $len) { $len = 2147483647; } \preg_match_all('/' . \SYMFONY_GRAPHEME_CLUSTER_RX . '/u', $s, $s); $slen = \count($s[0]); $start = (int) $start; if (0 > $start) { $start += $slen; } if (0 > $start) { if (\PHP_VERSION_ID < 80000) { return \false; } $start = 0; } if ($start >= $slen) { return \PHP_VERSION_ID >= 80000 ? '' : \false; } $rem = $slen - $start; if (0 > $len) { $len += $rem; } if (0 === $len) { return ''; } if (0 > $len) { return \PHP_VERSION_ID >= 80000 ? '' : \false; } if ($len > $rem) { $len = $rem; } return \implode('', \array_slice($s[0], $start, $len)); } public static function grapheme_strpos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 0); } public static function grapheme_stripos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 1); } public static function grapheme_strrpos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 2); } public static function grapheme_strripos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 3); } public static function grapheme_stristr($s, $needle, $beforeNeedle = \false) { return \mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); } public static function grapheme_strstr($s, $needle, $beforeNeedle = \false) { return \mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); } private static function grapheme_position($s, $needle, $offset, $mode) { $needle = (string) $needle; if (80000 > \PHP_VERSION_ID && !\preg_match('/./us', $needle)) { return \false; } $s = (string) $s; if (!\preg_match('/./us', $s)) { return \false; } if ($offset > 0) { $s = self::grapheme_substr($s, $offset); } elseif ($offset < 0) { if (2 > $mode) { $offset += self::grapheme_strlen($s); $s = self::grapheme_substr($s, $offset); if (0 > $offset) { $offset = 0; } } elseif (0 > ($offset += self::grapheme_strlen($needle))) { $s = self::grapheme_substr($s, 0, $offset); $offset = 0; } else { $offset = 0; } } // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8, // we can use normal binary string functions here. For case-insensitive searches, // case fold the strings first. $caseInsensitive = $mode & 1; $reverse = $mode & 2; if ($caseInsensitive) { // Use the same case folding mode as mbstring does for mb_stripos(). // Stick to SIMPLE case folding to avoid changing the length of the string, which // might result in offsets being shifted. $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER; $s = \mb_convert_case($s, $mode, 'UTF-8'); $needle = \mb_convert_case($needle, $mode, 'UTF-8'); if (!\defined('MB_CASE_FOLD_SIMPLE')) { $s = \str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); $needle = \str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle); } } if ($reverse) { $needlePos = \strrpos($s, $needle); } else { $needlePos = \strpos($s, $needle); } return \false !== $needlePos ? self::grapheme_strlen(\substr($s, 0, $needlePos)) + $offset : \false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Symfony\Polyfill\Intl\Grapheme as p; if (extension_loaded('intl')) { return; } if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; } if (!defined('GRAPHEME_EXTR_COUNT')) { define('GRAPHEME_EXTR_COUNT', 0); } if (!defined('GRAPHEME_EXTR_MAXBYTES')) { define('GRAPHEME_EXTR_MAXBYTES', 1); } if (!defined('GRAPHEME_EXTR_MAXCHARS')) { define('GRAPHEME_EXTR_MAXCHARS', 2); } if (!function_exists('grapheme_extract')) { function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); } } if (!function_exists('grapheme_stripos')) { function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); } } if (!function_exists('grapheme_stristr')) { function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); } } if (!function_exists('grapheme_strlen')) { function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); } } if (!function_exists('grapheme_strpos')) { function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); } } if (!function_exists('grapheme_strripos')) { function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); } } if (!function_exists('grapheme_strrpos')) { function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); } } if (!function_exists('grapheme_strstr')) { function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); } } if (!function_exists('grapheme_substr')) { function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } } Symfony Polyfill / Intl: Grapheme ================================= This component provides a partial, native PHP implementation of the [Grapheme functions](https://php.net/intl.grapheme) from the [Intl](https://php.net/intl) extension. - [`grapheme_extract`](https://php.net/grapheme_extract): Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8 - [`grapheme_stripos`](https://php.net/grapheme_stripos): Find position (in grapheme units) of first occurrence of a case-insensitive string - [`grapheme_stristr`](https://php.net/grapheme_stristr): Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack - [`grapheme_strlen`](https://php.net/grapheme_strlen): Get string length in grapheme units - [`grapheme_strpos`](https://php.net/grapheme_strpos): Find position (in grapheme units) of first occurrence of a string - [`grapheme_strripos`](https://php.net/grapheme_strripos): Find position (in grapheme units) of last occurrence of a case-insensitive string - [`grapheme_strrpos`](https://php.net/grapheme_strrpos): Find position (in grapheme units) of last occurrence of a string - [`grapheme_strstr`](https://php.net/grapheme_strstr): Returns part of haystack string from the first occurrence of needle to the end of haystack - [`grapheme_substr`](https://php.net/grapheme_substr): Return part of a string More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= This library is released under the [MIT license](LICENSE). { "name": "symfony\/polyfill-intl-grapheme", "type": "library", "description": "Symfony polyfill for intl's grapheme_* functions", "keywords": [ "polyfill", "shim", "compatibility", "portable", "intl", "grapheme" ], "homepage": "https:\/\/symfony.com", "license": "MIT", "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https:\/\/symfony.com\/contributors" } ], "require": { "php": ">=7.1" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" }, "files": [ "bootstrap.php" ] }, "suggest": { "ext-intl": "For best performance" }, "minimum-stability": "dev", "extra": { "thanks": { "name": "symfony\/polyfill", "url": "https:\/\/github.com\/symfony\/polyfill" } } }MIT License Copyright (c) 2012-2021 Ben Ramsey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ramsey/uuid *IMPORTANT: This is the 3.x series. Please upgrade to the 4.x series.* *NOTICE: Formerly known as `rhumsaa/uuid`, The package and namespace names have changed to `ramsey/uuid` and `Ramsey\Uuid`, respectively.* [![Source Code][badge-source]][source] [![Series][badge-series]][series] [![Upgrade][badge-upgrade]][upgrade] [![PHP Version][badge-php]][php] [![Software License][badge-license]][license] [![Build Status][badge-build]][build] [![Coverage Status][badge-coverage]][coverage] ramsey/uuid is a PHP 5.4+ library for generating and working with [RFC 4122][rfc4122] version 1, 3, 4, and 5 universally unique identifiers (UUID). This project adheres to a [Contributor Code of Conduct][conduct]. By participating in this project and its community, you are expected to uphold this code. From [Wikipedia](http://en.wikipedia.org/wiki/Universally_unique_identifier): > The intent of UUIDs is to enable distributed systems to uniquely identify > information without significant central coordination. In this context the word > unique should be taken to mean "practically unique" rather than "guaranteed > unique". Since the identifiers have a finite size, it is possible for two > differing items to share the same identifier. The identifier size and > generation process need to be selected so as to make this sufficiently > improbable in practice. Anyone can create a UUID and use it to identify > something with reasonable confidence that the same identifier will never be > unintentionally created by anyone to identify something else. Information > labeled with UUIDs can therefore be later combined into a single database > without needing to resolve identifier (ID) conflicts. Much inspiration for this library came from the [Java][javauuid] and [Python][pyuuid] UUID libraries. ## Installation The preferred method of installation is via [Composer][]. Run the following command to install the package and add it as a requirement to your project's `composer.json`: ```bash composer require ramsey/uuid ``` ## Upgrading from 2.x to 3.x While we have made significant internal changes to the library, we have made every effort to ensure a seamless upgrade path from the 2.x series of this library to 3.x. One major breaking change is the transition from the `Rhumsaa` root namespace to `Ramsey`. In most cases, all you will need is to change the namespace to `Ramsey` in your code, and everything will "just work." Here are full details on the breaking changes to the public API of this library: 1. All namespace references of `Rhumsaa` have changed to `Ramsey`. Simply change the namespace to `Ramsey` in your code and everything should work. 2. The console application has moved to [ramsey/uuid-console](https://packagist.org/packages/ramsey/uuid-console). If using the console functionality, use Composer to require `ramsey/uuid-console`. 3. The Doctrine field type mapping has moved to [ramsey/uuid-doctrine](https://packagist.org/packages/ramsey/uuid-doctrine). If using the Doctrine functionality, use Composer to require `ramsey/uuid-doctrine`. ## What to do if you see a "rhumsaa/uuid is abandoned" message When installing your project's dependencies using Composer, you might see the following message: ``` Package rhumsaa/uuid is abandoned, you should avoid using it. Use ramsey/uuid instead. ``` Don't panic. Simply execute the following commands with Composer: ``` bash composer remove rhumsaa/uuid composer require ramsey/uuid=^2.9 ``` After doing so, you will have the latest ramsey/uuid package in the 2.x series, and there will be no need to modify any code; the namespace in the 2.x series is still `Rhumsaa`. ## Requirements Some methods in this library have requirements due to integer size restrictions on 32-bit and 64-bit builds of PHP. A 64-bit build of PHP and the [Moontoast\Math][] library are recommended. However, this library is designed to work on 32-bit builds of PHP without Moontoast\Math, with some degraded functionality. Please check the API documentation for more information. If a particular requirement is not present, then an `UnsatisfiedDependencyException` is thrown, allowing one to catch a bad call in an environment where the call is not supported and gracefully degrade. ## Examples See the [cookbook on the wiki][wiki-cookbook] for more examples and approaches to specific use-cases. ```php toString() . "\n"; // i.e. e4eaaaf2-d142-11e1-b3e4-080027620cdd // Generate a version 3 (name-based and hashed with MD5) UUID object $uuid3 = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net'); echo $uuid3->toString() . "\n"; // i.e. 11a38b9a-b3da-360f-9353-a5a725514269 // Generate a version 4 (random) UUID object $uuid4 = Uuid::uuid4(); echo $uuid4->toString() . "\n"; // i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a // Generate a version 5 (name-based and hashed with SHA1) UUID object $uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'php.net'); echo $uuid5->toString() . "\n"; // i.e. c4a760a8-dbcf-5254-a0d9-6a4474bd1b62 } catch (UnsatisfiedDependencyException $e) { // Some dependency was not met. Either the method cannot be called on a // 32-bit system, or it can, but it relies on Moontoast\Math to be present. echo 'Caught exception: ' . $e->getMessage() . "\n"; } ``` ## Contributing Contributions are welcome! Please read [CONTRIBUTING.md][] for details. ## Copyright and License The ramsey/uuid library is copyright © [Ben Ramsey](https://benramsey.com/) and licensed for use under the MIT License (MIT). Please see [LICENSE][] for more information. [rfc4122]: http://tools.ietf.org/html/rfc4122 [conduct]: https://github.com/ramsey/uuid/blob/master/.github/CODE_OF_CONDUCT.md [javauuid]: http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html [pyuuid]: http://docs.python.org/3/library/uuid.html [composer]: http://getcomposer.org/ [moontoast\math]: https://packagist.org/packages/moontoast/math [wiki-cookbook]: https://github.com/ramsey/uuid/wiki/Ramsey%5CUuid-Cookbook [contributing.md]: https://github.com/ramsey/uuid/blob/master/.github/CONTRIBUTING.md [badge-source]: https://img.shields.io/badge/source-ramsey/uuid-blue.svg?style=flat-square [badge-series]: https://img.shields.io/badge/series-3.x-darkcyan.svg?style=flat-square [badge-upgrade]: https://img.shields.io/packagist/v/ramsey/uuid.svg?style=flat-square&label=upgrade&colorB=darkred [badge-license]: https://img.shields.io/packagist/l/ramsey/uuid.svg?style=flat-square&colorB=darkcyan [badge-php]: https://img.shields.io/packagist/php-v/ramsey/uuid/3.x-dev.svg?style=flat-square&colorB=%238892BF [badge-build]: https://img.shields.io/github/actions/workflow/status/ramsey/uuid/continuous-integration.yml?branch=3.x&logo=github&style=flat-square [badge-coverage]: https://img.shields.io/codecov/c/gh/ramsey/uuid/3.x.svg?style=flat-square&logo=codecov [source]: https://github.com/ramsey/uuid/tree/3.x [series]: https://packagist.org/packages/ramsey/uuid [upgrade]: https://packagist.org/packages/ramsey/uuid [license]: https://github.com/ramsey/uuid/blob/master/LICENSE [php]: https://php.net [build]: https://github.com/ramsey/uuid/actions/workflows/continuous-integration.yml?query=branch%3A3.x [coverage]: https://app.codecov.io/gh/ramsey/uuid/branch/3.x { "name": "ramsey\/uuid", "type": "library", "description": "Formerly rhumsaa\/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", "keywords": [ "uuid", "identifier", "guid" ], "homepage": "https:\/\/github.com\/ramsey\/uuid", "license": "MIT", "authors": [ { "name": "Ben Ramsey", "email": "ben@benramsey.com", "homepage": "https:\/\/benramsey.com" }, { "name": "Marijn Huizendveld", "email": "marijn.huizendveld@gmail.com" }, { "name": "Thibaud Fabre", "email": "thibaud@aztech.io" } ], "require": { "php": "^5.4 | ^7.0 | ^8.0", "ext-json": "*", "paragonie\/random_compat": "^1 | ^2 | ^9.99.99", "symfony\/polyfill-ctype": "^1.8" }, "require-dev": { "codeception\/aspect-mock": "^1 | ^2", "doctrine\/annotations": "^1.2", "goaop\/framework": "1.0.0-alpha.2 | ^1 | >=2.1.0 <=2.3.2", "mockery\/mockery": "^0.9.11 | ^1", "moontoast\/math": "^1.1", "nikic\/php-parser": "<=4.5.0", "paragonie\/random-lib": "^2", "php-mock\/php-mock-phpunit": "^0.3 | ^1.1 | ^2.6", "php-parallel-lint\/php-parallel-lint": "^1.3", "phpunit\/phpunit": ">=4.8.36 <9.0.0 | >=9.3.0", "squizlabs\/php_codesniffer": "^3.5", "yoast\/phpunit-polyfills": "^1.0" }, "suggest": { "ext-ctype": "Provides support for PHP Ctype functions", "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", "moontoast\/math": "Provides support for converting UUID to 128-bit integer (in string form).", "ramsey\/uuid-console": "A console application for generating UUIDs with ramsey\/uuid", "ramsey\/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type.", "paragonie\/random-lib": "Provides RandomLib for use with the RandomLibAdapter" }, "config": { "sort-packages": true, "allow-plugins": { "phpstan\/extension-installer": true, "dealerdirect\/phpcodesniffer-composer-installer": true, "ergebnis\/composer-normalize": true, "captainhook\/plugin-composer": true } }, "replace": { "rhumsaa\/uuid": "self.version" }, "autoload": { "psr-4": { "_ContaoManager\\Ramsey\\Uuid\\": "src\/" }, "files": [ "src\/functions.php" ] }, "autoload-dev": { "psr-4": { "_ContaoManager\\Ramsey\\Uuid\\Test\\": "tests\/" } }, "scripts": { "lint": "parallel-lint src tests", "phpcs": "phpcs src tests --standard=psr2 -sp --colors", "phpunit": "phpunit --verbose --colors=always", "phpunit-coverage": "phpunit --verbose --colors=always --coverage-html build\/coverage", "test": [ "@lint", "@phpcs", "@phpunit" ] }, "support": { "issues": "https:\/\/github.com\/ramsey\/uuid\/issues", "rss": "https:\/\/github.com\/ramsey\/uuid\/releases.atom", "source": "https:\/\/github.com\/ramsey\/uuid", "wiki": "https:\/\/github.com\/ramsey\/uuid\/wiki" } } * @license http://opensource.org/licenses/MIT MIT */ namespace _ContaoManager\Ramsey\Uuid; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * Generate a version 1 UUID from a host ID, sequence number, and the current time. * * @param int|string|null $node A 48-bit number representing the hardware address * This number may be represented as an integer or a hexadecimal string. * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes. * @return string * @throws UnsatisfiedDependencyException if called on a 32-bit system and * `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception if it was not possible to gather sufficient entropy */ function v1($node = null, $clockSeq = null) { return Uuid::uuid1($node, $clockSeq)->toString(); } /** * Generate a version 3 UUID based on the MD5 hash of a namespace identifier * (which is a UUID) and a name (which is a string). * * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID * @param string $name The name to create a UUID for * @return string * @throws InvalidUuidStringException */ function v3($ns, $name) { return Uuid::uuid3($ns, $name)->toString(); } /** * Generate a version 4 (random) UUID. * * @return string * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception */ function v4() { return Uuid::uuid4()->toString(); } /** * Generate a version 5 UUID based on the SHA-1 hash of a namespace * identifier (which is a UUID) and a name (which is a string). * * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID * @param string $name The name to create a UUID for * @return string * @throws InvalidUuidStringException */ function v5($ns, $name) { return Uuid::uuid5($ns, $name)->toString(); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter\Time; use _ContaoManager\Ramsey\Uuid\Converter\TimeConverterInterface; /** * PhpTimeConverter uses built-in PHP functions and standard math operations * available to the PHP programming language to provide facilities for * converting parts of time into representations that may be used in UUIDs */ class PhpTimeConverter implements TimeConverterInterface { /** * Uses the provided seconds and micro-seconds to calculate the time_low, * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs * * @param string $seconds * @param string $microSeconds * @return string[] An array containing `low`, `mid`, and `high` keys * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 */ public function calculateTime($seconds, $microSeconds) { // 0x01b21dd213814000 is the number of 100-ns intervals between the // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. $uuidTime = $seconds * 10000000 + $microSeconds * 10 + 0x1b21dd213814000; return ['low' => \sprintf('%08x', $uuidTime & 0xffffffff), 'mid' => \sprintf('%04x', $uuidTime >> 32 & 0xffff), 'hi' => \sprintf('%04x', $uuidTime >> 48 & 0xfff)]; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter\Time; use _ContaoManager\Ramsey\Uuid\Converter\TimeConverterInterface; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * DegradedTimeConverter throws `UnsatisfiedDependencyException` exceptions * if attempting to use time conversion functionality in an environment that * does not support large integers (i.e. when moontoast/math is not available) */ class DegradedTimeConverter implements TimeConverterInterface { /** * Throws an `UnsatisfiedDependencyException` * * @param string $seconds * @param string $microSeconds * @return void * @throws UnsatisfiedDependencyException if called on a 32-bit system and `Moontoast\Math\BigNumber` is not present */ public function calculateTime($seconds, $microSeconds) { throw new UnsatisfiedDependencyException('When calling ' . __METHOD__ . ' on a 32-bit system, ' . 'Moontoast\\Math\\BigNumber must be present.'); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter\Time; use _ContaoManager\Moontoast\Math\BigNumber; use _ContaoManager\Ramsey\Uuid\Converter\TimeConverterInterface; /** * BigNumberTimeConverter uses the moontoast/math library's `BigNumber` to * provide facilities for converting parts of time into representations that may * be used in UUIDs */ class BigNumberTimeConverter implements TimeConverterInterface { /** * Uses the provided seconds and micro-seconds to calculate the time_low, * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs * * @param string $seconds * @param string $microSeconds * @return string[] An array containing `low`, `mid`, and `high` keys * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 */ public function calculateTime($seconds, $microSeconds) { $uuidTime = new BigNumber('0'); $sec = new BigNumber($seconds); $sec->multiply('10000000'); $usec = new BigNumber($microSeconds); $usec->multiply('10'); $uuidTime->add($sec)->add($usec)->add('122192928000000000'); $uuidTimeHex = \sprintf('%016s', $uuidTime->convertToBase(16)); return ['low' => \substr($uuidTimeHex, 8), 'mid' => \substr($uuidTimeHex, 4, 4), 'hi' => \substr($uuidTimeHex, 0, 4)]; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter\Number; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; /** * DegradedNumberConverter throws `UnsatisfiedDependencyException` exceptions * if attempting to use number conversion functionality in an environment that * does not support large integers (i.e. when moontoast/math is not available) */ class DegradedNumberConverter implements NumberConverterInterface { /** * Throws an `UnsatisfiedDependencyException` * * @param string $hex The hexadecimal string representation to convert * @return void * @throws UnsatisfiedDependencyException */ public function fromHex($hex) { throw new UnsatisfiedDependencyException('Cannot call ' . __METHOD__ . ' without support for large ' . 'integers, since integer is an unsigned ' . '128-bit integer; Moontoast\\Math\\BigNumber is required.'); } /** * Throws an `UnsatisfiedDependencyException` * * @param mixed $integer An integer representation to convert * @return void * @throws UnsatisfiedDependencyException */ public function toHex($integer) { throw new UnsatisfiedDependencyException('Cannot call ' . __METHOD__ . ' without support for large ' . 'integers, since integer is an unsigned ' . '128-bit integer; Moontoast\\Math\\BigNumber is required. '); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter\Number; use _ContaoManager\Moontoast\Math\BigNumber; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; /** * BigNumberConverter converts UUIDs from hexadecimal characters into * moontoast/math `BigNumber` representations of integers and vice versa */ class BigNumberConverter implements NumberConverterInterface { /** * Converts a hexadecimal number into a `Moontoast\Math\BigNumber` representation * * @param string $hex The hexadecimal string representation to convert * @return BigNumber */ public function fromHex($hex) { $number = BigNumber::convertToBase10($hex, 16); return new BigNumber($number); } /** * Converts an integer or `Moontoast\Math\BigNumber` integer representation * into a hexadecimal string representation * * @param int|string|BigNumber $integer An integer or `Moontoast\Math\BigNumber` * @return string Hexadecimal string */ public function toHex($integer) { if (!$integer instanceof BigNumber) { $integer = new BigNumber($integer); } return BigNumber::convertFromBase10($integer, 16); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * TimeConverterInterface provides facilities for converting parts of time into * representations that may be used in UUIDs */ interface TimeConverterInterface { /** * Uses the provided seconds and micro-seconds to calculate the time_low, * time_mid, and time_high fields used by RFC 4122 version 1 UUIDs * * @param string $seconds * @param string $microSeconds * @return string[] An array guaranteed to contain `low`, `mid`, and `hi` keys * @throws UnsatisfiedDependencyException if called on a 32-bit system and * `Moontoast\Math\BigNumber` is not present * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 */ public function calculateTime($seconds, $microSeconds); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Converter; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * NumberConverterInterface converts UUIDs from hexadecimal characters into * representations of integers and vice versa */ interface NumberConverterInterface { /** * Converts a hexadecimal number into an integer representation of the number * * The integer representation returned may be an object or a string * representation of the integer, depending on the implementation. * * @param string $hex The hexadecimal string representation to convert * @return mixed * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present */ public function fromHex($hex); /** * Converts an integer representation into a hexadecimal string representation * of the number * * @param mixed $integer An integer representation to convert; this may be * a true integer, a string integer, or a object representation that * this converter can understand * @return string Hexadecimal string * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present */ public function toHex($integer); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Codec; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\UuidInterface; /** * TimestampFirstCombCodec encodes and decodes COMB UUIDs which have the timestamp as the first 48 bits. * To be used with MySQL, PostgreSQL, Oracle. */ class TimestampFirstCombCodec extends StringCodec { /** * Encodes a UuidInterface as a string representation of a timestamp first COMB UUID * * @param UuidInterface $uuid * * @return string Hexadecimal string representation of a GUID */ public function encode(UuidInterface $uuid) { $sixPieceComponents = \array_values($uuid->getFieldsHex()); $this->swapTimestampAndRandomBits($sixPieceComponents); return \vsprintf('%08s-%04s-%04s-%02s%02s-%012s', $sixPieceComponents); } /** * Encodes a UuidInterface as a binary representation of timestamp first COMB UUID * * @param UuidInterface $uuid * * @return string Binary string representation of timestamp first COMB UUID */ public function encodeBinary(UuidInterface $uuid) { $stringEncoding = $this->encode($uuid); return \hex2bin(\str_replace('-', '', $stringEncoding)); } /** * Decodes a string representation of timestamp first COMB UUID into a UuidInterface object instance * * @param string $encodedUuid * * @return UuidInterface * @throws InvalidUuidStringException */ public function decode($encodedUuid) { $fivePieceComponents = $this->extractComponents($encodedUuid); $this->swapTimestampAndRandomBits($fivePieceComponents); return $this->getBuilder()->build($this, $this->getFields($fivePieceComponents)); } /** * Decodes a binary representation of timestamp first COMB UUID into a UuidInterface object instance * * @param string $bytes * * @return UuidInterface * @throws InvalidUuidStringException */ public function decodeBytes($bytes) { return $this->decode(\bin2hex($bytes)); } /** * Swaps the first 48 bits with the last 48 bits * * @param array $components An array of UUID components (the UUID exploded on its dashes) * * @return void */ protected function swapTimestampAndRandomBits(array &$components) { $last48Bits = $components[4]; if (\count($components) == 6) { $last48Bits = $components[5]; $components[5] = $components[0] . $components[1]; } else { $components[4] = $components[0] . $components[1]; } $components[0] = \substr($last48Bits, 0, 8); $components[1] = \substr($last48Bits, 8, 4); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Codec; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\UuidInterface; /** * OrderedTimeCodec optimizes the bytes to increment UUIDs when time goes by, to improve database INSERTs. * The string value will be unchanged from StringCodec. Only works for UUID type 1. */ class OrderedTimeCodec extends StringCodec { /** * Encodes a UuidInterface as an optimized binary representation of a UUID * * @param UuidInterface $uuid * @return string Binary string representation of a UUID */ public function encodeBinary(UuidInterface $uuid) { $fields = $uuid->getFieldsHex(); $optimized = [$fields['time_hi_and_version'], $fields['time_mid'], $fields['time_low'], $fields['clock_seq_hi_and_reserved'], $fields['clock_seq_low'], $fields['node']]; return \hex2bin(\implode('', $optimized)); } /** * Decodes an optimized binary representation of a UUID into a UuidInterface object instance * * @param string $bytes * @return UuidInterface * @throws InvalidArgumentException if string has not 16 characters */ public function decodeBytes($bytes) { if (\strlen($bytes) !== 16) { throw new InvalidArgumentException('$bytes string should contain 16 characters.'); } $hex = \unpack('H*', $bytes)[1]; // Rearrange the fields to their original order $hex = \substr($hex, 8, 4) . \substr($hex, 12, 4) . \substr($hex, 4, 4) . \substr($hex, 0, 4) . \substr($hex, 16); return $this->decode($hex); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Codec; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\UuidInterface; /** * CodecInterface represents a UUID coder-decoder */ interface CodecInterface { /** * Encodes a UuidInterface as a string representation of a UUID * * @param UuidInterface $uuid * @return string Hexadecimal string representation of a UUID */ public function encode(UuidInterface $uuid); /** * Encodes a UuidInterface as a binary representation of a UUID * * @param UuidInterface $uuid * @return string Binary string representation of a UUID */ public function encodeBinary(UuidInterface $uuid); /** * Decodes a string representation of a UUID into a UuidInterface object instance * * @param string $encodedUuid * @return UuidInterface * @throws InvalidUuidStringException */ public function decode($encodedUuid); /** * Decodes a binary representation of a UUID into a UuidInterface object instance * * @param string $bytes * @return UuidInterface * @throws InvalidUuidStringException * @throws InvalidArgumentException if string has not 16 characters */ public function decodeBytes($bytes); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Codec; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\UuidInterface; /** * GuidStringCodec encodes and decodes globally unique identifiers (GUID) * * @link https://en.wikipedia.org/wiki/Globally_unique_identifier */ class GuidStringCodec extends StringCodec { /** * Encodes a UuidInterface as a string representation of a GUID * * @param UuidInterface $uuid * @return string Hexadecimal string representation of a GUID */ public function encode(UuidInterface $uuid) { $components = \array_values($uuid->getFieldsHex()); // Swap byte-order on the first three fields $this->swapFields($components); return \vsprintf('%08s-%04s-%04s-%02s%02s-%012s', $components); } /** * Encodes a UuidInterface as a binary representation of a GUID * * @param UuidInterface $uuid * @return string Binary string representation of a GUID */ public function encodeBinary(UuidInterface $uuid) { $components = \array_values($uuid->getFieldsHex()); return \hex2bin(\implode('', $components)); } /** * Decodes a string representation of a GUID into a UuidInterface object instance * * @param string $encodedUuid * @return UuidInterface * @throws InvalidUuidStringException */ public function decode($encodedUuid) { $components = $this->extractComponents($encodedUuid); $this->swapFields($components); return $this->getBuilder()->build($this, $this->getFields($components)); } /** * Decodes a binary representation of a GUID into a UuidInterface object instance * * @param string $bytes * @return UuidInterface * @throws InvalidUuidStringException */ public function decodeBytes($bytes) { // Specifically call parent::decode to preserve correct byte order return parent::decode(\bin2hex($bytes)); } /** * Swaps fields to support GUID byte order * * @param array $components An array of UUID components (the UUID exploded on its dashes) * @return void */ protected function swapFields(array &$components) { $hex = \unpack('H*', \pack('L', \hexdec($components[0]))); $components[0] = $hex[1]; $hex = \unpack('H*', \pack('S', \hexdec($components[1]))); $components[1] = $hex[1]; $hex = \unpack('H*', \pack('S', \hexdec($components[2]))); $components[2] = $hex[1]; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Codec; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Builder\UuidBuilderInterface; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\Uuid; use _ContaoManager\Ramsey\Uuid\UuidInterface; /** * StringCodec encodes and decodes RFC 4122 UUIDs * * @link http://tools.ietf.org/html/rfc4122 */ class StringCodec implements CodecInterface { /** * @var UuidBuilderInterface */ private $builder; /** * Constructs a StringCodec for use encoding and decoding UUIDs * * @param UuidBuilderInterface $builder The UUID builder to use when encoding UUIDs */ public function __construct(UuidBuilderInterface $builder) { $this->builder = $builder; } /** * Encodes a UuidInterface as a string representation of a UUID * * @param UuidInterface $uuid * @return string Hexadecimal string representation of a UUID */ public function encode(UuidInterface $uuid) { $fields = \array_values($uuid->getFieldsHex()); return \vsprintf('%08s-%04s-%04s-%02s%02s-%012s', $fields); } /** * Encodes a UuidInterface as a binary representation of a UUID * * @param UuidInterface $uuid * @return string Binary string representation of a UUID */ public function encodeBinary(UuidInterface $uuid) { return \hex2bin($uuid->getHex()); } /** * Decodes a string representation of a UUID into a UuidInterface object instance * * @param string $encodedUuid * @return UuidInterface * @throws InvalidUuidStringException */ public function decode($encodedUuid) { $components = $this->extractComponents($encodedUuid); $fields = $this->getFields($components); return $this->builder->build($this, $fields); } /** * Decodes a binary representation of a UUID into a UuidInterface object instance * * @param string $bytes * @return UuidInterface * @throws InvalidArgumentException if string has not 16 characters */ public function decodeBytes($bytes) { if (\strlen($bytes) !== 16) { throw new InvalidArgumentException('$bytes string should contain 16 characters.'); } $hexUuid = \unpack('H*', $bytes); return $this->decode($hexUuid[1]); } /** * Returns the UUID builder * * @return UuidBuilderInterface */ protected function getBuilder() { return $this->builder; } /** * Returns an array of UUID components (the UUID exploded on its dashes) * * @param string $encodedUuid * @return array * @throws InvalidUuidStringException */ protected function extractComponents($encodedUuid) { $nameParsed = \str_replace(['urn:', 'uuid:', '{', '}', '-'], '', $encodedUuid); // We have stripped out the dashes and are breaking up the string using // substr(). In this way, we can accept a full hex value that doesn't // contain dashes. $components = [\substr($nameParsed, 0, 8), \substr($nameParsed, 8, 4), \substr($nameParsed, 12, 4), \substr($nameParsed, 16, 4), \substr($nameParsed, 20)]; $nameParsed = \implode('-', $components); if (!Uuid::isValid($nameParsed)) { throw new InvalidUuidStringException('Invalid UUID string: ' . $encodedUuid); } return $components; } /** * Returns the fields that make up this UUID * * @see \Ramsey\Uuid\UuidInterface::getFieldsHex() * @param array $components * @return array */ protected function getFields(array $components) { return ['time_low' => \str_pad($components[0], 8, '0', \STR_PAD_LEFT), 'time_mid' => \str_pad($components[1], 4, '0', \STR_PAD_LEFT), 'time_hi_and_version' => \str_pad($components[2], 4, '0', \STR_PAD_LEFT), 'clock_seq_hi_and_reserved' => \str_pad(\substr($components[3], 0, 2), 2, '0', \STR_PAD_LEFT), 'clock_seq_low' => \str_pad(\substr($components[3], 2), 2, '0', \STR_PAD_LEFT), 'node' => \str_pad($components[4], 12, '0', \STR_PAD_LEFT)]; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Codec; /** * TimestampLastCombCodec encodes and decodes COMB UUIDs which have the timestamp as the last 48 bits. * To be used with MSSQL. */ class TimestampLastCombCodec extends StringCodec { } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid; use DateTime; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\Codec\CodecInterface; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use _ContaoManager\Ramsey\Uuid\Exception\UnsupportedOperationException; use ReturnTypeWillChange; /** * Represents a universally unique identifier (UUID), according to RFC 4122. * * This class provides immutable UUID objects (the Uuid class) and the static * methods `uuid1()`, `uuid3()`, `uuid4()`, and `uuid5()` for generating version * 1, 3, 4, and 5 UUIDs as specified in RFC 4122. * * If all you want is a unique ID, you should probably call `uuid1()` or `uuid4()`. * Note that `uuid1()` may compromise privacy since it creates a UUID containing * the computer’s network address. `uuid4()` creates a random UUID. * * @link http://tools.ietf.org/html/rfc4122 * @link http://en.wikipedia.org/wiki/Universally_unique_identifier * @link http://docs.python.org/3/library/uuid.html * @link http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html */ class Uuid implements UuidInterface { /** * When this namespace is specified, the name string is a fully-qualified domain name. * @link http://tools.ietf.org/html/rfc4122#appendix-C */ const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is a URL. * @link http://tools.ietf.org/html/rfc4122#appendix-C */ const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is an ISO OID. * @link http://tools.ietf.org/html/rfc4122#appendix-C */ const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is an X.500 DN in DER or a text output format. * @link http://tools.ietf.org/html/rfc4122#appendix-C */ const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; /** * The nil UUID is special form of UUID that is specified to have all 128 bits set to zero. * @link http://tools.ietf.org/html/rfc4122#section-4.1.7 */ const NIL = '00000000-0000-0000-0000-000000000000'; /** * Reserved for NCS compatibility. * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 */ const RESERVED_NCS = 0; /** * Specifies the UUID layout given in RFC 4122. * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 */ const RFC_4122 = 2; /** * Reserved for Microsoft compatibility. * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 */ const RESERVED_MICROSOFT = 6; /** * Reserved for future definition. * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 */ const RESERVED_FUTURE = 7; /** * Regular expression pattern for matching a valid UUID of any variant. */ const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; /** * Version 1 (time-based) UUID object constant identifier */ const UUID_TYPE_TIME = 1; /** * Version 2 (identifier-based) UUID object constant identifier */ const UUID_TYPE_IDENTIFIER = 2; /** * Version 3 (name-based and hashed with MD5) UUID object constant identifier */ const UUID_TYPE_HASH_MD5 = 3; /** * Version 4 (random) UUID object constant identifier */ const UUID_TYPE_RANDOM = 4; /** * Version 5 (name-based and hashed with SHA1) UUID object constant identifier */ const UUID_TYPE_HASH_SHA1 = 5; /** * The factory to use when creating UUIDs. * @var UuidFactoryInterface */ private static $factory = null; /** * The codec to use when encoding or decoding UUID strings. * @var CodecInterface */ protected $codec; /** * The fields that make up this UUID. * * This is initialized to the nil value. * * @var array * @see UuidInterface::getFieldsHex() */ protected $fields = ['time_low' => '00000000', 'time_mid' => '0000', 'time_hi_and_version' => '0000', 'clock_seq_hi_and_reserved' => '00', 'clock_seq_low' => '00', 'node' => '000000000000']; /** * The number converter to use for converting hex values to/from integers. * @var NumberConverterInterface */ protected $converter; /** * Creates a universally unique identifier (UUID) from an array of fields. * * Unless you're making advanced use of this library to generate identifiers * that deviate from RFC 4122, you probably do not want to instantiate a * UUID directly. Use the static methods, instead: * * ``` * use Ramsey\Uuid\Uuid; * * $timeBasedUuid = Uuid::uuid1(); * $namespaceMd5Uuid = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/'); * $randomUuid = Uuid::uuid4(); * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/'); * ``` * * @param array $fields An array of fields from which to construct a UUID; * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. * @param NumberConverterInterface $converter The number converter to use * for converting hex values to/from integers. * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings. */ public function __construct(array $fields, NumberConverterInterface $converter, CodecInterface $codec) { $this->fields = $fields; $this->codec = $codec; $this->converter = $converter; } /** * Converts this UUID object to a string when the object is used in any * string context. * * @return string * @link http://www.php.net/manual/en/language.oop5.magic.php#object.tostring */ public function __toString() { return $this->toString(); } /** * Converts this UUID object to a string when the object is serialized * with `json_encode()` * * @return string * @link http://php.net/manual/en/class.jsonserializable.php */ #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->toString(); } /** * Converts this UUID object to a string when the object is serialized * with `serialize()` * * @return string * @link http://php.net/manual/en/class.serializable.php */ #[\ReturnTypeWillChange] public function serialize() { return $this->toString(); } /** * @return array{string: string} */ #[\ReturnTypeWillChange] public function __serialize() { return ['string' => $this->toString()]; } /** * Re-constructs the object from its serialized form. * * @param string $serialized * @link http://php.net/manual/en/class.serializable.php * @throws InvalidUuidStringException */ #[\ReturnTypeWillChange] public function unserialize($serialized) { $uuid = self::fromString($serialized); $this->codec = $uuid->codec; $this->converter = $uuid->converter; $this->fields = $uuid->fields; } /** * @param array{string: string} $serialized * @return void * @throws InvalidUuidStringException */ #[\ReturnTypeWillChange] public function __unserialize(array $serialized) { // @codeCoverageIgnoreStart if (!isset($serialized['string'])) { throw new InvalidUuidStringException(); } // @codeCoverageIgnoreEnd $this->unserialize($serialized['string']); } public function compareTo(UuidInterface $other) { if ($this->getMostSignificantBitsHex() < $other->getMostSignificantBitsHex()) { return -1; } if ($this->getMostSignificantBitsHex() > $other->getMostSignificantBitsHex()) { return 1; } if ($this->getLeastSignificantBitsHex() < $other->getLeastSignificantBitsHex()) { return -1; } if ($this->getLeastSignificantBitsHex() > $other->getLeastSignificantBitsHex()) { return 1; } return 0; } public function equals($other) { if (!$other instanceof UuidInterface) { return \false; } return $this->compareTo($other) == 0; } public function getBytes() { return $this->codec->encodeBinary($this); } /** * Returns the high field of the clock sequence multiplexed with the variant * (bits 65-72 of the UUID). * * @return int Unsigned 8-bit integer value of clock_seq_hi_and_reserved */ public function getClockSeqHiAndReserved() { return \hexdec($this->getClockSeqHiAndReservedHex()); } public function getClockSeqHiAndReservedHex() { return $this->fields['clock_seq_hi_and_reserved']; } /** * Returns the low field of the clock sequence (bits 73-80 of the UUID). * * @return int Unsigned 8-bit integer value of clock_seq_low */ public function getClockSeqLow() { return \hexdec($this->getClockSeqLowHex()); } public function getClockSeqLowHex() { return $this->fields['clock_seq_low']; } /** * Returns the clock sequence value associated with this UUID. * * For UUID version 1, the clock sequence is used to help avoid * duplicates that could arise when the clock is set backwards in time * or if the node ID changes. * * For UUID version 3 or 5, the clock sequence is a 14-bit value * constructed from a name as described in RFC 4122, Section 4.3. * * For UUID version 4, clock sequence is a randomly or pseudo-randomly * generated 14-bit value as described in RFC 4122, Section 4.4. * * @return int Unsigned 14-bit integer value of clock sequence * @link http://tools.ietf.org/html/rfc4122#section-4.1.5 */ public function getClockSequence() { return ($this->getClockSeqHiAndReserved() & 0x3f) << 8 | $this->getClockSeqLow(); } public function getClockSequenceHex() { return \sprintf('%04x', $this->getClockSequence()); } public function getNumberConverter() { return $this->converter; } /** * @inheritdoc */ public function getDateTime() { if ($this->getVersion() != 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } $unixTimeNanoseconds = $this->getTimestamp() - 0x1b21dd213814000; $unixTime = ($unixTimeNanoseconds - $unixTimeNanoseconds % 10000000.0) / 10000000.0; return new DateTime("@{$unixTime}"); } /** * Returns an array of the fields of this UUID, with keys named according * to the RFC 4122 names for the fields. * * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer * * **time_hi_and_version**: The high field of the timestamp multiplexed with * the version number, an unsigned 16-bit integer * * **clock_seq_hi_and_reserved**: The high field of the clock sequence * multiplexed with the variant, an unsigned 8-bit integer * * **clock_seq_low**: The low field of the clock sequence, an unsigned * 8-bit integer * * **node**: The spatially unique node identifier, an unsigned 48-bit * integer * * @return array The UUID fields represented as integer values * @link http://tools.ietf.org/html/rfc4122#section-4.1.2 */ public function getFields() { return ['time_low' => $this->getTimeLow(), 'time_mid' => $this->getTimeMid(), 'time_hi_and_version' => $this->getTimeHiAndVersion(), 'clock_seq_hi_and_reserved' => $this->getClockSeqHiAndReserved(), 'clock_seq_low' => $this->getClockSeqLow(), 'node' => $this->getNode()]; } public function getFieldsHex() { return $this->fields; } public function getHex() { return \str_replace('-', '', $this->toString()); } /** * @inheritdoc */ public function getInteger() { return $this->converter->fromHex($this->getHex()); } /** * Returns the least significant 64 bits of this UUID's 128 bit value. * * @return mixed Converted representation of the unsigned 64-bit integer value * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present */ public function getLeastSignificantBits() { return $this->converter->fromHex($this->getLeastSignificantBitsHex()); } public function getLeastSignificantBitsHex() { return \sprintf('%02s%02s%012s', $this->fields['clock_seq_hi_and_reserved'], $this->fields['clock_seq_low'], $this->fields['node']); } /** * Returns the most significant 64 bits of this UUID's 128 bit value. * * @return mixed Converted representation of the unsigned 64-bit integer value * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present */ public function getMostSignificantBits() { return $this->converter->fromHex($this->getMostSignificantBitsHex()); } public function getMostSignificantBitsHex() { return \sprintf('%08s%04s%04s', $this->fields['time_low'], $this->fields['time_mid'], $this->fields['time_hi_and_version']); } /** * Returns the node value associated with this UUID * * For UUID version 1, the node field consists of an IEEE 802 MAC * address, usually the host address. For systems with multiple IEEE * 802 addresses, any available one can be used. The lowest addressed * octet (octet number 10) contains the global/local bit and the * unicast/multicast bit, and is the first octet of the address * transmitted on an 802.3 LAN. * * For systems with no IEEE address, a randomly or pseudo-randomly * generated value may be used; see RFC 4122, Section 4.5. The * multicast bit must be set in such addresses, in order that they * will never conflict with addresses obtained from network cards. * * For UUID version 3 or 5, the node field is a 48-bit value constructed * from a name as described in RFC 4122, Section 4.3. * * For UUID version 4, the node field is a randomly or pseudo-randomly * generated 48-bit value as described in RFC 4122, Section 4.4. * * @return int Unsigned 48-bit integer value of node * @link http://tools.ietf.org/html/rfc4122#section-4.1.6 */ public function getNode() { return \hexdec($this->getNodeHex()); } public function getNodeHex() { return $this->fields['node']; } /** * Returns the high field of the timestamp multiplexed with the version * number (bits 49-64 of the UUID). * * @return int Unsigned 16-bit integer value of time_hi_and_version */ public function getTimeHiAndVersion() { return \hexdec($this->getTimeHiAndVersionHex()); } public function getTimeHiAndVersionHex() { return $this->fields['time_hi_and_version']; } /** * Returns the low field of the timestamp (the first 32 bits of the UUID). * * @return int Unsigned 32-bit integer value of time_low */ public function getTimeLow() { return \hexdec($this->getTimeLowHex()); } public function getTimeLowHex() { return $this->fields['time_low']; } /** * Returns the middle field of the timestamp (bits 33-48 of the UUID). * * @return int Unsigned 16-bit integer value of time_mid */ public function getTimeMid() { return \hexdec($this->getTimeMidHex()); } public function getTimeMidHex() { return $this->fields['time_mid']; } /** * Returns the timestamp value associated with this UUID. * * The 60 bit timestamp value is constructed from the time_low, * time_mid, and time_hi fields of this UUID. The resulting * timestamp is measured in 100-nanosecond units since midnight, * October 15, 1582 UTC. * * The timestamp value is only meaningful in a time-based UUID, which * has version type 1. If this UUID is not a time-based UUID then * this method throws UnsupportedOperationException. * * @return int Unsigned 60-bit integer value of the timestamp * @throws UnsupportedOperationException If this UUID is not a version 1 UUID * @link http://tools.ietf.org/html/rfc4122#section-4.1.4 */ public function getTimestamp() { if ($this->getVersion() != 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return \hexdec($this->getTimestampHex()); } /** * @inheritdoc */ public function getTimestampHex() { if ($this->getVersion() != 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return \sprintf('%03x%04s%08s', $this->getTimeHiAndVersion() & 0xfff, $this->fields['time_mid'], $this->fields['time_low']); } public function getUrn() { return 'urn:uuid:' . $this->toString(); } public function getVariant() { $clockSeq = $this->getClockSeqHiAndReserved(); if (0 === ($clockSeq & 0x80)) { return self::RESERVED_NCS; } if (0 === ($clockSeq & 0x40)) { return self::RFC_4122; } if (0 === ($clockSeq & 0x20)) { return self::RESERVED_MICROSOFT; } return self::RESERVED_FUTURE; } public function getVersion() { if ($this->getVariant() == self::RFC_4122) { return (int) ($this->getTimeHiAndVersion() >> 12 & 0xf); } return null; } public function toString() { return $this->codec->encode($this); } /** * Returns the currently set factory used to create UUIDs. * * @return UuidFactoryInterface */ public static function getFactory() { if (!self::$factory) { self::$factory = new UuidFactory(); } return self::$factory; } /** * Sets the factory used to create UUIDs. * * @param UuidFactoryInterface $factory */ public static function setFactory(UuidFactoryInterface $factory) { self::$factory = $factory; } /** * Creates a UUID from a byte string. * * @param string $bytes * @return UuidInterface * @throws InvalidUuidStringException * @throws InvalidArgumentException */ public static function fromBytes($bytes) { return self::getFactory()->fromBytes($bytes); } /** * Creates a UUID from the string standard representation. * * @param string $name A string that specifies a UUID * @return UuidInterface * @throws InvalidUuidStringException */ public static function fromString($name) { return self::getFactory()->fromString($name); } /** * Creates a UUID from a 128-bit integer string. * * @param string $integer String representation of 128-bit integer * @return UuidInterface * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidUuidStringException */ public static function fromInteger($integer) { return self::getFactory()->fromInteger($integer); } /** * Check if a string is a valid UUID. * * @param string $uuid The string UUID to test * @return boolean */ public static function isValid($uuid) { $uuid = \str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); if ($uuid == self::NIL) { return \true; } if (!\preg_match('/' . self::VALID_PATTERN . '/D', $uuid)) { return \false; } return \true; } /** * Generate a version 1 UUID from a host ID, sequence number, and the current time. * * @param int|string $node A 48-bit number representing the hardware address * This number may be represented as an integer or a hexadecimal string. * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes. * @return UuidInterface * @throws UnsatisfiedDependencyException if called on a 32-bit system and * `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception if it was not possible to gather sufficient entropy */ public static function uuid1($node = null, $clockSeq = null) { return self::getFactory()->uuid1($node, $clockSeq); } /** * Generate a version 3 UUID based on the MD5 hash of a namespace identifier * (which is a UUID) and a name (which is a string). * * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID * @param string $name The name to create a UUID for * @return UuidInterface * @throws InvalidUuidStringException */ public static function uuid3($ns, $name) { return self::getFactory()->uuid3($ns, $name); } /** * Generate a version 4 (random) UUID. * * @return UuidInterface * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception */ public static function uuid4() { return self::getFactory()->uuid4(); } /** * Generate a version 5 UUID based on the SHA-1 hash of a namespace * identifier (which is a UUID) and a name (which is a string). * * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID * @param string $name The name to create a UUID for * @return UuidInterface * @throws InvalidUuidStringException */ public static function uuid5($ns, $name) { return self::getFactory()->uuid5($ns, $name); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider; use Exception; /** * NodeProviderInterface provides functionality to get the node ID (or host ID * in the form of the system's MAC address) from a specific type of node provider */ interface NodeProviderInterface { /** * Returns the system node ID * * @return string System node ID as a hexadecimal string * @throws Exception if it was not possible to gather sufficient entropy */ public function getNode(); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider\Time; use _ContaoManager\Ramsey\Uuid\Provider\TimeProviderInterface; /** * SystemTimeProvider uses built-in PHP functions to provide the time */ class SystemTimeProvider implements TimeProviderInterface { /** * Returns a timestamp array * * @return int[] Array containing `sec` and `usec` components of a timestamp */ public function currentTime() { return \gettimeofday(); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider\Time; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Provider\TimeProviderInterface; /** * FixedTimeProvider uses an previously-generated timestamp to provide the time * * This provider allows the use of a previously-generated timestamp, such as one * stored in a database, when creating version 1 UUIDs. */ class FixedTimeProvider implements TimeProviderInterface { /** * @var int[] Array containing `sec` and `usec` components of a timestamp */ private $fixedTime; /** * Constructs a `FixedTimeProvider` using the provided `$timestamp` * * @param int[] Array containing `sec` and `usec` components of a timestamp * @throws InvalidArgumentException if the `$timestamp` does not contain `sec` or `usec` components */ public function __construct(array $timestamp) { if (!\array_key_exists('sec', $timestamp) || !\array_key_exists('usec', $timestamp)) { throw new InvalidArgumentException('Array must contain sec and usec keys.'); } $this->fixedTime = $timestamp; } /** * Sets the `usec` component of the timestamp * * @param int $value The `usec` value to set */ public function setUsec($value) { $this->fixedTime['usec'] = $value; } /** * Sets the `sec` component of the timestamp * * @param int $value The `sec` value to set */ public function setSec($value) { $this->fixedTime['sec'] = $value; } /** * Returns a timestamp array * * @return int[] Array containing `sec` and `usec` components of a timestamp */ public function currentTime() { return $this->fixedTime; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider; /** * TimeProviderInterface provides functionality to get the time from a specific * type of time provider */ interface TimeProviderInterface { /** * Returns a timestamp array * * @return int[] Array guaranteed to contain `sec` and `usec` components of a timestamp */ public function currentTime(); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider\Node; use Exception; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; /** * RandomNodeProvider provides functionality to generate a random node ID, in * the event that the node ID could not be obtained from the host system * * @link http://tools.ietf.org/html/rfc4122#section-4.5 */ class RandomNodeProvider implements NodeProviderInterface { /** * Returns the system node ID * * @return string System node ID as a hexadecimal string * @throws Exception if it was not possible to gather sufficient entropy */ public function getNode() { $nodeBytes = \random_bytes(6); // Split the node bytes for math on 32-bit systems. $nodeMsb = \substr($nodeBytes, 0, 3); $nodeLsb = \substr($nodeBytes, 3); // Set the multicast bit; see RFC 4122, section 4.5. $nodeMsb = \hex2bin(\str_pad(\dechex(\hexdec(\bin2hex($nodeMsb)) | 0x10000), 6, '0', \STR_PAD_LEFT)); // Recombine the node bytes. $node = $nodeMsb . $nodeLsb; return \str_pad(\bin2hex($node), 12, '0', \STR_PAD_LEFT); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider\Node; use Exception; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; /** * FallbackNodeProvider attempts to gain the system host ID from an array of * providers, falling back to the next in line in the event a host ID can not be * obtained */ class FallbackNodeProvider implements NodeProviderInterface { /** * @var NodeProviderInterface[] */ private $nodeProviders; /** * Constructs a `FallbackNodeProvider` using an array of node providers * * @param NodeProviderInterface[] $providers Array of node providers */ public function __construct(array $providers) { $this->nodeProviders = $providers; } /** * Returns the system node ID by iterating over an array of node providers * and returning the first non-empty value found * * @return string System node ID as a hexadecimal string * @throws Exception */ public function getNode() { foreach ($this->nodeProviders as $provider) { if ($node = $provider->getNode()) { return $node; } } return null; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Provider\Node; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; /** * SystemNodeProvider provides functionality to get the system node ID (MAC * address) using external system calls */ class SystemNodeProvider implements NodeProviderInterface { /** * Returns the system node ID * * @return string|false System node ID as a hexadecimal string, or false if it is not found */ public function getNode() { static $node = null; if ($node !== null) { return $node; } $pattern = '/[^:]([0-9A-Fa-f]{2}([:-])[0-9A-Fa-f]{2}(\\2[0-9A-Fa-f]{2}){4})[^:]/'; $matches = []; // first try a linux specific way $node = $this->getSysfs(); // Search the ifconfig output for all MAC addresses and return // the first one found if ($node === \false) { if (\preg_match_all($pattern, $this->getIfconfig(), $matches, \PREG_PATTERN_ORDER)) { $node = $matches[1][0]; } } if ($node !== \false) { $node = \str_replace([':', '-'], '', $node); } return $node; } /** * Returns the network interface configuration for the system * * @codeCoverageIgnore * @return string */ protected function getIfconfig() { if (\strpos(\strtolower(\ini_get('disable_functions')), 'passthru') !== \false) { return ''; } \ob_start(); switch (\strtoupper(\substr(\constant('PHP_OS'), 0, 3))) { case 'WIN': \passthru('ipconfig /all 2>&1'); break; case 'DAR': \passthru('ifconfig 2>&1'); break; case 'FRE': \passthru('netstat -i -f link 2>&1'); break; case 'LIN': default: \passthru('netstat -ie 2>&1'); break; } return \ob_get_clean(); } /** * Returns mac address from the first system interface via the sysfs interface * * @return string|bool */ protected function getSysfs() { $mac = \false; if (\strtoupper(\constant('PHP_OS')) === 'LINUX') { $addressPaths = \glob('/sys/class/net/*/address', \GLOB_NOSORT); if (empty($addressPaths)) { return \false; } $macs = []; \array_walk($addressPaths, function ($addressPath) use(&$macs) { if (\is_readable($addressPath)) { $macs[] = \file_get_contents($addressPath); } }); $macs = \array_map('trim', $macs); // remove invalid entries $macs = \array_filter($macs, function ($mac) { return $mac !== '00:00:00:00:00:00' && \preg_match('/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i', $mac); }); $mac = \reset($macs); } return $mac; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid; use _ContaoManager\Ramsey\Uuid\Converter\TimeConverterInterface; use _ContaoManager\Ramsey\Uuid\Generator\PeclUuidTimeGenerator; use _ContaoManager\Ramsey\Uuid\Provider\Node\FallbackNodeProvider; use _ContaoManager\Ramsey\Uuid\Provider\Node\RandomNodeProvider; use _ContaoManager\Ramsey\Uuid\Provider\Node\SystemNodeProvider; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\Converter\Number\BigNumberConverter; use _ContaoManager\Ramsey\Uuid\Converter\Number\DegradedNumberConverter; use _ContaoManager\Ramsey\Uuid\Converter\Time\BigNumberTimeConverter; use _ContaoManager\Ramsey\Uuid\Converter\Time\DegradedTimeConverter; use _ContaoManager\Ramsey\Uuid\Converter\Time\PhpTimeConverter; use _ContaoManager\Ramsey\Uuid\Provider\Time\SystemTimeProvider; use _ContaoManager\Ramsey\Uuid\Builder\UuidBuilderInterface; use _ContaoManager\Ramsey\Uuid\Builder\DefaultUuidBuilder; use _ContaoManager\Ramsey\Uuid\Codec\CodecInterface; use _ContaoManager\Ramsey\Uuid\Codec\StringCodec; use _ContaoManager\Ramsey\Uuid\Codec\GuidStringCodec; use _ContaoManager\Ramsey\Uuid\Builder\DegradedUuidBuilder; use _ContaoManager\Ramsey\Uuid\Generator\RandomGeneratorFactory; use _ContaoManager\Ramsey\Uuid\Generator\RandomGeneratorInterface; use _ContaoManager\Ramsey\Uuid\Generator\TimeGeneratorFactory; use _ContaoManager\Ramsey\Uuid\Generator\TimeGeneratorInterface; use _ContaoManager\Ramsey\Uuid\Provider\TimeProviderInterface; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; /** * FeatureSet detects and exposes available features in the current environment * (32- or 64-bit, available dependencies, etc.) */ class FeatureSet { /** * @var bool */ private $disableBigNumber = \false; /** * @var bool */ private $disable64Bit = \false; /** * @var bool */ private $ignoreSystemNode = \false; /** * @var bool */ private $enablePecl = \false; /** * @var UuidBuilderInterface */ private $builder; /** * @var CodecInterface */ private $codec; /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var NumberConverterInterface */ private $numberConverter; /** * @var RandomGeneratorInterface */ private $randomGenerator; /** * @var TimeGeneratorInterface */ private $timeGenerator; /** * Constructs a `FeatureSet` for use by a `UuidFactory` to determine or set * features available to the environment * * @param bool $useGuids Whether to build UUIDs using the `GuidStringCodec` * @param bool $force32Bit Whether to force the use of 32-bit functionality * (primarily for testing purposes) * @param bool $forceNoBigNumber Whether to disable the use of moontoast/math * `BigNumber` (primarily for testing purposes) * @param bool $ignoreSystemNode Whether to disable attempts to check for * the system host ID (primarily for testing purposes) * @param bool $enablePecl Whether to enable the use of the `PeclUuidTimeGenerator` * to generate version 1 UUIDs */ public function __construct($useGuids = \false, $force32Bit = \false, $forceNoBigNumber = \false, $ignoreSystemNode = \false, $enablePecl = \false) { $this->disableBigNumber = $forceNoBigNumber; $this->disable64Bit = $force32Bit; $this->ignoreSystemNode = $ignoreSystemNode; $this->enablePecl = $enablePecl; $this->numberConverter = $this->buildNumberConverter(); $this->builder = $this->buildUuidBuilder(); $this->codec = $this->buildCodec($useGuids); $this->nodeProvider = $this->buildNodeProvider(); $this->randomGenerator = $this->buildRandomGenerator(); $this->setTimeProvider(new SystemTimeProvider()); } /** * Returns the builder configured for this environment * * @return UuidBuilderInterface */ public function getBuilder() { return $this->builder; } /** * Returns the UUID UUID coder-decoder configured for this environment * * @return CodecInterface */ public function getCodec() { return $this->codec; } /** * Returns the system node ID provider configured for this environment * * @return NodeProviderInterface */ public function getNodeProvider() { return $this->nodeProvider; } /** * Returns the number converter configured for this environment * * @return NumberConverterInterface */ public function getNumberConverter() { return $this->numberConverter; } /** * Returns the random UUID generator configured for this environment * * @return RandomGeneratorInterface */ public function getRandomGenerator() { return $this->randomGenerator; } /** * Returns the time-based UUID generator configured for this environment * * @return TimeGeneratorInterface */ public function getTimeGenerator() { return $this->timeGenerator; } /** * Sets the time provider for use in this environment * * @param TimeProviderInterface $timeProvider */ public function setTimeProvider(TimeProviderInterface $timeProvider) { $this->timeGenerator = $this->buildTimeGenerator($timeProvider); } /** * Determines which UUID coder-decoder to use and returns the configured * codec for this environment * * @param bool $useGuids Whether to build UUIDs using the `GuidStringCodec` * @return CodecInterface */ protected function buildCodec($useGuids = \false) { if ($useGuids) { return new GuidStringCodec($this->builder); } return new StringCodec($this->builder); } /** * Determines which system node ID provider to use and returns the configured * system node ID provider for this environment * * @return NodeProviderInterface */ protected function buildNodeProvider() { if ($this->ignoreSystemNode) { return new RandomNodeProvider(); } return new FallbackNodeProvider([new SystemNodeProvider(), new RandomNodeProvider()]); } /** * Determines which number converter to use and returns the configured * number converter for this environment * * @return NumberConverterInterface */ protected function buildNumberConverter() { if ($this->hasBigNumber()) { return new BigNumberConverter(); } return new DegradedNumberConverter(); } /** * Determines which random UUID generator to use and returns the configured * random UUID generator for this environment * * @return RandomGeneratorInterface */ protected function buildRandomGenerator() { return (new RandomGeneratorFactory())->getGenerator(); } /** * Determines which time-based UUID generator to use and returns the configured * time-based UUID generator for this environment * * @param TimeProviderInterface $timeProvider * @return TimeGeneratorInterface */ protected function buildTimeGenerator(TimeProviderInterface $timeProvider) { if ($this->enablePecl) { return new PeclUuidTimeGenerator(); } return (new TimeGeneratorFactory($this->nodeProvider, $this->buildTimeConverter(), $timeProvider))->getGenerator(); } /** * Determines which time converter to use and returns the configured * time converter for this environment * * @return TimeConverterInterface */ protected function buildTimeConverter() { if ($this->is64BitSystem()) { return new PhpTimeConverter(); } if ($this->hasBigNumber()) { return new BigNumberTimeConverter(); } return new DegradedTimeConverter(); } /** * Determines which UUID builder to use and returns the configured UUID * builder for this environment * * @return UuidBuilderInterface */ protected function buildUuidBuilder() { if ($this->is64BitSystem()) { return new DefaultUuidBuilder($this->numberConverter); } return new DegradedUuidBuilder($this->numberConverter); } /** * Returns true if the system has `Moontoast\Math\BigNumber` * * @return bool */ protected function hasBigNumber() { return \class_exists('_ContaoManager\\Moontoast\\Math\\BigNumber') && !$this->disableBigNumber; } /** * Returns true if the system is 64-bit, false otherwise * * @return bool */ protected function is64BitSystem() { return \PHP_INT_SIZE == 8 && !$this->disable64Bit; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid; use DateTime; use JsonSerializable; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use _ContaoManager\Ramsey\Uuid\Exception\UnsupportedOperationException; use Serializable; /** * UuidInterface defines common functionality for all universally unique * identifiers (UUIDs) */ interface UuidInterface extends JsonSerializable, Serializable { /** * Compares this UUID to the specified UUID. * * The first of two UUIDs is greater than the second if the most * significant field in which the UUIDs differ is greater for the first * UUID. * * * Q. What's the value of being able to sort UUIDs? * * A. Use them as keys in a B-Tree or similar mapping. * * @param UuidInterface $other UUID to which this UUID is compared * @return int -1, 0 or 1 as this UUID is less than, equal to, or greater than `$uuid` */ public function compareTo(UuidInterface $other); /** * Compares this object to the specified object. * * The result is true if and only if the argument is not null, is a UUID * object, has the same variant, and contains the same value, bit for bit, * as this UUID. * * @param object $other * @return bool True if `$other` is equal to this UUID */ public function equals($other); /** * Returns the UUID as a 16-byte string (containing the six integer fields * in big-endian byte order). * * @return string */ public function getBytes(); /** * Returns the number converter to use for converting hex values to/from integers. * * @return NumberConverterInterface */ public function getNumberConverter(); /** * Returns the hexadecimal value of the UUID. * * @return string */ public function getHex(); /** * Returns an array of the fields of this UUID, with keys named according * to the RFC 4122 names for the fields. * * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer * * **time_hi_and_version**: The high field of the timestamp multiplexed with * the version number, an unsigned 16-bit integer * * **clock_seq_hi_and_reserved**: The high field of the clock sequence * multiplexed with the variant, an unsigned 8-bit integer * * **clock_seq_low**: The low field of the clock sequence, an unsigned * 8-bit integer * * **node**: The spatially unique node identifier, an unsigned 48-bit * integer * * @return array The UUID fields represented as hexadecimal values */ public function getFieldsHex(); /** * Returns the high field of the clock sequence multiplexed with the variant * (bits 65-72 of the UUID). * * @return string Hexadecimal value of clock_seq_hi_and_reserved */ public function getClockSeqHiAndReservedHex(); /** * Returns the low field of the clock sequence (bits 73-80 of the UUID). * * @return string Hexadecimal value of clock_seq_low */ public function getClockSeqLowHex(); /** * Returns the clock sequence value associated with this UUID. * * @return string Hexadecimal value of clock sequence */ public function getClockSequenceHex(); /** * Returns a PHP `DateTime` object representing the timestamp associated * with this UUID. * * The timestamp value is only meaningful in a time-based UUID, which * has version type 1. If this UUID is not a time-based UUID then * this method throws `UnsupportedOperationException`. * * @return DateTime A PHP DateTime representation of the date * @throws UnsupportedOperationException If this UUID is not a version 1 UUID * @throws UnsatisfiedDependencyException if called in a 32-bit system and * `Moontoast\Math\BigNumber` is not present */ public function getDateTime(); /** * Returns the integer value of the UUID, converted to an appropriate number * representation. * * @return mixed Converted representation of the unsigned 128-bit integer value * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present */ public function getInteger(); /** * Returns the least significant 64 bits of this UUID's 128 bit value. * * @return string Hexadecimal value of least significant bits */ public function getLeastSignificantBitsHex(); /** * Returns the most significant 64 bits of this UUID's 128 bit value. * * @return string Hexadecimal value of most significant bits */ public function getMostSignificantBitsHex(); /** * Returns the node value associated with this UUID * * For UUID version 1, the node field consists of an IEEE 802 MAC * address, usually the host address. For systems with multiple IEEE * 802 addresses, any available one can be used. The lowest addressed * octet (octet number 10) contains the global/local bit and the * unicast/multicast bit, and is the first octet of the address * transmitted on an 802.3 LAN. * * For systems with no IEEE address, a randomly or pseudo-randomly * generated value may be used; see RFC 4122, Section 4.5. The * multicast bit must be set in such addresses, in order that they * will never conflict with addresses obtained from network cards. * * For UUID version 3 or 5, the node field is a 48-bit value constructed * from a name as described in RFC 4122, Section 4.3. * * For UUID version 4, the node field is a randomly or pseudo-randomly * generated 48-bit value as described in RFC 4122, Section 4.4. * * @return string Hexadecimal value of node * @link http://tools.ietf.org/html/rfc4122#section-4.1.6 */ public function getNodeHex(); /** * Returns the high field of the timestamp multiplexed with the version * number (bits 49-64 of the UUID). * * @return string Hexadecimal value of time_hi_and_version */ public function getTimeHiAndVersionHex(); /** * Returns the low field of the timestamp (the first 32 bits of the UUID). * * @return string Hexadecimal value of time_low */ public function getTimeLowHex(); /** * Returns the middle field of the timestamp (bits 33-48 of the UUID). * * @return string Hexadecimal value of time_mid */ public function getTimeMidHex(); /** * Returns the timestamp value associated with this UUID. * * The 60 bit timestamp value is constructed from the time_low, * time_mid, and time_hi fields of this UUID. The resulting * timestamp is measured in 100-nanosecond units since midnight, * October 15, 1582 UTC. * * The timestamp value is only meaningful in a time-based UUID, which * has version type 1. If this UUID is not a time-based UUID then * this method throws UnsupportedOperationException. * * @return string Hexadecimal value of the timestamp * @throws UnsupportedOperationException If this UUID is not a version 1 UUID * @link http://tools.ietf.org/html/rfc4122#section-4.1.4 */ public function getTimestampHex(); /** * Returns the string representation of the UUID as a URN. * * @return string * @link http://en.wikipedia.org/wiki/Uniform_Resource_Name */ public function getUrn(); /** * Returns the variant number associated with this UUID. * * The variant number describes the layout of the UUID. The variant * number has the following meaning: * * * 0 - Reserved for NCS backward compatibility * * 2 - The RFC 4122 variant (used by this class) * * 6 - Reserved, Microsoft Corporation backward compatibility * * 7 - Reserved for future definition * * @return int * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 */ public function getVariant(); /** * Returns the version number associated with this UUID. * * The version number describes how this UUID was generated and has the * following meaning: * * * 1 - Time-based UUID * * 2 - DCE security UUID * * 3 - Name-based UUID hashed with MD5 * * 4 - Randomly generated UUID * * 5 - Name-based UUID hashed with SHA-1 * * Returns null if this UUID is not an RFC 4122 variant, since version * is only meaningful for this variant. * * @return int|null * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 */ public function getVersion(); /** * Converts this UUID into a string representation. * * @return string */ public function toString(); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * UuidFactoryInterface defines common functionality all `UuidFactory` instances * must implement */ interface UuidFactoryInterface { /** * Generate a version 1 UUID from a host ID, sequence number, and the current time. * * @param int|string|null $node A 48-bit number representing the hardware address * This number may be represented as an integer or a hexadecimal string. * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes. * @return UuidInterface * @throws UnsatisfiedDependencyException if called on a 32-bit system and * `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception if it was not possible to gather sufficient entropy */ public function uuid1($node = null, $clockSeq = null); /** * Generate a version 3 UUID based on the MD5 hash of a namespace identifier * (which is a UUID) and a name (which is a string). * * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID * @param string $name The name to create a UUID for * @return UuidInterface * @throws InvalidUuidStringException */ public function uuid3($ns, $name); /** * Generate a version 4 (random) UUID. * * @return UuidInterface * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception */ public function uuid4(); /** * Generate a version 5 UUID based on the SHA-1 hash of a namespace * identifier (which is a UUID) and a name (which is a string). * * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID * @param string $name The name to create a UUID for * @return UuidInterface * @throws InvalidUuidStringException */ public function uuid5($ns, $name); /** * Creates a UUID from a byte string. * * @param string $bytes A 16-byte string representation of a UUID * @return UuidInterface * @throws InvalidUuidStringException * @throws InvalidArgumentException if string has not 16 characters */ public function fromBytes($bytes); /** * Creates a UUID from the string standard representation * * @param string $uuid A string representation of a UUID * @return UuidInterface * @throws InvalidUuidStringException */ public function fromString($uuid); /** * Creates a `Uuid` from an integer representation * * The integer representation may be a real integer, a string integer, or * an integer representation supported by a configured number converter. * * @param mixed $integer The integer to use when creating a `Uuid` from an * integer; may be of any type understood by the configured number converter * @return UuidInterface * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidUuidStringException */ public function fromInteger($integer); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use Exception; /** * RandomBytesGenerator provides functionality to generate strings of random * binary data using `random_bytes()` function in PHP 7+ or paragonie/random_compat * * @link http://php.net/random_bytes * @link https://github.com/paragonie/random_compat */ class RandomBytesGenerator implements RandomGeneratorInterface { /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string * @throws Exception if it was not possible to gather sufficient entropy */ public function generate($length) { return \random_bytes($length); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; /** * OpenSslRandomGenerator provides functionality to generate strings of random * binary data using the `openssl_random_pseudo_bytes()` PHP function * * The use of this generator requires PHP to be compiled using the * `--with-openssl` option. * * @deprecated The openssl_random_pseudo_bytes() function is not a reliable * source of randomness. The default RandomBytesGenerator, which uses the * random_bytes() function, is recommended as the safest and most reliable * source of randomness. * This generator will be removed in ramsey/uuid 4.0.0. * @link http://php.net/openssl_random_pseudo_bytes */ class OpenSslGenerator implements RandomGeneratorInterface { /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string */ public function generate($length) { return \openssl_random_pseudo_bytes($length); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; /** * A factory for retrieving a random generator, based on the environment */ class RandomGeneratorFactory { /** * Returns a default random generator, based on the current environment * * @return RandomGeneratorInterface */ public static function getGenerator() { return new RandomBytesGenerator(); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use _ContaoManager\Ramsey\Uuid\Converter\TimeConverterInterface; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; use _ContaoManager\Ramsey\Uuid\Provider\TimeProviderInterface; /** * A factory for retrieving a time generator, based on the environment */ class TimeGeneratorFactory { /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var TimeConverterInterface */ private $timeConverter; /** * @var TimeProviderInterface */ private $timeProvider; /** * Constructs a `TimeGeneratorFactory` using a node provider, time converter, * and time provider * * @param NodeProviderInterface $nodeProvider * @param TimeConverterInterface $timeConverter * @param TimeProviderInterface $timeProvider */ public function __construct(NodeProviderInterface $nodeProvider, TimeConverterInterface $timeConverter, TimeProviderInterface $timeProvider) { $this->nodeProvider = $nodeProvider; $this->timeConverter = $timeConverter; $this->timeProvider = $timeProvider; } /** * Returns a default time generator, based on the current environment * * @return TimeGeneratorInterface */ public function getGenerator() { return new DefaultTimeGenerator($this->nodeProvider, $this->timeConverter, $this->timeProvider); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\BinaryUtils; use _ContaoManager\Ramsey\Uuid\Converter\TimeConverterInterface; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; use _ContaoManager\Ramsey\Uuid\Provider\TimeProviderInterface; /** * DefaultTimeGenerator provides functionality to generate strings of binary * data for version 1 UUIDs based on a host ID, sequence number, and the current * time */ class DefaultTimeGenerator implements TimeGeneratorInterface { /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var TimeConverterInterface */ private $timeConverter; /** * @var TimeProviderInterface */ private $timeProvider; /** * Constructs a `DefaultTimeGenerator` using a node provider, time converter, * and time provider * * @param NodeProviderInterface $nodeProvider * @param TimeConverterInterface $timeConverter * @param TimeProviderInterface $timeProvider */ public function __construct(NodeProviderInterface $nodeProvider, TimeConverterInterface $timeConverter, TimeProviderInterface $timeProvider) { $this->nodeProvider = $nodeProvider; $this->timeConverter = $timeConverter; $this->timeProvider = $timeProvider; } /** * Generate a version 1 UUID from a host ID, sequence number, and the current time * * If $node is not given, we will attempt to obtain the local hardware * address. If $clockSeq is given, it is used as the sequence number; * otherwise a random 14-bit sequence number is chosen. * * @param int|string $node A 48-bit number representing the hardware address * This number may be represented as an integer or a hexadecimal string. * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes. * @return string A binary string * @throws UnsatisfiedDependencyException if called on a 32-bit system and * `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception if it was not possible to gather sufficient entropy */ public function generate($node = null, $clockSeq = null) { $node = $this->getValidNode($node); if ($clockSeq === null) { // Not using "stable storage"; see RFC 4122, Section 4.2.1.1 $clockSeq = \random_int(0, 0x3fff); } // Create a 60-bit time value as a count of 100-nanosecond intervals // since 00:00:00.00, 15 October 1582 $timeOfDay = $this->timeProvider->currentTime(); $uuidTime = $this->timeConverter->calculateTime($timeOfDay['sec'], $timeOfDay['usec']); $timeHi = BinaryUtils::applyVersion($uuidTime['hi'], 1); $clockSeqHi = BinaryUtils::applyVariant($clockSeq >> 8); $hex = \vsprintf('%08s%04s%04s%02s%02s%012s', [$uuidTime['low'], $uuidTime['mid'], \sprintf('%04x', $timeHi), \sprintf('%02x', $clockSeqHi), \sprintf('%02x', $clockSeq & 0xff), $node]); return \hex2bin($hex); } /** * Uses the node provider given when constructing this instance to get * the node ID (usually a MAC address) * * @param string|int $node A node value that may be used to override the node provider * @return string Hexadecimal representation of the node ID * @throws InvalidArgumentException * @throws Exception */ protected function getValidNode($node) { if ($node === null) { $node = $this->nodeProvider->getNode(); } // Convert the node to hex, if it is still an integer if (\is_int($node)) { $node = \sprintf('%012x', $node); } if (!\ctype_xdigit($node) || \strlen($node) > 12) { throw new InvalidArgumentException('Invalid node value'); } return \strtolower(\sprintf('%012s', $node)); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use _ContaoManager\RandomLib\Generator; use _ContaoManager\RandomLib\Factory; /** * RandomLibAdapter provides functionality to generate strings of random * binary data using the paragonie/random-lib library * * @link https://packagist.org/packages/paragonie/random-lib */ class RandomLibAdapter implements RandomGeneratorInterface { /** * @var Generator */ private $generator; /** * Constructs a `RandomLibAdapter` using a `RandomLib\Generator` * * By default, if no `Generator` is passed in, this creates a high-strength * generator to use when generating random binary data. * * @param Generator $generator An paragonie/random-lib `Generator` */ public function __construct(Generator $generator = null) { $this->generator = $generator; if ($this->generator === null) { $factory = new Factory(); $this->generator = $factory->getHighStrengthGenerator(); } } /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string */ public function generate($length) { return $this->generator->generate($length); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * TimeGeneratorInterface provides functionality to generate strings of binary * data for version 1 UUIDs based on a host ID, sequence number, and the current * time */ interface TimeGeneratorInterface { /** * Generate a version 1 UUID from a host ID, sequence number, and the current time * * @param int|string $node A 48-bit number representing the hardware address * This number may be represented as an integer or a hexadecimal string. * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes. * @return string A binary string * @throws UnsatisfiedDependencyException if called on a 32-bit system and * `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception if it was not possible to gather sufficient entropy */ public function generate($node = null, $clockSeq = null); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * CombGenerator provides functionality to generate COMB (combined GUID/timestamp) * sequential UUIDs * * @link https://en.wikipedia.org/wiki/Globally_unique_identifier#Sequential_algorithms */ class CombGenerator implements RandomGeneratorInterface { const TIMESTAMP_BYTES = 6; /** * @var RandomGeneratorInterface */ private $randomGenerator; /** * @var NumberConverterInterface */ private $converter; /** * Constructs a `CombGenerator` using a random-number generator and a number converter * * @param RandomGeneratorInterface $generator Random-number generator for the non-time part. * @param NumberConverterInterface $numberConverter Instance of number converter. */ public function __construct(RandomGeneratorInterface $generator, NumberConverterInterface $numberConverter) { $this->converter = $numberConverter; $this->randomGenerator = $generator; } /** * Generates a string of binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException if length is not a positive integer * @throws Exception */ public function generate($length) { if ($length < self::TIMESTAMP_BYTES || $length < 0) { throw new InvalidArgumentException('Length must be a positive integer.'); } $hash = ''; if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) { $hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES); } $lsbTime = \str_pad($this->converter->toHex($this->timestamp()), self::TIMESTAMP_BYTES * 2, '0', \STR_PAD_LEFT); return \hex2bin(\str_pad(\bin2hex($hash), $length - self::TIMESTAMP_BYTES, '0') . $lsbTime); } /** * Returns current timestamp as integer, precise to 0.00001 seconds * * @return string */ private function timestamp() { $time = \explode(' ', \microtime(\false)); return $time[1] . \substr($time[0], 2, 5); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; use Exception; use InvalidArgumentException; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; /** * RandomGeneratorInterface provides functionality to generate strings of random * binary data */ interface RandomGeneratorInterface { /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present * @throws InvalidArgumentException * @throws Exception if it was not possible to gather sufficient entropy */ public function generate($length); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; /** * PeclUuidRandomGenerator provides functionality to generate strings of random * binary data using the PECL UUID PHP extension * * @link https://pecl.php.net/package/uuid */ class PeclUuidRandomGenerator implements RandomGeneratorInterface { /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string */ public function generate($length) { $uuid = \uuid_create(\UUID_TYPE_RANDOM); return \uuid_parse($uuid); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; /** * MtRandRandomGenerator provides functionality to generate strings of random * binary data using the `mt_rand()` PHP function * * @deprecated The mt_rand() function is not a reliable source of randomness. * The default RandomBytesGenerator, which uses the random_bytes() function, * is recommended as the safest and most reliable source of randomness. * This generator will be removed in ramsey/uuid 4.0.0. * @link http://php.net/mt_rand */ class MtRandGenerator implements RandomGeneratorInterface { /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string */ public function generate($length) { $bytes = ''; for ($i = 1; $i <= $length; $i++) { $bytes = \chr(\mt_rand(0, 255)) . $bytes; } return $bytes; } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; /** * SodiumRandomGenerator provides functionality to generate strings of random * binary data using the PECL libsodium extension * * @deprecated As of PHP 7.2.0, the libsodium extension is bundled with PHP, and * the random_bytes() PHP function is now the recommended method for * generating random byes. The default RandomBytesGenerator uses the * random_bytes() function. * This generator will be removed in ramsey/uuid 4.0.0. * @link http://pecl.php.net/package/libsodium * @link https://paragonie.com/book/pecl-libsodium */ class SodiumRandomGenerator implements RandomGeneratorInterface { /** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string */ public function generate($length) { return \Sodium\randombytes_buf($length); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Generator; /** * PeclUuidTimeGenerator provides functionality to generate strings of binary * data for version 1 UUIDs using the PECL UUID PHP extension * * @link https://pecl.php.net/package/uuid */ class PeclUuidTimeGenerator implements TimeGeneratorInterface { /** * Generate a version 1 UUID using the PECL UUID extension * * @param int|string $node Not used in this context * @param int $clockSeq Not used in this context * @return string A binary string */ public function generate($node = null, $clockSeq = null) { $uuid = \uuid_create(\UUID_TYPE_TIME); return \uuid_parse($uuid); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\Exception\InvalidUuidStringException; use _ContaoManager\Ramsey\Uuid\Provider\NodeProviderInterface; use _ContaoManager\Ramsey\Uuid\Generator\RandomGeneratorInterface; use _ContaoManager\Ramsey\Uuid\Generator\TimeGeneratorInterface; use _ContaoManager\Ramsey\Uuid\Codec\CodecInterface; use _ContaoManager\Ramsey\Uuid\Builder\UuidBuilderInterface; class UuidFactory implements UuidFactoryInterface { /** * @var CodecInterface */ private $codec = null; /** * @var NodeProviderInterface */ private $nodeProvider = null; /** * @var NumberConverterInterface */ private $numberConverter = null; /** * @var RandomGeneratorInterface */ private $randomGenerator = null; /** * @var TimeGeneratorInterface */ private $timeGenerator = null; /** * @var UuidBuilderInterface */ private $uuidBuilder = null; /** * Constructs a `UuidFactory` for creating `Ramsey\Uuid\UuidInterface` instances * * @param FeatureSet $features A set of features for use when creating UUIDs */ public function __construct(FeatureSet $features = null) { $features = $features ?: new FeatureSet(); $this->codec = $features->getCodec(); $this->nodeProvider = $features->getNodeProvider(); $this->numberConverter = $features->getNumberConverter(); $this->randomGenerator = $features->getRandomGenerator(); $this->timeGenerator = $features->getTimeGenerator(); $this->uuidBuilder = $features->getBuilder(); } /** * Returns the UUID coder-decoder used by this factory * * @return CodecInterface */ public function getCodec() { return $this->codec; } /** * Sets the UUID coder-decoder used by this factory * * @param CodecInterface $codec */ public function setCodec(CodecInterface $codec) { $this->codec = $codec; } /** * Returns the system node ID provider used by this factory * * @return NodeProviderInterface */ public function getNodeProvider() { return $this->nodeProvider; } /** * Returns the random UUID generator used by this factory * * @return RandomGeneratorInterface */ public function getRandomGenerator() { return $this->randomGenerator; } /** * Returns the time-based UUID generator used by this factory * * @return TimeGeneratorInterface */ public function getTimeGenerator() { return $this->timeGenerator; } /** * Sets the time-based UUID generator this factory will use to generate version 1 UUIDs * * @param TimeGeneratorInterface $generator */ public function setTimeGenerator(TimeGeneratorInterface $generator) { $this->timeGenerator = $generator; } /** * Returns the number converter used by this factory * * @return NumberConverterInterface */ public function getNumberConverter() { return $this->numberConverter; } /** * Sets the random UUID generator this factory will use to generate version 4 UUIDs * * @param RandomGeneratorInterface $generator */ public function setRandomGenerator(RandomGeneratorInterface $generator) { $this->randomGenerator = $generator; } /** * Sets the number converter this factory will use * * @param NumberConverterInterface $converter */ public function setNumberConverter(NumberConverterInterface $converter) { $this->numberConverter = $converter; } /** * Returns the UUID builder this factory uses when creating `Uuid` instances * * @return UuidBuilderInterface $builder */ public function getUuidBuilder() { return $this->uuidBuilder; } /** * Sets the UUID builder this factory will use when creating `Uuid` instances * * @param UuidBuilderInterface $builder */ public function setUuidBuilder(UuidBuilderInterface $builder) { $this->uuidBuilder = $builder; } /** * @inheritdoc */ public function fromBytes($bytes) { return $this->codec->decodeBytes($bytes); } /** * @inheritdoc */ public function fromString($uuid) { $uuid = \strtolower($uuid); return $this->codec->decode($uuid); } /** * @inheritdoc */ public function fromInteger($integer) { $hex = $this->numberConverter->toHex($integer); $hex = \str_pad($hex, 32, '0', \STR_PAD_LEFT); return $this->fromString($hex); } /** * @inheritdoc */ public function uuid1($node = null, $clockSeq = null) { $bytes = $this->timeGenerator->generate($node, $clockSeq); $hex = \bin2hex($bytes); return $this->uuidFromHashedName($hex, 1); } /** * @inheritdoc */ public function uuid3($ns, $name) { return $this->uuidFromNsAndName($ns, $name, 3, 'md5'); } /** * @inheritdoc */ public function uuid4() { $bytes = $this->randomGenerator->generate(16); // When converting the bytes to hex, it turns into a 32-character // hexadecimal string that looks a lot like an MD5 hash, so at this // point, we can just pass it to uuidFromHashedName. $hex = \bin2hex($bytes); return $this->uuidFromHashedName($hex, 4); } /** * @inheritdoc */ public function uuid5($ns, $name) { return $this->uuidFromNsAndName($ns, $name, 5, 'sha1'); } /** * Returns a `Uuid` * * Uses the configured builder and codec and the provided array of hexadecimal * value UUID fields to construct a `Uuid` object. * * @param array $fields An array of fields from which to construct a UUID; * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. * @return UuidInterface */ public function uuid(array $fields) { return $this->uuidBuilder->build($this->codec, $fields); } /** * Returns a version 3 or 5 namespaced `Uuid` * * @param string|UuidInterface $ns The UUID namespace to use * @param string $name The string to hash together with the namespace * @param int $version The version of UUID to create (3 or 5) * @param string $hashFunction The hash function to use when hashing together * the namespace and name * @return UuidInterface * @throws InvalidUuidStringException */ protected function uuidFromNsAndName($ns, $name, $version, $hashFunction) { if (!$ns instanceof UuidInterface) { $ns = $this->codec->decode($ns); } $hash = \call_user_func($hashFunction, $ns->getBytes() . $name); return $this->uuidFromHashedName($hash, $version); } /** * Returns a `Uuid` created from `$hash` with the version field set to `$version` * and the variant field set for RFC 4122 * * @param string $hash The hash to use when creating the UUID * @param int $version The UUID version to set for this hash (1, 3, 4, or 5) * @return UuidInterface */ protected function uuidFromHashedName($hash, $version) { $timeHi = BinaryUtils::applyVersion(\substr($hash, 12, 4), $version); $clockSeqHi = BinaryUtils::applyVariant(\hexdec(\substr($hash, 16, 2))); $fields = ['time_low' => \substr($hash, 0, 8), 'time_mid' => \substr($hash, 8, 4), 'time_hi_and_version' => \str_pad(\dechex($timeHi), 4, '0', \STR_PAD_LEFT), 'clock_seq_hi_and_reserved' => \str_pad(\dechex($clockSeqHi), 2, '0', \STR_PAD_LEFT), 'clock_seq_low' => \substr($hash, 18, 2), 'node' => \substr($hash, 20, 12)]; return $this->uuid($fields); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid; use DateTime; use _ContaoManager\Moontoast\Math\BigNumber; use _ContaoManager\Ramsey\Uuid\Exception\UnsatisfiedDependencyException; use _ContaoManager\Ramsey\Uuid\Exception\UnsupportedOperationException; /** * DegradedUuid represents an RFC 4122 UUID on 32-bit systems * * @see Uuid */ class DegradedUuid extends Uuid { /** * @inheritdoc */ public function getDateTime() { if ($this->getVersion() != 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } $time = $this->converter->fromHex($this->getTimestampHex()); $ts = new BigNumber($time, 20); $ts->subtract('122192928000000000'); $ts->divide('10000000.0'); $ts->floor(); $unixTime = $ts->getValue(); return new DateTime("@{$unixTime}"); } /** * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when * called on a 32-bit system * * @throws UnsatisfiedDependencyException if called on a 32-bit system */ public function getFields() { throw new UnsatisfiedDependencyException('Cannot call ' . __METHOD__ . ' on a 32-bit system, since some ' . 'values overflow the system max integer value' . '; consider calling getFieldsHex instead'); } /** * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when * called on a 32-bit system * * @throws UnsatisfiedDependencyException if called on a 32-bit system */ public function getNode() { throw new UnsatisfiedDependencyException('Cannot call ' . __METHOD__ . ' on a 32-bit system, since node ' . 'is an unsigned 48-bit integer and can overflow the system ' . 'max integer value' . '; consider calling getNodeHex instead'); } /** * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when * called on a 32-bit system * * @throws UnsatisfiedDependencyException if called on a 32-bit system */ public function getTimeLow() { throw new UnsatisfiedDependencyException('Cannot call ' . __METHOD__ . ' on a 32-bit system, since time_low ' . 'is an unsigned 32-bit integer and can overflow the system ' . 'max integer value' . '; consider calling getTimeLowHex instead'); } /** * For degraded UUIDs, throws an `UnsatisfiedDependencyException` when * called on a 32-bit system * * @throws UnsatisfiedDependencyException if called on a 32-bit system * @throws UnsupportedOperationException If this UUID is not a version 1 UUID */ public function getTimestamp() { if ($this->getVersion() != 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } throw new UnsatisfiedDependencyException('Cannot call ' . __METHOD__ . ' on a 32-bit system, since timestamp ' . 'is an unsigned 60-bit integer and can overflow the system ' . 'max integer value' . '; consider calling getTimestampHex instead'); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Builder; use _ContaoManager\Ramsey\Uuid\Codec\CodecInterface; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\DegradedUuid; /** * DegradedUuidBuilder builds instances of DegradedUuid */ class DegradedUuidBuilder implements UuidBuilderInterface { /** * @var NumberConverterInterface */ private $converter; /** * Constructs the DegradedUuidBuilder * * @param NumberConverterInterface $converter The number converter to use when constructing the DegradedUuid */ public function __construct(NumberConverterInterface $converter) { $this->converter = $converter; } /** * Builds a DegradedUuid * * @param CodecInterface $codec The codec to use for building this DegradedUuid * @param array $fields An array of fields from which to construct the DegradedUuid; * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. * @return DegradedUuid */ public function build(CodecInterface $codec, array $fields) { return new DegradedUuid($fields, $this->converter, $codec); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Builder; use _ContaoManager\Ramsey\Uuid\Codec\CodecInterface; use _ContaoManager\Ramsey\Uuid\UuidInterface; /** * UuidBuilderInterface builds instances UuidInterface */ interface UuidBuilderInterface { /** * Builds an instance of a UuidInterface * * @param CodecInterface $codec The codec to use for building this UuidInterface instance * @param array $fields An array of fields from which to construct a UuidInterface instance; * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. * @return UuidInterface */ public function build(CodecInterface $codec, array $fields); } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Builder; use _ContaoManager\Ramsey\Uuid\Codec\CodecInterface; use _ContaoManager\Ramsey\Uuid\Converter\NumberConverterInterface; use _ContaoManager\Ramsey\Uuid\Uuid; /** * DefaultUuidBuilder is the default UUID builder for ramsey/uuid; it builds * instances of Uuid objects */ class DefaultUuidBuilder implements UuidBuilderInterface { /** * @var NumberConverterInterface */ private $converter; /** * Constructs the DefaultUuidBuilder * * @param NumberConverterInterface $converter The number converter to use when constructing the Uuid */ public function __construct(NumberConverterInterface $converter) { $this->converter = $converter; } /** * Builds a Uuid * * @param CodecInterface $codec The codec to use for building this Uuid * @param array $fields An array of fields from which to construct the Uuid; * see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure. * @return Uuid */ public function build(CodecInterface $codec, array $fields) { return new Uuid($fields, $this->converter, $codec); } } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Exception; use RuntimeException; /** * Thrown to indicate that the requested operation is not supported. */ class UnsupportedOperationException extends RuntimeException { } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Exception; use RuntimeException; /** * Thrown to indicate that the requested operation has dependencies that have not * been satisfied. */ class UnsatisfiedDependencyException extends RuntimeException { } * @license http://opensource.org/licenses/MIT MIT * @link https://benramsey.com/projects/ramsey-uuid/ Documentation * @link https://packagist.org/packages/ramsey/uuid Packagist * @link https://github.com/ramsey/uuid GitHub */ namespace _ContaoManager\Ramsey\Uuid\Exception; use InvalidArgumentException; /** * Thrown to indicate that the parsed UUID string is invalid. */ class InvalidUuidStringException extends InvalidArgumentException { } The MIT License (MIT) Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Changelog ## 3.1.0 (2023-11-16) * Feature: Full PHP 8.3 compatibility. (#255 by @clue) * Feature: Describe all callable arguments with types for `Promise` and `Deferred`. (#253 by @clue) * Update test suite and minor documentation improvements. (#251 by @ondrejmirtes and #250 by @SQKo) ## 3.0.0 (2023-07-11) A major new feature release, see [**release announcement**](https://clue.engineering/2023/announcing-reactphp-promise-v3). * We'd like to emphasize that this component is production ready and battle-tested. We plan to support all long-term support (LTS) releases for at least 24 months, so you have a rock-solid foundation to build on top of. * The v3 release will be the way forward for this package. However, we will still actively support v2 and v1 to provide a smooth upgrade path for those not yet on the latest versions. This update involves some major new features and a minor BC break over the `v2.0.0` release. We've tried hard to avoid BC breaks where possible and minimize impact otherwise. We expect that most consumers of this package will be affected by BC breaks, but updating should take no longer than a few minutes. See below for more details: * BC break: PHP 8.1+ recommended, PHP 7.1+ required. (#138 and #149 by @WyriHaximus) * Feature / BC break: The `PromiseInterface` now includes the functionality of the old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~. Each promise now always includes the `then()`, `catch()`, `finally()` and `cancel()` methods. The new `catch()` and `finally()` methods replace the deprecated ~~`otherwise()`~~ and ~~`always()`~~ methods which continue to exist for BC reasons. The old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~ are no longer needed and have been removed as a consequence. (#75 by @jsor and #208 by @clue and @WyriHaximus) ```php // old (multiple interfaces may or may not be implemented) assert($promise instanceof PromiseInterface); assert(method_exists($promise, 'then')); if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'otherwise')); } if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'always')); } if ($promise instanceof CancellablePromiseInterface) { assert(method_exists($promise, 'cancel')); } // new (single PromiseInterface with all methods) assert($promise instanceof PromiseInterface); assert(method_exists($promise, 'then')); assert(method_exists($promise, 'catch')); assert(method_exists($promise, 'finally')); assert(method_exists($promise, 'cancel')); ``` * Feature / BC break: Improve type safety of promises. Require `mixed` fulfillment value argument and `Throwable` (or `Exception`) as rejection reason. Add PHPStan template types to ensure strict types for `resolve(T $value): PromiseInterface` and `reject(Throwable $reason): PromiseInterface`. It is no longer possible to resolve a promise without a value (use `null` instead) or reject a promise without a reason (use `Throwable` instead). (#93, #141 and #142 by @jsor, #138, #149 and #247 by @WyriHaximus and #213 and #246 by @clue) ```php // old (arguments used to be optional) $promise = resolve(); $promise = reject(); // new (already supported before) $promise = resolve(null); $promise = reject(new RuntimeException()); ``` * Feature / BC break: Report all unhandled rejections by default and remove ~~`done()`~~ method. Add new `set_rejection_handler()` function to set the global rejection handler for unhandled promise rejections. (#248, #249 and #224 by @clue) ```php // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 reject(new RuntimeException('Unhandled')); ``` * BC break: Remove all deprecated APIs and reduce API surface. Remove ~~`some()`~~, ~~`map()`~~, ~~`reduce()`~~ functions, use `any()` and `all()` functions instead. Remove internal ~~`FulfilledPromise`~~ and ~~`RejectedPromise`~~ classes, use `resolve()` and `reject()` functions instead. Remove legacy promise progress API (deprecated third argument to `then()` method) and deprecated ~~`LazyPromise`~~ class. (#32 and #98 by @jsor and #164, #219 and #220 by @clue) * BC break: Make all classes final to encourage composition over inheritance. (#80 by @jsor) * Feature / BC break: Require `array` (or `iterable`) type for `all()` + `race()` + `any()` functions and bring in line with ES6 specification. These functions now require a single argument with a variable number of promises or values as input. (#225 by @clue and #35 by @jsor) * Fix / BC break: Fix `race()` to return a forever pending promise when called with an empty `array` (or `iterable`) and bring in line with ES6 specification. (#83 by @jsor and #225 by @clue) * Minor performance improvements by initializing `Deferred` in the constructor and avoiding `call_user_func()` calls. (#151 by @WyriHaximus and #171 by @Kubo2) * Minor documentation improvements. (#110 by @seregazhuk, #132 by @CharlotteDunois, #145 by @danielecr, #178 by @WyriHaximus, #189 by @srdante, #212 by @clue, #214, #239 and #243 by @SimonFrings and #231 by @nhedger) The following changes had to be ported to this release due to our branching strategy, but also appeared in the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x): * Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+). (#197 by @cdosoftei and @SimonFrings) * Feature: Support intersection types (PHP 8.1+). (#209 by @bzikarsky) * Feature: Support DNS types (PHP 8.2+). (#236 by @nhedger) * Feature: Port all memory improvements from `2.x` to `3.x`. (#150 by @clue and @WyriHaximus) * Fix: Fix checking whether cancellable promise is an object and avoid possible warning. (#161 by @smscr) * Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function. (#134 by @WyriHaximus) * Improve test suite, update PHPUnit and PHP versions and add `.gitattributes` to exclude dev files from exports. (#107 by @carusogabriel, #148 and #234 by @WyriHaximus, #153 by @reedy, #162, #230 and #240 by @clue, #173, #177, #185 and #199 by @SimonFrings, #193 by @woodongwong and #210 by @bzikarsky) The following changes were originally planned for this release but later reverted and are not part of the final release: * Add iterative callback queue handler to avoid recursion (later removed to improve Fiber support). (#28, #82 and #86 by @jsor, #158 by @WyriHaximus and #229 and #238 by @clue) * Trigger an `E_USER_ERROR` instead of throwing an exception from `done()` (later removed entire `done()` method to globally report unhandled rejections). (#97 by @jsor and #224 and #248 by @clue) * Add type declarations for `some()` (later removed entire `some()` function). (#172 by @WyriHaximus and #219 by @clue) ## 2.0.0 (2013-12-10) See [`2.x` CHANGELOG](https://github.com/reactphp/promise/blob/2.x/CHANGELOG.md) for more details. ## 1.0.0 (2012-11-07) See [`1.x` CHANGELOG](https://github.com/reactphp/promise/blob/1.x/CHANGELOG.md) for more details. Promise ======= A lightweight implementation of [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. [![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions) [![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise) Table of Contents ----------------- 1. [Introduction](#introduction) 2. [Concepts](#concepts) * [Deferred](#deferred) * [Promise](#promise-1) 3. [API](#api) * [Deferred](#deferred-1) * [Deferred::promise()](#deferredpromise) * [Deferred::resolve()](#deferredresolve) * [Deferred::reject()](#deferredreject) * [PromiseInterface](#promiseinterface) * [PromiseInterface::then()](#promiseinterfacethen) * [PromiseInterface::catch()](#promiseinterfacecatch) * [PromiseInterface::finally()](#promiseinterfacefinally) * [PromiseInterface::cancel()](#promiseinterfacecancel) * [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise) * [~~PromiseInterface::always()~~](#promiseinterfacealways) * [Promise](#promise-2) * [Functions](#functions) * [resolve()](#resolve) * [reject()](#reject) * [all()](#all) * [race()](#race) * [any()](#any) * [set_rejection_handler()](#set_rejection_handler) 4. [Examples](#examples) * [How to use Deferred](#how-to-use-deferred) * [How promise forwarding works](#how-promise-forwarding-works) * [Resolution forwarding](#resolution-forwarding) * [Rejection forwarding](#rejection-forwarding) * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) 5. [Install](#install) 6. [Tests](#tests) 7. [Credits](#credits) 8. [License](#license) Introduction ------------ Promise is a library implementing [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. It also provides several other useful promise-related concepts, such as joining multiple promises and mapping and reducing collections of promises. If you've never heard about promises before, [read this first](https://gist.github.com/domenic/3889970). Concepts -------- ### Deferred A **Deferred** represents a computation or unit of work that may not have completed yet. Typically (but not always), that computation will be something that executes asynchronously and completes at some point in the future. ### Promise While a deferred represents the computation itself, a **Promise** represents the result of that computation. Thus, each deferred has a promise that acts as a placeholder for its actual result. API --- ### Deferred A deferred represents an operation whose resolution is pending. It has separate promise and resolver parts. ```php $deferred = new React\Promise\Deferred(); $promise = $deferred->promise(); $deferred->resolve(mixed $value); $deferred->reject(\Throwable $reason); ``` The `promise` method returns the promise of the deferred. The `resolve` and `reject` methods control the state of the deferred. The constructor of the `Deferred` accepts an optional `$canceller` argument. See [Promise](#promise-2) for more information. #### Deferred::promise() ```php $promise = $deferred->promise(); ``` Returns the promise of the deferred, which you can hand out to others while keeping the authority to modify its state to yourself. #### Deferred::resolve() ```php $deferred->resolve(mixed $value); ``` Resolves the promise returned by `promise()`. All consumers are notified by having `$onFulfilled` (which they registered via `$promise->then()`) called with `$value`. If `$value` itself is a promise, the promise will transition to the state of this promise once it is resolved. See also the [`resolve()` function](#resolve). #### Deferred::reject() ```php $deferred->reject(\Throwable $reason); ``` Rejects the promise returned by `promise()`, signalling that the deferred's computation failed. All consumers are notified by having `$onRejected` (which they registered via `$promise->then()`) called with `$reason`. See also the [`reject()` function](#reject). ### PromiseInterface The promise interface provides the common interface for all promise implementations. See [Promise](#promise-2) for the only public implementation exposed by this package. A promise represents an eventual outcome, which is either fulfillment (success) and an associated value, or rejection (failure) and an associated reason. Once in the fulfilled or rejected state, a promise becomes immutable. Neither its state nor its result (or error) can be modified. #### PromiseInterface::then() ```php $transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null); ``` Transforms a promise's value by applying a function to the promise's fulfillment or rejection value. Returns a new promise for the transformed result. The `then()` method registers new fulfilled and rejection handlers with a promise (all parameters are optional): * `$onFulfilled` will be invoked once the promise is fulfilled and passed the result as the first argument. * `$onRejected` will be invoked once the promise is rejected and passed the reason as the first argument. It returns a new promise that will fulfill with the return value of either `$onFulfilled` or `$onRejected`, whichever is called, or will reject with the thrown exception if either throws. A promise makes the following guarantees about handlers registered in the same call to `then()`: 1. Only one of `$onFulfilled` or `$onRejected` will be called, never both. 2. `$onFulfilled` and `$onRejected` will never be called more than once. #### See also * [resolve()](#resolve) - Creating a resolved promise * [reject()](#reject) - Creating a rejected promise #### PromiseInterface::catch() ```php $promise->catch(callable $onRejected); ``` Registers a rejection handler for promise. It is a shortcut for: ```php $promise->then(null, $onRejected); ``` Additionally, you can type hint the `$reason` argument of `$onRejected` to catch only specific errors. ```php $promise ->catch(function (\RuntimeException $reason) { // Only catch \RuntimeException instances // All other types of errors will propagate automatically }) ->catch(function (\Throwable $reason) { // Catch other errors }); ``` #### PromiseInterface::finally() ```php $newPromise = $promise->finally(callable $onFulfilledOrRejected); ``` Allows you to execute "cleanup" type tasks in a promise chain. It arranges for `$onFulfilledOrRejected` to be called, with no arguments, when the promise is either fulfilled or rejected. * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, `$newPromise` will fulfill with the same value as `$promise`. * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a rejected promise, `$newPromise` will reject with the thrown exception or rejected promise's reason. * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, `$newPromise` will reject with the same reason as `$promise`. * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a rejected promise, `$newPromise` will reject with the thrown exception or rejected promise's reason. `finally()` behaves similarly to the synchronous finally statement. When combined with `catch()`, `finally()` allows you to write code that is similar to the familiar synchronous catch/finally pair. Consider the following synchronous code: ```php try { return doSomething(); } catch (\Throwable $e) { return handleError($e); } finally { cleanup(); } ``` Similar asynchronous code (with `doSomething()` that returns a promise) can be written: ```php return doSomething() ->catch('handleError') ->finally('cleanup'); ``` #### PromiseInterface::cancel() ``` php $promise->cancel(); ``` The `cancel()` method notifies the creator of the promise that there is no further interest in the results of the operation. Once a promise is settled (either fulfilled or rejected), calling `cancel()` on a promise has no effect. #### ~~PromiseInterface::otherwise()~~ > Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead. The `otherwise()` method registers a rejection handler for a promise. This method continues to exist only for BC reasons and to ease upgrading between versions. It is an alias for: ```php $promise->catch($onRejected); ``` #### ~~PromiseInterface::always()~~ > Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead. The `always()` method allows you to execute "cleanup" type tasks in a promise chain. This method continues to exist only for BC reasons and to ease upgrading between versions. It is an alias for: ```php $promise->finally($onFulfilledOrRejected); ``` ### Promise Creates a promise whose state is controlled by the functions passed to `$resolver`. ```php $resolver = function (callable $resolve, callable $reject) { // Do some work, possibly asynchronously, and then // resolve or reject. $resolve($awesomeResult); // or throw new Exception('Promise rejected'); // or $resolve($anotherPromise); // or $reject($nastyError); }; $canceller = function () { // Cancel/abort any running operations like network connections, streams etc. // Reject promise by throwing an exception throw new Exception('Promise cancelled'); }; $promise = new React\Promise\Promise($resolver, $canceller); ``` The promise constructor receives a resolver function and an optional canceller function which both will be called with two arguments: * `$resolve($value)` - Primary function that seals the fate of the returned promise. Accepts either a non-promise value, or another promise. When called with a non-promise value, fulfills promise with that value. When called with another promise, e.g. `$resolve($otherPromise)`, promise's fate will be equivalent to that of `$otherPromise`. * `$reject($reason)` - Function that rejects the promise. It is recommended to just throw an exception instead of using `$reject()`. If the resolver or canceller throw an exception, the promise will be rejected with that thrown exception as the rejection reason. The resolver function will be called immediately, the canceller function only once all consumers called the `cancel()` method of the promise. ### Functions Useful functions for creating and joining collections of promises. All functions working on promise collections (like `all()`, `race()`, etc.) support cancellation. This means, if you call `cancel()` on the returned promise, all promises in the collection are cancelled. #### resolve() ```php $promise = React\Promise\resolve(mixed $promiseOrValue); ``` Creates a promise for the supplied `$promiseOrValue`. If `$promiseOrValue` is a value, it will be the resolution value of the returned promise. If `$promiseOrValue` is a thenable (any object that provides a `then()` method), a trusted promise that follows the state of the thenable is returned. If `$promiseOrValue` is a promise, it will be returned as is. The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) and can be consumed like any other promise: ```php $promise = React\Promise\resolve(42); $promise->then(function (int $result): void { var_dump($result); }, function (\Throwable $e): void { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); ``` #### reject() ```php $promise = React\Promise\reject(\Throwable $reason); ``` Creates a rejected promise for the supplied `$reason`. Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and [`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to reject a promise, any language error or user land exception can be used to reject a promise. The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) and can be consumed like any other promise: ```php $promise = React\Promise\reject(new RuntimeException('Request failed')); $promise->then(function (int $result): void { var_dump($result); }, function (\Throwable $e): void { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); ``` Note that rejected promises should always be handled similar to how any exceptions should always be caught in a `try` + `catch` block. If you remove the last reference to a rejected promise that has not been handled, it will report an unhandled promise rejection: ```php function incorrect(): int { $promise = React\Promise\reject(new RuntimeException('Request failed')); // Commented out: No rejection handler registered here. // $promise->then(null, function (\Throwable $e): void { /* ignore */ }); // Returning from a function will remove all local variable references, hence why // this will report an unhandled promise rejection here. return 42; } // Calling this function will log an error message plus its stack trace: // Unhandled promise rejection with RuntimeException: Request failed in example.php:10 incorrect(); ``` A rejected promise will be considered "handled" if you catch the rejection reason with either the [`then()` method](#promiseinterfacethen), the [`catch()` method](#promiseinterfacecatch), or the [`finally()` method](#promiseinterfacefinally). Note that each of these methods return a new promise that may again be rejected if you re-throw an exception. A rejected promise will also be considered "handled" if you abort the operation with the [`cancel()` method](#promiseinterfacecancel) (which in turn would usually reject the promise if it is still pending). See also the [`set_rejection_handler()` function](#set_rejection_handler). #### all() ```php $promise = React\Promise\all(iterable $promisesOrValues); ``` Returns a promise that will resolve only once all the items in `$promisesOrValues` have resolved. The resolution value of the returned promise will be an array containing the resolution values of each of the items in `$promisesOrValues`. #### race() ```php $promise = React\Promise\race(iterable $promisesOrValues); ``` Initiates a competitive race that allows one winner. Returns a promise which is resolved in the same way the first settled promise resolves. The returned promise will become **infinitely pending** if `$promisesOrValues` contains 0 items. #### any() ```php $promise = React\Promise\any(iterable $promisesOrValues); ``` Returns a promise that will resolve when any one of the items in `$promisesOrValues` resolves. The resolution value of the returned promise will be the resolution value of the triggering item. The returned promise will only reject if *all* items in `$promisesOrValues` are rejected. The rejection value will be a `React\Promise\Exception\CompositeException` which holds all rejection reasons. The rejection reasons can be obtained with `CompositeException::getThrowables()`. The returned promise will also reject with a `React\Promise\Exception\LengthException` if `$promisesOrValues` contains 0 items. #### set_rejection_handler() ```php React\Promise\set_rejection_handler(?callable $callback): ?callable; ``` Sets the global rejection handler for unhandled promise rejections. Note that rejected promises should always be handled similar to how any exceptions should always be caught in a `try` + `catch` block. If you remove the last reference to a rejected promise that has not been handled, it will report an unhandled promise rejection. See also the [`reject()` function](#reject) for more details. The `?callable $callback` argument MUST be a valid callback function that accepts a single `Throwable` argument or a `null` value to restore the default promise rejection handler. The return value of the callback function will be ignored and has no effect, so you SHOULD return a `void` value. The callback function MUST NOT throw or the program will be terminated with a fatal error. The function returns the previous rejection handler or `null` if using the default promise rejection handler. The default promise rejection handler will log an error message plus its stack trace: ```php // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 React\Promise\reject(new RuntimeException('Unhandled')); ``` The promise rejection handler may be used to use customize the log message or write to custom log targets. As a rule of thumb, this function should only be used as a last resort and promise rejections are best handled with either the [`then()` method](#promiseinterfacethen), the [`catch()` method](#promiseinterfacecatch), or the [`finally()` method](#promiseinterfacefinally). See also the [`reject()` function](#reject) for more details. Examples -------- ### How to use Deferred ```php function getAwesomeResultPromise() { $deferred = new React\Promise\Deferred(); // Execute a Node.js-style function using the callback pattern computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) { if ($error) { $deferred->reject($error); } else { $deferred->resolve($result); } }); // Return the promise return $deferred->promise(); } getAwesomeResultPromise() ->then( function ($value) { // Deferred resolved, do something with $value }, function (\Throwable $reason) { // Deferred rejected, do something with $reason } ); ``` ### How promise forwarding works A few simple examples to show how the mechanics of Promises/A forwarding works. These examples are contrived, of course, and in real usage, promise chains will typically be spread across several function calls, or even several levels of your application architecture. #### Resolution forwarding Resolved promises forward resolution values to the next promise. The first promise, `$deferred->promise()`, will resolve with the value passed to `$deferred->resolve()` below. Each call to `then()` returns a new promise that will resolve with the return value of the previous handler. This creates a promise "pipeline". ```php $deferred = new React\Promise\Deferred(); $deferred->promise() ->then(function ($x) { // $x will be the value passed to $deferred->resolve() below // and returns a *new promise* for $x + 1 return $x + 1; }) ->then(function ($x) { // $x === 2 // This handler receives the return value of the // previous handler. return $x + 1; }) ->then(function ($x) { // $x === 3 // This handler receives the return value of the // previous handler. return $x + 1; }) ->then(function ($x) { // $x === 4 // This handler receives the return value of the // previous handler. echo 'Resolve ' . $x; }); $deferred->resolve(1); // Prints "Resolve 4" ``` #### Rejection forwarding Rejected promises behave similarly, and also work similarly to try/catch: When you catch an exception, you must rethrow for it to propagate. Similarly, when you handle a rejected promise, to propagate the rejection, "rethrow" it by either returning a rejected promise, or actually throwing (since promise translates thrown exceptions into rejections) ```php $deferred = new React\Promise\Deferred(); $deferred->promise() ->then(function ($x) { throw new \Exception($x + 1); }) ->catch(function (\Exception $x) { // Propagate the rejection throw $x; }) ->catch(function (\Exception $x) { // Can also propagate by returning another rejection return React\Promise\reject( new \Exception($x->getMessage() + 1) ); }) ->catch(function ($x) { echo 'Reject ' . $x->getMessage(); // 3 }); $deferred->resolve(1); // Prints "Reject 3" ``` #### Mixed resolution and rejection forwarding Just like try/catch, you can choose to propagate or not. Mixing resolutions and rejections will still forward handler results in a predictable way. ```php $deferred = new React\Promise\Deferred(); $deferred->promise() ->then(function ($x) { return $x + 1; }) ->then(function ($x) { throw new \Exception($x + 1); }) ->catch(function (\Exception $x) { // Handle the rejection, and don't propagate. // This is like catch without a rethrow return $x->getMessage() + 1; }) ->then(function ($x) { echo 'Mixed ' . $x; // 4 }); $deferred->resolve(1); // Prints "Mixed 4" ``` Install ------- The recommended way to install this library is [through Composer](https://getcomposer.org/). [New to Composer?](https://getcomposer.org/doc/00-intro.md) This project follows [SemVer](https://semver.org/). This will install the latest supported version from this branch: ```bash composer require react/promise:^3.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions and supports running on PHP 7.1 through current PHP 8+. It's *highly recommended to use the latest supported PHP version* for this project. We're committed to providing long-term support (LTS) options and to provide a smooth upgrade path. If you're using an older PHP version, you may use the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or [`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both provide a compatible API but do not take advantage of newer language features. You may target multiple versions at the same time to support a wider range of PHP versions like this: ```bash composer require "react/promise:^3 || ^2 || ^1" ``` ## Tests To run the test suite, you first need to clone this repo and then install all dependencies [through Composer](https://getcomposer.org/): ```bash composer install ``` To run the test suite, go to the project root and run: ```bash vendor/bin/phpunit ``` On top of this, we use PHPStan on max level to ensure type safety across the project: ```bash vendor/bin/phpstan ``` Credits ------- Promise is a port of [when.js](https://github.com/cujojs/when) by [Brian Cavalier](https://github.com/briancavalier). Also, large parts of the documentation have been ported from the when.js [Wiki](https://github.com/cujojs/when/wiki) and the [API docs](https://github.com/cujojs/when/blob/master/docs/api.md). License ------- Released under the [MIT](LICENSE) license. { "name": "react\/promise", "description": "A lightweight implementation of CommonJS Promises\/A for PHP", "license": "MIT", "authors": [ { "name": "Jan Sorgalla", "homepage": "https:\/\/sorgalla.com\/", "email": "jsorgalla@gmail.com" }, { "name": "Christian L\u00fcck", "homepage": "https:\/\/clue.engineering\/", "email": "christian@clue.engineering" }, { "name": "Cees-Jan Kiewiet", "homepage": "https:\/\/wyrihaximus.net\/", "email": "reactphp@ceesjankiewiet.nl" }, { "name": "Chris Boden", "homepage": "https:\/\/cboden.dev\/", "email": "cboden@gmail.com" } ], "require": { "php": ">=7.1.0" }, "require-dev": { "phpstan\/phpstan": "1.10.39 || 1.4.10", "phpunit\/phpunit": "^9.6 || ^7.5" }, "autoload": { "psr-4": { "React\\Promise\\": "src\/" }, "files": [ "src\/functions_include.php" ] }, "autoload-dev": { "psr-4": { "React\\Promise\\": [ "tests\/fixtures\/", "tests\/" ] }, "files": [ "tests\/Fiber.php" ] }, "keywords": [ "promise", "promises" ] }|TFulfilled)) $onFulfilled * @param ?(callable(\Throwable): (PromiseInterface|TRejected)) $onRejected * @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))> */ public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : \React\Promise\PromiseInterface; /** * Registers a rejection handler for promise. It is a shortcut for: * * ```php * $promise->then(null, $onRejected); * ``` * * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch * only specific errors. * * @template TThrowable of \Throwable * @template TRejected * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected * @return PromiseInterface */ public function catch(callable $onRejected) : \React\Promise\PromiseInterface; /** * Allows you to execute "cleanup" type tasks in a promise chain. * * It arranges for `$onFulfilledOrRejected` to be called, with no arguments, * when the promise is either fulfilled or rejected. * * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, * `$newPromise` will fulfill with the same value as `$promise`. * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a * rejected promise, `$newPromise` will reject with the thrown exception or * rejected promise's reason. * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, * `$newPromise` will reject with the same reason as `$promise`. * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a * rejected promise, `$newPromise` will reject with the thrown exception or * rejected promise's reason. * * `finally()` behaves similarly to the synchronous finally statement. When combined * with `catch()`, `finally()` allows you to write code that is similar to the familiar * synchronous catch/finally pair. * * Consider the following synchronous code: * * ```php * try { * return doSomething(); * } catch(\Exception $e) { * return handleError($e); * } finally { * cleanup(); * } * ``` * * Similar asynchronous code (with `doSomething()` that returns a promise) can be * written: * * ```php * return doSomething() * ->catch('handleError') * ->finally('cleanup'); * ``` * * @param callable(): (void|PromiseInterface) $onFulfilledOrRejected * @return PromiseInterface */ public function finally(callable $onFulfilledOrRejected) : \React\Promise\PromiseInterface; /** * The `cancel()` method notifies the creator of the promise that there is no * further interest in the results of the operation. * * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on * a promise has no effect. * * @return void */ public function cancel() : void; /** * [Deprecated] Registers a rejection handler for a promise. * * This method continues to exist only for BC reasons and to ease upgrading * between versions. It is an alias for: * * ```php * $promise->catch($onRejected); * ``` * * @template TThrowable of \Throwable * @template TRejected * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected * @return PromiseInterface * @deprecated 3.0.0 Use catch() instead * @see self::catch() */ public function otherwise(callable $onRejected) : \React\Promise\PromiseInterface; /** * [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain. * * This method continues to exist only for BC reasons and to ease upgrading * between versions. It is an alias for: * * ```php * $promise->finally($onFulfilledOrRejected); * ``` * * @param callable(): (void|PromiseInterface) $onFulfilledOrRejected * @return PromiseInterface * @deprecated 3.0.0 Use finally() instead * @see self::finally() */ public function always(callable $onFulfilledOrRejected) : \React\Promise\PromiseInterface; } |T $promiseOrValue * @return PromiseInterface */ function resolve($promiseOrValue) : \React\Promise\PromiseInterface { if ($promiseOrValue instanceof \React\Promise\PromiseInterface) { return $promiseOrValue; } if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) { $canceller = null; if (\method_exists($promiseOrValue, 'cancel')) { $canceller = [$promiseOrValue, 'cancel']; \assert(\is_callable($canceller)); } /** @var Promise */ return new \React\Promise\Promise(function (callable $resolve, callable $reject) use($promiseOrValue) : void { $promiseOrValue->then($resolve, $reject); }, $canceller); } return new FulfilledPromise($promiseOrValue); } /** * Creates a rejected promise for the supplied `$reason`. * * If `$reason` is a value, it will be the rejection value of the * returned promise. * * If `$reason` is a promise, its completion value will be the rejected * value of the returned promise. * * This can be useful in situations where you need to reject a promise without * throwing an exception. For example, it allows you to propagate a rejection with * the value of another promise. * * @return PromiseInterface */ function reject(\Throwable $reason) : \React\Promise\PromiseInterface { return new RejectedPromise($reason); } /** * Returns a promise that will resolve only once all the items in * `$promisesOrValues` have resolved. The resolution value of the returned promise * will be an array containing the resolution values of each of the items in * `$promisesOrValues`. * * @template T * @param iterable|T> $promisesOrValues * @return PromiseInterface> */ function all(iterable $promisesOrValues) : \React\Promise\PromiseInterface { $cancellationQueue = new \React\Promise\Internal\CancellationQueue(); /** @var Promise> */ return new \React\Promise\Promise(function (callable $resolve, callable $reject) use($promisesOrValues, $cancellationQueue) : void { $toResolve = 0; /** @var bool */ $continue = \true; $values = []; foreach ($promisesOrValues as $i => $promiseOrValue) { $cancellationQueue->enqueue($promiseOrValue); $values[$i] = null; ++$toResolve; resolve($promiseOrValue)->then(function ($value) use($i, &$values, &$toResolve, &$continue, $resolve) : void { $values[$i] = $value; if (0 === --$toResolve && !$continue) { $resolve($values); } }, function (\Throwable $reason) use(&$continue, $reject) : void { $continue = \false; $reject($reason); }); if (!$continue && !\is_array($promisesOrValues)) { break; } } $continue = \false; if ($toResolve === 0) { $resolve($values); } }, $cancellationQueue); } /** * Initiates a competitive race that allows one winner. Returns a promise which is * resolved in the same way the first settled promise resolves. * * The returned promise will become **infinitely pending** if `$promisesOrValues` * contains 0 items. * * @template T * @param iterable|T> $promisesOrValues * @return PromiseInterface */ function race(iterable $promisesOrValues) : \React\Promise\PromiseInterface { $cancellationQueue = new \React\Promise\Internal\CancellationQueue(); /** @var Promise */ return new \React\Promise\Promise(function (callable $resolve, callable $reject) use($promisesOrValues, $cancellationQueue) : void { $continue = \true; foreach ($promisesOrValues as $promiseOrValue) { $cancellationQueue->enqueue($promiseOrValue); resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use(&$continue) : void { $continue = \false; }); if (!$continue && !\is_array($promisesOrValues)) { break; } } }, $cancellationQueue); } /** * Returns a promise that will resolve when any one of the items in * `$promisesOrValues` resolves. The resolution value of the returned promise * will be the resolution value of the triggering item. * * The returned promise will only reject if *all* items in `$promisesOrValues` are * rejected. The rejection value will be an array of all rejection reasons. * * The returned promise will also reject with a `React\Promise\Exception\LengthException` * if `$promisesOrValues` contains 0 items. * * @template T * @param iterable|T> $promisesOrValues * @return PromiseInterface */ function any(iterable $promisesOrValues) : \React\Promise\PromiseInterface { $cancellationQueue = new \React\Promise\Internal\CancellationQueue(); /** @var Promise */ return new \React\Promise\Promise(function (callable $resolve, callable $reject) use($promisesOrValues, $cancellationQueue) : void { $toReject = 0; $continue = \true; $reasons = []; foreach ($promisesOrValues as $i => $promiseOrValue) { $cancellationQueue->enqueue($promiseOrValue); ++$toReject; resolve($promiseOrValue)->then(function ($value) use($resolve, &$continue) : void { $continue = \false; $resolve($value); }, function (\Throwable $reason) use($i, &$reasons, &$toReject, $reject, &$continue) : void { $reasons[$i] = $reason; if (0 === --$toReject && !$continue) { $reject(new CompositeException($reasons, 'All promises rejected.')); } }); if (!$continue && !\is_array($promisesOrValues)) { break; } } $continue = \false; if ($toReject === 0 && !$reasons) { $reject(new \React\Promise\Exception\LengthException('Must contain at least 1 item but contains only 0 items.')); } elseif ($toReject === 0) { $reject(new CompositeException($reasons, 'All promises rejected.')); } }, $cancellationQueue); } /** * Sets the global rejection handler for unhandled promise rejections. * * Note that rejected promises should always be handled similar to how any * exceptions should always be caught in a `try` + `catch` block. If you remove * the last reference to a rejected promise that has not been handled, it will * report an unhandled promise rejection. See also the [`reject()` function](#reject) * for more details. * * The `?callable $callback` argument MUST be a valid callback function that * accepts a single `Throwable` argument or a `null` value to restore the * default promise rejection handler. The return value of the callback function * will be ignored and has no effect, so you SHOULD return a `void` value. The * callback function MUST NOT throw or the program will be terminated with a * fatal error. * * The function returns the previous rejection handler or `null` if using the * default promise rejection handler. * * The default promise rejection handler will log an error message plus its * stack trace: * * ```php * // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 * React\Promise\reject(new RuntimeException('Unhandled')); * ``` * * The promise rejection handler may be used to use customize the log message or * write to custom log targets. As a rule of thumb, this function should only be * used as a last resort and promise rejections are best handled with either the * [`then()` method](#promiseinterfacethen), the * [`catch()` method](#promiseinterfacecatch), or the * [`finally()` method](#promiseinterfacefinally). * See also the [`reject()` function](#reject) for more details. * * @param callable(\Throwable):void|null $callback * @return callable(\Throwable):void|null */ function set_rejection_handler(?callable $callback) : ?callable { static $current = null; $previous = $current; $current = $callback; return $previous; } /** * @internal */ function _checkTypehint(callable $callback, \Throwable $reason) : bool { if (\is_array($callback)) { $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); } elseif (\is_object($callback) && !$callback instanceof \Closure) { $callbackReflection = new \ReflectionMethod($callback, '__invoke'); } else { \assert($callback instanceof \Closure || \is_string($callback)); $callbackReflection = new \ReflectionFunction($callback); } $parameters = $callbackReflection->getParameters(); if (!isset($parameters[0])) { return \true; } $expectedException = $parameters[0]; // Extract the type of the argument and handle different possibilities $type = $expectedException->getType(); $isTypeUnion = \true; $types = []; switch (\true) { case $type === null: break; case $type instanceof \ReflectionNamedType: $types = [$type]; break; case $type instanceof \ReflectionIntersectionType: $isTypeUnion = \false; case $type instanceof \ReflectionUnionType: $types = $type->getTypes(); break; default: throw new \LogicException('Unexpected return value of ReflectionParameter::getType'); } // If there is no type restriction, it matches if (empty($types)) { return \true; } foreach ($types as $type) { if ($type instanceof \ReflectionIntersectionType) { foreach ($type->getTypes() as $typeToMatch) { \assert($typeToMatch instanceof \ReflectionNamedType); $name = $typeToMatch->getName(); if (!($matches = !$typeToMatch->isBuiltin() && $reason instanceof $name)) { break; } } \assert(isset($matches)); } else { \assert($type instanceof \ReflectionNamedType); $name = $type->getName(); $matches = !$type->isBuiltin() && $reason instanceof $name; } // If we look for a single match (union), we can return early on match // If we look for a full match (intersection), we can return early on mismatch if ($matches) { if ($isTypeUnion) { return \true; } } else { if (!$isTypeUnion) { return \false; } } } // If we look for a single match (union) and did not return early, we matched no type and are false // If we look for a full match (intersection) and did not return early, we matched all types and are true return $isTypeUnion ? \false : \true; } */ final class Promise implements \React\Promise\PromiseInterface { /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */ private $canceller; /** @var ?PromiseInterface */ private $result; /** @var list):void> */ private $handlers = []; /** @var int */ private $requiredCancelRequests = 0; /** @var bool */ private $cancelled = \false; /** * @param callable(callable(T):void,callable(\Throwable):void):void $resolver * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller */ public function __construct(callable $resolver, callable $canceller = null) { $this->canceller = $canceller; // Explicitly overwrite arguments with null values before invoking // resolver function. This ensure that these arguments do not show up // in the stack trace in PHP 7+ only. $cb = $resolver; $resolver = $canceller = null; $this->call($cb); } public function then(callable $onFulfilled = null, callable $onRejected = null) : \React\Promise\PromiseInterface { if (null !== $this->result) { return $this->result->then($onFulfilled, $onRejected); } if (null === $this->canceller) { return new static($this->resolver($onFulfilled, $onRejected)); } // This promise has a canceller, so we create a new child promise which // has a canceller that invokes the parent canceller if all other // followers are also cancelled. We keep a reference to this promise // instance for the static canceller function and clear this to avoid // keeping a cyclic reference between parent and follower. $parent = $this; ++$parent->requiredCancelRequests; return new static($this->resolver($onFulfilled, $onRejected), static function () use(&$parent) : void { \assert($parent instanceof self); --$parent->requiredCancelRequests; if ($parent->requiredCancelRequests <= 0) { $parent->cancel(); } $parent = null; }); } /** * @template TThrowable of \Throwable * @template TRejected * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected * @return PromiseInterface */ public function catch(callable $onRejected) : \React\Promise\PromiseInterface { return $this->then(null, static function (\Throwable $reason) use($onRejected) { if (!_checkTypehint($onRejected, $reason)) { return new RejectedPromise($reason); } /** * @var callable(\Throwable):(PromiseInterface|TRejected) $onRejected */ return $onRejected($reason); }); } public function finally(callable $onFulfilledOrRejected) : \React\Promise\PromiseInterface { return $this->then(static function ($value) use($onFulfilledOrRejected) : \React\Promise\PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use($value) { return $value; }); }, static function (\Throwable $reason) use($onFulfilledOrRejected) : \React\Promise\PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use($reason) : RejectedPromise { return new RejectedPromise($reason); }); }); } public function cancel() : void { $this->cancelled = \true; $canceller = $this->canceller; $this->canceller = null; $parentCanceller = null; if (null !== $this->result) { // Forward cancellation to rejected promise to avoid reporting unhandled rejection if ($this->result instanceof RejectedPromise) { $this->result->cancel(); } // Go up the promise chain and reach the top most promise which is // itself not following another promise $root = $this->unwrap($this->result); // Return if the root promise is already resolved or a // FulfilledPromise or RejectedPromise if (!$root instanceof self || null !== $root->result) { return; } $root->requiredCancelRequests--; if ($root->requiredCancelRequests <= 0) { $parentCanceller = [$root, 'cancel']; } } if (null !== $canceller) { $this->call($canceller); } // For BC, we call the parent canceller after our own canceller if ($parentCanceller) { $parentCanceller(); } } /** * @deprecated 3.0.0 Use `catch()` instead * @see self::catch() */ public function otherwise(callable $onRejected) : \React\Promise\PromiseInterface { return $this->catch($onRejected); } /** * @deprecated 3.0.0 Use `finally()` instead * @see self::finally() */ public function always(callable $onFulfilledOrRejected) : \React\Promise\PromiseInterface { return $this->finally($onFulfilledOrRejected); } private function resolver(callable $onFulfilled = null, callable $onRejected = null) : callable { return function (callable $resolve, callable $reject) use($onFulfilled, $onRejected) : void { $this->handlers[] = static function (\React\Promise\PromiseInterface $promise) use($onFulfilled, $onRejected, $resolve, $reject) : void { $promise = $promise->then($onFulfilled, $onRejected); if ($promise instanceof self && $promise->result === null) { $promise->handlers[] = static function (\React\Promise\PromiseInterface $promise) use($resolve, $reject) : void { $promise->then($resolve, $reject); }; } else { $promise->then($resolve, $reject); } }; }; } private function reject(\Throwable $reason) : void { if (null !== $this->result) { return; } $this->settle(reject($reason)); } /** * @param PromiseInterface $result */ private function settle(\React\Promise\PromiseInterface $result) : void { $result = $this->unwrap($result); if ($result === $this) { $result = new RejectedPromise(new \LogicException('Cannot resolve a promise with itself.')); } if ($result instanceof self) { $result->requiredCancelRequests++; } else { // Unset canceller only when not following a pending promise $this->canceller = null; } $handlers = $this->handlers; $this->handlers = []; $this->result = $result; foreach ($handlers as $handler) { $handler($result); } // Forward cancellation to rejected promise to avoid reporting unhandled rejection if ($this->cancelled && $result instanceof RejectedPromise) { $result->cancel(); } } /** * @param PromiseInterface $promise * @return PromiseInterface */ private function unwrap(\React\Promise\PromiseInterface $promise) : \React\Promise\PromiseInterface { while ($promise instanceof self && null !== $promise->result) { /** @var PromiseInterface $promise */ $promise = $promise->result; } return $promise; } /** * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb */ private function call(callable $cb) : void { // Explicitly overwrite argument with null value. This ensure that this // argument does not show up in the stack trace in PHP 7+ only. $callback = $cb; $cb = null; // Use reflection to inspect number of arguments expected by this callback. // We did some careful benchmarking here: Using reflection to avoid unneeded // function arguments is actually faster than blindly passing them. // Also, this helps avoiding unnecessary function arguments in the call stack // if the callback creates an Exception (creating garbage cycles). if (\is_array($callback)) { $ref = new \ReflectionMethod($callback[0], $callback[1]); } elseif (\is_object($callback) && !$callback instanceof \Closure) { $ref = new \ReflectionMethod($callback, '__invoke'); } else { \assert($callback instanceof \Closure || \is_string($callback)); $ref = new \ReflectionFunction($callback); } $args = $ref->getNumberOfParameters(); try { if ($args === 0) { $callback(); } else { // Keep references to this promise instance for the static resolve/reject functions. // By using static callbacks that are not bound to this instance // and passing the target promise instance by reference, we can // still execute its resolving logic and still clear this // reference when settling the promise. This helps avoiding // garbage cycles if any callback creates an Exception. // These assumptions are covered by the test suite, so if you ever feel like // refactoring this, go ahead, any alternative suggestions are welcome! $target =& $this; $callback(static function ($value) use(&$target) : void { if ($target !== null) { $target->settle(resolve($value)); $target = null; } }, static function (\Throwable $reason) use(&$target) : void { if ($target !== null) { $target->reject($reason); $target = null; } }); } } catch (\Throwable $e) { $target = null; $this->reject($e); } } } */ private $promise; /** @var callable(T):void */ private $resolveCallback; /** @var callable(\Throwable):void */ private $rejectCallback; /** * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller */ public function __construct(callable $canceller = null) { $this->promise = new \React\Promise\Promise(function ($resolve, $reject) : void { $this->resolveCallback = $resolve; $this->rejectCallback = $reject; }, $canceller); } /** * @return PromiseInterface */ public function promise() : \React\Promise\PromiseInterface { return $this->promise; } /** * @param T $value */ public function resolve($value) : void { ($this->resolveCallback)($value); } public function reject(\Throwable $reason) : void { ($this->rejectCallback)($reason); } } */ final class RejectedPromise implements PromiseInterface { /** @var \Throwable */ private $reason; /** @var bool */ private $handled = \false; /** * @param \Throwable $reason */ public function __construct(\Throwable $reason) { $this->reason = $reason; } /** @throws void */ public function __destruct() { if ($this->handled) { return; } $handler = set_rejection_handler(null); if ($handler === null) { $message = 'Unhandled promise rejection with ' . \get_class($this->reason) . ': ' . $this->reason->getMessage() . ' in ' . $this->reason->getFile() . ':' . $this->reason->getLine() . \PHP_EOL; $message .= 'Stack trace:' . \PHP_EOL . $this->reason->getTraceAsString(); \error_log($message); return; } try { $handler($this->reason); } catch (\Throwable $e) { $message = 'Fatal error: Uncaught ' . \get_class($e) . ' from unhandled promise rejection handler: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() . \PHP_EOL; $message .= 'Stack trace:' . \PHP_EOL . $e->getTraceAsString(); \error_log($message); exit(255); } } /** * @template TRejected * @param ?callable $onFulfilled * @param ?(callable(\Throwable): (PromiseInterface|TRejected)) $onRejected * @return PromiseInterface<($onRejected is null ? never : TRejected)> */ public function then(callable $onFulfilled = null, callable $onRejected = null) : PromiseInterface { if (null === $onRejected) { return $this; } $this->handled = \true; try { return resolve($onRejected($this->reason)); } catch (\Throwable $exception) { return new \React\Promise\Internal\RejectedPromise($exception); } } /** * @template TThrowable of \Throwable * @template TRejected * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected * @return PromiseInterface */ public function catch(callable $onRejected) : PromiseInterface { if (!_checkTypehint($onRejected, $this->reason)) { return $this; } /** * @var callable(\Throwable):(PromiseInterface|TRejected) $onRejected */ return $this->then(null, $onRejected); } public function finally(callable $onFulfilledOrRejected) : PromiseInterface { return $this->then(null, function (\Throwable $reason) use($onFulfilledOrRejected) : PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use($reason) : PromiseInterface { return new \React\Promise\Internal\RejectedPromise($reason); }); }); } public function cancel() : void { $this->handled = \true; } /** * @deprecated 3.0.0 Use `catch()` instead * @see self::catch() */ public function otherwise(callable $onRejected) : PromiseInterface { return $this->catch($onRejected); } /** * @deprecated 3.0.0 Use `always()` instead * @see self::always() */ public function always(callable $onFulfilledOrRejected) : PromiseInterface { return $this->finally($onFulfilledOrRejected); } } */ final class FulfilledPromise implements PromiseInterface { /** @var T */ private $value; /** * @param T $value * @throws \InvalidArgumentException */ public function __construct($value = null) { if ($value instanceof PromiseInterface) { throw new \InvalidArgumentException('You cannot create React\\Promise\\FulfilledPromise with a promise. Use React\\Promise\\resolve($promiseOrValue) instead.'); } $this->value = $value; } /** * @template TFulfilled * @param ?(callable((T is void ? null : T)): (PromiseInterface|TFulfilled)) $onFulfilled * @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)> */ public function then(callable $onFulfilled = null, callable $onRejected = null) : PromiseInterface { if (null === $onFulfilled) { return $this; } try { /** * @var PromiseInterface|T $result */ $result = $onFulfilled($this->value); return resolve($result); } catch (\Throwable $exception) { return new \React\Promise\Internal\RejectedPromise($exception); } } public function catch(callable $onRejected) : PromiseInterface { return $this; } public function finally(callable $onFulfilledOrRejected) : PromiseInterface { return $this->then(function ($value) use($onFulfilledOrRejected) : PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use($value) { return $value; }); }); } public function cancel() : void { } /** * @deprecated 3.0.0 Use `catch()` instead * @see self::catch() */ public function otherwise(callable $onRejected) : PromiseInterface { return $this->catch($onRejected); } /** * @deprecated 3.0.0 Use `finally()` instead * @see self::finally() */ public function always(callable $onFulfilledOrRejected) : PromiseInterface { return $this->finally($onFulfilledOrRejected); } } started) { return; } $this->started = \true; $this->drain(); } /** * @param mixed $cancellable */ public function enqueue($cancellable) : void { if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) { return; } $length = \array_push($this->queue, $cancellable); if ($this->started && 1 === $length) { $this->drain(); } } private function drain() : void { for ($i = \key($this->queue); isset($this->queue[$i]); $i++) { $cancellable = $this->queue[$i]; \assert(\method_exists($cancellable, 'cancel')); $exception = null; try { $cancellable->cancel(); } catch (\Throwable $exception) { } unset($this->queue[$i]); if ($exception) { throw $exception; } } $this->queue = []; } } throwables = $throwables; } /** * @return \Throwable[] */ public function getThrowables() : array { return $this->throwables; } } Copyright (c) 2020 terminal42 gmbh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. logger = $logger; } /** * @ServiceTag("kernel.event_listener", event="kernel.request") */ public function onKernelRequest(GetResponseEvent $event) { $this->logger->debug('Request for '.$event->getRequest()->getRequestUri()); } } ``` If an annotation is added to a method instead of the class, the method name is automatically added to the service tag "method" argument. ## Extending the annotations If your bundle provides new tags to other services, you can improve DX by providing your own annotations. Good IDEs like PhpStorm can then provide autocomplete support. **Example:** ```php use Doctrine\Common\Annotations\Annotation\Attribute; use Doctrine\Common\Annotations\Annotation\Attributes; use Doctrine\Common\Annotations\Annotation\Target; use Terminal42\ServiceAnnotationBundle\Annotation\ServiceTagInterface; /** * @Annotation * @Target("CLASS") * @Attributes({ * @Attribute("channel", type = "string", required = true), * }) */ class Logger implements ServiceTagInterface { public $channel; public function getName(): string { return 'monolog.logger'; } public function getAttributes(): array { return ['channel' => $this->channel]; } } ``` Applying this to the example above, the class annotation can be simplified like this: ```php // src/EventListener/KernelListener.php namespace App\EventListener; /** * @Logger(channel="routing") */ class KernelListener { // Same class as before } ``` { "name": "terminal42\/service-annotation-bundle", "type": "symfony-bundle", "license": "MIT", "description": "Add service tags from class annotations", "authors": [ { "name": "terminal42 gmbh", "homepage": "https:\/\/www.terminal42.ch\/" } ], "funding": [ { "type": "github", "url": "https:\/\/github.com\/sponsors\/terminal42" }, { "type": "other", "url": "https:\/\/ko-fi.com\/terminal42" } ], "support": { "issues": "https:\/\/github.com\/terminal42\/service-annotation-bundle\/issues", "source": "https:\/\/github.com\/terminal42\/service-annotation-bundle" }, "require": { "php": "^7.1 || ^8.0", "doctrine\/annotations": "^1.6 || ^2.0", "symfony\/dependency-injection": "^4.0 || ^5.0 || ^6.0", "symfony\/http-kernel": "^4.0 || ^5.0 || ^6.0" }, "require-dev": { "contao\/easy-coding-standard": "^3.0" }, "autoload": { "psr-4": { "_ContaoManager\\Terminal42\\ServiceAnnotationBundle\\": "src\/" } }, "scripts": { "cs-fixer": [ "vendor\/bin\/ecs check src\/ --fix --ansi" ] } } * @license MIT * @link http://github.com/terminal42/service-annotation-bundle */ namespace _ContaoManager\Terminal42\ServiceAnnotationBundle; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\PassConfig; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\HttpKernel\Bundle\Bundle; use _ContaoManager\Terminal42\ServiceAnnotationBundle\DependencyInjection\Compiler\ServiceAnnotationPass; class Terminal42ServiceAnnotationBundle extends Bundle { public function build(ContainerBuilder $container) : void { parent::build($container); // Priority must be higher than ResolveInstanceofConditionalsPass so annotations // are added before autoconfiguration adds tags for interfaces etc. // See Symfony\Component\DependencyInjection\Compiler\PassConfig $container->addCompilerPass(new ServiceAnnotationPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 110); } } * @license MIT * @link http://github.com/terminal42/service-annotation-bundle */ namespace _ContaoManager\Terminal42\ServiceAnnotationBundle; /** * Marker interface for Symfony service auto configuration. * * @deprecated No longer needed as of Version 1.1. */ interface ServiceAnnotationInterface { } * @license MIT * @link http://github.com/terminal42/service-annotation-bundle */ namespace _ContaoManager\Terminal42\ServiceAnnotationBundle\Annotation; use _ContaoManager\Doctrine\Common\Annotations\Annotation\Attribute; use _ContaoManager\Doctrine\Common\Annotations\Annotation\Attributes; use _ContaoManager\Doctrine\Common\Annotations\Annotation\Target; /** * @Annotation * @Target({"CLASS", "METHOD"}) * @Attributes({ * @Attribute("value", required = true, type = "string"), * }) */ class ServiceTag implements ServiceTagInterface { /** * @var string */ protected $name; /** * @var array */ protected $attributes = []; public function __construct(array $data) { $this->name = $data['value']; unset($data['value']); $this->attributes = $data; } public function getName() : string { return $this->name; } public function getAttributes() : array { return $this->attributes; } } * @license MIT * @link http://github.com/terminal42/service-annotation-bundle */ namespace _ContaoManager\Terminal42\ServiceAnnotationBundle\Annotation; interface ServiceTagInterface { public function getName() : string; public function getAttributes() : array; } * @license MIT * @link http://github.com/terminal42/service-annotation-bundle */ namespace _ContaoManager\Terminal42\ServiceAnnotationBundle\DependencyInjection\Compiler; use _ContaoManager\Doctrine\Common\Annotations\AnnotationException; use _ContaoManager\Doctrine\Common\Annotations\Reader; use _ContaoManager\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use _ContaoManager\Symfony\Component\DependencyInjection\ContainerBuilder; use _ContaoManager\Symfony\Component\DependencyInjection\Definition; use _ContaoManager\Terminal42\ServiceAnnotationBundle\Annotation\ServiceTagInterface; class ServiceAnnotationPass implements CompilerPassInterface { /** * @var Reader */ private $annotationReader; /** * {@inheritdoc} */ public function process(ContainerBuilder $container) : void { if (!$container->has('annotation_reader')) { return; } $this->annotationReader = $container->get('annotation_reader'); foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isAbstract() || $definition->isSynthetic()) { continue; } $class = $definition->getClass(); // See Symfony\Component\DependencyInjection\Compiler\ResolveClassPass // Needs to be done here because this compiler pass runs before ResolveClassPass if (null === $class && \preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+(?:\\\\[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*+)++$/', $id)) { $class = $id; } $class = $container->getParameterBag()->resolveValue($class); if (!$class || null === ($reflection = $container->getReflectionClass($class, \false)) || $reflection->isAbstract()) { continue; } $this->parseClassAnnotations($reflection, $definition); $this->parseMethodAnnotations($reflection, $definition); } } private function parseClassAnnotations(\ReflectionClass $reflection, Definition $definition) : void { try { $annotations = $this->annotationReader->getClassAnnotations($reflection); } catch (AnnotationException $e) { // Ignore this class if annotations can't be parsed. return; } foreach ($annotations as $annotation) { if (!$annotation instanceof ServiceTagInterface) { continue; } $definition->addTag($annotation->getName(), $annotation->getAttributes()); } } private function parseMethodAnnotations(\ReflectionClass $reflection, Definition $definition) : void { foreach ($reflection->getMethods() as $method) { try { $annotations = $this->annotationReader->getMethodAnnotations($method); } catch (AnnotationException $e) { // Ignore this method if annotations can't be parsed. continue; } foreach ($annotations as $annotation) { if (!$annotation instanceof ServiceTagInterface) { continue; } $attributes = $annotation->getAttributes(); $attributes['method'] = $method->getName(); $definition->addTag($annotation->getName(), $attributes); } } } } A6Q&sqiKGBMB